├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── pydiff.py ├── screenshot.png ├── tests ├── left.txt ├── left_dir │ ├── dir1 │ │ ├── file1.txt │ │ └── file2.txt │ ├── dir2 │ │ └── file4.txt │ └── file3.txt ├── right.txt └── right_dir │ ├── dir1 │ ├── file1.txt │ └── file2.txt │ └── dir2 │ └── file4.txt ├── ui ├── __init__.py ├── mainwindow.py ├── mainwindow_ui.py └── searchtextdialog.py └── utilities ├── __init__.py └── fileio.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # Jupyter Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | .venv/ 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "difflibparser"] 2 | path = difflibparser 3 | url = https://github.com/yebrahim/difflibparser.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Yasser Elsayed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pydiff - A Minimalistic Difflib GUI 2 | An open source Tkinter GUI for python's difflib comparing two text files or two directory trees, complete with highlighting of differences and line numbers. 3 | 4 |

5 | 6 |

7 | 8 | You can open File -> Compare Files to diff two text files, or choose File -> Compare Directories to diff directories. In the case of directories, the tool will show the directory structure in a tree sidebar to the left, and highlight files red if they're in left directory only, green if in the right one only, yellow if in both with changes, and white if in both with no changes. 9 | 10 | The tool makes use of [this parser](https://github.com/yebrahim/difflibparser) that I wrote for python's difflib ndiff output, which converts the text output into diff objects that can be used in code. 11 | 12 | ## Requirements 13 | pydiff works with stock Python2.7 and takes only one dependency on `tkinter`, which is built-in on MacOS so it should work out of the box. On Ubuntu, you can get it by running `sudo apt-get install python-tk`. 14 | 15 | ## Install 16 | You can just clone the repo to your disk, just note that it uses a submodule, so you need to clone recursively: 17 | 18 | `git clone --recursive https://github.com/yebrahim/pydiff.git` 19 | 20 | Please open issues if you see any, and feel free to fix and send pull requests. 21 | 22 | ## Usage 23 | 24 | `python pydiff.py` 25 | 26 | You can also give it executable permissions and run it directly on unix systems: 27 | 28 | `chmod +x pydiff.py` 29 | 30 | `./pydiff.py` 31 | 32 | To diff two paths directly (files or directories): 33 | 34 | `python pydiff.py -p path1 path2` 35 | -------------------------------------------------------------------------------- /pydiff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | MIT License 4 | 5 | Copyright (c) 2016 Yasser Elsayed 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | """ 25 | 26 | from ui.mainwindow import * 27 | import argparse, sys 28 | 29 | parser = argparse.ArgumentParser(description="pydiff - Tkinter GUI tool based on Python's difflib") 30 | parser.add_argument('-p', '--paths', metavar=('path1', 'path2'), nargs=2, help='Two paths to compare', required=False) 31 | 32 | args = parser.parse_args() 33 | 34 | leftpath = args.paths[0] if args.paths else None 35 | rightpath = args.paths[1] if args.paths else None 36 | 37 | main_window = MainWindow() 38 | main_window.start(leftpath, rightpath) 39 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yebrahim/pydiff/eb37e17849c80671d8ffe6892b21f257260f9c9c/screenshot.png -------------------------------------------------------------------------------- /tests/left.txt: -------------------------------------------------------------------------------- 1 | /// 2 | /// An empty page that can be used on its own or navigated to within a Frame. 3 | /// 4 | public sealed partial class GamePage : Page 5 | { 6 | int currentPlayer = 1; 7 | int lastMoveRow = -1, lastMoveCol = -1; 8 | public GameEngine game = new GameEngine(); 9 | Dictionary> elementToCell = new Dictionary>(); 10 | Dictionary, UIElement> cellToElement = new Dictionary, UIElement>(); 11 | Rectangle[,] rects = new Rectangle[9, 9]; 12 | Image[,] imgs = new Image[9, 9]; 13 | double cellHeight = 0, cellWidth = 0; 14 | double imgHeight = 0, imgWidth = 0; 15 | double turnImgWidth = 100, turnImgHeight = 50; 16 | double appMargin = 20; 17 | int rows = 9, cols = 9; 18 | double imgToCellPerc = 0.8; 19 | List flashStoryboards = new List(); 20 | string xturnSrc = "Assets/xturn.png"; 21 | string oturnSrc = "Assets/oturn.png"; 22 | GameMode gameMode = GameMode.TwoPlayer; 23 | int greetingImgWidth = 220; 24 | 25 | 26 | int greetingImgHeight = 75; 27 | int gameOverImgWidth = 284; 28 | int gameOverImgHeight = 153; 29 | int newGameImgWidth = 200; 30 | 31 | 32 | int newGameImgHeight = 70; 33 | double greetingDurationSec = 2; 34 | string[] randomGreetings = new string[] { "awesome", "goodjob", "nicework" }; 35 | bool soundEffectsEnabled = true; 36 | MoveState lastMoveState = MoveState.SUCCESS_GAME_ON; 37 | 38 | WavePlayer wavePlayer = new WavePlayer(); 39 | 40 | bool UIEnabled = true; 41 | 42 | enum GameColor 43 | { 44 | BlackBackground, 45 | NormalCell, 46 | AvailableBoardGlow, 47 | BoardWonX, 48 | BoardWonO, 49 | BoardDraw, 50 | Border 51 | } 52 | 53 | public GamePage() 54 | { 55 | this.InitializeComponent(); 56 | wavePlayer.AddWave("playerX", "Assets/Sounds/Tap1.wav"); 57 | wavePlayer.AddWave("playerO", "Assets/Sounds/Tap2.wav"); 58 | wavePlayer.AddWave("boardWon", "Assets/Sounds/boardWon.wav"); 59 | wavePlayer.AddWave("gameWon", "Assets/Sounds/gameWon.wav"); 60 | } 61 | 62 | protected override void OnNavigatedFrom(NavigationEventArgs e) 63 | { 64 | Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed; 65 | } 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | async void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e) 92 | { 93 | e.Handled = true; 94 | // if at least one move has been played, and the game is still running 95 | if (lastMoveCol > -1 && lastMoveState != MoveState.GAME_OVER) 96 | { 97 | var dialog = new Windows.UI.Popups.MessageDialog("Are you sure you want to leave this game? Your progress will be lost!"); 98 | bool? exitSelected = null; 99 | dialog.Commands.Add(new UICommand("Exit", new UICommandInvokedHandler((cmd) => exitSelected = true))); 100 | dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler((cmd) => exitSelected = false))); 101 | await dialog.ShowAsync(); 102 | 103 | if (exitSelected.HasValue && !exitSelected.Value) 104 | { 105 | e.Handled = true; 106 | return; 107 | } 108 | } 109 | 110 | if (Frame.CanGoBack) 111 | { 112 | //Indicate the back button press is handled so the app does not exit 113 | e.Handled = true; 114 | Frame.Navigate(typeof(StartPage)); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tests/left_dir/dir1/file1.txt: -------------------------------------------------------------------------------- 1 | /// 2 | /// An empty page that can be used on its own or navigated to within a Frame. 3 | /// 4 | public sealed partial class GamePage : Page 5 | { 6 | int currentPlayer = 1; 7 | int lastMoveRow = -1, lastMoveCol = -1; 8 | public GameEngine game = new GameEngine(); 9 | Dictionary> elementToCell = new Dictionary>(); 10 | Dictionary, UIElement> cellToElement = new Dictionary -1 && lastMoveState != MoveState.GAME_OVER) 48 | { 49 | var dialog = new Windows.UI.Popups.MessageDialog("Are you sure you want to leave this game? Your progress will be lost!"); 50 | bool? exitSelected = null; 51 | dialog.Commands.Add(new UICommand("Exit", new UICommandInvokedHandler((cmd) => exitSelected = true))); 52 | dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler((cmd) => exitSelected = false))); 53 | await dialog.ShowAsync(); 54 | 55 | if (exitSelected.HasValue && !exitSelected.Value) 56 | { 57 | e.Handled = true; 58 | return; 59 | } 60 | } 61 | 62 | if (Frame.CanGoBack) 63 | { 64 | //Indicate the back button press is handled so the app does not exit 65 | e.Handled = true; 66 | Frame.Navigate(typeof(StartPage)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/left_dir/dir1/file2.txt: -------------------------------------------------------------------------------- 1 | /// 2 | /// An empty page that can be used on its own or navigated to within a Frame. 3 | /// 4 | public sealed partial class GamePage : Page 5 | { 6 | int currentPlayer = 1; 7 | int lastMoveRow = -1, lastMoveCol = -1; 8 | public GameEngine game = new GameEngine(); 9 | Dictionary> elementToCell = new Dictionary>(); 10 | Dictionary, UIElement> cellToElement = new Dictionary, UIElement>(); 11 | Rectangle[,] rects = new Rectangle[9, 9]; 12 | Image[,] imgs = new Image[9, 9]; 13 | double cellHeight = 0, cellWidth = 0; 14 | double imgHeight = 0, imgWidth = 0; 15 | double turnImgWidth = 100, turnImgHeight = 50; 16 | double appMargin = 20; 17 | int rows = 9, cols = 9; 18 | double imgToCellPerc = 0.8; 19 | List flashStoryboards = new List(); 20 | string xturnSrc = "Assets/xturn.png"; 21 | string oturnSrc = "Assets/oturn.png"; 22 | GameMode gameMode = GameMode.TwoPlayer; 23 | int greetingImgWidth = 220; 24 | int greetingImgHeight = 75; 25 | int gameOverImgWidth = 284; 26 | int gameOverImgHeight = 153; 27 | int newGameImgWidth = 200; 28 | int newGameImgHeight = 70; 29 | double greetingDurationSec = 2; 30 | string[] randomGreetings = new string[] { "awesome", "goodjob", "nicework" }; 31 | bool soundEffectsEnabled = true; 32 | MoveState lastMoveState = MoveState.SUCCESS_GAME_ON; 33 | 34 | WavePlayer wavePlayer = new WavePlayer(); 35 | 36 | bool UIEnabled = true; 37 | 38 | public GamePage() 39 | { 40 | this.InitializeComponent(); 41 | wavePlayer.AddWave("playerX", "Assets/Sounds/Tap1.wav"); 42 | wavePlayer.AddWave("playerO", "Assets/Sounds/Tap2.wav"); 43 | wavePlayer.AddWave("boardWon", "Assets/Sounds/boardWon.wav"); 44 | wavePlayer.AddWave("gameWon", "Assets/Sounds/gameWon.wav"); 45 | } 46 | 47 | protected override void OnNavigatedFrom(NavigationEventArgs e) 48 | { 49 | Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed; 50 | } 51 | 52 | async void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e) 53 | { 54 | e.Handled = false; 55 | if (lastMoveCol > -1 && lastMoveState != MoveState.GAME_OVER) 56 | { 57 | var dialog = new Windows.UI.Popups.MessageDialog("Are you sure you want to leave this game? Your progress will be lost!"); 58 | bool? exitSelected = null; 59 | dialog.Commands.Add(new UICommand("Exit", new UICommandInvokedHandler((cmd) => exitSelected = true))); 60 | dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler((cmd) => exitSelected = false))); 61 | await dialog.ShowAsync(); 62 | 63 | // if at least one move has been played, and the game is still running 64 | if (exitSelected.HasValue && !exitSelected.Value) 65 | { 66 | e.Handled = true; 67 | return; 68 | } 69 | } 70 | 71 | if (Frame.CanGoBack && some other condition()) 72 | { 73 | //Indicate the back button press is handled so the app does not exit 74 | //e.Handled = trux; 75 | Frame.Navigate(typeof(StartPage)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/left_dir/dir2/file4.txt: -------------------------------------------------------------------------------- 1 | /// 2 | /// An empty page that can be used on its own or navigated to within a Frame. 3 | /// 4 | public sealed partial class GamePage : Page 5 | { 6 | int currentPlayer = 1; 7 | int lastMoveRow = -1, lastMoveCol = -1; 8 | public GameEngine game = new GameEngine(); 9 | Dictionary> elementToCell = new Dictionary>(); 10 | Dictionary, UIElement> cellToElement = new Dictionary, UIElement>(); 11 | Rectangle[,] rects = new Rectangle[9, 9]; 12 | Image[,] imgs = new Image[9, 9]; 13 | double cellHeight = 0, cellWidth = 0; 14 | double imgHeight = 0, imgWidth = 0; 15 | double turnImgWidth = 100, turnImgHeight = 50; 16 | double appMargin = 20; 17 | int rows = 9, cols = 9; 18 | double imgToCellPerc = 0.8; 19 | List flashStoryboards = new List(); 20 | string xturnSrc = "Assets/xturn.png"; 21 | string oturnSrc = "Assets/oturn.png"; 22 | GameMode gameMode = GameMode.TwoPlayer; 23 | int greetingImgWidth = 220; 24 | int greetingImgHeight = 75; 25 | int gameOverImgWidth = 284; 26 | int gameOverImgHeight = 153; 27 | int newGameImgWidth = 200; 28 | int newGameImgHeight = 70; 29 | double greetingDurationSec = 2; 30 | string[] randomGreetings = new string[] { "awesome", "goodjob", "nicework" }; 31 | bool soundEffectsEnabled = true; 32 | MoveState lastMoveState = MoveState.SUCCESS_GAME_ON; 33 | 34 | WavePlayer wavePlayer = new WavePlayer(); 35 | 36 | bool UIEnabled = true; 37 | 38 | public GamePage() 39 | { 40 | this.InitializeComponent(); 41 | wavePlayer.AddWave("playerX", "Assets/Sounds/Tap1.wav"); 42 | wavePlayer.AddWave("playerO", "Assets/Sounds/Tap2.wav"); 43 | wavePlayer.AddWave("boardWon", "Assets/Sounds/boardWon.wav"); 44 | wavePlayer.AddWave("gameWon", "Assets/Sounds/gameWon.wav"); 45 | } 46 | 47 | protected override void OnNavigatedFrom(NavigationEventArgs e) 48 | { 49 | //Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed; 50 | } 51 | 52 | async void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e) 53 | { 54 | e.Handled = false; 55 | if (lastMoveCol > -1 && lastMoveStaasdte != MoveState.GAME_OVER) 56 | { 57 | var dialog = new Windows.UI.Popups.MessageDialog("Are you sure you want to leave this game? Your progress will be lost!"); 58 | bool? exitSelected = null; 59 | dialog.Commands.Add(new UICommand("Exit", new UICommandInvokedHandler((cmd) => exitSelected = true))); 60 | dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler((cmd) => exitSelected = false))); 61 | await dialog.ShowAsync(); 62 | 63 | // if at least one move has been played, and the game is still running 64 | if (exitSelected.HasValue && !exitSelected.Value) 65 | { 66 | e.Handled = true; 67 | return; 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /tests/left_dir/file3.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yebrahim/pydiff/eb37e17849c80671d8ffe6892b21f257260f9c9c/tests/left_dir/file3.txt -------------------------------------------------------------------------------- /tests/right.txt: -------------------------------------------------------------------------------- 1 | /// 2 | /// An empty page that can be used on its own or navigated to within a Frame. 3 | /// 4 | public sealed partial class GamePage : Page 5 | { 6 | int currentPlayer = 1; 7 | int lastMoveRow = -1, lastMoveCol = -1; 8 | public GameEngine game = new GameEngine(); 9 | Dictionary> elementToCell = new Dictionary>(); 10 | Dictionary, UIElement> cellToElement = new Dictionary, UIElement>(); 11 | Rectangle[,] rects = new Rectangle[9, 9]; 12 | Image[,] imgs = new Image[9, 9]; 13 | double cellHeight = 0, cellWidth = 0; 14 | double imgHeight = 0, imgWidth = 0; 15 | double turnImgWidth = 100, turnImgHeight = 50; 16 | double appMargin = 20; 17 | int rows = 9, cols = 9; 18 | double imgToCellPerc = 0.8; 19 | List flashStoryboards = new List(); 20 | string xturnSrc = "Assets/xturn.png"; 21 | string oturnSrc = "Assets/oturn.png"; 22 | GameMode gameMode = GameMode.TwoPlayer; 23 | int greetingImgWidth = 220; 24 | int greetingImgHeight = 75; 25 | int gameOverImgWidth = 284; 26 | int gameOverImgHeight = 153; 27 | int newGameImgWidth = 200; 28 | int newGameImgHeight = 70; 29 | double greetingDurationSec = 2; 30 | string[] randomGreetings = new string[] { "awesome", "goodjob", "nicework" }; 31 | bool soundEffectsEnabled = true; 32 | MoveState lastMoveState = MoveState.SUCCESS_GAME_ON; 33 | 34 | WavePlayer wavePlayer = new WavePlayer(); 35 | 36 | bool UIEnabled = true; 37 | 38 | public GamePage() 39 | { 40 | this.InitializeComponent(); 41 | wavePlayer.AddWave("playerX", "Assets/Sounds/Tap1.wav"); 42 | wavePlayer.AddWave("playerO", "Assets/Sounds/Tap2.wav"); 43 | wavePlayer.AddWave("boardWon", "Assets/Sounds/boardWon.wav"); 44 | wavePlayer.AddWave("gameWon", "Assets/Sounds/gameWon.wav"); 45 | } 46 | 47 | protected override void OnNavigatedFrom(NavigationEventArgs e) 48 | { 49 | Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed; 50 | } 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | async void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e) 76 | { 77 | e.Handled = false; 78 | if (lastMoveCol > -1 && lastMoveState != MoveState.GAME_OVER) 79 | { 80 | var dialog = new Windows.UI.Popups.MessageDialog("Are you sure you want to leave this game? Your progress will be lost!"); 81 | bool? exitSelected = null; 82 | dialog.Commands.Add(new UICommand("Exit", new UICommandInvokedHandler((cmd) => exitSelected = true))); 83 | dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler((cmd) => exitSelected = false))); 84 | await dialog.ShowAsync(); 85 | 86 | // if at least one move has been played, and the game is still running 87 | if (exitSelected.HasValue && !exitSelected.Value) 88 | { 89 | e.Handled = true; 90 | return; 91 | } 92 | } 93 | 94 | if (Frame.CanGoBack && some other condition()) 95 | { 96 | //Indicate the back button press is handled so the app does not exit 97 | //e.Handled = trux; 98 | Frame.Navigate(typeof(StartPage)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/right_dir/dir1/file1.txt: -------------------------------------------------------------------------------- 1 | /// 2 | /// An empty page that can be used on its own or navigated to within a Frame. 3 | /// 4 | public sealed partial class GamePage : Page 5 | { 6 | int currentPlayer = 1; 7 | int lastMoveRow = -1, lastMoveCol = -1; 8 | public GameEngine game = new GameEngine(); 9 | Dictionary> elementToCell = new Dictionary>(); 10 | Dictionary, UIElement> cellToElement = new Dictionary, UIElement>(); 11 | Rectangle[,] rects = new Rectangle[9, 9]; 12 | Image[,] imgs = new Image[9, 9]; 13 | double cellHeight = 0, cellWidth = 0; 14 | double imgHeight = 0, imgWidth = 0; 15 | double turnImgWidth = 100, turnImgHeight = 50; 16 | double appMargin = 20; 17 | int rows = 9, cols = 9; 18 | double imgToCellPerc = 0.8; 19 | List flashStoryboards = new List(); 20 | string xturnSrc = "Assets/xturn.png"; 21 | string oturnSrc = "Assets/oturn.png"; 22 | GameMode gameMode = GameMode.TwoPlayer; 23 | int greetingImgWidth = 220; 24 | int greetingImgHeight = 75; 25 | int gameOverImgWidth = 284; 26 | int gameOverImgHeight = 153; 27 | int newGameImgWidth = 200; 28 | int newGameImgHeight = 70; 29 | double greetingDurationSec = 2; 30 | string[] randomGreetings = new string[] { "awesome", "goodjob", "nicework" }; 31 | bool soundEffectsEnabled = true; 32 | MoveState lastMoveState = MoveState.SUCCESS_GAME_ON; 33 | 34 | WavePlayer wavePlayer = new WavePlayer(); 35 | 36 | bool UIEnabled = true; 37 | 38 | enum GameColor 39 | { 40 | BlackBackground, 41 | NormalCell, 42 | AvailableBoardGlow, 43 | BoardWonX, 44 | BoardWonO, 45 | BoardDraw, 46 | Border 47 | } 48 | 49 | public GamePage() 50 | { 51 | this.InitializeComponent(); 52 | wavePlayer.AddWave("playerX", "Assets/Sounds/Tap1.wav"); 53 | wavePlayer.AddWave("playerO", "Assets/Sounds/Tap2.wav"); 54 | wavePlayer.AddWave("boardWon", "Assets/Sounds/boardWon.wav"); 55 | wavePlayer.AddWave("gameWon", "Assets/Sounds/gameWon.wav"); 56 | } 57 | 58 | protected override void OnNavigatedFrom(NavigationEventArgs e) 59 | { 60 | Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed; 61 | } 62 | 63 | async void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e) 64 | { 65 | e.Handled = true; 66 | // if at least one move has been played, and the game is still running 67 | if (lastMoveCol > -1 && lastMoveState != MoveState.GAME_OVER) 68 | { 69 | var dialog = new Windows.UI.Popups.MessageDialog("Are you sure you want to leave this game? Your progress will be lost!"); 70 | bool? exitSelected = null; 71 | dialog.Commands.Add(new UICommand("Exit", new UICommandInvokedHandler((cmd) => exitSelected = true))); 72 | dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler((cmd) => exitSelected = false))); 73 | await dialog.ShowAsync(); 74 | 75 | if (exitSelected.HasValue && !exitSelected.Value) 76 | { 77 | e.Handled = true; 78 | return; 79 | } 80 | } 81 | 82 | if (Frame.CanGoBack) 83 | { 84 | //Indicate the back button press is handled so the app does not exit 85 | e.Handled = true; 86 | Frame.Navigate(typeof(StartPage)); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/right_dir/dir1/file2.txt: -------------------------------------------------------------------------------- 1 | /// 2 | /// An empty page that can be used on its own or navigated to within a Frame. 3 | /// 4 | public sealed partial class GamePage : Page 5 | { 6 | int currentPlayer = 1; 7 | int lastMoveRow = -1, lastMoveCol = -1; 8 | public GameEngine game = new GameEngine(); 9 | Dictionary> elementToCell = new Dictionary>(); 10 | Dictionary, UIElement> cellToElement = new Dictionary, UIElement>(); 11 | Rectangle[,] rects = new Rectangle[9, 9]; 12 | Image[,] imgs = new Image[9, 9]; 13 | double cellHeight = 0, cellWidth = 0; 14 | double imgHeight = 0, imgWidth = 0; 15 | double turnImgWidth = 100, turnImgHeight = 50; 16 | double appMargin = 20; 17 | int rows = 9, cols = 9; 18 | double imgToCellPerc = 0.8; 19 | List flashStoryboards = new List(); 20 | string xturnSrc = "Assets/xturn.png"; 21 | string oturnSrc = "Assets/oturn.png"; 22 | GameMode gameMode = GameMode.TwoPlayer; 23 | int greetingImgWidth = 220; 24 | int greetingImgHeight = 75; 25 | int gameOverImgWidth = 284; 26 | int gameOverImgHeight = 153; 27 | int newGameImgWidth = 200; 28 | int newGameImgHeight = 70; 29 | double greetingDurationSec = 2; 30 | string[] randomGreetings = new string[] { "awesome", "goodjob", "nicework" }; 31 | bool soundEffectsEnabled = true; 32 | MoveState lastMoveState = MoveState.SUCCESS_GAME_ON; 33 | 34 | WavePlayer wavePlayer = new WavePlayer(); 35 | 36 | bool UIEnabled = true; 37 | 38 | public GamePage() 39 | { 40 | this.InitializeComponent(); 41 | wavePlayer.AddWave("playerX", "Assets/Sounds/Tap1.wav"); 42 | wavePlayer.AddWave("playerO", "Assets/Sounds/Tap2.wav"); 43 | wavePlayer.AddWave("boardWon", "Assets/Sounds/boardWon.wav"); 44 | wavePlayer.AddWave("gameWon", "Assets/Sounds/gameWon.wav"); 45 | } 46 | 47 | protected override void OnNavigatedFrom(NavigationEventArgs e) 48 | { 49 | Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed; 50 | } 51 | 52 | async void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e) 53 | { 54 | e.Handled = false; 55 | if (lastMoveCol > -1 && lastMoveState != MoveState.GAME_OVER) 56 | { 57 | var dialog = new Windows.UI.Popups.MessageDialog("Are you sure you want to leave this game? Your progress will be lost!"); 58 | bool? exitSelected = null; 59 | dialog.Commands.Add(new UICommand("Exit", new UICommandInvokedHandler((cmd) => exitSelected = true))); 60 | dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler((cmd) => exitSelected = false))); 61 | await dialog.ShowAsync(); 62 | 63 | // if at least one move has been played, and the game is still running 64 | if (exitSelected.HasValue && !exitSelected.Value) 65 | { 66 | e.Handled = true; 67 | return; 68 | } 69 | } 70 | 71 | if (Frame.CanGoBack && some other condition()) 72 | { 73 | //Indicate the back button press is handled so the app does not exit 74 | //e.Handled = trux; 75 | Frame.Navigate(typeof(StartPage)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/right_dir/dir2/file4.txt: -------------------------------------------------------------------------------- 1 | /// 2 | /// An empty page that can be used on its own or navigated to within a Frame. 3 | /// 4 | public sealed partial class GamePage : Page 5 | { 6 | int currentPlayer = 1; 7 | int lastMoveRow = -1, lastMoveCol = -1; 8 | public GameEngine game = new GameEngine(); 9 | Dictionary> elementToCell = new Dictionary>(); 10 | Dictionary, UIElement> cellToElement = new Dictionary, UIElement>(); 11 | Rectangle[,] rects = new Rectangle[9, 9]; 12 | Image[,] imgs = new Image[9, 9]; 13 | double cellHeight = 0, cellWidth = 0; 14 | double imgHeight = 0, imgWidth = 0; 15 | double turnImgWidth = 100, turnImgHeight = 50; 16 | double appMargin = 20; 17 | int rows = 9, cols = 9; 18 | double imgToCellPerc = 0.8; 19 | List flashStoryboards = new List(); 20 | string xturnSrc = "Assets/xturn.png"; 21 | string oturnSrc = "Assets/oturn.png"; 22 | GameMode gameMode = GameMode.TwoPlayer; 23 | int greetingImgWidth = 220; 24 | int greetingImgHeight = 75; 25 | int gameOverImgWidth = 284; 26 | int gameOverImgHeight = 153; 27 | int newGameImgWidth = 200; 28 | int newGameImgHeight = 70; 29 | double greetingDurationSec = 2; 30 | string[] randomGreetings = new string[] { "awesome", "goodjob", "nicework" }; 31 | bool soundEffectsEnabled = true; 32 | MoveState lastMoveState = MoveState.SUCCESS_GAME_ON; 33 | 34 | WavePlayer wavePlayer = new WavePlayer(); 35 | 36 | bool UIEnabled = true; 37 | 38 | public GamePage() 39 | { 40 | this.InitializeComponent(); 41 | wavePlayer.AddWave("playerX", "Assets/Sounds/Tap1.wav"); 42 | wavePlayer.AddWave("playerO", "Assets/Sounds/Tap2.wav"); 43 | wavePlayer.AddWave("boardWon", "Assets/Sounds/boardWon.wav"); 44 | wavePlayer.AddWave("gameWon", "Assets/Sounds/gameWon.wav"); 45 | } 46 | 47 | protected override void OnNavigatedFrom(NavigationEventArgs e) 48 | { 49 | Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed; 50 | } 51 | 52 | async void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e) 53 | { 54 | e.Handled = false; 55 | if (lastMoveCol > -1 && lastMoveState != MoveState.GAME_OVER) 56 | { 57 | var dialog = new Windows.UI.Popups.MessageDialog("Are you sure you want to leave this game? Your progress will be lost!"); 58 | bool? exitSelected = null; 59 | dialog.Commands.Add(new UICommand("Exit", new UICommandInvokedHandler((cmd) => exitSelected = true))); 60 | dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler((cmd) => exitSelected = false))); 61 | await dialog.ShowAsync(); 62 | 63 | // if at least one move has been played, and the game is still running 64 | if (exitSelected.HasValue && !exitSelected.Value) 65 | { 66 | e.Handled = true; 67 | return; 68 | } 69 | } 70 | 71 | if (Frame.CanGoBack && some other condition()) 72 | { 73 | //Indicate the back button press is handled so the app does not exit 74 | //e.Handled = trux; 75 | Frame.Navigate(typeof(StartPage)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yebrahim/pydiff/eb37e17849c80671d8ffe6892b21f257260f9c9c/ui/__init__.py -------------------------------------------------------------------------------- /ui/mainwindow.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2016 Yasser Elsayed 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | import os, mimetypes, filecmp 26 | from difflibparser.difflibparser import * 27 | from ui.mainwindow_ui import MainWindowUI 28 | try: # for Python2 29 | from Tkinter import * 30 | from tkFileDialog import askopenfilename, askdirectory 31 | from tkSimpleDialog import askstring 32 | except ImportError: # for Python3 33 | from tkinter import * 34 | from tkinter.filedialog import askopenfilename, askdirectory 35 | from tkinter.simpledialog import askstring 36 | 37 | class MainWindow: 38 | def start(self, leftpath = None, rightpath = None): 39 | self.main_window = Tk() 40 | self.main_window.title('Pydiff') 41 | self.__main_window_ui = MainWindowUI(self.main_window) 42 | 43 | self.leftFile = '' 44 | self.rightFile = '' 45 | 46 | self.__main_window_ui.center_window() 47 | self.__main_window_ui.create_file_path_labels() 48 | self.__main_window_ui.create_text_areas() 49 | self.__main_window_ui.create_search_text_entry(self.__findNext) 50 | self.__main_window_ui.create_line_numbers() 51 | self.__main_window_ui.create_scroll_bars() 52 | self.__main_window_ui.create_file_treeview() 53 | path_to_my_project = os.getcwd() 54 | self.__main_window_ui.add_menu('File', [ 55 | {'name': 'Compare Files', 'command': self.__browse_files}, 56 | {'name': 'Compare Directories', 'command': self.__browse_directories}, 57 | {'separator'}, 58 | {'name': 'Exit', 'command': self.__exit, 'accelerator': 'Alt+F4'} 59 | ]) 60 | self.__main_window_ui.add_menu('Edit', [ 61 | {'name': 'Find', 'command': self.__startFindText, 'accelerator': 'Ctrl+F'}, 62 | {'separator'}, 63 | {'name': 'Cut', 'command': self.__cut, 'accelerator': 'Ctrl+X'}, 64 | {'name': 'Copy', 'command': self.__copy, 'accelerator': 'Ctrl+C'}, 65 | {'name': 'Paste', 'command': self.__paste, 'accelerator': 'Ctrl+P'}, 66 | {'separator'}, 67 | {'name': 'Go To Line', 'command': self.__goToLine, 'accelerator': 'Ctrl+G'} 68 | ]) 69 | self.__main_window_ui.fileTreeView.bind('<>', lambda *x:self.treeViewItemSelected()) 70 | 71 | if (leftpath and os.path.isdir(leftpath)) or (rightpath and os.path.isdir(rightpath)): 72 | self.__load_directories(leftpath, rightpath) 73 | else: 74 | self.leftFile = leftpath if leftpath else '' 75 | self.rightFile = rightpath if rightpath else '' 76 | self.filesChanged() 77 | 78 | self.__bind_key_shortcuts() 79 | 80 | self.main_window.mainloop() 81 | 82 | def __bind_key_shortcuts(self): 83 | self.main_window.bind('', lambda *x: self.__startFindText()) 84 | self.main_window.bind('', lambda *x: self.__goToLine()) 85 | self.main_window.bind('', lambda *x: self.__endFindText()) 86 | self.main_window.bind('', self.__main_window_ui.searchTextDialog.nextResult) 87 | 88 | def __browse_files(self): 89 | self.__load_file('left') 90 | self.__load_file('right') 91 | self.filesChanged() 92 | self.__main_window_ui.fileTreeView.grid_remove() 93 | self.__main_window_ui.fileTreeYScrollbar.grid_remove() 94 | self.__main_window_ui.fileTreeXScrollbar.grid_remove() 95 | 96 | # Load directories into the treeview 97 | def __browse_directories(self): 98 | leftDir = self.__load_directory('left') 99 | rightDir = self.__load_directory('right') 100 | self.__load_directories(leftDir, rightDir) 101 | 102 | def __load_directories(self, leftDir, rightDir): 103 | if leftDir and rightDir: 104 | self.__main_window_ui.fileTreeView.grid() 105 | self.__main_window_ui.fileTreeYScrollbar.grid() 106 | self.__main_window_ui.fileTreeXScrollbar.grid() 107 | self.__main_window_ui.fileTreeView.delete(*self.__main_window_ui.fileTreeView.get_children()) 108 | self.__browse_process_directory('', leftDir, rightDir) 109 | 110 | # Recursive method to fill the treevie with given directory hierarchy 111 | def __browse_process_directory(self, parent, leftPath, rightPath): 112 | if parent == '': 113 | leftPath = leftPath.rstrip('/') 114 | rightPath = rightPath.rstrip('/') 115 | leftDirName = os.path.basename(leftPath) 116 | rightDirName = os.path.basename(rightPath) 117 | self.__main_window_ui.fileTreeView.heading('#0', text=leftDirName + ' / ' + rightDirName, anchor=W) 118 | leftListing = os.listdir(leftPath) 119 | rightListing = os.listdir(rightPath) 120 | mergedListing = list(set(leftListing) | set(rightListing)) 121 | painted = FALSE 122 | for l in mergedListing: 123 | newLeftPath = leftPath + '/' + l 124 | newRightPath = rightPath + '/' + l 125 | bindValue = (newLeftPath, newRightPath) 126 | # Item in left dir only 127 | if l in leftListing and l not in rightListing: 128 | self.__main_window_ui.fileTreeView.insert(parent, 'end', text=l, value=bindValue, open=False, tags=('red','simple')) 129 | painted = TRUE 130 | # Item in right dir only 131 | elif l in rightListing and l not in leftListing: 132 | self.__main_window_ui.fileTreeView.insert(parent, 'end', text=l, value=bindValue, open=False, tags=('green','simple')) 133 | painted = TRUE 134 | # Item in both dirs 135 | else: 136 | # If one of the diffed items is a file and the other is a directory, show in yellow indicating a difference 137 | if (not os.path.isdir(newLeftPath) and os.path.isdir(newRightPath)) or (os.path.isdir(newLeftPath) and not os.path.isdir(newRightPath)): 138 | self.__main_window_ui.fileTreeView.insert(parent, 'end', text=l, value=bindValue, open=False, tags=('yellow','simple')) 139 | painted = TRUE 140 | else: 141 | # If both are directories, show in white and recurse on contents 142 | if os.path.isdir(newLeftPath) and os.path.isdir(newRightPath): 143 | oid = self.__main_window_ui.fileTreeView.insert(parent, 'end', text=l, open=False) 144 | painted = self.__browse_process_directory(oid, newLeftPath, newRightPath) 145 | if painted: 146 | self.__main_window_ui.fileTreeView.item(oid, tags=('purpleLight', 'simple')) 147 | else: 148 | # Both are files. diff the two files to either show them in white or yellow 149 | if (filecmp.cmp(newLeftPath, newRightPath)): 150 | oid = self.__main_window_ui.fileTreeView.insert(parent, 'end', text=l, value=bindValue, open=False, tags=('simple')) 151 | else: 152 | oid = self.__main_window_ui.fileTreeView.insert(parent, 'end', text=l, value=bindValue, open=False, tags=('yellow','simple')) 153 | painted = TRUE 154 | return painted 155 | 156 | def __load_file(self, pos): 157 | fname = askopenfilename() 158 | if fname: 159 | if pos == 'left': 160 | self.leftFile = fname 161 | else: 162 | self.rightFile = fname 163 | return fname 164 | else: 165 | return None 166 | 167 | def __load_directory(self, pos): 168 | dirName = askdirectory() 169 | if dirName: 170 | if pos == 'left': 171 | self.__main_window_ui.leftFileLabel.config(text=dirName) 172 | else: 173 | self.__main_window_ui.rightFileLabel.config(text=dirName) 174 | return dirName 175 | else: 176 | return None 177 | 178 | # Callback for changing a file path 179 | def filesChanged(self): 180 | self.__main_window_ui.leftLinenumbers.grid_remove() 181 | self.__main_window_ui.rightLinenumbers.grid_remove() 182 | 183 | if not self.leftFile or not self.rightFile: 184 | self.__main_window_ui.leftFileTextArea.config(background=self.__main_window_ui.grayColor) 185 | self.__main_window_ui.rightFileTextArea.config(background=self.__main_window_ui.grayColor) 186 | return 187 | 188 | if os.path.exists(self.leftFile): 189 | self.__main_window_ui.leftFileLabel.config(text=self.leftFile) 190 | self.__main_window_ui.leftFileTextArea.config(background=self.__main_window_ui.whiteColor) 191 | self.__main_window_ui.leftLinenumbers.grid() 192 | else: 193 | self.__main_window_ui.leftFileLabel.config(text='') 194 | 195 | if os.path.exists(self.rightFile): 196 | self.__main_window_ui.rightFileLabel.config(text=self.rightFile) 197 | self.__main_window_ui.rightFileTextArea.config(background=self.__main_window_ui.whiteColor) 198 | self.__main_window_ui.rightLinenumbers.grid() 199 | else: 200 | self.__main_window_ui.rightFileLabel.config(text='') 201 | 202 | self.diff_files_into_text_areas() 203 | 204 | def treeViewItemSelected(self): 205 | item_id = self.__main_window_ui.fileTreeView.focus() 206 | paths = self.__main_window_ui.fileTreeView.item(item_id)['values'] 207 | if paths == None or len(paths) == 0: 208 | return 209 | self.leftFile = paths[0] 210 | self.rightFile = paths[1] 211 | self.filesChanged() 212 | 213 | # Insert file contents into text areas and highlight differences 214 | def diff_files_into_text_areas(self): 215 | try: 216 | leftFileContents = open(self.leftFile).read() 217 | except: 218 | leftFileContents = '' 219 | try: 220 | rightFileContents = open(self.rightFile).read() 221 | except: 222 | rightFileContents = '' 223 | 224 | diff = DifflibParser(leftFileContents.splitlines(), rightFileContents.splitlines()) 225 | 226 | # enable text area edits so we can clear and insert into them 227 | self.__main_window_ui.leftFileTextArea.config(state=NORMAL) 228 | self.__main_window_ui.rightFileTextArea.config(state=NORMAL) 229 | self.__main_window_ui.leftLinenumbers.config(state=NORMAL) 230 | self.__main_window_ui.rightLinenumbers.config(state=NORMAL) 231 | 232 | # clear all text areas 233 | self.__main_window_ui.leftFileTextArea.delete(1.0, END) 234 | self.__main_window_ui.rightFileTextArea.delete(1.0, END) 235 | self.__main_window_ui.leftLinenumbers.delete(1.0, END) 236 | self.__main_window_ui.rightLinenumbers.delete(1.0, END) 237 | 238 | leftlineno = rightlineno = 1 239 | for line in diff: 240 | if line['code'] == DiffCode.SIMILAR: 241 | self.__main_window_ui.leftFileTextArea.insert('end', line['line'] + '\n') 242 | self.__main_window_ui.rightFileTextArea.insert('end', line['line'] + '\n') 243 | elif line['code'] == DiffCode.RIGHTONLY: 244 | self.__main_window_ui.leftFileTextArea.insert('end', '\n', 'gray') 245 | self.__main_window_ui.rightFileTextArea.insert('end', line['line'] + '\n', 'green') 246 | elif line['code'] == DiffCode.LEFTONLY: 247 | self.__main_window_ui.leftFileTextArea.insert('end', line['line'] + '\n', 'red') 248 | self.__main_window_ui.rightFileTextArea.insert('end', '\n', 'gray') 249 | elif line['code'] == DiffCode.CHANGED: 250 | for (i,c) in enumerate(line['line']): 251 | self.__main_window_ui.leftFileTextArea.insert('end', c, 'darkred' if i in line['leftchanges'] else 'red') 252 | for (i,c) in enumerate(line['newline']): 253 | self.__main_window_ui.rightFileTextArea.insert('end', c, 'darkgreen' if i in line['rightchanges'] else 'green') 254 | self.__main_window_ui.leftFileTextArea.insert('end', '\n') 255 | self.__main_window_ui.rightFileTextArea.insert('end', '\n') 256 | 257 | if line['code'] == DiffCode.LEFTONLY: 258 | self.__main_window_ui.leftLinenumbers.insert('end', str(leftlineno) + '\n', 'line') 259 | self.__main_window_ui.rightLinenumbers.insert('end', '\n', 'line') 260 | leftlineno += 1 261 | elif line['code'] == DiffCode.RIGHTONLY: 262 | self.__main_window_ui.leftLinenumbers.insert('end', '\n', 'line') 263 | self.__main_window_ui.rightLinenumbers.insert('end', str(rightlineno) + '\n', 'line') 264 | rightlineno += 1 265 | else: 266 | self.__main_window_ui.leftLinenumbers.insert('end', str(leftlineno) + '\n', 'line') 267 | self.__main_window_ui.rightLinenumbers.insert('end', str(rightlineno) + '\n', 'line') 268 | leftlineno += 1 269 | rightlineno += 1 270 | 271 | # calc width of line numbers texts and set it 272 | self.__main_window_ui.leftLinenumbers.config(width=len(str(leftlineno))) 273 | self.__main_window_ui.rightLinenumbers.config(width=len(str(rightlineno))) 274 | 275 | # disable text areas to prevent further editing 276 | self.__main_window_ui.leftFileTextArea.config(state=DISABLED) 277 | self.__main_window_ui.rightFileTextArea.config(state=DISABLED) 278 | self.__main_window_ui.leftLinenumbers.config(state=DISABLED) 279 | self.__main_window_ui.rightLinenumbers.config(state=DISABLED) 280 | 281 | def __cut(self): 282 | area = self.__getActiveTextArea() 283 | if area: 284 | area.event_generate("<>") 285 | 286 | def __copy(self): 287 | area = self.__getActiveTextArea() 288 | if area: 289 | area.event_generate("<>") 290 | 291 | def __paste(self): 292 | area = self.__getActiveTextArea() 293 | if area: 294 | area.event_generate("<>") 295 | 296 | def __getActiveTextArea(self): 297 | if self.main_window.focus_get() == self.__main_window_ui.leftFileTextArea: 298 | return self.__main_window_ui.leftFileTextArea 299 | elif self.main_window.focus_get() == self.__main_window_ui.rightFileTextArea: 300 | return self.__main_window_ui.rightFileTextArea 301 | else: 302 | return None 303 | 304 | def __goToLine(self): 305 | line = askstring('Go to line', 'Enter line number:') 306 | if line: 307 | try: 308 | linenumber = int(line) 309 | self.__main_window_ui.leftFileTextArea.see(float(linenumber) + 5) 310 | except: 311 | pass 312 | 313 | def __startFindText(self): 314 | self.__main_window_ui.searchTextDialog.grid() 315 | self.__main_window_ui.searchTextDialog.focus() 316 | 317 | def __endFindText(self): 318 | self.__main_window_ui.leftFileTextArea.tag_remove('search', 1.0, END) 319 | self.__main_window_ui.rightFileTextArea.tag_remove('search', 1.0, END) 320 | self.__main_window_ui.searchTextDialog.unfocus() 321 | self.__main_window_ui.searchTextDialog.grid_remove() 322 | 323 | def __findNext(self, searchresult): 324 | term,leftpos,rightpos = searchresult['term'], searchresult['indices'][0], searchresult['indices'][1] 325 | if leftpos != -1: 326 | self.__main_window_ui.leftFileTextArea.tag_remove('search', 1.0, END) 327 | self.__main_window_ui.leftFileTextArea.tag_add('search', leftpos, '%s + %sc' % (leftpos, len(term))) 328 | # scroll to position plus five lines for visibility 329 | self.__main_window_ui.leftFileTextArea.see(float(leftpos) + 5) 330 | if rightpos != -1: 331 | self.__main_window_ui.rightFileTextArea.tag_remove('search', 1.0, END) 332 | self.__main_window_ui.rightFileTextArea.tag_add('search', rightpos, '%s + %sc' % (rightpos, len(term))) 333 | # scroll to position plus five lines for visibility 334 | self.__main_window_ui.rightFileTextArea.see(float(rightpos) + 5) 335 | 336 | def __exit(self): 337 | self.main_window.destroy() 338 | -------------------------------------------------------------------------------- /ui/mainwindow_ui.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2016 Yasser Elsayed 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | try: # for Python2 26 | from Tkinter import * 27 | from tkMessageBox import showerror 28 | from tkFont import Font 29 | import ttk 30 | from ttk import Treeview 31 | except ImportError: # for Python3 32 | from tkinter import * 33 | from tkinter.messagebox import showerror 34 | from tkinter import font 35 | from tkinter import ttk 36 | from tkinter.ttk import Treeview 37 | import os 38 | from ui.searchtextdialog import * 39 | 40 | class MainWindowUI: 41 | 42 | # | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 43 | # +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ 44 | # | menu bar | 45 | # +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ 46 | # | | search bar | 47 | # | | search entry | button| 48 | # | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ 49 | # | | | | 50 | # | | | | 51 | # | | | | 52 | # | treeview | | | 53 | # | | text area 1 | text area 2 | 54 | # | | | | 55 | # | | | | 56 | # | | | | 57 | # | | | | 58 | # +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ 59 | # | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 60 | 61 | # Rows 62 | fileTreeRow = filePathLabelsRow = 0 63 | searchTextRow = 1 64 | uniScrollbarRow = lineNumbersRow = textAreasRow = 2 65 | horizontalScrollbarRow = 3 66 | 67 | # Columns 68 | fileTreeCol = 0 69 | fileTreeScrollbarCol = 1 70 | leftLineNumbersCol = leftFilePathLabelsCol = 2 # should span at least two columns 71 | leftTextAreaCol = leftHorizontalScrollbarCol = 3 72 | uniScrollbarCol = 4 73 | rightLineNumbersCol = rightFilePathLabelsCol = 5 # should span at least two columns 74 | rightTextAreaCol = rightHorizontalScrollbarCol = 6 75 | 76 | # Colors 77 | whiteColor = '#ffffff' 78 | redColor = '#ffc4c4' 79 | darkredColor = '#ff8282' 80 | grayColor = '#dddddd' 81 | lightGrayColor = '#eeeeee' 82 | greenColor = '#c9fcd6' 83 | darkgreenColor = '#50c96e' 84 | yellowColor = '#f0f58c' 85 | darkYellowColor = '#ffff00' 86 | purpleLight = '#F5EBFC' 87 | 88 | def __init__(self, window): 89 | self.main_window = window 90 | self.main_window.grid_rowconfigure(self.filePathLabelsRow, weight=0) 91 | self.main_window.grid_rowconfigure(self.searchTextRow, weight=0) 92 | self.main_window.grid_rowconfigure(self.textAreasRow, weight=1) 93 | 94 | self.main_window.grid_columnconfigure(self.fileTreeCol, weight=0) 95 | self.main_window.grid_columnconfigure(self.fileTreeScrollbarCol, weight=0) 96 | self.main_window.grid_columnconfigure(self.leftLineNumbersCol, weight=0) 97 | self.main_window.grid_columnconfigure(self.leftTextAreaCol, weight=1) 98 | self.main_window.grid_columnconfigure(self.uniScrollbarCol, weight=0) 99 | self.main_window.grid_columnconfigure(self.rightLineNumbersCol, weight=0) 100 | self.main_window.grid_columnconfigure(self.rightTextAreaCol, weight=1) 101 | self.menubar = Menu(self.main_window) 102 | self.menus = {} 103 | self.text_area_font = 'TkFixedFont' 104 | 105 | # Center window and set its size 106 | def center_window(self): 107 | sw = self.main_window.winfo_screenwidth() 108 | sh = self.main_window.winfo_screenheight() 109 | 110 | w = 0.7 * sw 111 | h = 0.7 * sh 112 | 113 | x = (sw - w)/2 114 | y = (sh - h)/2 115 | self.main_window.geometry('%dx%d+%d+%d' % (w, h, x, y)) 116 | self.main_window.minsize(int(0.3 * sw), int(0.3 * sh)) 117 | 118 | # Menu bar 119 | def add_menu(self, menuName, commandList): 120 | self.menus[menuName] = Menu(self.menubar,tearoff=0) 121 | for c in commandList: 122 | if 'separator' in c: self.menus[menuName].add_separator() 123 | else: self.menus[menuName].add_command(label=c['name'], command=c['command'], accelerator=c['accelerator'] if 'accelerator' in c else '') 124 | self.menubar.add_cascade(label=menuName, menu=self.menus[menuName]) 125 | self.main_window.config(menu=self.menubar) 126 | 127 | # Labels 128 | def create_file_path_labels(self): 129 | self.leftFileLabel = Label(self.main_window, anchor='center', width=1000, background=self.lightGrayColor) 130 | self.leftFileLabel.grid(row=self.filePathLabelsRow, column=self.leftFilePathLabelsCol, columnspan=2) 131 | self.rightFileLabel = Label(self.main_window, anchor='center', width=1000, background=self.lightGrayColor) 132 | self.rightFileLabel.grid(row=self.filePathLabelsRow, column=self.rightFilePathLabelsCol, columnspan=2) 133 | 134 | # Search text entry 135 | def create_search_text_entry(self, searchButtonCallback): 136 | self.searchTextDialog = SearchTextDialog(self.main_window, [self.leftFileTextArea, self.rightFileTextArea], searchButtonCallback) 137 | self.searchTextDialog.grid(row=self.searchTextRow, column=self.leftFilePathLabelsCol, columnspan=5, sticky=EW) 138 | 139 | self.searchTextDialog.grid_remove() 140 | 141 | # File treeview 142 | def create_file_treeview(self): 143 | self.fileTreeView = Treeview(self.main_window) 144 | self.fileTreeYScrollbar = Scrollbar(self.main_window, orient='vertical', command=self.fileTreeView.yview) 145 | self.fileTreeXScrollbar = Scrollbar(self.main_window, orient='horizontal', command=self.fileTreeView.xview) 146 | self.fileTreeView.configure(yscroll=self.fileTreeYScrollbar.set, xscroll=self.fileTreeXScrollbar.set) 147 | 148 | self.fileTreeView.grid(row=self.fileTreeRow, column=self.fileTreeCol, sticky=NS, rowspan=3) 149 | self.fileTreeYScrollbar.grid(row=self.fileTreeRow, column=self.fileTreeScrollbarCol, sticky=NS, rowspan=3) 150 | self.fileTreeXScrollbar.grid(row=self.horizontalScrollbarRow, column=self.fileTreeCol, sticky=EW) 151 | 152 | self.fileTreeView.tag_configure('red', background=self.redColor) 153 | self.fileTreeView.tag_configure('green', background=self.greenColor) 154 | self.fileTreeView.tag_configure('yellow', background=self.yellowColor) 155 | self.fileTreeView.tag_configure('purpleLight', background=self.purpleLight) 156 | 157 | # hide it until needed 158 | self.fileTreeView.grid_remove() 159 | self.fileTreeYScrollbar.grid_remove() 160 | self.fileTreeXScrollbar.grid_remove() 161 | 162 | # Text areas 163 | def create_text_areas(self): 164 | self.leftFileTextArea = Text(self.main_window, padx=5, pady=5, width=1, height=1, bg=self.grayColor) 165 | self.leftFileTextArea.grid(row=self.textAreasRow, column=self.leftTextAreaCol, sticky=NSEW) 166 | self.leftFileTextArea.config(font=self.text_area_font) 167 | self.leftFileTextArea.config(wrap='none') 168 | 169 | self.rightFileTextArea = Text(self.main_window, padx=5, pady=5, width=1, height=1, bg=self.grayColor) 170 | self.rightFileTextArea.grid(row=self.textAreasRow, column=self.rightTextAreaCol, sticky=NSEW) 171 | self.rightFileTextArea.config(font=self.text_area_font) 172 | self.rightFileTextArea.config(wrap='none') 173 | 174 | # configuring highlight tags 175 | self.leftFileTextArea.tag_configure('red', background=self.redColor) 176 | self.leftFileTextArea.tag_configure('darkred', background=self.darkredColor) 177 | self.leftFileTextArea.tag_configure('gray', background=self.grayColor) 178 | self.leftFileTextArea.tag_configure('search', background=self.darkYellowColor) 179 | self.rightFileTextArea.tag_configure('green', background=self.greenColor) 180 | self.rightFileTextArea.tag_configure('darkgreen', background=self.darkgreenColor) 181 | self.rightFileTextArea.tag_configure('gray', background=self.grayColor) 182 | self.rightFileTextArea.tag_configure('search', background=self.darkYellowColor) 183 | self.rightFileTextArea.tag_configure('purpleLight', background=self.purpleLight) 184 | 185 | # disable the text areas 186 | self.leftFileTextArea.config(state=DISABLED) 187 | self.rightFileTextArea.config(state=DISABLED) 188 | 189 | # Line numbers 190 | def create_line_numbers(self): 191 | self.leftLinenumbers = Text(self.main_window, width=3, padx=5, pady=5, height=1, bg=self.lightGrayColor) 192 | self.leftLinenumbers.grid(row=self.lineNumbersRow, column=self.leftLineNumbersCol, sticky=NS) 193 | self.leftLinenumbers.config(font=self.text_area_font) 194 | self.leftLinenumbers.tag_configure('line', justify='right') 195 | 196 | self.rightLinenumbers = Text(self.main_window, width=3, padx=5, pady=5, height=1, bg=self.lightGrayColor) 197 | self.rightLinenumbers.grid(row=self.lineNumbersRow, column=self.rightLineNumbersCol, sticky=NS) 198 | self.rightLinenumbers.config(font=self.text_area_font) 199 | self.rightLinenumbers.tag_configure('line', justify='right') 200 | 201 | # disable the line numbers 202 | self.leftLinenumbers.config(state=DISABLED) 203 | self.rightLinenumbers.config(state=DISABLED) 204 | 205 | # Scroll bars 206 | def scrollBothY(self, action, position, type=None): 207 | self.leftFileTextArea.yview_moveto(position) 208 | self.rightFileTextArea.yview_moveto(position) 209 | self.leftLinenumbers.yview_moveto(position) 210 | self.rightLinenumbers.yview_moveto(position) 211 | 212 | def updateScrollY(self, first, last, type=None): 213 | self.leftFileTextArea.yview_moveto(first) 214 | self.rightFileTextArea.yview_moveto(first) 215 | self.leftLinenumbers.yview_moveto(first) 216 | self.rightLinenumbers.yview_moveto(first) 217 | self.uniScrollbar.set(first, last) 218 | 219 | def scrollBothX(self, action, position, type=None): 220 | self.leftFileTextArea.xview_moveto(position) 221 | self.rightFileTextArea.xview_moveto(position) 222 | 223 | def updateScrollX(self, first, last, type=None): 224 | self.leftFileTextArea.xview_moveto(first) 225 | self.rightFileTextArea.xview_moveto(first) 226 | self.leftHorizontalScrollbar.set(first, last) 227 | self.rightHorizontalScrollbar.set(first, last) 228 | 229 | def create_scroll_bars(self): 230 | self.uniScrollbar = Scrollbar(self.main_window) 231 | self.uniScrollbar.grid(row=self.uniScrollbarRow, column=self.uniScrollbarCol, sticky=NS) 232 | self.uniScrollbar.config(command=self.scrollBothY) 233 | self.leftFileTextArea.config(yscrollcommand=self.updateScrollY) 234 | self.rightFileTextArea.config(yscrollcommand=self.updateScrollY) 235 | self.leftLinenumbers.config(yscrollcommand=self.updateScrollY) 236 | self.rightLinenumbers.config(yscrollcommand=self.updateScrollY) 237 | 238 | self.leftHorizontalScrollbar = Scrollbar(self.main_window, orient=HORIZONTAL) 239 | self.leftHorizontalScrollbar.grid(row=self.horizontalScrollbarRow, column=self.leftHorizontalScrollbarCol, sticky=EW) 240 | self.leftHorizontalScrollbar.config(command=self.scrollBothX) 241 | self.leftFileTextArea.config(xscrollcommand=self.updateScrollX) 242 | 243 | self.rightHorizontalScrollbar = Scrollbar(self.main_window, orient=HORIZONTAL) 244 | self.rightHorizontalScrollbar.grid(row=self.horizontalScrollbarRow, column=self.rightHorizontalScrollbarCol, sticky=EW) 245 | self.rightHorizontalScrollbar.config(command=self.scrollBothX) 246 | self.rightFileTextArea.config(xscrollcommand=self.updateScrollX) 247 | -------------------------------------------------------------------------------- /ui/searchtextdialog.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2016 Yasser Elsayed 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | try: # for Python2 26 | from Tkinter import * 27 | except ImportError: # for Python3 28 | from tkinter import * 29 | 30 | class SearchTextDialog(Frame): 31 | 32 | def __init__(self, parent, textwidgets, searchButtonCallback): 33 | Frame.__init__(self, parent) 34 | 35 | self.parent = parent 36 | self.__searchCallback = searchButtonCallback 37 | self.__textwidgets = textwidgets 38 | self.__searchStr = None 39 | self.initUI() 40 | 41 | def initUI(self): 42 | self.searchTextFrame = Frame(self.parent) 43 | 44 | self.searchTextEntry = Entry(self) 45 | self.searchTextEntry.pack(fill=BOTH, expand=True, side=LEFT) 46 | 47 | self.__matchCaseVar = IntVar() 48 | self.__matchCaseVar.set(0) 49 | self.searchTextCheckbutton = Checkbutton(self, text='Match case', variable=self.__matchCaseVar, command=lambda *x: self.clearSearch()) 50 | self.searchTextCheckbutton.pack(side=LEFT, padx=10) 51 | 52 | self.searchTextButton = Button(self, text='Find', command=self.nextResult) 53 | self.searchTextButton.pack(side=LEFT) 54 | 55 | self.searchTextEntry.bind('', self.nextResult) 56 | self.searchTextButton.bind('', self.nextResult) 57 | self.__curSearchResult = {'term': None, 'indices': ['0.0'] * len(self.__textwidgets)} 58 | self.__insession = False 59 | 60 | def getSearchTerm(self): 61 | return self.searchTextEntry.get() 62 | 63 | def focus(self): 64 | self.searchTextEntry.focus_set() 65 | self.searchTextEntry.select_range(0, END) 66 | self.__insession = True 67 | 68 | def nextResult(self, *args): 69 | if not self.__insession: return 70 | 71 | searchStr = self.searchTextEntry.get() 72 | 73 | if not self.__searchStr or self.__searchStr != searchStr: 74 | self.__searchStr = searchStr 75 | self.__curSearchResult = {'term': searchStr, 'indices': ['0.0'] * len(self.__textwidgets)} 76 | 77 | if searchStr in ['', None]: return 78 | 79 | countVar = StringVar() 80 | for i,t in enumerate(self.__textwidgets): 81 | if self.__curSearchResult['indices'][i] == -1: continue 82 | nextIdx = float(self.__curSearchResult['indices'][i]) + 1 83 | pos = t.search(self.__searchStr, nextIdx, END, nocase=self.__matchCaseVar.get() == 0) 84 | self.__curSearchResult['indices'][i] = pos if pos else -1 85 | 86 | self.__searchCallback(self.__curSearchResult) 87 | 88 | def clearSearch(self): 89 | self.__curSearchResult['indices'] = ['0.0'] * len(self.__textwidgets) 90 | 91 | def unfocus(self): 92 | self.clearSearch() 93 | self.__insession = False 94 | -------------------------------------------------------------------------------- /utilities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yebrahim/pydiff/eb37e17849c80671d8ffe6892b21f257260f9c9c/utilities/__init__.py -------------------------------------------------------------------------------- /utilities/fileio.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2016 Yasser Elsayed 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | from tkinter import * 26 | 27 | class FileIO: 28 | def load_file_to_text_area(self, fname, textArea): 29 | textArea.config(state=NORMAL) 30 | try: 31 | text = open(fname).read() 32 | textArea.delete(1.0, END) 33 | textArea.insert(1.0, text) 34 | except Exception as e: 35 | showerror('Open Source File', 'Failed to read file\n"%s". Error: %s' % (fname, e)) 36 | finally: 37 | textArea.config(state=DISABLED) --------------------------------------------------------------------------------