diff --git a/doc/en/index.docbook b/doc/en/index.docbook index 4d21f6c..6841bcf 100644 --- a/doc/en/index.docbook +++ b/doc/en/index.docbook @@ -1,2085 +1,2086 @@ KDiff3"> ASCII"> ]> The &kdiff3; Handbook Michael Reeves
reeves.87@gmail.com
Joachim Eibl
joachim.eibl at gmx.de
2002-2007 Joachim Eibl 2017-2019 Michael Reeves &FDLNotice; 2019-02-22 1.08.70 - &kdiff3; is a file and directory diff and merge tool which + &kdiff3; is a file and folder diff and merge tool which -compares and merges two or three text input files or directories, +compares and merges two or three text input files or folders, shows the differences line by line and character by character(!), provides an automatic merge-facility, has an editor for comfortable solving of merge-conflicts, provides network transparency via KIO, has options to highlight or hide changes in white-space or comments, supports Unicode, UTF-8 and other file encodings, prints differences, supports version control keyword and history merging. This document describes &kdiff3;-version 1.8. KDE kdiff3 diff merge CVS triple diff compare files +folders directories version control three-way-merge in-line-differences synchronise kpart kio network transparent editor white space comments
Introduction Yet Another Diff Frontend? Several graphical diff tools exist. Why choose &kdiff3;? Let me say, why I wrote it. &kdiff3; started because I had to do a difficult merge. Merging is necessary when several people work on the same files in a project. A merge can be somewhat automated, when the merge-tool not only has the new modified files (called "branches"), but also the original file (called "base"). The merge tool will automatically choose any modification that was only done in one branch. When several contributors change the same lines, then the merge tool detects a conflict which must be solved manually. The merge then was difficult because one contributor had changed many things and corrected the indentation in many places. Another contributor also had changed much text in the same file, which resulted in several merge conflicts. The tool I used then, only showed the changed lines, but not what had changed within these lines. And there was no information about where only the indentation was changed. The merge was a little nightmare. So this was the start. The first version could show differences within a line and showed white space differences. Later many other features were added to increase the usefulness. For example if you want to compare some text quickly, then you can copy it to the clipboard and paste it into either diff window. -A feature that required a big effort was the directory comparison and merge facility, which turned +A feature that required a big effort was the folder comparison and merge facility, which turned the program almost into a full file browser. I hope &kdiff3; works for you too. Have fun! Joachim Eibl (2003) Screenshots and Features This screenshot shows the difference between two text files (using an early version of &kdiff3;): 3-way-merging is fully supported. This is useful if two people change code independently. The original file (the base) is used to help &kdiff3; to automatically select the correct changes. The merge-editor below the diff-windows allows you to solve conflicts, while showing you the output you will get. You can even edit the output. This screenshot shows three input files being merged: -&kdiff3; also helps you to compare and merge complete directories. -This screenshot shows &kdiff3; during a directory merge: +&kdiff3; also helps you to compare and merge complete folders. +This screenshot shows &kdiff3; during a folder merge: More Features Line-By-Line And Char-By-Char Diff-Viewer By using the possibilities of a graphical color display &kdiff3; shows exactly what the difference is. When you have to do many code-reviews, you will like this. See White-Space Differences At One Glance Spaces and tabs that differ appear visibly. When lines differ only in the amount of white space this can be seen at one look in the summary column on the left side. (No more worries when people change the indentation.) Triple-Diff Analyze three files and see where they differ. The left/middle/right windows are named A/B/C and have the blue/green/magenta color respectively. If one file is the same and one file is different on a line then the color shows which file is different. The red color means that both other files are different. Comfortable Merge Of Two Or Three Input Files &kdiff3; can be used to merge two or three input files and automatically merges as much as possible. The result is presented in an editable window where most conflicts can be solved with a single mouseclick: Select the respective ... buttons at the A/ B/C windows to select the source that should be used. You can also select more than one source. Since this output window is an editor even conflicts which need further corrections can be solved here without requiring another tool. And ... Fast navigation via buttons. A mouse-click into a summary column sync's all windows to show the same position. Select and copy from any window and paste into the merge result window. Overview column that shows where the changes and conflicts are. The colors are adjustable to your specific preferences. Adjustable Tab size. Option to insert spaces instead of tabs. Open files comfortably via dialog or specify files on the command line. Search for strings in all text windows with EditFind (&Ctrl;F) and EditFind Next (F3) menu items. Show the line numbers for each line. Paste clipboard or drag text into a diff input window. Network transparency via KIO. Can be used as diff-viewer from the &kdevelop; 3. Word-wrap for long lines. Support for Unicode, UTF-8 and other codecs. Support for right to left languages. ... File Comparison And Merge Command-Line Options Comparing 2 files: kdiff3 file1 file2 Merging 2 files: kdiff3 file1 file2 -m kdiff3 file1 file2 -o outputfile Comparing 3 files: kdiff3 file1 file2 file3 Merging 3 files: kdiff3 file1 file2 file3 -m kdiff3 file1 file2 file3 -o outputfile Note that file1 will be treated as base of file2 and file3. Special case: Files with the same name -If all files have the same name but are in different directories, you can +If all files have the same name but are in different folders, you can reduce typework by specifying the filename only for the first file, ⪚: - kdiff3 dir1/filename dir2 dir3 + kdiff3 folder1/filename folder2 folder3 -Commandline for starting a directory comparison or merge: -This is very similar, but now it's about directories. +Commandline for starting a folder comparison or merge: +This is very similar, but now it's about folders. - kdiff3 dir1 dir2 - kdiff3 dir1 dir2 -o destdir - kdiff3 dir1 dir2 dir3 - kdiff3 dir1 dir2 dir3 -o destdir + kdiff3 folder1 folder2 + kdiff3 folder1 folder2 -o destdir + kdiff3 folder1 folder2 folder3 + kdiff3 folder1 folder2 folder3 -o destdir -For directory comparison and merge you can continue to read here. +For folder comparison and merge you can continue to read here. Other command line options To see all available command line options type kdiff3 --help Example output: Options: -m, --merge Merge the input. -b, --base file Explicit base file. For compatibility with certain tools. -o, --output file Output file. Implies -m. E.g.: -o newfile.txt --out file Output file, again. (For compatibility with certain tools.) --auto No GUI if all conflicts are auto-solvable. (Needs -o file) --qall Don't solve conflicts automatically. (For compatibility...) --L1 alias1 Visible name replacement for input file 1 (base). --L2 alias2 Visible name replacement for input file 2. --L3 alias3 Visible name replacement for input file 3. -L, --fname alias Alternative visible name replacement. Supply this once for every input. --cs string Override a config setting. Use once for every setting. E.g.: --cs "AutoAdvance=1" --confighelp Show list of config settings and current values. --config file Use a different config file. -The option allows you to adjust a configuration value that is otherwise only adjustable via the configure dialogs. -But be aware that when &kdiff3; then terminates the changed value will be stored along with the other settings. +The option allows you to adjust a configuration value that is otherwise only adjustable via the configure dialogs. +But be aware that when &kdiff3; then terminates the changed value will be stored along with the other settings. With you can find out the names of the available items and current values. -Via you can specify a different config file. When you often use &kdiff3; +Via you can specify a different config file. When you often use &kdiff3; with completely different setups this allows you to easily switch between them. Ignorable command line options Many people want to use &kdiff3; with some version control system. But when that version control system calls &kdiff3; using command line parameters that &kdiff3; doesn't recognise, then &kdiff3; terminates with an error. -The Integration item in the settings dialog allow to specify command line parameters that should be ignored by &kdiff3;. +The Integration item in the settings dialog allow to specify command line parameters that should be ignored by &kdiff3;. They will appear in the usage help like in this example: --foo Ignored. (User defined.) Command line options to ignore: - A list of options, separated via semicolon ';'. When one of these options appears on the commandline, - then &kdiff3; will ignore it and run without reporting an error. + A list of options, separated via semicolon ';'. When one of these options appears on the commandline, + then &kdiff3; will ignore it and run without reporting an error. (Default is "u;query;html;abort"). When this isn't enough, then it is recommended to write a shell script that does the option translation. Open-Dialog Since many input files must be selectable, the program has a special open dialog: The open dialog allows you to edit the filenames by hand, selecting a file via the file-browser via the File... button or allows you to choose recent files from the drop-down lists. If you open the dialog again, then the current names still remain there. The third input file is not required. If the entry for C remains empty, then only a two file diff analysis will be done. - You can also select a directory via the Dir... button. - If for A a directory is specified then a directory-comparison + You can also select a folder via the Folder... button. + If for A a folder is specified then a folder-comparison /merge starts. If A specifies a file but B, - C or the output specify a directory, then &kdiff3; uses the - filename from A in the specified directories. + C or the output specify a folder, then &kdiff3; uses the + filename from A in the specified folders. If Merge check box is selected, then the Output line becomes editable. But it is not required to specify the output filename immediately. You can also postpone this until saving. The Configure... button opens the settings dialog, so that you can set the options before running the analysis. Paste and Drop Input Sometimes you want to compare parts of a text that is not an own file. &kdiff3; also allows you to paste text from the clipboard into the diff input window that has the focus. The diff analysis happens immediately then. In the open dialog you need not specify files then, but just close it via Cancel button. You can also use drag and drop: Drag a file from a file manager or selected text from an editor and drop it onto a diff input window. What's the idea? Sometimes a file contains two similar functions, but checking how similar they really are is a big effort if you first must create two files and then load them. Now you can simply copy, paste and compare the relevant sections. Currently you cannot drag anything from &kdiff3;. Only dropping in the diff input is supported. Some editors still interpret the drag and drop into another program like cut (instead of copy) and paste. Your original data might be lost then. Comparing Files And Interpreting The Information In The Input Windows Info Line At the top of each text window is its "info line". The info lines of the input windows contain a letter A, B or C, the editable filename, a button for browsing, and the line number of the first visible line in the window. (Note that window C is optional.) Each info line appears in a different color. - When you selected another file via browsing or finished editing the filename here - by pressing &Enter;, the new file will be loaded and + When you selected another file via browsing or finished editing the filename here + by pressing &Enter;, the new file will be loaded and compared with the already loaded file(s). Coloring The three input windows are assigned the letters A, B and C. A has color blue, B has green and C has magenta. (These are the defaults, but can be changed in the Color item in the settings dialog.) When a difference is detected then the color shows which input file differs. When both other input files differ then the color used to express this is red by default (Conflict color option in the Color item in the settings dialog). This colorscheme is especially useful in the case of three input files, which will be seen in the next section (Merging). Summary Column Left of each text is the "summary" column. If differences occurred on a line then the summary column shows the respective color. For a white-space-only difference the summary is chequered. For programming languages where white space is not so important this is useful to see at one glance if anything of importance was modified. (In C/C++ white space is only interesting within strings, comments, for the preprocessor, and some only very esoteric situations.) The vertical line separating the summary column and the text is interrupted - if the input file had no lines there. When word-wrap is enabled then this vertical + if the input file had no lines there. When word-wrap is enabled then this vertical line appears dotted for wrapped lines. Overview Column On the right side a "overview" column is visible left of the vertical scrollbar. It shows the compressed summary column of input A. All the differences and conflicts are visible at one glance. When only two input windows are used, then all differences appear red here because every difference is also a conflict. A black rectangle frames the visible part of the inputs. For very long input files, when the number of input lines is bigger than the height of the overview column in pixels, then several input lines share one overview line. A conflict then has top priority over simple differences, which have priority over no change, so that no difference or conflict is lost here. By clicking into this overview column the corresponding text will be shown. Manually Aligning Lines - Sometimes the algorithm places the wrong lines next to each other. Or you want to compare + Sometimes the algorithm places the wrong lines next to each other. Or you want to compare one piece of text with text at a completely different position in the other file. - For these situations you can manually instruct &kdiff3; to align certain lines. - Mark the text for which you want to improve the alignment with the mouse as you would - for copy and paste in the first diff view and then choose DiffviewAdd Manual Diff Alignment menu item (&Ctrl;Y). An orange bar will appear in - the summary column next to the chosen text. Repeat this for the second and + For these situations you can manually instruct &kdiff3; to align certain lines. + Mark the text for which you want to improve the alignment with the mouse as you would + for copy and paste in the first diff view and then choose DiffviewAdd Manual Diff Alignment menu item (&Ctrl;Y). An orange bar will appear in + the summary column next to the chosen text. Repeat this for the second and (if available) third diff view. &kdiff3; will immediately recalculate the differences everytime you do this, - and will align the chosen lines. Of course some of the previously matching lines in between + and will align the chosen lines. Of course some of the previously matching lines in between might not match anymore. Currently merging doesn't support the use of manual diff help. Manually Joining and Splitting Diff Sections - In some cases &kdiff3; will see too many or too few diff sections for merging. In such a + In some cases &kdiff3; will see too many or too few diff sections for merging. In such a case you can join or split existing sections. - Add new sections by first selecting text in the lines that belong together in either input window (as for copying to the clipboard). + Add new sections by first selecting text in the lines that belong together in either input window (as for copying to the clipboard). Then choose MergeSplit Diff At Selection menu item. Splits will be added above the first line and below the last line of the selected text. If you only want to add one section, then select text beginning at another section-split. For joining sections in either input window select something in the lines from the sections to join. (You can join several sections in one step too.) Then choose MergeJoin Selected Diffs menu item. Merging And The Merge Output Editor Window The merge output editor window (below the diff input windows) also has an info line at the top showing Output:, the filename and [Modified] tag if you edited something. Usually it will contain some text through the automatic merge facilities, but often it will also contain conflicts. !!! Saving is disabled until all conflicts are resolved !!! (Use the Go to Previous/Next Unsolved Conflict buttons to find the remaining conflicts.) With only two input files every difference is also a conflict that must be solved manually. With three input files the first file is treated as base, while the second and third input files contain modifications. When at any line only either input B or input C have changed but not both then the changed source will automatically be selected. Only when B and C have changed on the same lines, then the tool detects a conflict that must be solved manually. When B and C are the same, but not the same as A, then C is selected. The Summary Column The merge output editor window also has a summary column on the left. It shows the letter of the input from which a line was selected or nothing if all three sources where equal on a line. For conflicts it shows a questionmark "?" and the line shows "<Merge Conflict>", all in red. Because solving conflicts line by line would take very long, the lines are grouped into groups that have the same difference and conflict characteristics. But only-white-space-conflicts are separated from non-white-space-conflicts in order to ease the merging of files were the indentation changed for many lines. Setting The Current Group And Synchronising Merge And Diff View Position When clicking into the summary column with the &LMB; in either window then the beginning of the group belonging to that line will shown in all windows. This group then becomes the "current group". It is highlighted with the Current range diff background color option of Integration item in the settings dialog and a black bar appears on the left side of the text. Choosing Inputs A, B or C For Current Conflict And Editing The Button bar below the menubar contains three input selector buttons containing the letters A, B and C. Click the input selector button to insert (or remove if already inserted) the lines from the respective source. To choose the lines from several inputs click the respective buttons in the needed order. For example if you want that the lines from B appear before the lines from A in the output, first click B, then A. If you use the Automatically Go to Next Unsolved Conflict After Source Selection button (see Auto-Advance section), you should disable this before choosing lines from several inputs or if you want to edit the lines after your choice. Otherwise &kdiff3; will jump to the next conflict after choosing the first input. - It is often helpful directly edit the merge output. - The summary column will show "m" for every line that was manually modified. - When for instance the differences are aligned in a way that simply choosing - the inputs won't be satisfactory, then you can mark the needed text and use + It is often helpful directly edit the merge output. + The summary column will show "m" for every line that was manually modified. + When for instance the differences are aligned in a way that simply choosing + the inputs won't be satisfactory, then you can mark the needed text and use normal copy and paste to put it into the merge output. Sometimes, when a line is removed either by automatic merge or by editing and no other lines remain in that group, then the text <No src line> will appear in that line. This is just a placeholder for the group for when you might change your mind and select some source again. This text won't appear in the saved file or in any selections you want to copy and paste. The text "<Merge Conflict>" will appear in the clipboard if you copy and paste some text containing such a line. But still be careful to do so. Choosing Input A, B, or C for All Conflicts The normal merge will start by solving simple conflicts automatically. But the Merge menu provides some actions for other common needs. If you have to select the same source for most conflicts, then you can choose A, B or C everywhere, or only for the remaining unsolved conflicts, or for unsolved white space conflicts. If you want to decide every single delta yourself, you can Set Deltas to Conflicts. Or if you want to return to the automatic choices of &kdiff3; then select Automatically Solve Simple Conflicts. &kdiff3; then restarts the merge. For actions that change your previous modifications &kdiff3; will ask for your confirmation before proceeding. When choosing either source for unsolved white space conflicts and the Ignore numbers (treat as white space) or Ignore C/C++ comments (treat as white space) options at Diff item in the settings dialog are used then changes in numbers or comments will be treated like white space too. Automatic Merge of Version Control Keywords and History (Log) Many version control systems support special keywords in the file. (⪚ "$Date$", "$Header$", "$Author$", "$Log$", &etc;) During the check-in the version control system (VCS) changes these lines. For instance "$Date$" will turn into "$Date: 2005/03/22 18:45:01 $". Since this line will be different in every version of the file, it would require manual interaction during the merge. &kdiff3; offers automatic merge for these items at Merge item in the settings dialog. For simple lines that match the Auto merge regular expression option in all input-files &kdiff3; will choose the line from B or - if available - from C. (Additionally it is necessary that the lines in question line up in the comparison and the previous line contains no conflict.) This auto merge can either be run immediately after a merge starts (activate the option Run regular expression auto merge on merge start) or later via MergeRun Regular Expression Auto Merge menu item. Automatic merge for version control history (also called "log") is also supported. The history automerge can either run immediately when the merge starts by activating the option Merge version control history on merge start at Merge item in the settings dialog or later via the MergeAutomatically Solve History Conflicts menu item. Usually the version control history begins with a line containing the keyword "$Log$". This must be matched by the History start regular expression: option. &kdiff3; detects which subsequent lines are in the history by analysing the leading characters that came before the "$Log$" keyword. If the same "leading comment" characters also appears in the following lines, then they are also included in the history. -During each check-in the VCS writes a unique line specifying version-, date- and time-information +During each check-in the VCS writes a unique line specifying version-, date- and time-information followed by lines with user comments. -These lines form one history-entry. This history section grows with every check-in and the -most recent entries appear at the top (after the history start line). +These lines form one history-entry. This history section grows with every check-in and the +most recent entries appear at the top (after the history start line). -When for parallel development two or more developers check-in a branch of the file then -the merge history will contain several entries that appear as conflicts during the merge -of the branches. Since merging these can become very tedious, &kdiff3; offers support with two +When for parallel development two or more developers check-in a branch of the file then +the merge history will contain several entries that appear as conflicts during the merge +of the branches. Since merging these can become very tedious, &kdiff3; offers support with two possible strategies: Just insert the history information from both contributors at the top or sort the history information by a user defined key. The just-insert-all-entries-method is easier to configure. &kdiff3; just needs a method to detect, which lines belong to one history entry. Most VCS insert an empty line after each history entry. If there are no other empty lines, this is a sufficient criterion for &kdiff3;. Just set an empty History entry start regular expression at Merge item in the settings dialog. If the empty line criterion isn't sufficient, you can specify a regular expression to detect the history entry start. Note that &kdiff3; will remove duplicate history entries. If a history entry appeared several times in the history of a input file, only one entry will remain in the output. If you want to sort the history, then you have to specify how the sort key should be built. Use parentheses in the History entry start regular expression at Merge item in the settings dialog to group parts of the regular expression that should later be used for the sort key. Then specify the History entry start sort key order option specifying a comma "," separated list of numbers referring to the position of the group in the regular expression. Because this is not so easy to get right immediately, you are able to test and improve the regular expressions and key-generation in a dedicated dialog by pressing the Test your regular expressions button. Example: Assume a history that looks like this: /************************************************************************** ** HISTORY: $Log: \toms_merge_main_view\MyApplication\src\complexalgorithm.cpp $ ** ** \main\integration_branch_12 2 Apr 2001 10:45:41 tom ** Merged branch simon_branch_15. ** ** \main\henry_bugfix_branch_7\1 30 Mar 2001 19:22:05 henry ** Improved the speed for subroutine convertToMesh(). ** Fixed crash. **************************************************************************/ The history start line matches the regular expression ".*\$Log.*\$.*". Then follow the history entries. The line with the "$Log$" keyword begins with two "*" after which follows a space. &kdiff3; uses the first non-white-space string as "leading comment" and assumes that the history ends in the first line without this leading comment. In this example the last line ends with a string that also starts with two "*", but instead of a space character more "*" follow. Hence this line ends the history. If history sorting isn't required then the history entry start line regular expression could look like this. (This line is split in two because it wouldn't fit otherwise.) \s*\\main\\\S+\s+[0-9]+ (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9][0-9][0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\s+.* For details about regular expressions please see the regular expression documentation. Note that "\s" (with lowercase "s") matches any white space and "\S" (with uppercase "S") matches any non-white-space. In our example the history entry start contains first the version info with reg. exp. "\\main\\\S+", the date consisting of day "[0-9]+", month "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)" and year "[0-9][0-9][0-9][0-9]", the time "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]" and finally the developers login name ".*". Note that the "leading comment" characters (in the example "**") will already be removed by &kdiff3; before trying to match, hence the regular expression begins with a match for none or more white-space characters "\s*". Because comment characters can differ in each file (⪚ C/C++ uses other comment characters than a Perl script) &kdiff3; takes care of the leading comment characters and you should not specify them in the regular expression. -If you require a sorted history. Then the sortkey must be calculated. For this the -relevant parts in the regular expression must be grouped by parentheses. +If you require a sorted history. Then the sortkey must be calculated. For this the +relevant parts in the regular expression must be grouped by parentheses. (The extra parentheses can also stay in if history sorting is disabled.) \s*\\main\\(\S+)\s+([0-9]+) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([0-9][0-9][0-9][0-9]) ([0-9][0-9]:[0-9][0-9]:[0-9][0-9])\s+(.*) - The parentheses now contain 1. version info, 2. day, 3. month, 4. year, 5. time, 6. name. + The parentheses now contain 1. version info, 2. day, 3. month, 4. year, 5. time, 6. name. But if we want to sort by date and time, we need to construct a key with the elements in a different order of appearance: First the year, followed by month, day, time, version info and name. Hence the sortkey order to specify is "4,3,2,5,1,6". Because month names aren't good for sorting ("Apr" would be first) &kdiff3; detects in which order the month names were given and uses that number instead ("Apr" -> "04"). And if a pure number is found it will be transformed to a 4-digit value with leading zeros for sorting. Finally the resulting sort key for the first history entry start line will be: -2001 04 0002 10:45:41 integration_branch_12 tom +2001 04 0002 10:45:41 integration_branch_12 tom For more information also see Merge Settings section. Navigation And Editing Much navigation will be done with the scroll bars and the mouse but you can also navigate with the keys. If you click into either window then you can use the shortcuts &Left;, &Right;, &Up;, &Down;, &PgUp;, &PgDn;, &Home;, &End;, &Ctrl;&Home; and &Ctrl;&End; as you would in other programs. The overview-column next to the vertical scroll bar of the input files can also be used for navigating by clicking into it. You can also use the wheel mouse to scroll up and down. In the merge output editor you can also use the other keys for editing. You can toggle between insert and overwrite mode with the &Ins; key. (Default is insert-mode.) A click with the &LMB; into any summary column will synchronise all windows to show the beginning of the same group of lines (as explained in section Setting The Current Group And Synchronising Merge And Diff View Position). The Button bar also contains nine navigation buttons with which you can jump to the current/first/last difference, to the previous/next difference (&Ctrl;&Down;/&Ctrl;&Up;), to the previous/next conflict (&Ctrl;&PgDn;/&Ctrl;&PgUp;), or to the previous/next unsolved conflict. Note that for &kdiff3; a "conflict" that was not automatically solved at the start of the merge stays a "conflict" even if it is solved. Hence the necessity to distinguish "unsolved conflicts". Auto-Advance There also is a button Automatically Go to Next Unsolved Conflict After Source Selection (Auto-Advance). If you enable this, then, when one source is selected, &kdiff3; will jump to and select the next unsolved conflict automatically. This can help when you always want to choose one source only. When you need both sources, or you want to edit after selecting, then you probably want to switch this off. Before proceeding to the next unsolved conflict &kdiff3; shows you the effect of your choice for a short time. This delay is adjustable in the Merge item in the settings dialog: You can specify the Auto advance delay (ms): in milli seconds between 0 and 2000. Hint: Tired of many clicks? - Use a small Auto-Advance-delay and the shortcuts &Ctrl;1/2/3 to select A/B/C for many conflicts. Select, Copy And Paste The input windows don't show a cursor, so selections must be made with the mouse by clicking with the &RMB; at the start, holding down the mousebutton and moving to the end, where you release the mouse button again. You can also select a word by double clicking it. In the merge output editor you can also select via the keyboard by holding the &Shift; key and navigation with the cursor keys. - If the selection exceeds the visible range you can move the mouse over the - window borders which causes &kdiff3; to scroll in that direction. + If the selection exceeds the visible range you can move the mouse over the + window borders which causes &kdiff3; to scroll in that direction. - For very large selections you can also use the navigation keys while holding down - the mouse. E.g. use &PgUp; and &PgDn; to quickly go to a certain position. At the + For very large selections you can also use the navigation keys while holding down + the mouse. E.g. use &PgUp; and &PgDn; to quickly go to a certain position. At the end position release the mouse button. In order to select everything in the current window use Edit Select All menu item (&Ctrl;A). To copy to the clipboard you must press the Copy button (&Ctrl;C or &Ctrl;&Ins;). But there exists an option Auto copy selection at Editor item in the settings dialog. If this is enabled, then whatever you select is copied immediately and you don't need to explicitly copy. But pay attention when using this because the contents of the clipboard might then be destroyed accidentally. Cut (&Ctrl;X or &Shift;∇) copies to the clipboard and deletes the selected text. Paste (&Ctrl;V or &Shift;&Ins;) inserts the text in the clipboard at the cursorposition or over the current selection. - If you paste to either diff input window the contents of the clipboard will - be shown in that window and the comparison will restart immediately. This is - useful if you want to quickly grab a piece of text from somewhere and + If you paste to either diff input window the contents of the clipboard will + be shown in that window and the comparison will restart immediately. This is + useful if you want to quickly grab a piece of text from somewhere and compare it with something else without first creating files. Saving - Saving will only be allowed, when all conflicts were solved. If the file already exists and the Backup files option is enabled at Directory item in the settings dialog then the existing file will be renamed with an .orig extension, but if such a file exists it will be deleted. When you exit or start another diff-analysis and data wasn't saved yet, then &kdiff3; will ask if you want to save, cancel or proceed without saving. (&kdiff3; does not catch any signals. So if you "kill" a &kdiff3; instance then your data will be lost.) + Saving will only be allowed, when all conflicts were solved. If the file already exists and the Backup files option is enabled at Folder item in the settings dialog then the existing file will be renamed with an .orig extension, but if such a file exists it will be deleted. When you exit or start another diff-analysis and data wasn't saved yet, then &kdiff3; will ask if you want to save, cancel or proceed without saving. (&kdiff3; does not catch any signals. So if you "kill" a &kdiff3; instance then your data will be lost.) Line endings are saved according to the normal method on the underlying operating system. For Unices each line ends with an linefeed-character "\n", while for Win32-based systems each line ends with a carriage-return + a linefeed "\r\n". &kdiff3; does not preserve the line-endings of the input files, which also means that you shouldn't use &kdiff3; with binary files. Finding Strings You can search for a string in any text-window of &kdiff3;. The EditFind... menu item (&Ctrl;F) opens a dialog that lets you specify the string to search for. You can also select the windows which should be searched. Searching will always start at the top. Use the EditFind Next... menu item (F3) to proceed to the next occurrence. If you select to search several windows then the first window will be searched from top to bottom before the search starts in the next window at the top again, &etc; Printing &kdiff3; supports printing for textfile differences. The FilePrint... menu item (&Ctrl;P) opens a dialog that allows you to select the printer and to adjust other options. - There are several possibilities to adjust the range. Due to different printing + There are several possibilities to adjust the range. Due to different printing dialogs on different operating systems, the method to achieve certain range selections varies. All:Print everything. - Current:Print a page starting at the first visible line in the window. + Current:Print a page starting at the first visible line in the window. (On systems without this option this can be achieved by specifying page number 10000 for printing.) Selection: - Before choosing to print select text with the mouse (like for copy and paste) - in one of the diff input windows to define the start and end line. If no text - in one of the diff input windows was selected, then this won't be an available - choice. (On systems without this option this can be achived by specifying page + Before choosing to print select text with the mouse (like for copy and paste) + in one of the diff input windows to define the start and end line. If no text + in one of the diff input windows was selected, then this won't be an available + choice. (On systems without this option this can be achived by specifying page number 9999 for printing.) Range:Specify the first and last page. Other important options for printing will be taken from the normal options: Font, font size Show line numbers Word wrap Colors &etc; Landscape formatting is also recommended for printing. Options Options and the recent-file-list will be saved when you exit the program, - and reloaded when you start it. (Settings + and reloaded when you start it. (Settings Configure &kdiff3;... menu item). Font Select a fixed width font. (On some systems this dialog will also present variable width fonts, but you should not use them.) Italic font for deltas: If you select this, then text differences will be drawn with the italic version of the selected font. If the font doesn't support italic, then this does nothing. Colors Editor and Diff Views: Foreground color: Usually black. Background color: Usually white. Diff background color: Usually light gray. Color A: Usually dark blue. Color B: Usually dark green. Color C: Usually dark magenta. Conflict color: Usually red. Current range background color: Usually light yellow. Current range diff background color: Usually dark yellow. Color for manually aligned difference ranges: Usually orange. - Directory Comparison View: + Folder Comparison View: Newest file color: Usually green. Oldest file color: Usually red. Middle age file color: Usually strong yellow. Color for missing files: Usually black. - Changing the colors for directory comparison will be effective only when starting the next directory comparison. + Changing the colors for folder comparison will be effective only when starting the next folder comparison. On systems with only 16 or 256 colors some colors are not available in pure form. On such systems the Defaults button will choose a pure color. Editor Settings - Tab inserts spaces + Tab inserts spaces If this is disabled and you press the key, a tab-character is inserted, otherwise the appropriate amount of characters is inserted. Tab size: Can be adjusted for your specific needs. Default is 8. Auto indentation When pressing &Enter; or Return the indentation of the previous line is used for the new line. Auto copy selection Every selection is immediately copied to the clipboard when active and you needn't explicitly copy it. Line end style: When saving you can select what line end style you prefer. The default setting is the common choice for the used operating system. Diff Settings When comparing files, &kdiff3; first it tries to match lines that are equal in all input files. Only during this step it might ignore white space. The second step compares each line. In this step white space will not be ignored. Also during the merge white space will not be ignored. Ignore numbers (treat as white space) Default is off. Number characters ('0'-'9', '.', '-') will be ignored in the first part of the analysis in which the line matching is done. In the result the differences will be shown nevertheless, but they are treated as white space. Ignore C/C++ comments (treat as white space) Default is off. Changes in comments will be treated like changes in white space. - Ignore case (treat as white space) Default is off. + Ignore case (treat as white space) Default is off. Case-differences of characters (like 'A' vs. 'a') will be treated like changes in white space. Preprocessor command: See next section. Line-matching preprocessor command: See next section. Try hard (slower) Try hard to find an even smaller delta. (Default is on.) This will probably be effective for complicated and big files. And slow for very big files. Align B and C for 3 input files Try to align B and C when comparing or merging three input files. Not recommended for merging because merge might get more complicated. (Default is off.) Merge Settings Auto advance delay (ms): When in auto-advance-mode this setting specifies how long to show the result of the selection before jumping to the next unsolved conflict. Show info dialogs Show a dialog with information about the number of conflicts. White space 2/3-file merge default: Automatically solve all white-space conflict by choosing the specified file. (Default is manual choice.) Useful if white space really isn't important in many files. If you need this only occasionally better use Choose A/B/C for All Unsolved Whitespace Conflicts in the Merge menu. Note that if you enable either Ignore numbers (treat as white space) or Ignore C/C++ comments (treat as white space) then this auto-choice also applies for conflicts in numbers or comments. Auto merge regular expression: Regular expression for lines where &kdiff3; should automatically choose one source. See also Automatic Merge.... Run regular expression auto merge on merge start If activated &kdiff3; runs the automatic merge using the Auto merge regular expression: option when a merge is started. History entry start regular expression: Regular expression for the start of the merge history entry. Usually this line contains the "$Log$" keyword. Default value: ".*\$Log.*\$.*" History entry start regular expression: A merge history entry consists of several lines. Specify the regular expression to detect the first line (without the leading comment). Use parentheses to group the keys you want to use for sorting. If left empty, then &kdiff3; assumes that empty lines separate history entries. See also Automatic Merge.... History merge sorting Enable version control history sorting. History entry start sort key order: Each pair of parentheses used in the regular expression for the history start entry groups a key that can be used for sorting. Specify the list of keys (that are numbered in order of occurrence starting with 1) using ',' as separator (⪚ "4,5,6,1,2,3,7"). If left empty, then no sorting will be done. See also Automatic Merge.... Merge version control history on merge start If activated &kdiff3; runs the automatic history merging using aforementioned options when a merge is started. Max number of history entries: &kdiff3; truncates the history list after the specified number of entries. Use -1 to avoid truncation. (Default is -1). Test your regular expressions This button shows a dialog that allows you to improve and test the regular expressions above. Just copy the respective data from your files into the example lines. The Match result: option will immediately show whether the match succeeds or not. The Sort key result: will display the key used for history merge sorting. Irrelevant merge command: - Specify a command of your own that should be called when &kdiff3; detects - that for a three file merge the file from B doesn't contribute any + Specify a command of your own that should be called when &kdiff3; detects + that for a three file merge the file from B doesn't contribute any relevant data that isn't already contained in the file from C. The command is called with the three filenames as parameters. - Data matched by the Auto merge regular expression or in the + Data matched by the Auto merge regular expression or in the history isn't considered relevant. Auto save and quit on merge without conflicts If &kdiff3; was started for a file-merge from the command line and all conflicts are solvable without user interaction then automatically save and quit. (Similar to command line option .) -Directory Merge +Folder Merge - These options are concerned with scanning the directory and handling the - merge: See the Directory Comparison/Merge + These options are concerned with scanning the folder and handling the + merge: See the Folder Comparison/Merge Docs for details. Yet there is one option here that is also relevant for saving single files: Backup files (.orig) When a file is saved and an older version already exists, then the original version will be renamed with an .orig extension. If an old backup file with .orig extension already exists then this will be deleted without backup. Regional and Language Options Use the same encoding for everythingThe following encoding options can be adjusted separately for each item or if this option is true, all values will take the first value. Note: Local Encoding is "..."Above the codec-selectors appears this note that tells you what the local encoding is. (This is not adjustable but for your information just in case you don't know your local encoding, but need to select it.) File Encoding for A/B/C: Adjust the file encoding for input files. This has an effect on how the special characters are interpreted. Since you can adjust each codec separately you can even compare and merge files that were saved using different codecs. File Encoding for Merge Output and Saving: When you have edited a file, then you can adjust which encoding will be used when saving to disk. File Encoding for Preprocessor Files:When you define preprocessors then they might not be able to operate on your codec. (⪚: Your files are 16 bit unicode and your preprocessor can only take 8 bit &ASCII;.) With this option you can define the encoding of preprocessor output. Right To Left Language:Some languages are written right to left. When this option is enabled, &kdiff3; draws the text from right to left in the diff input windows and in the merge output window. Note that if you start &kdiff3; with the command line option then all layouting will be done right to left too. (This is a feature provided by &Qt;.) This documentation was written assuming that this option or reverse layout are disabled. So some references to "left" or "right" must be replaced by their respective counterpart if you use these options. Miscellaneous (These options and actions are available in menus or the buttonbar.) Overview options: These choices are only available when you compare three files. In normal mode all - differences are shown in one color-coded overview-column. But sometimes you are + differences are shown in one color-coded overview-column. But sometimes you are especially interested in the differences between only two of these three files. - Selecting "A vs. B", "A vs. C" or "B vs. C"-overview will show a second overview + Selecting "A vs. B", "A vs. C" or "B vs. C"-overview will show a second overview column with the required information next to the normal overview. Word Wrap Diff Windows Wrap lines when their length would exceed the width of a window. Show Window A/B/C: Sometimes you want to use the space on the screen better for long lines. Hide the windows that are not important. (In the Windows-menu.) Toggle Split Orientation Switch between diff windows shown next to each other (A left of B left of C) or above each other (A above B above C). This should also help for long lines. (In the Window menu.) Merge Current File Works if you only compare two files. A single click starts the merge and uses the filename of the last input-file as the default output filename. (When this is used to restart a merge, then the output filename will be preserved.) Show White Space Turn this off to suppress any highlighting of white-space-only changes in the text or overview-columns. - (Note that this also applies to changes in numbers or comments if the options + (Note that this also applies to changes in numbers or comments if the options Ignore numbers (treat as white space) or Ignore C/C++ comments (treat as white space) are active.) Show Space && Tabulator Characters Sometimes the visible spaces and tabs are disturbing. You can turn this off. Show Line Numbers You can select if line numbers should be shown for the input files. Configuring Keyboard-Shortcuts Currently only the &frameworks; version supports user-configurable keyboard-shortcuts. (Menu SettingsConfigure Shortcuts...) Preprocessor Commands &kdiff3; supports two preprocessor options. Preprocessor command: When any file is read, it will be piped through this external command. The output of this command will be visible instead of the original file. You can write your own preprocessor that fulfills your specific needs. Use this to cut away disturbing parts of the file, or to automatically correct the indentation &etc; Line-matching preprocessor command: When any file is read, it will be piped through this external command. If a preprocessor-command (see above) is also specified, then the output of the preprocessor is the input of the line-matching preprocessor. The output will only be used during the line matching phase of the analysis. You can write your own preprocessor that fulfills your specific needs. Each input line must have a corresponding output line. The idea is to allow the user greater flexibility while configuring the diff-result. But this requires an external program, and many users don't want to write one themselves. -The good news is that very often sed or perl -will do the job. +The good news is that very often sed or perl +will do the job. Example: Simple testcase: Consider file a.txt (6 lines): aa ba ca da ea fa And file b.txt (3 lines): cg dg eg Without a preprocessor the following lines would be placed next to each other: aa - cg ba - dg ca - eg da ea fa This is probably not wanted since the first letter contains the actually interesting information. -To help the matching algorithm to ignore the second letter we can use a line matching preprocessor +To help the matching algorithm to ignore the second letter we can use a line matching preprocessor command, that replaces 'g' with 'a': sed 's/g/a/' With this command the result of the comparison would be: aa ba ca - cg da - dg ea - eg fa Internally the matching algorithm sees the files after running the line matching preprocessor, -but on the screen the file is unchanged. (The normal preprocessor would change the data also on +but on the screen the file is unchanged. (The normal preprocessor would change the data also on the screen.) <command>sed</command> Basics This section only introduces some very basic features of sed. For more -information see info:/sed or +information see info:/sed or https://www.gnu.org/software/sed/manual/html_mono/sed.html. A precompiled version for &Windows; can be found at http://unxutils.sourceforge.net. -Note that the following examples assume that the sed command is in some +Note that the following examples assume that the sed command is in some folder in the PATH environment variable. If this is not the case, you have to specify the full absolute -path for the command. +path for the command. In this context only the sed substitute command is used: sed 's/REGEXP/REPLACEMENT/FLAGS' Before you use a new command within &kdiff3;, you should first test it in a console. Here the echo command is useful. Example: echo abrakadabra | sed 's/a/o/' -> obrakadabra -This example shows a very simple sed-command that replaces the first occurrence +This example shows a very simple sed-command that replaces the first occurrence of "a" with "o". If you want to replace all occurrences then you need the "g" flag: echo abrakadabra | sed 's/a/o/g' -> obrokodobro -The "|"-symbol is the pipe-command that transfers the output of the previous +The "|"-symbol is the pipe-command that transfers the output of the previous command to the input of the following command. If you want to test with a longer file -then you can use cat on &UNIX; like systems or type +then you can use cat on &UNIX; like systems or type on &Windows; like systems. sed will do the substitution for each line. cat filename | sed options Examples For <command>sed</command> Use In &kdiff3; Ignoring Other Types Of Comments Currently &kdiff3; understands only C/C++ comments. Using the Line-matching preprocessor command: option you can also ignore other types of comments, by converting them into C/C++-comments. Example: To ignore comments starting with "#", you would like to convert them to "//". Note that you also must enable the Ignore C/C++ comments (treat as white space) option to get an effect. An appropriate Line-matching preprocessor command: would be: sed 's/#/\/\//' Since for sed the "/" character has a special meaning, it is necessary to place the "\" character before each "/" in the replacement-string. Sometimes the "\" is required to add or remove a special meaning of certain characters. The single quotation marks (') are only important when testing on the command shell as it will otherwise attempt to process some characters. &kdiff3; does not do this except for the escape sequences '\"' and '\\'. Caseinsensitive Diff Use the following Line-matching preprocessor command: to convert all input to uppercase: sed 's/\(.*\)/\U\1/' -Here the ".*" is a regular expression that matches any string and in this context matches all characters in the line. +Here the ".*" is a regular expression that matches any string and in this context matches all characters in the line. The "\1" in the replacement string refers to the matched text within the first pair of "\(" and "\)". The "\U" converts the inserted text to uppercase. Ignoring Version Control Keywords CVS and other version control systems use several keywords to insert automatically generated strings (info:/cvs/Keyword substitution). All of them follow the pattern "$KEYWORD generated text$". We now need a line-matching preprocessor command that removes only the generated text: sed 's/\$\(Revision\|Author\|Log\|Header\|Date\).*\$/\$\1\$/' -The "\|" separates the possible keywords. You might want to modify this list +The "\|" separates the possible keywords. You might want to modify this list according to your needs. The "\" before the "$" is necessary because otherwise the "$" matches the end of the line. While experimenting with sed you might come to understand and even like -these regular expressions. They are useful because there are many other programs that also +these regular expressions. They are useful because there are many other programs that also support similar things. Ignoring Numbers Ignoring numbers actually is a built-in option. But as another example, this is how it would look as a line-matching preprocessor command. sed 's/[0123456789.-]//g' Any character within '[' and ']' is a match and will be replaced with nothing. Ignoring Certain Columns Sometimes a text is very strictly formatted, and contains columns that you always want to ignore, while there are -other columns you want to preserve for analysis. In the following example the first five columns (characters) are +other columns you want to preserve for analysis. In the following example the first five columns (characters) are ignored, the next ten columns are preserved, then again five columns are ignored and the rest of the line is preserved. sed 's/.....\(..........\).....\(.*\)/\1\2/' -Each dot '.' matches any single character. The "\1" and "\2" in the replacement string refer to the matched text within the first +Each dot '.' matches any single character. The "\1" and "\2" in the replacement string refer to the matched text within the first and second pair of "\(" and "\)" denoting the text to be preserved. Combining Several Substitutions -Sometimes you want to apply several substitutions at once. You can then use the +Sometimes you want to apply several substitutions at once. You can then use the semicolon ';' to separate these from each other. Example: echo abrakadabra | sed 's/a/o/g;s/\(.*\)/\U\1/' -> OBROKODOBRO Using <command>perl</command> instead of <command>sed</command> -Instead of sed you might want to use something else like +Instead of sed you might want to use something else like perl. perl -p -e 's/REGEXP/REPLACEMENT/FLAGS' -But some details are different in perl. Note that where +But some details are different in perl. Note that where sed needed "\(" and "\)" perl requires the simpler "(" and ")" without preceding '\'. Example: sed 's/\(.*\)/\U\1/' perl -p -e 's/(.*)/\U\1/' Order Of Preprocessor Execution -The data is piped through all internal and external preprocessors in the +The data is piped through all internal and external preprocessors in the following order: Normal preprocessor, Line-matching preprocessor, Ignore case (treat as white space) (conversion to uppercase), Detection of C/C++ comments, Ignore numbers (treat as white space), Ignore white space The data after the normal preprocessor will be preserved for display and merging. The other operations only modify the data that the line-matching-diff-algorithm sees. -In the rare cases where you use a normal preprocessor note that +In the rare cases where you use a normal preprocessor note that the line-matching-preprocessor sees the output of the normal preprocessor as input. Warning The preprocessor-commands are often very useful, but as with any option that modifies -your texts or hides away certain differences automatically, you might accidentally overlook +your texts or hides away certain differences automatically, you might accidentally overlook certain differences and in the worst case destroy important data. For this reason during a merge if a normal preprocessor-command is being used &kdiff3; will tell you so and ask you if it should be disabled or not. But it won't warn you if a Line-matching preprocessor command: option is active. The merge will not complete until all conflicts are solved. If you disabled DiffviewShow White Space menu item then the differences that were removed with the Line-matching preprocessor command: option will also be invisible. If the Save button remains disabled during a merge (because of remaining conflicts), make sure to enable DiffviewShow White Space menu item. If you don't want to merge these less important differences manually you can select MergeChoose [A|B|C] for All Unsolved Whitespace Conflicts menu item. -Directory Comparison and Merge with &kdiff3; -Introduction into Directory Comparison and Merge +Folder Comparison and Merge with &kdiff3; +Introduction into Folder Comparison and Merge - Often programmers must modify many files in a directory to achieve their - purpose. For this &kdiff3; also lets you compare and merge complete directories + Often programmers must modify many files in a folder to achieve their + purpose. For this &kdiff3; also lets you compare and merge complete folders recursively! - Even though comparing and merging directories seems to be quite obvious, + Even though comparing and merging folders seems to be quite obvious, there are several details that you should know about. Most important is of course the fact that now many files might be affected by each operation. If you don't have backups of your original data, then it can be very hard or even impossible to return to the original state. So before starting a merge, make sure that your data is safe, and going back is possible. If you make an archive or use some version control system is your decision, but even experienced programmers and integrators need the old sources now and then. And note that even though I (the author of &kdiff3;) try to do my best, I can't guarantee that there are no bugs. According to the &GNU; GPL there is NO WARRANTY whatsoever for this program. So be humble and always keep in mind:
To err is human, but to really mess things up you need a computer.
So this is what this program can do for you: &kdiff3; ... - ... reads and compares two or three directories recursively, + ... reads and compares two or three folders recursively, ... takes special care of symbolic links, ... lets you browse files on mouse double click, ... for each item proposes a merge operation, which you can change - before starting the directory merge, + before starting the folder merge, ... lets you simulate the merge and lists the actions that would take place, without actually doing them, ... lets you really do the merge, and lets you interact whenever manual interaction is needed, ... lets you run the selected operation for all items (F7 key) or the selected item (F6 key), ... lets you continue the merge after manual interaction with F7 key, ... optionally creates backups, with the .orig extension, ...
-Starting Directory Comparison Or Merge +Starting Folder Comparison Or Merge This is very similar to the single file merge and comparison. You just - have to specify directories on the command line or in the file-open + have to specify folders on the command line or in the file-open dialog. -Compare/Merge two directories: +Compare/Merge two folders: - kdiff3 dir1 dir2 - kdiff3 dir1 dir2 -o destdir + kdiff3 folder1 folder2 + kdiff3 folder1 folder2 -o destdir - If no destination directory is specified, then &kdiff3; will use dir2. + If no destination folder is specified, then &kdiff3; will use folder2. -Compare/Merge three directories: +Compare/Merge three folders: - kdiff3 dir1 dir2 dir3 - kdiff3 dir1 dir2 dir3 -o destdir + kdiff3 folder1 folder2 folder3 + kdiff3 folder1 folder2 folder3 -o destdir - When three directories are merged then dir1 + When three folders are merged then folder1 is used as the base for the merge. - If no destination directory is specified, then &kdiff3; will use dir3 - as the destination directory for the merge. + If no destination folder is specified, then &kdiff3; will use folder3 + as the destination folder for the merge. Note that only the comparison starts automatically, not the merge. For this you first must select a menu entry or the F7 key. (More details later.) -Directory Merge Visible Information +Folder Merge Visible Information - While reading the directories a message-box appears that informs you of - the progress. If you abort the directory scan, then only files that have + While reading the folders a message-box appears that informs you of + the progress. If you abort the folder scan, then only files that have been compared until then will be listed. - When the directory scan is complete then &kdiff3; will show a listbox with + When the folder scan is complete then &kdiff3; will show a listbox with the results left, ... - + ... and details about the currently selected item on the right: The Name Column - Each file and directory that was found during the scan is shown here in + Each file and folder that was found during the scan is shown here in a tree. You can select an item by clicking it with the mouse once. - The directories are collapsed by default. You can expand and collapse + The folders are collapsed by default. You can expand and collapse them by clicking on the "+"/"-" or by double-clicking the item or - by using the &Left;/&Right; cursor keys. The Directory menu also contains two - actions Fold All Subdirs and Unfold All Subdirs with which you can - collapse or expand all directories at once. + by using the &Left;/&Right; cursor keys. The Folder menu also contains two + actions Fold All Subfolders and Unfold All Subfolders with which you can + collapse or expand all folders at once. If you double-click a file item then the file comparison starts and the file-diff-window will appear. The image in the name column reflects the file type in the first - directory (A). It can be one of these: + folder (A). It can be one of these: Normal file - Normal directory (directory-image) - Link to a file (file-image with a link arrow) - Link to a directory (directory-image with a link arrow) + Normal folder (folder image) + Link to a file (file image with a link arrow) + Link to a folder (folder image with a link arrow) - If the file type is different in the other directories, then this is visible + If the file type is different in the other folders, then this is visible in the columns A/B/C and in the window that shows the details about the selected item. Note that for such a case no merge operation can be selected automatically. When starting the merge, then the user will be informed of problems of that kind. The Columns A/B/C and the Coloring Scheme As can be seen in the image above the colors red, green, yellow and black are used in the columns A/B/C. - Black: This item doesn't exist in this directory. + Black: This item doesn't exist in this folder. Green: Newest item. Yellow: Older than green, newer than red. Red: Oldest item. But for items that were identical in the comparison their color also is identical even if the age is not. - Directories are considered equal if all items they contain are identical. - Then they also will have the same color. But the age of a directory is not + Folders are considered equal if all items they contain are identical. + Then they also will have the same color. But the age of a folder is not considered for its color. The idea for this coloring scheme I came upon in dirdiff command. The colors resemble the colors of a leaf that is green when new, turns yellow later and red when old. The Operation Column - After comparing the directories &kdiff3; also evaluates a proposal for a + After comparing the folders &kdiff3; also evaluates a proposal for a merge operation. This is shown in the Operation column. You can modify the operation by clicking on the operation you want to change. A small menu will popup and allows you to select an operation for that item. (You can also select the most needed operations via keyboard. &Ctrl;1/2/3/4/∇ will select A/B/C/Merge/ Delete respectively if available.) This operation will be executed during the merge. It depends on the item and on the merge-mode you are in, what operations are available. The merge-mode is one of - Three directory-merge (A is treated as older base of both). - Two directory-merge. - Two directory-sync-mode (activate via Synchronize directories option). + Three-folder merge (A is treated as older base of both). + Two-folder merge. + Two-folder sync-mode (activate via Synchronize folders option). - In three directory merge the operation proposal will be: If for an item ... + In three-folder merge the operation proposal will be: If for an item ... - ... all three directories are equal: Copy from C + ... all three folders are equal: Copy from C ... A and C are equal but B is not: Copy from B (or if B does not exist, delete the destination if exists) ... A and B are equal but C is not: Copy from C (or if C does not exist, delete the destination if exists) ... B and C are equal but A is not: Copy from C (or if C does not exist, delete the destination if exists) ... only A exists: Delete the destination (if exists) ... only B exists: Copy from B ... only C exists: Copy from C ... A, B and C are not equal: Merge - ... A, B and C don't have the same file type (⪚ A is a directory, B is a file): "Error: Conflicting File Types". While such items exist the directory merge cannot start. + ... A, B and C don't have the same file type (⪚ A is a folder, B is a file): "Error: Conflicting File Types". While such items exist the folder merge cannot start. - In two directory merge the operation proposal will be: If for an item ... + In two-folder merge the operation proposal will be: If for an item ... - ... both directories are equal: Copy from B + ... both folders are equal: Copy from B ... A exists, but not B: Copy from A ... B exists, but not A: Copy from B ... A and B exist but are not equal: Merge - ... A and B don't have the same file type (⪚ A is a directory, B is a file): "Error: Conflicting File Types". While such items exist the directory merge cannot start. + ... A and B don't have the same file type (⪚ A is a folder, B is a file): "Error: Conflicting File Types". While such items exist the folder merge cannot start. - Sync-mode is active if only two directories and no explicit destination - were specified and if the Synchronize directories option is active. &kdiff3; - then selects a default operation so that both directories are the same afterwards. + Sync-mode is active if only two folders and no explicit destination + were specified and if the Synchronize folders option is active. &kdiff3; + then selects a default operation so that both folders are the same afterwards. If for an item ... - ... both directories are equal: Nothing will be done. + ... both folders are equal: Nothing will be done. ... A exists, but not B: Copy A to B ... B exists, but not A: Copy B to A - ... A and B exist, but are not equal: Merge and store the result in both directories. (For the user the visible save-filename is B, but then &kdiff3; copies B also to A.) + ... A and B exist, but are not equal: Merge and store the result in both folders. (For the user the visible save-filename is B, but then &kdiff3; copies B also to A.) - ... A and B don't have the same file type (⪚ A is a directory, B is a file): "Error: Conflicting File Types". While such items exist the directory merge cannot start. + ... A and B don't have the same file type (⪚ A is a folder, B is a file): "Error: Conflicting File Types". While such items exist the folder merge cannot start. - When two directories are merged and the Copy newer instead of merging (unsafe) option is selected, then &kdiff3; looks at the dates and proposes to choose the newer file. If the files are not equal but have equal dates, then the operation will contain "Error: Dates are equal but files are not." While such items exist the directory merge cannot start. + When two folders are merged and the Copy newer instead of merging (unsafe) option is selected, then &kdiff3; looks at the dates and proposes to choose the newer file. If the files are not equal but have equal dates, then the operation will contain "Error: Dates are equal but files are not." While such items exist the folder merge cannot start. The Status Column During the merge one file after the other will be processed. The status column will show Done for items where the merge operation has succeeded, and other texts if something unexpected happened. When a merge is complete, then you should make a last check to see if the status for all items is agreeable. Statistics Columns When the file comparison mode Full Analysis is enabled in the options, then &kdiff3; will show extra columns containing the numbers of Unsolved, Solved, Nonwhite and Whitespace - conflicts. (The Solved column will only show when comparing or merging three directories.) + conflicts. (The Solved column will only show when comparing or merging three folders.) Selecting Listed Files - - Several options influence which files are listed here. Some are accessible in the - settings dialog. The Directory menu contains the entries: + + Several options influence which files are listed here. Some are accessible in the + settings dialog. The Folder menu contains the entries: - Show Identical Files: Files that have been detected equal in all input directories. + Show Identical Files: Files that have been detected equal in all input folders. - Show Different Files: Files that exist in two or more directories but are not equal. + Show Different Files: Files that exist in two or more folders but are not equal. Show Files only in A: Files that exist only in A, but not in B or C. Show Files only in B: Files that exist only in B, but not in A or C. Show Files only in C: Files that exist only in C, but not in A or B. Activate only the Show options for the items you want listed. If for example you only want to list all items that exist either in A or in B but not in both, you'll have to activate Show Files only in A and Show Files only in B and deactivate all others (Show Identical Files, Show Different Files, Show Files only in C). The list will be updated immediately to reflect the change. - These options also apply for directories with one exception: Disabling - Show Different Files will not hide any complete directories. This will + These options also apply for folders with one exception: Disabling + Show Different Files will not hide any complete folders. This will work only for files within. - Note that of these only the Show Identical Files option is persistant. The others are enabled when starting &kdiff3;. + Note that of these only the Show Identical Files option is persistant. The others are enabled when starting &kdiff3;. -Doing A Directory Merge +Doing A Folder Merge - You can either merge the currently selected item (file or directory), or all items. - When you have made all your operation choices (in all subdirectories too) + You can either merge the currently selected item (file or folder), or all items. + When you have made all your operation choices (in all subfolders too) then you can start the merge. - Be aware that if you didn't specify a destination directory explicitly, then the destination will be C in three directory mode, B in two directory merge mode, and in sync-mode it will be A or/and B. + Be aware that if you didn't specify a destination folder explicitly, then the destination will be C in three-folder mode, B in two-folder merge mode, and in sync-mode it will be A or/and B. - If you have specified a destination directory also check that all items + If you have specified a destination folder also check that all items that should be in the output, are in the tree. There are some options that - cause certain items to be omitted from the directory comparison and merge. + cause certain items to be omitted from the folder comparison and merge. Check these options to avoid unpleasant surprises: - Recursive Directories: If this is off, then items in subdirectories will not be found. + Recursive Folders: If this is off, then items in subfolders will not be found. Pattern/Anti-Pattern: Include/exclude items that match Exclude Hidden Files Show options (Show Identical/Different Files, Files only in A/B/C) If you change the settings in order to list more files, you must do a rescan - via menu DirectoryRescan + via menu FolderRescan yourself. (The reason for this is that for faster comparison-speed &kdiff3; omits the comparison for files suppressed by these criteria.) If you - changed your file and dir patterns to exclude files, then the file-list will + changed your file and folder patterns to exclude files, then the file-list will immediately be updated on closing the options-dialog. - Note that when you write to a completely new directory then you usually also want to copy the identical files. - In that case enable the Show Identical Files option. If your destination-directory is one of the inputs, + Note that when you write to a completely new folder then you usually also want to copy the identical files. + In that case enable the Show Identical Files option. If your destination folder is one of the inputs, then this isn't necessary because the file is already there. If you are satisfied so far, the rest is easy. - To merge all items: Select Start/Continue Directory Merge - in the Directory menu or press F7 (which is the + To merge all items: Select Start/Continue Folder Merge + in the Folder menu or press F7 (which is the default shortcut). To merge only the current item: Select Run Operation for Current Item or press F6. If due to conflicting filetypes still some items with invalid operations exist, then a messagebox will appear and these items will be pointed out, so you can select a valid operation for the item. - If you merge all items a dialog will appear giving you the options - Do it, Simulate it and + If you merge all items a dialog will appear giving you the options + Do it, Simulate it and Cancel. Select Simulate it if you want to see what would be done without actually doing it. A verbose list of all operations will be shown. Otherwise select Do it to really start merging. Then &kdiff3; will run the specified operation for all items. If manual interaction is required (single file merge), then a merge window will open (see the big screenshot). When you have finished with manually merging a file, again select Start/ - Continue Directory Merge or the F7 key. If you haven't + Continue Folder Merge or the F7 key. If you haven't saved it yet, a dialog will ask you to do so. Then &kdiff3; will continue with the next item. When &kdiff3; encounters an error, it will tell you so and will show the verbose-status-information. At the bottom of this list, there will be some error messages which should help you to understand the cause of the problem. When you continue merging (F7 key) &kdiff3; will give you the choice to retry or skip the item that caused the problem. This means that before continuing you can choose another operation or solve the problem by other means. When the merge is complete, then &kdiff3; will inform you via a message box. - If some items were merged individually before running the directorymerge then + If some items were merged individually before running the directorymerge then &kdiff3; remembers this (while this merge-session goes on), and doesn't merge them again when later the merge for all items is run. Even when the merge was skipped or nothing was saved these - items count as completed. Only when you change the merge operation the + items count as completed. Only when you change the merge operation the Done status of the item will be removed and it can be merged again. -Options for Comparing and Merging Directories +Options for Comparing and Merging Folders The &kdiff3; preferences (menu Settings Configure &kdiff3;...) has - a section called "Directory Merge" with these options: + a section called "Folder Merge" with these options: - Recursive directories Select whether to search directories + Recursive folders Select whether to search folders recursively. File pattern(s): Only files that match any pattern here will be put in the tree. More than one pattern may be specified here by using the semicolon ";" as separator. Valid wildcards: '*' and '?'. (⪚ "*.cpp;*.h"). - Default is "*". This pattern is not used on directories. + Default is "*". This pattern is not used on folders. File-anti-pattern(s): Files that match this pattern will be excluded from the tree. More than one pattern may be specified here via using the semicolon ";" as separator. Valid wildcards: '*' and '?'. Default is "*.orig;*.o;*.obj". - Dir-anti-pattern(s): Directories that match this pattern + Folder-anti-pattern(s): Folders that match this pattern will be excluded from the tree. More than one pattern may be specified here via using the semicolon ";" as separator. Valid wildcards: '*' and '?'. Default is "CVS;deps;.svn". Use .cvsignore - Ignore files and directories that would also be ignored by CVS. + Ignore files and folders that would also be ignored by CVS. Many automatically generated files are ignored by CVS. - The big advantage is that this can be directory specific via a local .cvsignore file. + The big advantage is that this can be folder-specific via a local .cvsignore file. (See info:/cvs/cvsignore.) - Find hidden files and directories On some file systems files + Find hidden files and folders On some file systems files have an "Hidden"-attribute. On other systems a filename starting with a dot "." causes it to be hidden. This option allows you to decide whether to include these files in the tree or not. Default is on. Follow file links For links to files: When disabled, then the symbolic links are compared. When enabled, then the files behind the links are compared. Default is off. - Follow directory links For links to directories: When disabled, + Follow folder links For links to folders: When disabled, then the symbolic links will be compared. When enabled then the link will - be treated like a directory and it will be scanned recursively. (Note that - the program doesn't check if the link is "recursive". So for example a directory - that contains a link to the directory would cause an infinite loop, and after + be treated like a folder and it will be scanned recursively. (Note that + the program doesn't check if the link is "recursive". So for example a folder + that contains a link to the folder would cause an infinite loop, and after some time when the stack overflows or all memory is used up, crash the program.) Default is off. - Case sensitive filename comparison + Case sensitive filename comparison Default is false on &Windows;, true for other operating systems. File Comparison Mode: - + Binary comparison This is the default file comparison mode. - + Full analysis Do a full analysis of each file and show the statistics information columns. (Number of Solved, Unsolved, Nonwhite and White conflicts.) The full analysis is slower than a simple binary analysis, and much - slower when used on files that don't contain text. + slower when used on files that don't contain text. (Specify the appropriate file-antipatterns.) - + Trust the size and modification date (unsafe) - If you compare big directories over a slow network, it might be faster to + If you compare big folders over a slow network, it might be faster to compare the modification dates and file length alone. But this speed improvement comes with the price of a little uncertainty. Use this option with care. Default is off. Trust the size (unsafe) Similar to trusting the modification date. No real comparison happens. Two files are considered equal if their file-sizes are equal. This is useful when the file-copy operation didn't preserve the modification date. Use this option with care. Default is off. - - Synchronize directories - Activates sync-mode when two directories are compared and no explicit destination - directory was specified. In this mode the proposed operations will be chosen so - that both source directories are equal afterwards. Also the merge result will be - written to both directories. Default is off. + + Synchronize folders + Activates sync-mode when two folders are compared and no explicit destination + folder was specified. In this mode the proposed operations will be chosen so + that both source folders are equal afterwards. Also the merge result will be + written to both folders. Default is off. Copy newer instead of merging (unsafe) Instead of merging the proposed operation will copy the newer source if changes happened. (Considered unsafe, because it implies that you know, that the other file hasn't been edited too. Check to make sure in every case.) Default is off. Backup files (.orig) - If a file or complete directory is replaced by another or is deleted then the + If a file or complete folder is replaced by another or is deleted then the original version will be renamed with an .orig extension. If an old backup file with .orig extension already exists then this will be deleted without backup. This also - affects the normal merging of single files, not only in directory-merge mode. + affects the normal merging of single files, not only in folder-merge mode. Default is on. -Other Functions in Directory Merge Window +Other Functions in Folder Merge Window Split/Full Screen Mode - Usually the directory merge list view remains visible while a single file is compared or merged. With the mouse you can move the splitter bar that separates the file list from the text-diff windows. If you don't want this, you can disable DirectoryDir && Text Split Screen View menu item. Then you can use DirectoryToggle View menu item to switch between the file list and the text-diff view that then occupy the full screen. + Usually the folder-merge list view remains visible while a single file is compared or merged. With the mouse you can move the splitter bar that separates the file list from the text-diff windows. If you don't want this, you can disable FolderFolder && Text Split Screen View menu item. Then you can use FolderToggle View menu item to switch between the file list and the text-diff view that then occupy the full screen. Comparing or Merging a Single File Probably you will prefer a simple double mouse click on a file in order - to compare it. Nevertheless there also exists an entry in the Directory + to compare it. Nevertheless there also exists an entry in the Folder menu. You can also directly merge a single file by selecting it and choosing MergeMerge Current File - menu item. On saving the result, the status will be set to done, and the file - will not be merged again if a directory merge is started. + menu item. On saving the result, the status will be set to done, and the file + will not be merged again if a folder merge is started. - But note that this status information will be lost when you rerun a directory - scan: DirectoryRescan + But note that this status information will be lost when you rerun a folder + scan: FolderRescan Comparing or Merging Files with Different Names - Sometimes you need to compare or merge files with different names (⪚ the current + Sometimes you need to compare or merge files with different names (⪚ the current file and the backup in the same folder). Select the exact file by clicking onto the icon in the column A, B or C. The first file selected thus will be marked with an A, the second and third with B and C regardless on what column they are in. Only up to three files can be chosen like this. - Proceed by choosing DirectoryCompare Explicitly Selected Files or DirectoryMerge Explicitly Selected Files menu item. For your convenience these menu entries also appear as context menu when you right-click the last selected file. + Proceed by choosing FolderCompare Explicitly Selected Files or FolderMerge Explicitly Selected Files menu item. For your convenience these menu entries also appear as context menu when you right-click the last selected file. - The comparison or merge of a file will happen in the same window. - If this method is used for directories a new window will be opened. + The comparison or merge of a file will happen in the same window. + If this method is used for folders a new window will be opened.
Miscellaneous Topics Network transparency via KIO KIO-Slaves The KIO library from &frameworks; supports network transparency via KIO-slaves. -&kdiff3; uses this for reading input files and for scanning directories. -This means that you can specify files and directories on local and +&kdiff3; uses this for reading input files and for scanning folders. +This means that you can specify files and folders on local and remote resources via &URL;s. Example: kdiff3 test.cpp ftp://ftp.faraway.org/test.cpp - kdiff3 tar:/home/hacker/archive.tar.gz/dir ./dir + kdiff3 tar:/home/hacker/archive.tar.gz/folder ./folder The first line compares a local file with a file on an &FTP; server. The second line -compares a directory within an compressed archive with a local directory. +compares a folder within an compressed archive with a local folder. Other KIO-slaves that are interesting are: Files from the WWW (http:), Files from the &FTP; (ftp:), Encrypted file transfer (fish:, sftp:), &Windows; resources (smb:), Local files (file:), Other things that are possible, but probably less useful are: Man-pages (man:), Info-pages (info:), How To Write &URL;s - An &URL; has a different syntax compared with paths for local files and directories. + An &URL; has a different syntax compared with paths for local files and folders. Some things should be considered: A path can be relative and can contain "." or "..". This is not possible for &URL;s which are always absolute. Special characters must be written with "escaping". ("#" -> "%23", space ->"%20", &etc;) E.g. a file with the name "#foo#" would have the &URL; "file:/%23foo%23". When &URL;s don't work as expected, try to open them in &konqueror; first. Capabilities of KIO-Slaves Network transparency has one drawback: Not all resources have the same capabilities. Sometimes this is due to the file system of the server, sometimes due to the protocol. Here is a short list of restrictions: Sometimes there is no support for links. - Or there is no way to distinguish if a link points to a file or a directory; always + Or there is no way to distinguish if a link points to a file or a folder; always assuming a file. (ftp:, sftp:). Can't always determine the filesize. Limited support for permissions. No possibility to modify permissions or modification time, so permissions or time of a copy will differ from the original. (See the Trust the size (unsafe) option.) (To modify permissions or modification time is only possible for local files.) Using &kdiff3; as a KPart &kdiff3; is a KPart. Currently it implements the KParts::ReadOnlyPart interface. It's main use is as difference-viewer in &kdevelop;. &kdevelop; always starts the internal difference viewer first. To invoke &kdiff3; press the &RMB; on the difference viewer window and select Show in KDiff3Part from the context menu. &kdiff3; normally requires two complete files as input. When used as part &kdiff3; will assume that the input file is a patch-file in the unified format. &kdiff3; then retrieves the original filenames from the patch-file. At least one of the two files must be available. &kdiff3; will then invoke patch to recreate the second file. In &dolphin; you can select a patch-file and select Preview inKDiff3Part item from the context menu. Be aware that this won't work if none of the original files are available, and it is not reliable if the original file(s) have changed since the patch-file was generated. When run as a part &kdiff3; only provides the a two-file-diff, a very small toolbar -and menu. Merging or directory-comparison are not supported then. +and menu. Merging or folder comparison are not supported then. Using &kdiff3; as a &git; Diff and Merging Tool &kdiff3; can be used as a &git; diff and merge tool. Just add the following lines into your gitconfig file. [diff] tool = kdiff3 [difftool "kdiff3"] path = <path to kdiff3 binary in your system> [difftool] prompt = false keepBackup = false trustExitCode = false [merge] tool = kdiff3 [mergetool] prompt = false keepBackup = false keepTemporaries = false [mergetool "kdiff3"] path = <path to kdiff3 binary in your system> Then to see the difference between two commits use git difftool first_hash second_hash --tool=kdiff3 --cc some_file_in_the_git_tree To merge a branch with &kdiff3; use git merge branch_name && git mergetool --tool=kdiff3 After resolving merging conflicts in the usual way it is enough to commit the changes to do the job. Questions and Answers &reporting.bugs; &updating.documentation; Why is it called "&kdiff3;"? Tools named KDiff and KDiff2 (now called &kompare;) already exist. Also &kdiff3; should suggest that it can merge like the diff3 tool in the Diff-Tool collection. Why did I release it under GPL? I'm using GPL programs for a very long time now and learned very much by having a look at many of the sources. Hence this is my "Thank You" to all programmers that also did so or will do the same. Some buttons and functions are missing. What's wrong? You compiled from source but you probably didn't specify the correct prefix for the installation. By default cmake wants to install in /usr/local but then the user-interface resource file (&ie; kdiff3ui.rc) can't be found. The README file contains more information about the correct prefix. Often lines that are similar but not identical appear next to each other but sometimes not. Why? Lines where only the amount of white space characters is different are treated as "equal" at first, while just one different non-white character causes the lines to be "different". If similar lines appear next to each other, this actually is coincidence but this fortunately is often the case. See also Manual Diff Help. Why must all conflicts be solved before the merge result can be saved? For each equal or different section the editor in the merge result window remembers where it begins or ends. This is needed so that conflicts can be solved manually by simply selecting the source button (A, B or C). This information is lost while saving as text and it is too much effort to create a special file format that supports saving and restoring all necessary information. How can I synchronise the diff and merge views, so that all views show the same text position? Click into the summary column left of the text. (See also here.) Why does the editor in the merge result window not have an "undo"-function? This was too much effort until now. You can always restore a version from one source (A, B or C) by clicking the respective button. For big editing the use of another editor is recommended anyway. When I removed some text, then suddenly "<No src line>" appeared and cannot be deleted. What does that mean and how can one remove this? For each equal or different section the editor in the merge result window remembers where it begins or ends. "<No src line>" means that there is nothing left in a section, not even a new line character. This can happen either while merging automatically or by editing. This is no problem, since this hint won't appear in the saved file. If you want the orignal source back just select the section (click on the left summary column) and then click the source button with the needed contents (A/B or C). Why doesn't &kdiff3; support syntax-highlighting? &kdiff3; already uses many colors for difference highlighting. More highlighting would be confusing. Use another editor for this. Can I use &kdiff3; to compare OpenOffice.org, &Microsoft; Word, &Microsoft; Excel, &PDF;, &etc; files? Although &kdiff3; will analyse any kind of file the result will probably not be very satisfactory for you. &kdiff3; was made to compare pure text files. OpenOffice.org, &Microsoft; Word, &Microsoft; Excel, &etc; store much more information in the files (about fonts, pictures, pages, colors, &etc;) which &kdiff3; doesn't know about. So &kdiff3; will show you the contents of the file interpreted as pure text, but this might be unreadable or at least it will look very odd. - Since most programs nowadays store their contents in &XML; format, you might + Since most programs nowadays store their contents in &XML; format, you might be able to read it as pure text. So if the change was only small, &kdiff3; still might help you. The best solution if you only want to compare the text (without embedded objects like pictures) is to use EditSelect All and EditCopy menu items in your program to copy the interesting text to the clipboard and then in &kdiff3; paste the text into either diff input window. (See also Select, Copy And Paste.) - Where has the directory option List only deltas gone? + Where has the folder option List only deltas gone? - There are now several "Show" options in the - Directory menu. Disabling Show Identical Files + There are now several "Show" options in the + Folder menu. Disabling Show Identical Files will achieve what enabling List only deltas used to do. How can I make a big selection in the diff input window because scrolling takes so long? Start the selection as usual (click and hold the &LMB;). Then use the navigation keys (⪚ &PgUp;, &PgDn;) while holding the &LMB; down. (See also Select, Copy And Paste.) There is so much information here, but your question is still not answered? Please send me your question. I appreciate every comment. Credits and License -&kdiff3; - File and Directory Comparison and Merge Tool +&kdiff3; - File and Folder Comparison and Merge Tool Program copyright 2002-2007 Joachim Eibl joachim.eibl at gmx.de Several cool ideas and bugreports came from colleagues and many people out in the Wild Wild Web. Thank you! Documentation Copyright © 2002-2007 Joachim Eibl joachim.eibl at gmx.de Documentation Copyright © 2017-2019 Michael Reeves reeves.87 at gmail.com &underFDL; &underGPL; &documentation.index;
diff --git a/doc/en/man-kdiff3.1.docbook b/doc/en/man-kdiff3.1.docbook index f77de83..c2c6080 100644 --- a/doc/en/man-kdiff3.1.docbook +++ b/doc/en/man-kdiff3.1.docbook @@ -1,206 +1,206 @@ ]> KDiff3 User's Manual BurkhardLueck KDiff3 man page. &Burkhard.Lueck.mail; 2019-02-22 kdiff3 1.8 kdiff3 1 kdiff3 -Tool for Comparison and Merge of Files and Directories +Tool for Comparison and Merge of Files and Folders kdiff3 file file file alias1 alias2 alias3 alias string file Description -Compares two or three input files or directories. +Compares two or three input files or folders. Options Ignored. (User defined.) Ignored. (User defined.) Ignored. (User defined.) Ignored. (User defined.) Merge the input. file Explicit base file. For compatibility with certain tools. file Output file. Implies -m. ⪚: -o newfile.txt file Output file, again. (For compatibility with certain tools.) No GUI if all conflicts are auto-solvable. (Needs file) Don't solve conflicts automatically. alias1 Visible name replacement for input file 1 (base). alias2 Visible name replacement for input file 2. alias3 Visible name replacement for input file 3. alias Alternative visible name replacement. Supply this once for every input. string Override a config setting. Use once for every setting. ⪚: AutoAdvance=1. Show list of config settings and current values. file Use a different config file. file1 to open (base, if not specified via --) file2 to open file2 to open See Also More detailed user documentation is available from help:/kdiff3 (either enter this &URL; into &konqueror;, or run khelpcenter help:/kdiff3). kf5options(7) qt5options(7) Authors This manual page was written by &Burkhard.Lueck; &Burkhard.Lueck.mail;. diff --git a/kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp b/kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp index b434770..d4e98d4 100644 --- a/kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp +++ b/kdiff3fileitemactionplugin/kdiff3fileitemaction.cpp @@ -1,293 +1,293 @@ /* This file is part of the KDiff3 project Copyright (C) 2008 Joachim Eibl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdiff3fileitemaction.h" #include #include #include #include #include #include #include #include #include #include #include //#include static QStringList* s_pHistory=nullptr; class KDiff3PluginHistory { KConfig* m_pConfig; KConfigGroup* m_pConfigGroup; public: KDiff3PluginHistory() { m_pConfig = nullptr; if (s_pHistory==nullptr) { //std::cout << "New History: " << instanceName << std::endl; s_pHistory = new QStringList; m_pConfig = new KConfig( "kdiff3fileitemactionrc", KConfig::SimpleConfig ); m_pConfigGroup = new KConfigGroup( m_pConfig, "KDiff3Plugin" ); *s_pHistory = m_pConfigGroup->readEntry("HistoryStack", QStringList() ); } } ~KDiff3PluginHistory() { //std::cout << "Delete History" << std::endl; if ( s_pHistory && m_pConfigGroup ) m_pConfigGroup->writeEntry("HistoryStack",*s_pHistory); delete s_pHistory; delete m_pConfigGroup; delete m_pConfig; s_pHistory = nullptr; m_pConfig = nullptr; } }; KDiff3PluginHistory s_history; K_PLUGIN_FACTORY_WITH_JSON(KDiff3FileItemActionFactory, "kdiff3fileitemaction.json", registerPlugin();) #include "kdiff3fileitemaction.moc" KDiff3FileItemAction::KDiff3FileItemAction (QObject* pParent, const QVariantList & /*args*/) : KAbstractFileItemActionPlugin(pParent) { } QList KDiff3FileItemAction::actions( const KFileItemListProperties& fileItemInfos, QWidget* pParentWidget ) { QList< QAction* > actions; if (QStandardPaths::findExecutable("kdiff3").isEmpty ()) return actions; //m_fileItemInfos = fileItemInfos; m_pParentWidget = pParentWidget; QAction *pMenuAction = new QAction(QIcon::fromTheme(QStringLiteral("kdiff3")), i18n("KDiff3..."), this); QMenu *pActionMenu = new QMenu(); pMenuAction->setMenu( pActionMenu ); // remember currently selected files m_list = fileItemInfos.urlList(); /* Menu structure: KDiff3 -> (1 File selected): Save 'selection' for later comparison (push onto history stack) Compare 'selection' with first file on history stack. Compare 'selection' with -> choice from history stack Merge 'selection' with first file on history stack. Merge 'selection' with last two files on history stack. (2 Files selected): Compare 's1' with 's2' Merge 's1' with 's2' (3 Files selected): Compare 's1', 's2' and 's3' */ QAction* pAction = nullptr; QString s; if(m_list.count() == 1) { int historyCount = s_pHistory ? s_pHistory->count() : 0; s = i18n("Compare with %1", (historyCount>0 ? s_pHistory->first() : QString()) ); pAction = new QAction ( s,this ); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotCompareWith); pAction->setEnabled( m_list.count()>0 && historyCount>0 ); pActionMenu->addAction(pAction); s = i18n("Merge with %1", historyCount>0 ? s_pHistory->first() : QString() ); pAction = new QAction( s, this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotMergeWith); pAction->setEnabled( m_list.count()>0 && historyCount>0 ); pActionMenu->addAction (pAction); s = i18n("Save '%1' for later", ( m_list.first().toDisplayString(QUrl::PreferLocalFile) ) ); pAction = new QAction ( s, this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotSaveForLater); pAction->setEnabled( m_list.count()>0 ); pActionMenu->addAction(pAction); pAction = new QAction (i18n("3-way merge with base"), this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotMergeThreeWay); pAction->setEnabled( m_list.count()>0 && historyCount>=2 ); pActionMenu->addAction (pAction); if (s_pHistory && !s_pHistory->empty()) { QAction* pHistoryMenuAction = new QAction( i18n("Compare with..."), this ); QMenu* pHistoryMenu = new QMenu(); pHistoryMenuAction->setMenu( pHistoryMenu ); pHistoryMenu->setEnabled( m_list.count()>0 && historyCount>0 ); pActionMenu->addAction(pHistoryMenuAction); for(const QString& file : qAsConst(*s_pHistory)) { pAction = new QAction(file, this); pAction->setData(file); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotCompareWithHistoryItem); pHistoryMenu->addAction(pAction); } pAction = new QAction(i18n("Clear list"), this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotClearList); pActionMenu->addAction(pAction); pAction->setEnabled( historyCount>0 ); } } else if(m_list.count() == 2) { pAction = new QAction (i18n("Compare"), this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotCompareTwoFiles); pActionMenu->addAction (pAction); } else if ( m_list.count() == 3 ) { pAction = new QAction (i18n("3 way comparison"), this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotCompareThreeFiles); pActionMenu->addAction (pAction); } pAction = new QAction (i18n("About KDiff3 menu plugin..."), this); connect(pAction, &QAction::triggered, this, &KDiff3FileItemAction::slotAbout); pActionMenu->addAction (pAction); //pMenu->addSeparator(); //pMenu->addAction( pActionMenu ); //pMenu->addSeparator(); actions << pMenuAction; return actions; } KDiff3FileItemAction::~KDiff3FileItemAction () { } void KDiff3FileItemAction::slotCompareWith() { if ( m_list.count() > 0 && s_pHistory && ! s_pHistory->empty() ) { QStringList args; args << s_pHistory->first(); args << m_list.first().toDisplayString(QUrl::PreferLocalFile); KProcess::startDetached("kdiff3", args); } } void KDiff3FileItemAction::slotCompareWithHistoryItem() { const QAction* pAction = dynamic_cast( sender() ); if (!m_list.isEmpty() && pAction) { QStringList args; args << pAction->data().toString(); args << m_list.first().toDisplayString(QUrl::PreferLocalFile); KProcess::startDetached ("kdiff3", args); } } void KDiff3FileItemAction::slotCompareTwoFiles() { if (m_list.count() == 2) { QStringList args; args << m_list.first().toDisplayString(QUrl::PreferLocalFile); args << m_list.last().toDisplayString(QUrl::PreferLocalFile); KProcess::startDetached ("kdiff3", args); } } void KDiff3FileItemAction::slotCompareThreeFiles() { if ( m_list.count() == 3 ) { QStringList args; args << m_list.at(0).toDisplayString(QUrl::PreferLocalFile); args << m_list.at(1).toDisplayString(QUrl::PreferLocalFile); args << m_list.at(2).toDisplayString(QUrl::PreferLocalFile); KProcess::startDetached ("kdiff3", args); } } void KDiff3FileItemAction::slotMergeWith() { if ( m_list.count() > 0 && s_pHistory && ! s_pHistory->empty() ) { QStringList args; args << s_pHistory->first(); args << m_list.first().toDisplayString(QUrl::PreferLocalFile); args << ( "-o" + m_list.first().toDisplayString(QUrl::PreferLocalFile) ); KProcess::startDetached ("kdiff3", args); } } void KDiff3FileItemAction::slotMergeThreeWay() { if ( m_list.count() > 0 && s_pHistory && s_pHistory->count()>=2 ) { QStringList args; args << s_pHistory->at(1); args << s_pHistory->at(0); args << m_list.first().toDisplayString(QUrl::PreferLocalFile); args << ("-o" + m_list.first().toDisplayString(QUrl::PreferLocalFile)); KProcess::startDetached ("kdiff3", args); } } void KDiff3FileItemAction::slotSaveForLater() { if (!m_list.isEmpty() && s_pHistory) { while (s_pHistory->count()>=10) { s_pHistory->removeLast(); } const QString file = m_list.first().toDisplayString(QUrl::PreferLocalFile); s_pHistory->removeAll(file); s_pHistory->prepend(file); } } void KDiff3FileItemAction::slotClearList() { if (s_pHistory) { s_pHistory->clear(); } } void KDiff3FileItemAction::slotAbout() { QString s = i18n("KDiff3 File Item Action Plugin: Copyright (C) 2011 Joachim Eibl\n"); s += i18n("Using the context menu extension:\n" "For simple comparison of two selected files choose \"Compare\".\n" "If the other file is somewhere else \"Save\" the first file for later. " "It will appear in the \"Compare with...\" submenu. " "Then use \"Compare With\" on the second file.\n" "For a 3-way merge first \"Save\" the base file, then the branch to merge and " "choose \"3-way merge with base\" on the other branch which will be used as destination.\n" - "Same also applies to directory comparison and merge."); + "Same also applies to folder comparison and merge."); KMessageBox::information(m_pParentWidget, s, i18n("About KDiff3 File Item Action Plugin") ); } diff --git a/kdiff3fileitemactionplugin/kdiff3fileitemaction.json b/kdiff3fileitemactionplugin/kdiff3fileitemaction.json index e799c05..d7546f6 100644 --- a/kdiff3fileitemactionplugin/kdiff3fileitemaction.json +++ b/kdiff3fileitemactionplugin/kdiff3fileitemaction.json @@ -1,60 +1,60 @@ { "KPlugin": { "MimeTypes": [ "application/octet-stream", "inode/directory" ], - "Name": "Compare/Merge Files/Directories with KDiff3", + "Name": "Compare/Merge Files/Folders with KDiff3", "Name[ca@valencia]": "Compara/fusiona fitxers/directoris amb el KDiff3", "Name[ca]": "Compara/fusiona fitxers/directoris amb el KDiff3", "Name[cs]": "Porovnávejte/slučujte soubory/adresáře pomocí KDiff3", "Name[de]": "Dateien und Ordner vergleichen und zusammenführen mit KDiff3", "Name[el]": "Σύγκριση/Συγχώνευση αρχείων/καταλόγων με το KDiff3", "Name[en_GB]": "Compare/Merge Files/Directories with KDiff3", "Name[es]": "Comparar o fusionar archivos o directorios con KDiff3", "Name[et]": "Failide/kataloogide võrdlemine/ühendamine KDiff3 abil", "Name[fi]": "Vertaa ja yhdistä tiedostoja ja kansioita KDiff3:lla", "Name[fr]": "Comparaison et fusion de fichiers ou de dossiers à l'aide de « KDiff3 »", "Name[gl]": "Comparar ou mesturar ficheiros ou directorios con KDiff3", "Name[it]": "Confronta e fonde i file o le cartelle con KDiff3", "Name[ko]": "KDiff3으로 파일과 디렉터리를 비교하거나 병합", "Name[nl]": "Vergelijk/Voeg samen bestanden/mappen met KDiff3", "Name[pl]": "Porównuj/Scalaj pliki/katalogi z KDiff3", "Name[pt]": "Comparar/Reunir os Ficheiros/Pastas com o KDiff3", "Name[pt_BR]": "Comparar/Mesclar arquivos/pastas com o KDiff3", "Name[sk]": "Porovnať/zlúčiť súbory/adresáre s KDiff3", "Name[sv]": "Jämför/Sammanfoga filer/kataloger med KDiff3", "Name[uk]": "Порівняння та об'єднання файлів та каталогів за допомогою KDiff3", - "Name[x-test]": "xxCompare/Merge Files/Directories with KDiff3xx", + "Name[x-test]": "xxCompare/Merge Files/Folders with KDiff3xx", "Name[zh_CN]": "使用KDiff3比较/合并文件或文件夹", "Name[zh_TW]": "使用 KDiff3 比較/合併檔案或資料夾", "ServiceTypes": [ "KFileItemAction/Plugin" ] }, "MimeType": "application/octet-stream;inode/directory;", "X-KDE-Submenu": "KDiff3", "X-KDE-Submenu[bg]": "KDiff3", "X-KDE-Submenu[bs]": "KDiff3", "X-KDE-Submenu[ca]": "KDiff3", "X-KDE-Submenu[cs]": "KDiff3", "X-KDE-Submenu[da]": "KDiff3", "X-KDE-Submenu[de]": "KDiff3", "X-KDE-Submenu[el]": "KDiff3", "X-KDE-Submenu[es]": "KDiff3", "X-KDE-Submenu[et]": "KDiff3", "X-KDE-Submenu[fr]": "KDiff3", "X-KDE-Submenu[hu]": "KDiff3", "X-KDE-Submenu[it]": "KDiff3", "X-KDE-Submenu[nb]": "KDiff3", "X-KDE-Submenu[nds]": "KDiff3", "X-KDE-Submenu[nl]": "KDiff3", "X-KDE-Submenu[pl]": "KDiff3", "X-KDE-Submenu[pt]": "KDiff3", "X-KDE-Submenu[pt_BR]": "KDiff3", "X-KDE-Submenu[ru]": "KDiff3", "X-KDE-Submenu[sv]": "Kdiff3", "X-KDE-Submenu[uk]": "KDiff3", "X-KDE-Submenu[x-test]": "xxKDiff3xx", "X-KDE-Submenu[zh_TW]": "KDiff3" } diff --git a/src/directorymergewindow.cpp b/src/directorymergewindow.cpp index 6365c62..52add5a 100644 --- a/src/directorymergewindow.cpp +++ b/src/directorymergewindow.cpp @@ -1,3064 +1,3064 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "directorymergewindow.h" #include "DirectoryInfo.h" #include "MergeFileInfos.h" #include "PixMapUtils.h" #include "Utils.h" #include "guiutils.h" #include "kdiff3.h" #include "options.h" #include "progress.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct DirectoryMergeWindow::t_ItemInfo { bool bExpanded; bool bOperationComplete; QString status; e_MergeOperation eMergeOperation; }; class StatusInfo : public QDialog { private: KTextEdit* m_pTextEdit; public: explicit StatusInfo(QWidget* pParent) : QDialog(pParent) { QVBoxLayout* pVLayout = new QVBoxLayout(this); m_pTextEdit = new KTextEdit(this); pVLayout->addWidget(m_pTextEdit); setObjectName("StatusInfo"); setWindowFlags(Qt::Dialog); m_pTextEdit->setWordWrapMode(QTextOption::NoWrap); m_pTextEdit->setReadOnly(true); QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Close, this); connect(box, &QDialogButtonBox::rejected, this, &QDialog::accept); pVLayout->addWidget(box); } bool isEmpty() { return m_pTextEdit->toPlainText().isEmpty(); } void addText(const QString& s) { m_pTextEdit->append(s); } void clear() { m_pTextEdit->clear(); } void setVisible(bool bVisible) override { if(bVisible) { m_pTextEdit->moveCursor(QTextCursor::End); m_pTextEdit->moveCursor(QTextCursor::StartOfLine); m_pTextEdit->ensureCursorVisible(); } QDialog::setVisible(bVisible); if(bVisible) setWindowState(windowState() | Qt::WindowMaximized); } }; enum Columns { s_NameCol = 0, s_ACol = 1, s_BCol = 2, s_CCol = 3, s_OpCol = 4, s_OpStatusCol = 5, s_UnsolvedCol = 6, // Nr of unsolved conflicts (for 3 input files) s_SolvedCol = 7, // Nr of auto-solvable conflicts (for 3 input files) s_NonWhiteCol = 8, // Nr of nonwhite deltas (for 2 input files) s_WhiteCol = 9 // Nr of white deltas (for 2 input files) }; static Qt::CaseSensitivity s_eCaseSensitivity = Qt::CaseSensitive; //TODO: clean up this mess. class DirectoryMergeWindow::DirectoryMergeWindowPrivate : public QAbstractItemModel { friend class DirMergeItem; public: DirectoryMergeWindow* mWindow; explicit DirectoryMergeWindowPrivate(DirectoryMergeWindow* pDMW) { mWindow = pDMW; m_pStatusInfo = new StatusInfo(mWindow); m_pStatusInfo->hide(); } ~DirectoryMergeWindowPrivate() override { delete m_pRoot; } // Implement QAbstractItemModel QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; //Qt::ItemFlags flags ( const QModelIndex & index ) const QModelIndex parent(const QModelIndex& index) const override { MergeFileInfos* pMFI = getMFI(index); if(pMFI == nullptr || pMFI == m_pRoot || pMFI->parent() == m_pRoot) return QModelIndex(); MergeFileInfos* pParentsParent = pMFI->parent()->parent(); return createIndex(pParentsParent->children().indexOf(pMFI->parent()), 0, pMFI->parent()); } int rowCount(const QModelIndex& parent = QModelIndex()) const override { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI != nullptr) return pParentMFI->children().count(); else return m_pRoot->children().count(); } int columnCount(const QModelIndex& /*parent*/) const override { return 10; } QModelIndex index(int row, int column, const QModelIndex& parent) const override { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI == nullptr && row < m_pRoot->children().count()) return createIndex(row, column, m_pRoot->children()[row]); else if(pParentMFI != nullptr && row < pParentMFI->children().count()) return createIndex(row, column, pParentMFI->children()[row]); else return QModelIndex(); } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void sort(int column, Qt::SortOrder order) override; // private data and helper methods MergeFileInfos* getMFI(const QModelIndex& mi) const { if(mi.isValid()) return (MergeFileInfos*)mi.internalPointer(); else return nullptr; } bool isThreeWay() const { if(rootMFI() == nullptr) return false; return rootMFI()->isThreeWay(); } MergeFileInfos* rootMFI() const { return m_pRoot; } QSharedPointer m_pOptions = nullptr; void calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges); void mergeContinue(bool bStart, bool bVerbose); void prepareListView(ProgressProxy& pp); void calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp); void setAllMergeOperations(e_MergeOperation eDefaultOperation); bool canContinue(); QModelIndex treeIterator(QModelIndex mi, bool bVisitChildren = true, bool bFindInvisible = false); void prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose); bool executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge); void scanDirectory(const QString& dirName, t_DirectoryList& dirList); void scanLocalDirectory(const QString& dirName, t_DirectoryList& dirList); void setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive = true); bool isDir(const QModelIndex& mi) const; QString getFileName(const QModelIndex& mi) const; bool copyFLD(const QString& srcName, const QString& destName); bool deleteFLD(const QString& name, bool bCreateBackup); bool makeDir(const QString& name, bool bQuiet = false); bool renameFLD(const QString& srcName, const QString& destName); bool mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge); void buildMergeMap(const QSharedPointer& dirInfo); private: class FileKey { private: const FileAccess* m_pFA; public: explicit FileKey(const FileAccess& fa) : m_pFA(&fa) {} quint32 getParents(const FileAccess* pFA, const FileAccess* v[], quint32 maxSize) const { quint32 s = 0; for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s) { if(s == maxSize) break; v[s] = pFA; } return s; } // This is essentially the same as // int r = filePath().compare( fa.filePath() ) // if ( r<0 ) return true; // if ( r==0 ) return m_col < fa.m_col; // return false; bool operator<(const FileKey& fk) const { const FileAccess* v1[100]; const FileAccess* v2[100]; quint32 v1Size = getParents(m_pFA, v1, 100); quint32 v2Size = getParents(fk.m_pFA, v2, 100); for(quint32 i = 0; i < v1Size && i < v2Size; ++i) { int r = v1[v1Size - i - 1]->fileName().compare(v2[v2Size - i - 1]->fileName(), s_eCaseSensitivity); if(r < 0) return true; else if(r > 0) return false; } return v1Size < v2Size; } }; typedef QMap t_fileMergeMap; MergeFileInfos* m_pRoot = new MergeFileInfos(); t_fileMergeMap m_fileMergeMap; public: bool m_bFollowDirLinks = false; bool m_bFollowFileLinks = false; bool m_bSimulatedMergeStarted = false; bool m_bRealMergeStarted = false; bool m_bError = false; bool m_bSyncMode = false; bool m_bDirectoryMerge = false; // if true, then merge is the default operation, otherwise it's diff. bool m_bCaseSensitive = true; bool m_bUnfoldSubdirs = false; bool m_bSkipDirStatus = false; bool m_bScanning = false; // true while in init() DirectoryMergeInfo* m_pDirectoryMergeInfo = nullptr; StatusInfo* m_pStatusInfo = nullptr; typedef std::list MergeItemList; // linked list MergeItemList m_mergeItemList; MergeItemList::iterator m_currentIndexForOperation; QModelIndex m_selection1Index; QModelIndex m_selection2Index; QModelIndex m_selection3Index; void selectItemAndColumn(const QModelIndex& mi, bool bContextMenu); QAction* m_pDirStartOperation; QAction* m_pDirRunOperationForCurrentItem; QAction* m_pDirCompareCurrent; QAction* m_pDirMergeCurrent; QAction* m_pDirRescan; QAction* m_pDirChooseAEverywhere; QAction* m_pDirChooseBEverywhere; QAction* m_pDirChooseCEverywhere; QAction* m_pDirAutoChoiceEverywhere; QAction* m_pDirDoNothingEverywhere; QAction* m_pDirFoldAll; QAction* m_pDirUnfoldAll; KToggleAction* m_pDirShowIdenticalFiles; KToggleAction* m_pDirShowDifferentFiles; KToggleAction* m_pDirShowFilesOnlyInA; KToggleAction* m_pDirShowFilesOnlyInB; KToggleAction* m_pDirShowFilesOnlyInC; KToggleAction* m_pDirSynchronizeDirectories; KToggleAction* m_pDirChooseNewerFiles; QAction* m_pDirCompareExplicit; QAction* m_pDirMergeExplicit; QAction* m_pDirCurrentDoNothing; QAction* m_pDirCurrentChooseA; QAction* m_pDirCurrentChooseB; QAction* m_pDirCurrentChooseC; QAction* m_pDirCurrentMerge; QAction* m_pDirCurrentDelete; QAction* m_pDirCurrentSyncDoNothing; QAction* m_pDirCurrentSyncCopyAToB; QAction* m_pDirCurrentSyncCopyBToA; QAction* m_pDirCurrentSyncDeleteA; QAction* m_pDirCurrentSyncDeleteB; QAction* m_pDirCurrentSyncDeleteAAndB; QAction* m_pDirCurrentSyncMergeToA; QAction* m_pDirCurrentSyncMergeToB; QAction* m_pDirCurrentSyncMergeToAAndB; QAction* m_pDirSaveMergeState; QAction* m_pDirLoadMergeState; bool init(const QSharedPointer& dirInfo, bool bDirectoryMerge, bool bReload); void setOpStatus(const QModelIndex& mi, e_OperationStatus eOpStatus) { if(MergeFileInfos* pMFI = getMFI(mi)) { pMFI->setOpStatus(eOpStatus); Q_EMIT dataChanged(mi, mi); } } QModelIndex nextSibling(const QModelIndex& mi); }; QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::data(const QModelIndex& index, int role) const { MergeFileInfos* pMFI = getMFI(index); if(pMFI) { if(role == Qt::DisplayRole) { switch(index.column()) { case s_NameCol: return QFileInfo(pMFI->subPath()).fileName(); case s_ACol: return i18n("A"); case s_BCol: return i18n("B"); case s_CCol: return i18n("C"); //case s_OpCol: return i18n("Operation"); //case s_OpStatusCol: return i18n("Status"); case s_UnsolvedCol: return pMFI->diffStatus().getUnsolvedConflicts(); case s_SolvedCol: return pMFI->diffStatus().getSolvedConflicts(); case s_NonWhiteCol: return pMFI->diffStatus().getNonWhitespaceConflicts(); case s_WhiteCol: return pMFI->diffStatus().getWhitespaceConflicts(); //default : return QVariant(); } if(s_OpCol == index.column()) { bool bDir = pMFI->hasDir(); switch(pMFI->getOperation()) { case eNoOperation: return ""; break; case eCopyAToB: return i18n("Copy A to B"); break; case eCopyBToA: return i18n("Copy B to A"); break; case eDeleteA: return i18n("Delete A"); break; case eDeleteB: return i18n("Delete B"); break; case eDeleteAB: return i18n("Delete A & B"); break; case eMergeToA: return i18n("Merge to A"); break; case eMergeToB: return i18n("Merge to B"); break; case eMergeToAB: return i18n("Merge to A & B"); break; case eCopyAToDest: return i18n("A"); break; case eCopyBToDest: return i18n("B"); break; case eCopyCToDest: return i18n("C"); break; case eDeleteFromDest: return i18n("Delete (if exists)"); break; case eMergeABCToDest: case eMergeABToDest: return bDir ? i18n("Merge") : i18n("Merge (manual)"); break; case eConflictingFileTypes: return i18n("Error: Conflicting File Types"); break; case eChangedAndDeleted: return i18n("Error: Changed and Deleted"); break; case eConflictingAges: return i18n("Error: Dates are equal but files are not."); break; default: Q_ASSERT(true); break; } } if(s_OpStatusCol == index.column()) { switch(pMFI->getOpStatus()) { case eOpStatusNone: return ""; case eOpStatusDone: return i18n("Done"); case eOpStatusError: return i18n("Error"); case eOpStatusSkipped: return i18n("Skipped."); case eOpStatusNotSaved: return i18n("Not saved."); case eOpStatusInProgress: return i18n("In progress..."); case eOpStatusToDo: return i18n("To do."); } } } else if(role == Qt::DecorationRole) { if(s_NameCol == index.column()) { return PixMapUtils::getOnePixmap(eAgeEnd, pMFI->hasLink(), pMFI->hasDir()); } if(s_ACol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->getAgeA(), pMFI->isLinkA(), pMFI->isDirA()); } if(s_BCol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->getAgeB(), pMFI->isLinkB(), pMFI->isDirB()); } if(s_CCol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->getAgeC(), pMFI->isLinkC(), pMFI->isDirC()); } } else if(role == Qt::TextAlignmentRole) { if(s_UnsolvedCol == index.column() || s_SolvedCol == index.column() || s_NonWhiteCol == index.column() || s_WhiteCol == index.column()) return Qt::AlignRight; } } return QVariant(); } QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation == Qt::Horizontal && section >= 0 && section < columnCount(QModelIndex()) && role == Qt::DisplayRole) { switch(section) { case s_NameCol: return i18n("Name"); case s_ACol: return i18n("A"); case s_BCol: return i18n("B"); case s_CCol: return i18n("C"); case s_OpCol: return i18n("Operation"); case s_OpStatusCol: return i18n("Status"); case s_UnsolvedCol: return i18n("Unsolved"); case s_SolvedCol: return i18n("Solved"); case s_NonWhiteCol: return i18n("Nonwhite"); case s_WhiteCol: return i18n("White"); default: return QVariant(); } } return QVariant(); } int DirectoryMergeWindow::getIntFromIndex(const QModelIndex& index) const { return index == d->m_selection1Index ? 1 : index == d->m_selection2Index ? 2 : index == d->m_selection3Index ? 3 : 0; } const QSharedPointer& DirectoryMergeWindow::getOptions() const { return d->m_pOptions; } // Previously Q3ListViewItem::paintCell(p,cg,column,width,align); class DirectoryMergeWindow::DirMergeItemDelegate : public QStyledItemDelegate { private: DirectoryMergeWindow* m_pDMW; const QSharedPointer& getOptions() const { return m_pDMW->getOptions(); } public: explicit DirMergeItemDelegate(DirectoryMergeWindow* pParent) : QStyledItemDelegate(pParent), m_pDMW(pParent) { } void paint(QPainter* thePainter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { QtNumberType column = index.column(); if(column == s_ACol || column == s_BCol || column == s_CCol) { QVariant value = index.data(Qt::DecorationRole); QPixmap icon; if(value.isValid()) { if(value.type() == QVariant::Icon) { icon = qvariant_cast(value).pixmap(16, 16); //icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), icon.actualSize(option.decorationSize, iconMode, iconState)); } else { icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect()); } } int x = option.rect.left(); int y = option.rect.top(); //QPixmap icon = value.value(); //pixmap(column); if(!icon.isNull()) { int yOffset = (sizeHint(option, index).height() - icon.height()) / 2; thePainter->drawPixmap(x + 2, y + yOffset, icon); int i = m_pDMW->getIntFromIndex(index); if(i != 0) { QColor c(i == 1 ? getOptions()->m_colorA : i == 2 ? getOptions()->m_colorB : getOptions()->m_colorC); thePainter->setPen(c); // highlight() ); thePainter->drawRect(x + 2, y + yOffset, icon.width(), icon.height()); thePainter->setPen(QPen(c, 0, Qt::DotLine)); thePainter->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); thePainter->setPen(Qt::white); QString s(QChar('A' + i - 1)); thePainter->drawText(x + 2 + (icon.width() - Utils::getHorizontalAdvance(thePainter->fontMetrics(), s)) / 2, y + yOffset + (icon.height() + thePainter->fontMetrics().ascent()) / 2 - 1, s); } else { thePainter->setPen(m_pDMW->palette().window().color()); thePainter->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); } return; } } QStyleOptionViewItem option2 = option; if(column >= s_UnsolvedCol) { option2.displayAlignment = Qt::AlignRight; } QStyledItemDelegate::paint(thePainter, option2, index); } QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override { QSize sz = QStyledItemDelegate::sizeHint(option, index); return sz.expandedTo(QSize(0, 18)); } }; DirectoryMergeWindow::DirectoryMergeWindow(QWidget* pParent, const QSharedPointer& pOptions) : QTreeView(pParent) { d = new DirectoryMergeWindowPrivate(this); setModel(d); setItemDelegate(new DirMergeItemDelegate(this)); connect(this, &DirectoryMergeWindow::doubleClicked, this, &DirectoryMergeWindow::onDoubleClick); connect(this, &DirectoryMergeWindow::expanded, this, &DirectoryMergeWindow::onExpanded); d->m_pOptions = pOptions; setSortingEnabled(true); } DirectoryMergeWindow::~DirectoryMergeWindow() { delete d; } void DirectoryMergeWindow::setDirectoryMergeInfo(DirectoryMergeInfo* p) { d->m_pDirectoryMergeInfo = p; } bool DirectoryMergeWindow::isDirectoryMergeInProgress() { return d->m_bRealMergeStarted; } bool DirectoryMergeWindow::isSyncMode() { return d->m_bSyncMode; } bool DirectoryMergeWindow::isScanning() { return d->m_bScanning; } int DirectoryMergeWindow::totalColumnWidth() { int w = 0; for(int i = 0; i < s_OpStatusCol; ++i) { w += columnWidth(i); } return w; } void DirectoryMergeWindow::reload() { if(isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, - i18n("You are currently doing a directory merge. Are you sure, you want to abort the merge and rescan the directory?"), + i18n("You are currently doing a folder merge. Are you sure, you want to abort the merge and rescan the folder?"), i18n("Warning"), KGuiItem(i18n("Rescan")), KGuiItem(i18n("Continue Merging"))); if(result != KMessageBox::Yes) return; } init(d->rootMFI()->getDirectoryInfo(), true); //fix file visibilities after reload or menu will be out of sync with display if changed from defaults. updateFileVisibilities(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI->hasDir()) { ++nofDirs; } else { ++nofFiles; if(pMFI->isEqualAB() && (!bThreeDirs || pMFI->isEqualAC())) { ++nofEqualFiles; } else { if(pMFI->getOperation() == eMergeABCToDest || pMFI->getOperation() == eMergeABToDest) ++nofManualMerges; } } for(int childIdx = 0; childIdx < rowCount(mi); ++childIdx) calcDirStatus(bThreeDirs, index(childIdx, 0, mi), nofFiles, nofDirs, nofEqualFiles, nofManualMerges); } bool DirectoryMergeWindow::init( const QSharedPointer& dirInfo, bool bDirectoryMerge, bool bReload) { return d->init(dirInfo, bDirectoryMerge, bReload); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::buildMergeMap(const QSharedPointer& dirInfo) { t_DirectoryList::iterator dirIterator; if(dirInfo->dirA().isValid()) { for(dirIterator = dirInfo->getDirListA().begin(); dirIterator != dirInfo->getDirListA().end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoA(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } if(dirInfo->dirB().isValid()) { for(dirIterator = dirInfo->getDirListB().begin(); dirIterator != dirInfo->getDirListB().end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoB(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } if(dirInfo->dirC().isValid()) { for(dirIterator = dirInfo->getDirListC().begin(); dirIterator != dirInfo->getDirListC().end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoC(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::init( const QSharedPointer& dirInfo, bool bDirectoryMerge, bool bReload) { //set root data now that we have the directory info. rootMFI()->setDirectoryInfo(dirInfo); if(m_pOptions->m_bDmFullAnalysis) { // A full analysis uses the same resources that a normal text-diff/merge uses. // So make sure that the user saves his data first. bool bCanContinue = false; Q_EMIT mWindow->checkIfCanContinue(bCanContinue); if(!bCanContinue) return false; Q_EMIT mWindow->startDiffMerge("", "", "", "", "", "", "", nullptr); // hide main window } mWindow->show(); mWindow->setUpdatesEnabled(true); std::map expandedDirsMap; if(bReload) { // Remember expanded items TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // t_ItemInfo& ii = expandedDirsMap[ pDMI->m_pMFI->subPath() ]; // ii.bExpanded = pDMI->isExpanded(); // ii.bOperationComplete = pDMI->m_pMFI->m_bOperationComplete; // ii.status = pDMI->text( s_OpStatusCol ); // ii.eMergeOperation = pDMI->m_pMFI->getOperation(); // ++it; //} } ProgressProxy pp; m_bFollowDirLinks = m_pOptions->m_bDmFollowDirLinks; m_bFollowFileLinks = m_pOptions->m_bDmFollowFileLinks; m_bSimulatedMergeStarted = false; m_bRealMergeStarted = false; m_bError = false; m_bDirectoryMerge = bDirectoryMerge; m_selection1Index = QModelIndex(); m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); m_bCaseSensitive = m_pOptions->m_bDmCaseSensitiveFilenameComparison; m_bUnfoldSubdirs = m_pOptions->m_bDmUnfoldSubdirs; m_bSkipDirStatus = m_pOptions->m_bDmSkipDirStatus; beginResetModel(); m_pRoot->clear(); m_mergeItemList.clear(); endResetModel(); m_currentIndexForOperation = m_mergeItemList.end(); if(!bReload) { m_pDirShowIdenticalFiles->setChecked(true); m_pDirShowDifferentFiles->setChecked(true); m_pDirShowFilesOnlyInA->setChecked(true); m_pDirShowFilesOnlyInB->setChecked(true); m_pDirShowFilesOnlyInC->setChecked(true); } Q_ASSERT(dirInfo != nullptr); FileAccess dirA = dirInfo->dirA(); FileAccess dirB = dirInfo->dirB(); FileAccess dirC = dirInfo->dirC(); const FileAccess dirDest = dirInfo->destDir(); // Check if all input directories exist and are valid. The dest dir is not tested now. // The test will happen only when we are going to write to it. if(!dirA.isDir() || !dirB.isDir() || (dirC.isValid() && !dirC.isDir())) { - QString text(i18n("Opening of directories failed:")); + QString text(i18n("Opening of folders failed:")); text += "\n\n"; if(!dirA.isDir()) { - text += i18n("Dir A \"%1\" does not exist or is not a directory.\n", dirA.prettyAbsPath()); + text += i18n("Folder A \"%1\" does not exist or is not a folder.\n", dirA.prettyAbsPath()); } if(!dirB.isDir()) { - text += i18n("Dir B \"%1\" does not exist or is not a directory.\n", dirB.prettyAbsPath()); + text += i18n("Folder B \"%1\" does not exist or is not a folder.\n", dirB.prettyAbsPath()); } if(dirC.isValid() && !dirC.isDir()) { - text += i18n("Dir C \"%1\" does not exist or is not a directory.\n", dirC.prettyAbsPath()); + text += i18n("Folder C \"%1\" does not exist or is not a folder.\n", dirC.prettyAbsPath()); } - KMessageBox::sorry(mWindow, text, i18n("Directory Open Error")); + KMessageBox::sorry(mWindow, text, i18n("Folder Opening Error")); return false; } if(dirC.isValid() && (dirDest.prettyAbsPath() == dirA.prettyAbsPath() || dirDest.prettyAbsPath() == dirB.prettyAbsPath())) { KMessageBox::error(mWindow, - i18n("The destination directory must not be the same as A or B when " - "three directories are merged.\nCheck again before continuing."), + i18n("The destination folder must not be the same as A or B when " + "three folders are merged.\nCheck again before continuing."), i18n("Parameter Warning")); return false; } m_bScanning = true; - Q_EMIT mWindow->statusBarMessage(i18n("Scanning directories...")); + Q_EMIT mWindow->statusBarMessage(i18n("Scanning folders...")); m_bSyncMode = m_pOptions->m_bDmSyncMode && dirInfo->allowSyncMode(); m_fileMergeMap.clear(); s_eCaseSensitivity = m_bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; // calc how many directories will be read: double nofScans = (dirA.isValid() ? 1 : 0) + (dirB.isValid() ? 1 : 0) + (dirC.isValid() ? 1 : 0); int currentScan = 0; //TODO setColumnWidthMode(s_UnsolvedCol, Q3ListView::Manual); // setColumnWidthMode(s_SolvedCol, Q3ListView::Manual); // setColumnWidthMode(s_WhiteCol, Q3ListView::Manual); // setColumnWidthMode(s_NonWhiteCol, Q3ListView::Manual); mWindow->setColumnHidden(s_CCol, !dirC.isValid()); mWindow->setColumnHidden(s_WhiteCol, !m_pOptions->m_bDmFullAnalysis); mWindow->setColumnHidden(s_NonWhiteCol, !m_pOptions->m_bDmFullAnalysis); mWindow->setColumnHidden(s_UnsolvedCol, !m_pOptions->m_bDmFullAnalysis); mWindow->setColumnHidden(s_SolvedCol, !(m_pOptions->m_bDmFullAnalysis && dirC.isValid())); bool bListDirSuccessA = true; bool bListDirSuccessB = true; bool bListDirSuccessC = true; if(dirA.isValid()) { - pp.setInformation(i18n("Reading Directory A")); + pp.setInformation(i18n("Reading Folder A")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessA = dirInfo->listDirA(*m_pOptions); } if(dirB.isValid()) { - pp.setInformation(i18n("Reading Directory B")); + pp.setInformation(i18n("Reading Folder B")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessB = dirInfo->listDirB(*m_pOptions); } e_MergeOperation eDefaultMergeOp; if(dirC.isValid()) { - pp.setInformation(i18n("Reading Directory C")); + pp.setInformation(i18n("Reading Folder C")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessC = dirInfo->listDirC(*m_pOptions); eDefaultMergeOp = eMergeABCToDest; } else eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest; buildMergeMap(dirInfo); bool bContinue = true; if(!bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC) { - QString s = i18n("Some subdirectories were not readable in"); + QString s = i18n("Some subfolders were not readable in"); if(!bListDirSuccessA) s += "\nA: " + dirA.prettyAbsPath(); if(!bListDirSuccessB) s += "\nB: " + dirB.prettyAbsPath(); if(!bListDirSuccessC) s += "\nC: " + dirC.prettyAbsPath(); s += '\n'; - s += i18n("Check the permissions of the subdirectories."); + s += i18n("Check the permissions of the subfolders."); bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel(mWindow, s); } if(bContinue) { prepareListView(pp); mWindow->updateFileVisibilities(); for(int childIdx = 0; childIdx < rowCount(); ++childIdx) { QModelIndex mi = index(childIdx, 0, QModelIndex()); calcSuggestedOperation(mi, eDefaultMergeOp); } } mWindow->sortByColumn(0, Qt::AscendingOrder); for(int column = 0; column < columnCount(QModelIndex()); ++column) mWindow->resizeColumnToContents(column); // Try to improve the view a little bit. QWidget* pParent = mWindow->parentWidget(); QSplitter* pSplitter = static_cast(pParent); if(pSplitter != nullptr) { QList sizes = pSplitter->sizes(); int total = sizes[0] + sizes[1]; if(total < 10) total = 100; sizes[0] = total * 6 / 10; sizes[1] = total - sizes[0]; pSplitter->setSizes(sizes); } m_bScanning = false; Q_EMIT mWindow->statusBarMessage(i18n("Ready.")); if(bContinue && !m_bSkipDirStatus) { // Generate a status report int nofFiles = 0; int nofDirs = 0; int nofEqualFiles = 0; int nofManualMerges = 0; //TODO for(int childIdx = 0; childIdx < rowCount(); ++childIdx) calcDirStatus(dirC.isValid(), index(childIdx, 0, QModelIndex()), nofFiles, nofDirs, nofEqualFiles, nofManualMerges); QString s; - s = i18n("Directory Comparison Status\n\n" - "Number of subdirectories: %1\n" + s = i18n("Folder Comparison Status\n\n" + "Number of subfolders: %1\n" "Number of equal files: %2\n" "Number of different files: %3", nofDirs, nofEqualFiles, nofFiles - nofEqualFiles); if(dirC.isValid()) s += '\n' + i18n("Number of manual merges: %1", nofManualMerges); KMessageBox::information(mWindow, s); // //TODO //if ( topLevelItemCount()>0 ) //{ // topLevelItem(0)->setSelected(true); // setCurrentItem( topLevelItem(0) ); //} } if(bReload) { // Remember expanded items //TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // std::map::iterator i = expandedDirsMap.find( pDMI->m_pMFI->subPath() ); // if ( i!=expandedDirsMap.end() ) // { // t_ItemInfo& ii = i->second; // pDMI->setExpanded( ii.bExpanded ); // //pDMI->m_pMFI->setMergeOperation( ii.eMergeOperation, false ); unsafe, might have changed // pDMI->m_pMFI->m_bOperationComplete = ii.bOperationComplete; // pDMI->setText( s_OpStatusCol, ii.status ); // } // ++it; //} } else if(m_bUnfoldSubdirs) { m_pDirUnfoldAll->trigger(); } return true; } inline QString DirectoryMergeWindow::getDirNameA() const { return d->rootMFI()->getDirectoryInfo()->dirA().prettyAbsPath(); } inline QString DirectoryMergeWindow::getDirNameB() const { return d->rootMFI()->getDirectoryInfo()->dirB().prettyAbsPath(); } inline QString DirectoryMergeWindow::getDirNameC() const { return d->rootMFI()->getDirectoryInfo()->dirC().prettyAbsPath(); } inline QString DirectoryMergeWindow::getDirNameDest() const { return d->rootMFI()->getDirectoryInfo()->destDir().prettyAbsPath(); } void DirectoryMergeWindow::onExpanded() { resizeColumnToContents(s_NameCol); } void DirectoryMergeWindow::slotChooseAEverywhere() { d->setAllMergeOperations(eCopyAToDest); } void DirectoryMergeWindow::slotChooseBEverywhere() { d->setAllMergeOperations(eCopyBToDest); } void DirectoryMergeWindow::slotChooseCEverywhere() { d->setAllMergeOperations(eCopyCToDest); } void DirectoryMergeWindow::slotAutoChooseEverywhere() { e_MergeOperation eDefaultMergeOp = d->isThreeWay() ? eMergeABCToDest : d->m_bSyncMode ? eMergeToAB : eMergeABToDest; d->setAllMergeOperations(eDefaultMergeOp); } void DirectoryMergeWindow::slotNoOpEverywhere() { d->setAllMergeOperations(eNoOperation); } void DirectoryMergeWindow::slotFoldAllSubdirs() { collapseAll(); } void DirectoryMergeWindow::slotUnfoldAllSubdirs() { expandAll(); } // Merge current item (merge mode) void DirectoryMergeWindow::slotCurrentDoNothing() { d->setMergeOperation(currentIndex(), eNoOperation); } void DirectoryMergeWindow::slotCurrentChooseA() { d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyAToB : eCopyAToDest); } void DirectoryMergeWindow::slotCurrentChooseB() { d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyBToA : eCopyBToDest); } void DirectoryMergeWindow::slotCurrentChooseC() { d->setMergeOperation(currentIndex(), eCopyCToDest); } void DirectoryMergeWindow::slotCurrentMerge() { bool bThreeDirs = d->isThreeWay(); d->setMergeOperation(currentIndex(), bThreeDirs ? eMergeABCToDest : eMergeABToDest); } void DirectoryMergeWindow::slotCurrentDelete() { d->setMergeOperation(currentIndex(), eDeleteFromDest); } // Sync current item void DirectoryMergeWindow::slotCurrentCopyAToB() { d->setMergeOperation(currentIndex(), eCopyAToB); } void DirectoryMergeWindow::slotCurrentCopyBToA() { d->setMergeOperation(currentIndex(), eCopyBToA); } void DirectoryMergeWindow::slotCurrentDeleteA() { d->setMergeOperation(currentIndex(), eDeleteA); } void DirectoryMergeWindow::slotCurrentDeleteB() { d->setMergeOperation(currentIndex(), eDeleteB); } void DirectoryMergeWindow::slotCurrentDeleteAAndB() { d->setMergeOperation(currentIndex(), eDeleteAB); } void DirectoryMergeWindow::slotCurrentMergeToA() { d->setMergeOperation(currentIndex(), eMergeToA); } void DirectoryMergeWindow::slotCurrentMergeToB() { d->setMergeOperation(currentIndex(), eMergeToB); } void DirectoryMergeWindow::slotCurrentMergeToAAndB() { d->setMergeOperation(currentIndex(), eMergeToAB); } void DirectoryMergeWindow::keyPressEvent(QKeyEvent* e) { if((e->QInputEvent::modifiers() & Qt::ControlModifier) != 0) { MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI == nullptr) return; bool bThreeDirs = pMFI->isThreeWay(); bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : pMFI->conflictingFileTypes(); if(bMergeMode) { switch(e->key()) { case Qt::Key_1: if(pMFI->existsInA()) { slotCurrentChooseA(); } return; case Qt::Key_2: if(pMFI->existsInB()) { slotCurrentChooseB(); } return; case Qt::Key_3: if(pMFI->existsInC()) { slotCurrentChooseC(); } return; case Qt::Key_Space: slotCurrentDoNothing(); return; case Qt::Key_4: if(!bFTConflict) { slotCurrentMerge(); } return; case Qt::Key_Delete: slotCurrentDelete(); return; default: break; } } else { switch(e->key()) { case Qt::Key_1: if(pMFI->existsInA()) { slotCurrentCopyAToB(); } return; case Qt::Key_2: if(pMFI->existsInB()) { slotCurrentCopyBToA(); } return; case Qt::Key_Space: slotCurrentDoNothing(); return; case Qt::Key_4: if(!bFTConflict) { slotCurrentMergeToAAndB(); } return; case Qt::Key_Delete: if(pMFI->existsInA() && pMFI->existsInB()) slotCurrentDeleteAAndB(); else if(pMFI->existsInA()) slotCurrentDeleteA(); else if(pMFI->existsInB()) slotCurrentDeleteB(); return; default: break; } } } else if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { onDoubleClick(currentIndex()); return; } QTreeView::keyPressEvent(e); } void DirectoryMergeWindow::focusInEvent(QFocusEvent*) { Q_EMIT updateAvailabilities(); } void DirectoryMergeWindow::focusOutEvent(QFocusEvent*) { Q_EMIT updateAvailabilities(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setAllMergeOperations(e_MergeOperation eDefaultOperation) { if(KMessageBox::Yes == KMessageBox::warningYesNo(mWindow, i18n("This affects all merge operations."), i18n("Changing All Merge Operations"), KStandardGuiItem::cont(), KStandardGuiItem::cancel())) { for(int i = 0; i < rowCount(); ++i) { calcSuggestedOperation(index(i, 0, QModelIndex()), eDefaultOperation); } } } QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::nextSibling(const QModelIndex& mi) { QModelIndex miParent = mi.parent(); int currentIdx = mi.row(); if(currentIdx + 1 < mi.model()->rowCount(miParent)) return mi.model()->index(mi.row() + 1, 0, miParent); // next child of parent return QModelIndex(); } // Iterate through the complete tree. Start by specifying QListView::firstChild(). QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::treeIterator(QModelIndex mi, bool bVisitChildren, bool bFindInvisible) { if(mi.isValid()) { do { if(bVisitChildren && mi.model()->rowCount(mi) != 0) mi = mi.model()->index(0, 0, mi); else { QModelIndex miNextSibling = nextSibling(mi); if(miNextSibling.isValid()) mi = miNextSibling; else { mi = mi.parent(); while(mi.isValid()) { miNextSibling = nextSibling(mi); if(miNextSibling.isValid()) { mi = miNextSibling; break; } else { mi = mi.parent(); } } } } } while(mi.isValid() && mWindow->isRowHidden(mi.row(), mi.parent()) && !bFindInvisible); } return mi; } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareListView(ProgressProxy& pp) { QStringList errors; //TODO clear(); PixMapUtils::initPixmaps(m_pOptions->m_newestFileColor, m_pOptions->m_oldestFileColor, m_pOptions->m_midAgeFileColor, m_pOptions->m_missingFileColor); mWindow->setRootIsDecorated(true); t_fileMergeMap::iterator j; int nrOfFiles = m_fileMergeMap.size(); int currentIdx = 1; QTime t; t.start(); pp.setMaxNofSteps(nrOfFiles); for(j = m_fileMergeMap.begin(); j != m_fileMergeMap.end(); ++j) { MergeFileInfos& mfi = j.value(); // const QString& fileName = j->first; const QString& fileName = mfi.subPath(); pp.setInformation( i18n("Processing %1 / %2\n%3", currentIdx, nrOfFiles, fileName), currentIdx, false); if(pp.wasCancelled()) break; ++currentIdx; // The comparisons and calculations for each file take place here. mfi.compareFilesAndCalcAges(errors, m_pOptions, mWindow); // Get dirname from fileName: Search for "/" from end: int pos = fileName.lastIndexOf('/'); QString dirPart; QString filePart; if(pos == -1) { // Top dir filePart = fileName; } else { dirPart = fileName.left(pos); filePart = fileName.mid(pos + 1); } if(dirPart.isEmpty()) // Top level { m_pRoot->addChild(&mfi); //new DirMergeItem( this, filePart, &mfi ); mfi.setParent(m_pRoot); } else { FileAccess* pFA = mfi.getFileInfoA() ? mfi.getFileInfoA() : mfi.getFileInfoB() ? mfi.getFileInfoB() : mfi.getFileInfoC(); MergeFileInfos& dirMfi = pFA->parent() ? m_fileMergeMap[FileKey(*pFA->parent())] : *m_pRoot; // parent dirMfi.addChild(&mfi); //new DirMergeItem( dirMfi.m_pDMI, filePart, &mfi ); mfi.setParent(&dirMfi); // // Equality for parent dirs is set in updateFileVisibilities() } mfi.updateAge(); } if(errors.size() > 0) { if(errors.size() < 15) { KMessageBox::errorList(mWindow, i18n("Some files could not be processed."), errors); } else { KMessageBox::error(mWindow, i18n("Some files could not be processed.")); } } beginResetModel(); endResetModel(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; bool bCheckC = pMFI->isThreeWay(); bool bCopyNewer = m_pOptions->m_bDmCopyNewer; bool bOtherDest = !((pMFI->getDirectoryInfo()->destDir().absoluteFilePath() == pMFI->getDirectoryInfo()->dirA().absoluteFilePath()) || (pMFI->getDirectoryInfo()->destDir().absoluteFilePath() == pMFI->getDirectoryInfo()->dirB().absoluteFilePath()) || (bCheckC && pMFI->getDirectoryInfo()->destDir().absoluteFilePath() == pMFI->getDirectoryInfo()->dirC().absoluteFilePath())); if(eDefaultMergeOp == eMergeABCToDest && !bCheckC) { eDefaultMergeOp = eMergeABToDest; } if(eDefaultMergeOp == eMergeToAB && bCheckC) { Q_ASSERT(true); } if(eDefaultMergeOp == eMergeToA || eDefaultMergeOp == eMergeToB || eDefaultMergeOp == eMergeABCToDest || eDefaultMergeOp == eMergeABToDest || eDefaultMergeOp == eMergeToAB) { if(!bCheckC) { if(pMFI->isEqualAB()) { setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation); } else if(pMFI->existsInA() && pMFI->existsInB()) { if(!bCopyNewer || pMFI->isDirA()) setMergeOperation(mi, eDefaultMergeOp); else if(bCopyNewer && pMFI->conflictingAges()) { setMergeOperation(mi, eConflictingAges); } else { if(pMFI->getAgeA() == eNew) setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyAToB : eCopyAToDest); else setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyBToA : eCopyBToDest); } } else if(!pMFI->existsInA() && pMFI->existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyBToDest); else if(eDefaultMergeOp == eMergeToB) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyBToA); } else if(pMFI->existsInA() && !pMFI->existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyAToDest); else if(eDefaultMergeOp == eMergeToA) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyAToB); } else //if ( !pMFI->existsInA() && !pMFI->existsInB() ) { setMergeOperation(mi, eNoOperation); } } else { if(pMFI->isEqualAB() && pMFI->isEqualAC()) { setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation); } else if(pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->isEqualAB() || pMFI->isEqualBC()) setMergeOperation(mi, eCopyCToDest); else if(pMFI->isEqualAC()) setMergeOperation(mi, eCopyBToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) { if(pMFI->isEqualAB()) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->isEqualAC()) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(!pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->isEqualBC()) setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(!pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()) { setMergeOperation(mi, eCopyCToDest); } else if(!pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) { setMergeOperation(mi, eCopyBToDest); } else if(pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC()) { setMergeOperation(mi, eDeleteFromDest); } else //if ( !pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC() ) { setMergeOperation(mi, eNoOperation); } } // Now check if file/dir-types fit. if(pMFI->conflictingFileTypes()) { setMergeOperation(mi, eConflictingFileTypes); } } else { e_MergeOperation eMO = eDefaultMergeOp; switch(eDefaultMergeOp) { case eConflictingFileTypes: case eChangedAndDeleted: case eConflictingAges: case eDeleteA: case eDeleteB: case eDeleteAB: case eDeleteFromDest: case eNoOperation: break; case eCopyAToB: if(!pMFI->existsInA()) { eMO = eDeleteB; } break; case eCopyBToA: if(!pMFI->existsInB()) { eMO = eDeleteA; } break; case eCopyAToDest: if(!pMFI->existsInA()) { eMO = eDeleteFromDest; } break; case eCopyBToDest: if(!pMFI->existsInB()) { eMO = eDeleteFromDest; } break; case eCopyCToDest: if(!pMFI->existsInC()) { eMO = eDeleteFromDest; } break; case eMergeToA: case eMergeToB: case eMergeToAB: case eMergeABCToDest: case eMergeABToDest: break; default: Q_ASSERT(true); break; } setMergeOperation(mi, eMO); } } void DirectoryMergeWindow::onDoubleClick(const QModelIndex& mi) { if(!mi.isValid()) return; d->m_bSimulatedMergeStarted = false; if(d->m_bDirectoryMerge) mergeCurrentFile(); else compareCurrentFile(); } void DirectoryMergeWindow::currentChanged(const QModelIndex& current, const QModelIndex& previous) { QTreeView::currentChanged(current, previous); MergeFileInfos* pMFI = d->getMFI(current); if(pMFI == nullptr) return; d->m_pDirectoryMergeInfo->setInfo(pMFI->getDirectoryInfo()->dirA(), pMFI->getDirectoryInfo()->dirB(), pMFI->getDirectoryInfo()->dirC(), pMFI->getDirectoryInfo()->destDir(), *pMFI); } void DirectoryMergeWindow::mousePressEvent(QMouseEvent* e) { QTreeView::mousePressEvent(e); QModelIndex mi = indexAt(e->pos()); int c = mi.column(); QPoint p = e->globalPos(); MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) return; if(c == s_OpCol) { bool bThreeDirs = d->isThreeWay(); QMenu m(this); if(bThreeDirs) { m.addAction(d->m_pDirCurrentDoNothing); int count = 0; if(pMFI->existsInA()) { m.addAction(d->m_pDirCurrentChooseA); ++count; } if(pMFI->existsInB()) { m.addAction(d->m_pDirCurrentChooseB); ++count; } if(pMFI->existsInC()) { m.addAction(d->m_pDirCurrentChooseC); ++count; } if(!pMFI->conflictingFileTypes() && count > 1) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } else if(d->m_bSyncMode) { m.addAction(d->m_pDirCurrentSyncDoNothing); if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncCopyAToB); if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncCopyBToA); if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncDeleteA); if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncDeleteB); if(pMFI->existsInA() && pMFI->existsInB()) { m.addAction(d->m_pDirCurrentSyncDeleteAAndB); if(!pMFI->conflictingFileTypes()) { m.addAction(d->m_pDirCurrentSyncMergeToA); m.addAction(d->m_pDirCurrentSyncMergeToB); m.addAction(d->m_pDirCurrentSyncMergeToAAndB); } } } else { m.addAction(d->m_pDirCurrentDoNothing); if(pMFI->existsInA()) { m.addAction(d->m_pDirCurrentChooseA); } if(pMFI->existsInB()) { m.addAction(d->m_pDirCurrentChooseB); } if(!pMFI->conflictingFileTypes() && pMFI->existsInA() && pMFI->existsInB()) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } m.exec(p); } else if(c == s_ACol || c == s_BCol || c == s_CCol) { QString itemPath; if(c == s_ACol && pMFI->existsInA()) { itemPath = pMFI->fullNameA(); } else if(c == s_BCol && pMFI->existsInB()) { itemPath = pMFI->fullNameB(); } else if(c == s_CCol && pMFI->existsInC()) { itemPath = pMFI->fullNameC(); } if(!itemPath.isEmpty()) { d->selectItemAndColumn(mi, e->button() == Qt::RightButton); } } } #ifndef QT_NO_CONTEXTMENU void DirectoryMergeWindow::contextMenuEvent(QContextMenuEvent* e) { QModelIndex mi = indexAt(e->pos()); int c = mi.column(); MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) return; if(c == s_ACol || c == s_BCol || c == s_CCol) { QString itemPath; if(c == s_ACol && pMFI->existsInA()) { itemPath = pMFI->fullNameA(); } else if(c == s_BCol && pMFI->existsInB()) { itemPath = pMFI->fullNameB(); } else if(c == s_CCol && pMFI->existsInC()) { itemPath = pMFI->fullNameC(); } if(!itemPath.isEmpty()) { d->selectItemAndColumn(mi, true); QMenu m(this); m.addAction(d->m_pDirCompareExplicit); m.addAction(d->m_pDirMergeExplicit); m.popup(e->globalPos()); } } } #endif QString DirectoryMergeWindow::DirectoryMergeWindowPrivate::getFileName(const QModelIndex& mi) const { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->getFileInfoA()->absoluteFilePath() : mi.column() == s_BCol ? pMFI->getFileInfoB()->absoluteFilePath() : mi.column() == s_CCol ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""); } return QString(); } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::isDir(const QModelIndex& mi) const { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->isDirA() : mi.column() == s_BCol ? pMFI->isDirB() : pMFI->isDirC(); } return false; } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::selectItemAndColumn(const QModelIndex& mi, bool bContextMenu) { if(bContextMenu && (mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index)) return; QModelIndex old1 = m_selection1Index; QModelIndex old2 = m_selection2Index; QModelIndex old3 = m_selection3Index; bool bReset = false; if(m_selection1Index.isValid()) { if(isDir(m_selection1Index) != isDir(mi)) bReset = true; } if(bReset || m_selection3Index.isValid() || mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index) { // restart m_selection1Index = QModelIndex(); m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); } else if(!m_selection1Index.isValid()) { m_selection1Index = mi; m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); } else if(!m_selection2Index.isValid()) { m_selection2Index = mi; m_selection3Index = QModelIndex(); } else if(!m_selection3Index.isValid()) { m_selection3Index = mi; } if(old1.isValid()) Q_EMIT dataChanged(old1, old1); if(old2.isValid()) Q_EMIT dataChanged(old2, old2); if(old3.isValid()) Q_EMIT dataChanged(old3, old3); if(m_selection1Index.isValid()) Q_EMIT dataChanged(m_selection1Index, m_selection1Index); if(m_selection2Index.isValid()) Q_EMIT dataChanged(m_selection2Index, m_selection2Index); if(m_selection3Index.isValid()) Q_EMIT dataChanged(m_selection3Index, m_selection3Index); Q_EMIT mWindow->updateAvailabilities(); } //TODO //void DirMergeItem::init(MergeFileInfos* pMFI) //{ // pMFI->m_pDMI = this; //no not here // m_pMFI = pMFI; // TotalDiffStatus& tds = pMFI->m_totalDiffStatus; // if ( m_pMFI->dirA() || m_pMFI->dirB() || m_pMFI->isDirC() ) // { // } // else // { // setText( s_UnsolvedCol, QString::number( tds.getUnsolvedConflicts() ) ); // setText( s_SolvedCol, QString::number( tds.getSolvedConflicts() ) ); // setText( s_NonWhiteCol, QString::number( tds.getUnsolvedConflicts() + tds.getSolvedConflicts() - tds.getWhitespaceConflicts() ) ); // setText( s_WhiteCol, QString::number( tds.getWhitespaceConflicts() ) ); // } // setSizeHint( s_ACol, QSize(17,17) ); // Iconsize // setSizeHint( s_BCol, QSize(17,17) ); // Iconsize // setSizeHint( s_CCol, QSize(17,17) ); // Iconsize //} void DirectoryMergeWindow::DirectoryMergeWindowPrivate::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); beginResetModel(); m_pRoot->sort(order); endResetModel(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; if(eMergeOp != pMFI->getOperation()) { pMFI->startOperation(); setOpStatus(mi, eOpStatusNone); } pMFI->setOperation(eMergeOp); if(bRecursive) { e_MergeOperation eChildrenMergeOp = pMFI->getOperation(); if(eChildrenMergeOp == eConflictingFileTypes) eChildrenMergeOp = eMergeABCToDest; for(int childIdx = 0; childIdx < pMFI->children().count(); ++childIdx) { calcSuggestedOperation(index(childIdx, 0, mi), eChildrenMergeOp); } } } void DirectoryMergeWindow::compareCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } if(MergeFileInfos* pMFI = d->getMFI(currentIndex())) { if(!(pMFI->hasDir())) { Q_EMIT startDiffMerge( pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""), pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""), pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""), "", "", "", "", nullptr); } } Q_EMIT updateAvailabilities(); } void DirectoryMergeWindow::slotCompareExplicitlySelectedFiles() { if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } Q_EMIT startDiffMerge( d->getFileName(d->m_selection1Index), d->getFileName(d->m_selection2Index), d->getFileName(d->m_selection3Index), "", "", "", "", nullptr); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); Q_EMIT updateAvailabilities(); update(); } void DirectoryMergeWindow::slotMergeExplicitlySelectedFiles() { if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } QString fn1 = d->getFileName(d->m_selection1Index); QString fn2 = d->getFileName(d->m_selection2Index); QString fn3 = d->getFileName(d->m_selection3Index); Q_EMIT startDiffMerge(fn1, fn2, fn3, fn3.isEmpty() ? fn2 : fn3, "", "", "", nullptr); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); Q_EMIT updateAvailabilities(); update(); } bool DirectoryMergeWindow::isFileSelected() { if(MergeFileInfos* pMFI = d->getMFI(currentIndex())) { return !(pMFI->hasDir() || pMFI->conflictingFileTypes()); } return false; } void DirectoryMergeWindow::mergeResultSaved(const QString& fileName) { QModelIndex mi = (d->m_mergeItemList.empty() || d->m_currentIndexForOperation == d->m_mergeItemList.end()) ? QModelIndex() : *d->m_currentIndexForOperation; MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) { // This can happen if the same file is saved and modified and saved again. Nothing to do then. return; } if(fileName == pMFI->fullNameDest()) { if(pMFI->getOperation() == eMergeToAB) { bool bSuccess = d->copyFLD(pMFI->fullNameB(), pMFI->fullNameA()); if(!bSuccess) { KMessageBox::error(this, i18n("An error occurred while copying.")); d->m_pStatusInfo->setWindowTitle(i18n("Merge Error")); d->m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); d->m_bError = true; d->setOpStatus(mi, eOpStatusError); pMFI->setOperation(eCopyBToA); return; } } d->setOpStatus(mi, eOpStatusDone); pMFI->endOperation(); if(d->m_mergeItemList.size() == 1) { d->m_mergeItemList.clear(); d->m_bRealMergeStarted = false; } } Q_EMIT updateAvailabilities(); } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::canContinue() { bool bCanContinue = false; Q_EMIT mWindow->checkIfCanContinue(bCanContinue); if(bCanContinue && !m_bError) { QModelIndex mi = (m_mergeItemList.empty() || m_currentIndexForOperation == m_mergeItemList.end()) ? QModelIndex() : *m_currentIndexForOperation; MergeFileInfos* pMFI = getMFI(mi); if(pMFI && pMFI->isOperationRunning()) { setOpStatus(mi, eOpStatusNotSaved); pMFI->endOperation(); if(m_mergeItemList.size() == 1) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } } return bCanContinue; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge) { bool bCreateBackups = m_pOptions->m_bDmCreateBakFiles; // First decide destname QString destName; switch(mfi.getOperation()) { case eNoOperation: case eDeleteAB: break; case eMergeToAB: // let the user save in B. In mergeResultSaved() the file will be copied to A. case eMergeToB: case eDeleteB: case eCopyAToB: destName = mfi.fullNameB(); break; case eMergeToA: case eDeleteA: case eCopyBToA: destName = mfi.fullNameA(); break; case eMergeABToDest: case eMergeABCToDest: case eCopyAToDest: case eCopyBToDest: case eCopyCToDest: case eDeleteFromDest: destName = mfi.fullNameDest(); break; default: KMessageBox::error(mWindow, i18n("Unknown merge operation. (This must never happen!)")); } bool bSuccess = false; bSingleFileMerge = false; switch(mfi.getOperation()) { case eNoOperation: bSuccess = true; break; case eCopyAToDest: case eCopyAToB: bSuccess = copyFLD(mfi.fullNameA(), destName); break; case eCopyBToDest: case eCopyBToA: bSuccess = copyFLD(mfi.fullNameB(), destName); break; case eCopyCToDest: bSuccess = copyFLD(mfi.fullNameC(), destName); break; case eDeleteFromDest: case eDeleteA: case eDeleteB: bSuccess = deleteFLD(destName, bCreateBackups); break; case eDeleteAB: bSuccess = deleteFLD(mfi.fullNameA(), bCreateBackups) && deleteFLD(mfi.fullNameB(), bCreateBackups); break; case eMergeABToDest: case eMergeToA: case eMergeToAB: case eMergeToB: bSuccess = mergeFLD(mfi.fullNameA(), mfi.fullNameB(), "", destName, bSingleFileMerge); break; case eMergeABCToDest: bSuccess = mergeFLD( mfi.existsInA() ? mfi.fullNameA() : QString(""), mfi.existsInB() ? mfi.fullNameB() : QString(""), mfi.existsInC() ? mfi.fullNameC() : QString(""), destName, bSingleFileMerge); break; default: KMessageBox::error(mWindow, i18n("Unknown merge operation.")); } return bSuccess; } // Check if the merge can start, and prepare the m_mergeItemList which then contains all // items that must be merged. void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose) { if(bVerbose) { int status = KMessageBox::warningYesNoCancel(mWindow, i18n("The merge is about to begin.\n\n" "Choose \"Do it\" if you have read the instructions and know what you are doing.\n" "Choosing \"Simulate it\" will tell you what would happen.\n\n" "Be aware that this program still has beta status " "and there is NO WARRANTY whatsoever! Make backups of your vital data!"), i18n("Starting Merge"), KGuiItem(i18n("Do It")), KGuiItem(i18n("Simulate It"))); if(status == KMessageBox::Yes) m_bRealMergeStarted = true; else if(status == KMessageBox::No) m_bSimulatedMergeStarted = true; else return; } else { m_bRealMergeStarted = true; } m_mergeItemList.clear(); if(!miBegin.isValid()) return; for(QModelIndex mi = miBegin; mi != miEnd; mi = treeIterator(mi)) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI && pMFI->isOperationRunning()) { m_mergeItemList.push_back(mi); QString errorText; if(pMFI->getOperation() == eConflictingFileTypes) { - errorText = i18n("The highlighted item has a different type in the different directories. Select what to do."); + errorText = i18n("The highlighted item has a different type in the different folders. Select what to do."); } if(pMFI->getOperation() == eConflictingAges) { errorText = i18n("The modification dates of the file are equal but the files are not. Select what to do."); } if(pMFI->getOperation() == eChangedAndDeleted) { - errorText = i18n("The highlighted item was changed in one directory and deleted in the other. Select what to do."); + errorText = i18n("The highlighted item was changed in one folder and deleted in the other. Select what to do."); } if(!errorText.isEmpty()) { mWindow->scrollTo(mi, QAbstractItemView::EnsureVisible); mWindow->setCurrentIndex(mi); KMessageBox::error(mWindow, errorText); m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } } } m_currentIndexForOperation = m_mergeItemList.begin(); } void DirectoryMergeWindow::slotRunOperationForCurrentItem() { if(!d->canContinue()) return; bool bVerbose = false; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = currentIndex(); QModelIndex miEnd = d->treeIterator(miBegin, false, false); // find next visible sibling (no children) d->prepareMergeStart(miBegin, miEnd, bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::slotRunOperationForAllItems() { if(!d->canContinue()) return; bool bVerbose = true; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); d->prepareMergeStart(miBegin, QModelIndex(), bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::mergeCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { - KMessageBox::sorry(this, i18n("This operation is currently not possible because directory merge is currently running."), i18n("Operation Not Possible")); + KMessageBox::sorry(this, i18n("This operation is currently not possible because folder merge is currently running."), i18n("Operation Not Possible")); return; } if(isFileSelected()) { MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI != nullptr) { d->m_mergeItemList.clear(); d->m_mergeItemList.push_back(currentIndex()); d->m_currentIndexForOperation = d->m_mergeItemList.begin(); bool bDummy = false; d->mergeFLD( pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""), pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""), pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""), pMFI->fullNameDest(), bDummy); } } Q_EMIT updateAvailabilities(); } // When bStart is true then m_currentIndexForOperation must still be processed. // When bVerbose is true then a messagebox will tell when the merge is complete. void DirectoryMergeWindow::DirectoryMergeWindowPrivate::mergeContinue(bool bStart, bool bVerbose) { ProgressProxy pp; if(m_mergeItemList.empty()) return; int nrOfItems = 0; int nrOfCompletedItems = 0; int nrOfCompletedSimItems = 0; // Count the number of completed items (for the progress bar). for(const QModelIndex& i : m_mergeItemList) { MergeFileInfos* pMFI = getMFI(i); ++nrOfItems; if(!pMFI->isOperationRunning()) ++nrOfCompletedItems; if(!pMFI->isSimOpRunning()) ++nrOfCompletedSimItems; } m_pStatusInfo->hide(); m_pStatusInfo->clear(); QModelIndex miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; bool bContinueWithCurrentItem = bStart; // true for first item, else false bool bSkipItem = false; if(!bStart && m_bError && miCurrent.isValid()) { int status = KMessageBox::warningYesNoCancel(mWindow, i18n("There was an error in the last step.\n" "Do you want to continue with the item that caused the error or do you want to skip this item?"), i18n("Continue merge after an error"), KGuiItem(i18n("Continue With Last Item")), KGuiItem(i18n("Skip Item"))); if(status == KMessageBox::Yes) bContinueWithCurrentItem = true; else if(status == KMessageBox::No) bSkipItem = true; else return; m_bError = false; } pp.setMaxNofSteps(nrOfItems); bool bSuccess = true; bool bSingleFileMerge = false; bool bSim = m_bSimulatedMergeStarted; while(bSuccess) { MergeFileInfos* pMFI = getMFI(miCurrent); if(pMFI == nullptr) { m_mergeItemList.clear(); m_bRealMergeStarted = false; break; } if(pMFI != nullptr && !bContinueWithCurrentItem) { if(bSim) { if(rowCount(miCurrent) == 0) { pMFI->endSimOp(); } } else { if(rowCount(miCurrent) == 0) { if(pMFI->isOperationRunning()) { setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone); pMFI->endOperation(); bSkipItem = false; } } else { setOpStatus(miCurrent, eOpStatusInProgress); } } } if(!bContinueWithCurrentItem) { // Depth first QModelIndex miPrev = miCurrent; ++m_currentIndexForOperation; miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; if((!miCurrent.isValid() || miCurrent.parent() != miPrev.parent()) && miPrev.parent().isValid()) { // Check if the parent may be set to "Done" QModelIndex miParent = miPrev.parent(); bool bDone = true; while(bDone && miParent.isValid()) { for(int childIdx = 0; childIdx < rowCount(miParent); ++childIdx) { pMFI = getMFI(index(childIdx, 0, miParent)); if((!bSim && pMFI->isOperationRunning()) || (bSim && !pMFI->isSimOpRunning())) { bDone = false; break; } } if(bDone) { pMFI = getMFI(miParent); if(bSim) pMFI->endSimOp(); else { setOpStatus(miParent, eOpStatusDone); pMFI->endOperation(); } } miParent = miParent.parent(); } } } if(!miCurrent.isValid()) // end? { if(m_bRealMergeStarted) { if(bVerbose) { KMessageBox::information(mWindow, i18n("Merge operation complete."), i18n("Merge Complete")); } m_bRealMergeStarted = false; m_pStatusInfo->setWindowTitle(i18n("Merge Complete")); } if(m_bSimulatedMergeStarted) { m_bSimulatedMergeStarted = false; QModelIndex mi = rowCount() > 0 ? index(0, 0, QModelIndex()) : QModelIndex(); for(; mi.isValid(); mi = treeIterator(mi)) { getMFI(mi)->startSimOp(); } m_pStatusInfo->setWindowTitle(i18n("Simulated merge complete: Check if you agree with the proposed operations.")); m_pStatusInfo->exec(); } m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } pMFI = getMFI(miCurrent); pp.setInformation(pMFI->subPath(), bSim ? nrOfCompletedSimItems : nrOfCompletedItems, false // bRedrawUpdate ); bSuccess = executeMergeOperation(*pMFI, bSingleFileMerge); // Here the real operation happens. if(bSuccess) { if(bSim) ++nrOfCompletedSimItems; else ++nrOfCompletedItems; bContinueWithCurrentItem = false; } if(pp.wasCancelled()) break; } // end while //g_pProgressDialog->hide(); mWindow->setCurrentIndex(miCurrent); mWindow->scrollTo(miCurrent, EnsureVisible); if(!bSuccess && !bSingleFileMerge) { KMessageBox::error(mWindow, i18n("An error occurred. Press OK to see detailed information.")); m_pStatusInfo->setWindowTitle(i18n("Merge Error")); m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); m_bError = true; setOpStatus(miCurrent, eOpStatusError); } else { m_bError = false; } Q_EMIT mWindow->updateAvailabilities(); if(m_currentIndexForOperation == m_mergeItemList.end()) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::deleteFLD(const QString& name, bool bCreateBackup) { FileAccess fi(name, true); if(!fi.exists()) return true; if(bCreateBackup) { bool bSuccess = renameFLD(name, name + ".orig"); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: While deleting %1: Creating backup failed.", name)); return false; } } else { if(fi.isDir() && !fi.isSymLink()) - m_pStatusInfo->addText(i18n("delete directory recursively( %1 )", name)); + m_pStatusInfo->addText(i18n("delete folder recursively( %1 )", name)); else m_pStatusInfo->addText(i18n("delete( %1 )", name)); if(m_bSimulatedMergeStarted) { return true; } if(fi.isDir() && !fi.isSymLink()) // recursive directory delete only for real dirs, not symlinks { t_DirectoryList dirList; bool bSuccess = fi.listDir(&dirList, false, true, "*", "", "", false, false); // not recursive, find hidden files if(!bSuccess) { // No Permission to read directory or other error. - m_pStatusInfo->addText(i18n("Error: delete dir operation failed while trying to read the directory.")); + m_pStatusInfo->addText(i18n("Error: delete folder operation failed while trying to read the folder.")); return false; } t_DirectoryList::iterator it; // create list iterator for(it = dirList.begin(); it != dirList.end(); ++it) // for each file... { FileAccess& fi2 = *it; Q_ASSERT(fi2.fileName() != "." && fi2.fileName() != ".."); bSuccess = deleteFLD(fi2.absoluteFilePath(), false); if(!bSuccess) break; } if(bSuccess) { bSuccess = FileAccess::removeDir(name); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: rmdir( %1 ) operation failed.", name)); // krazy:exclude=syscalls return false; } } } else { bool bSuccess = fi.removeFile(); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: delete operation failed.")); return false; } } } return true; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge) { FileAccess fi(nameA); if(fi.isDir()) { return makeDir(nameDest); } // Make sure that the dir exists, into which we will save the file later. int pos = nameDest.lastIndexOf('/'); if(pos > 0) { QString parentName = nameDest.left(pos); bool bSuccess = makeDir(parentName, true /*quiet*/); if(!bSuccess) return false; } m_pStatusInfo->addText(i18n("manual merge( %1, %2, %3 -> %4)", nameA, nameB, nameC, nameDest)); if(m_bSimulatedMergeStarted) { m_pStatusInfo->addText(i18n(" Note: After a manual merge the user should continue by pressing F7.")); return true; } bSingleFileMerge = true; setOpStatus(*m_currentIndexForOperation, eOpStatusInProgress); mWindow->scrollTo(*m_currentIndexForOperation, EnsureVisible); Q_EMIT mWindow->startDiffMerge(nameA, nameB, nameC, nameDest, "", "", "", nullptr); return false; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::copyFLD(const QString& srcName, const QString& destName) { bool bSuccess = false; if(srcName == destName) return true; FileAccess fi(srcName); FileAccess faDest(destName, true); if(faDest.exists() && !(fi.isDir() && faDest.isDir() && (fi.isSymLink() == faDest.isSymLink()))) { bSuccess = deleteFLD(destName, m_pOptions->m_bDmCreateBakFiles); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: copy( %1 -> %2 ) failed." "Deleting existing destination failed.", srcName, destName)); return bSuccess; } } if(fi.isSymLink() && ((fi.isDir() && !m_bFollowDirLinks) || (!fi.isDir() && !m_bFollowFileLinks))) { m_pStatusInfo->addText(i18n("copyLink( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } FileAccess destFi(destName); if(!destFi.isLocal() || !fi.isLocal()) { m_pStatusInfo->addText(i18n("Error: copyLink failed: Remote links are not yet supported.")); return false; } bSuccess = false; QString linkTarget = fi.readLink(); if(!linkTarget.isEmpty()) { bSuccess = FileAccess::symLink(linkTarget, destName); if(!bSuccess) m_pStatusInfo->addText(i18n("Error: copyLink failed.")); } return bSuccess; } if(fi.isDir()) { if(faDest.exists()) return true; bSuccess = makeDir(destName); return bSuccess; } int pos = destName.lastIndexOf('/'); if(pos > 0) { QString parentName = destName.left(pos); bSuccess = makeDir(parentName, true /*quiet*/); if(!bSuccess) return false; } m_pStatusInfo->addText(i18n("copy( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } FileAccess faSrc(srcName); bSuccess = faSrc.copyFile(destName); if(!bSuccess) m_pStatusInfo->addText(faSrc.getStatusText()); return bSuccess; } // Rename is not an operation that can be selected by the user. // It will only be used to create backups. // Hence it will delete an existing destination without making a backup (of the old backup.) bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::renameFLD(const QString& srcName, const QString& destName) { if(srcName == destName) return true; FileAccess destFile = FileAccess(destName, true); if(destFile.exists()) { bool bSuccess = deleteFLD(destName, false /*no backup*/); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during rename( %1 -> %2 ): " "Cannot delete existing destination.", srcName, destName)); return false; } } m_pStatusInfo->addText(i18n("rename( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess(srcName).rename(destFile); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: Rename failed.")); return false; } return true; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::makeDir(const QString& name, bool bQuiet) { FileAccess fi(name, true); if(fi.exists() && fi.isDir()) return true; if(fi.exists() && !fi.isDir()) { bool bSuccess = deleteFLD(name, true); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during makeDir of %1. " "Cannot delete existing file.", name)); return false; } } int pos = name.lastIndexOf('/'); if(pos > 0) { QString parentName = name.left(pos); bool bSuccess = makeDir(parentName, true); if(!bSuccess) return false; } if(!bQuiet) m_pStatusInfo->addText(i18n("makeDir( %1 )", name)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess::makeDir(name); if(!bSuccess) { - m_pStatusInfo->addText(i18n("Error while creating directory.")); + m_pStatusInfo->addText(i18n("Error while creating folder.")); return false; } return true; } DirectoryMergeInfo::DirectoryMergeInfo(QWidget* pParent) : QFrame(pParent) { QVBoxLayout* topLayout = new QVBoxLayout(this); topLayout->setMargin(0); QGridLayout* grid = new QGridLayout(); topLayout->addLayout(grid); grid->setColumnStretch(1, 10); int line = 0; m_pA = new QLabel(i18n("A"), this); grid->addWidget(m_pA, line, 0); m_pInfoA = new QLabel(this); grid->addWidget(m_pInfoA, line, 1); ++line; m_pB = new QLabel(i18n("B"), this); grid->addWidget(m_pB, line, 0); m_pInfoB = new QLabel(this); grid->addWidget(m_pInfoB, line, 1); ++line; m_pC = new QLabel(i18n("C"), this); grid->addWidget(m_pC, line, 0); m_pInfoC = new QLabel(this); grid->addWidget(m_pInfoC, line, 1); ++line; m_pDest = new QLabel(i18n("Dest"), this); grid->addWidget(m_pDest, line, 0); m_pInfoDest = new QLabel(this); grid->addWidget(m_pInfoDest, line, 1); ++line; m_pInfoList = new QTreeWidget(this); topLayout->addWidget(m_pInfoList); - m_pInfoList->setHeaderLabels({i18n("Dir"), i18n("Type"), i18n("Size"), + m_pInfoList->setHeaderLabels({i18n("Folder"), i18n("Type"), i18n("Size"), i18n("Attr"), i18n("Last Modification"), i18n("Link-Destination")}); setMinimumSize(100, 100); m_pInfoList->installEventFilter(this); m_pInfoList->setRootIsDecorated(false); } bool DirectoryMergeInfo::eventFilter(QObject* o, QEvent* e) { if(e->type() == QEvent::FocusIn && o == m_pInfoList) Q_EMIT gotFocus(); return false; } void DirectoryMergeInfo::addListViewItem(const QString& dir, const QString& basePath, FileAccess* fi) { if(basePath.isEmpty()) { return; } if(fi != nullptr && fi->exists()) { QString dateString = fi->lastModified().toString(QLocale::system().dateTimeFormat()); m_pInfoList->addTopLevelItem(new QTreeWidgetItem( m_pInfoList, - {dir, QString(fi->isDir() ? i18n("Dir") : i18n("File")) + (fi->isSymLink() ? i18n("-Link") : ""), QString::number(fi->size()), QLatin1String(fi->isReadable() ? "r" : " ") + QLatin1String(fi->isWritable() ? "w" : " ") + QLatin1String((fi->isExecutable() ? "x" : " ")), dateString, QString(fi->isSymLink() ? (" -> " + fi->readLink()) : QString(""))})); + {dir, QString(fi->isDir() ? i18n("Folder") : i18n("File")) + (fi->isSymLink() ? i18n("-Link") : ""), QString::number(fi->size()), QLatin1String(fi->isReadable() ? "r" : " ") + QLatin1String(fi->isWritable() ? "w" : " ") + QLatin1String((fi->isExecutable() ? "x" : " ")), dateString, QString(fi->isSymLink() ? (" -> " + fi->readLink()) : QString(""))})); } else { m_pInfoList->addTopLevelItem(new QTreeWidgetItem( m_pInfoList, {dir, i18n("not available"), "", "", "", ""})); } } void DirectoryMergeInfo::setInfo( const FileAccess& dirA, const FileAccess& dirB, const FileAccess& dirC, const FileAccess& dirDest, MergeFileInfos& mfi) { bool bHideDest = false; if(dirA.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pA->setText(i18n("A (Dest): ")); bHideDest = true; } else m_pA->setText(!dirC.isValid() ? i18n("A: ") : i18n("A (Base): ")); m_pInfoA->setText(dirA.prettyAbsPath()); if(dirB.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pB->setText(i18n("B (Dest): ")); bHideDest = true; } else m_pB->setText(i18n("B: ")); m_pInfoB->setText(dirB.prettyAbsPath()); if(dirC.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pC->setText(i18n("C (Dest): ")); bHideDest = true; } else m_pC->setText(i18n("C: ")); m_pInfoC->setText(dirC.prettyAbsPath()); m_pDest->setText(i18n("Dest: ")); m_pInfoDest->setText(dirDest.prettyAbsPath()); if(!dirC.isValid()) { m_pC->hide(); m_pInfoC->hide(); } else { m_pC->show(); m_pInfoC->show(); } if(!dirDest.isValid() || bHideDest) { m_pDest->hide(); m_pInfoDest->hide(); } else { m_pDest->show(); m_pInfoDest->show(); } m_pInfoList->clear(); addListViewItem(i18n("A"), dirA.prettyAbsPath(), mfi.getFileInfoA()); addListViewItem(i18n("B"), dirB.prettyAbsPath(), mfi.getFileInfoB()); addListViewItem(i18n("C"), dirC.prettyAbsPath(), mfi.getFileInfoC()); if(!bHideDest) { FileAccess fiDest(dirDest.prettyAbsPath() + '/' + mfi.subPath(), true); addListViewItem(i18n("Dest"), dirDest.prettyAbsPath(), &fiDest); } for(int i = 0; i < m_pInfoList->columnCount(); ++i) m_pInfoList->resizeColumnToContents(i); } void DirectoryMergeWindow::slotSaveMergeState() { //slotStatusMsg(i18n("Saving Directory Merge State ...")); - QString dirMergeStateFilename = QFileDialog::getSaveFileName(this, i18n("Save Directory Merge State As..."), QDir::currentPath()); + QString dirMergeStateFilename = QFileDialog::getSaveFileName(this, i18n("Save Folder Merge State As..."), QDir::currentPath()); if(!dirMergeStateFilename.isEmpty()) { QFile file(dirMergeStateFilename); bool bSuccess = file.open(QIODevice::WriteOnly); if(bSuccess) { QTextStream ts(&file); QModelIndex mi(d->index(0, 0, QModelIndex())); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); ts << *pMFI; mi = d->treeIterator(mi, true, true); } } } //slotStatusMsg(i18n("Ready.")); } void DirectoryMergeWindow::slotLoadMergeState() { } void DirectoryMergeWindow::updateFileVisibilities() { bool bShowIdentical = d->m_pDirShowIdenticalFiles->isChecked(); bool bShowDifferent = d->m_pDirShowDifferentFiles->isChecked(); bool bShowOnlyInA = d->m_pDirShowFilesOnlyInA->isChecked(); bool bShowOnlyInB = d->m_pDirShowFilesOnlyInB->isChecked(); bool bShowOnlyInC = d->m_pDirShowFilesOnlyInC->isChecked(); bool bThreeDirs = d->isThreeWay(); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); // in first run set all dirs to equal and determine if they are not equal. // on second run don't change the equal-status anymore; it is needed to // set the visibility (when bShowIdentical is false). for(int loop = 0; loop < 2; ++loop) { QModelIndex mi = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); bool bDir = pMFI->hasDir(); if(loop == 0 && bDir) { //Treat all links and directories to equal by default. pMFI->updateDirectoryOrLink(); } bool bVisible = (bShowIdentical && pMFI->existsEveryWhere() && pMFI->isEqualAB() && (pMFI->isEqualAC() || !bThreeDirs)) || ((bShowDifferent || bDir) && pMFI->existsCount() >= 2 && (!pMFI->isEqualAB() || !(pMFI->isEqualAC() || !bThreeDirs))) || (bShowOnlyInA && pMFI->onlyInA()) || (bShowOnlyInB && pMFI->onlyInB()) || (bShowOnlyInC && pMFI->onlyInC()); QString fileName = pMFI->fileName(); bVisible = bVisible && ((bDir && !Utils::wildcardMultiMatch(d->m_pOptions->m_DmDirAntiPattern, fileName, d->m_bCaseSensitive)) || (Utils::wildcardMultiMatch(d->m_pOptions->m_DmFilePattern, fileName, d->m_bCaseSensitive) && !Utils::wildcardMultiMatch(d->m_pOptions->m_DmFileAntiPattern, fileName, d->m_bCaseSensitive))); if(loop != 0) setRowHidden(mi.row(), mi.parent(), !bVisible); bool bEqual = bThreeDirs ? pMFI->isEqualAB() && pMFI->isEqualAC() : pMFI->isEqualAB(); if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal" { pMFI->updateParents(); } mi = d->treeIterator(mi, true, true); } } } void DirectoryMergeWindow::slotShowIdenticalFiles() { d->m_pOptions->m_bDmShowIdenticalFiles = d->m_pDirShowIdenticalFiles->isChecked(); updateFileVisibilities(); } void DirectoryMergeWindow::slotShowDifferentFiles() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInA() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInB() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInC() { updateFileVisibilities(); } void DirectoryMergeWindow::slotSynchronizeDirectories() {} void DirectoryMergeWindow::slotChooseNewerFiles() {} void DirectoryMergeWindow::initDirectoryMergeActions(KDiff3App* pKDiff3App, KActionCollection* ac) { #include "xpm/showequalfiles.xpm" #include "xpm/showfilesonlyina.xpm" #include "xpm/showfilesonlyinb.xpm" #include "xpm/showfilesonlyinc.xpm" #include "xpm/startmerge.xpm" - d->m_pDirStartOperation = GuiUtils::createAction(i18n("Start/Continue Directory Merge"), QKeySequence(Qt::Key_F7), this, &DirectoryMergeWindow::slotRunOperationForAllItems, ac, "dir_start_operation"); + d->m_pDirStartOperation = GuiUtils::createAction(i18n("Start/Continue Folder Merge"), QKeySequence(Qt::Key_F7), this, &DirectoryMergeWindow::slotRunOperationForAllItems, ac, "dir_start_operation"); d->m_pDirRunOperationForCurrentItem = GuiUtils::createAction(i18n("Run Operation for Current Item"), QKeySequence(Qt::Key_F6), this, &DirectoryMergeWindow::slotRunOperationForCurrentItem, ac, "dir_run_operation_for_current_item"); d->m_pDirCompareCurrent = GuiUtils::createAction(i18n("Compare Selected File"), this, &DirectoryMergeWindow::compareCurrentFile, ac, "dir_compare_current"); d->m_pDirMergeCurrent = GuiUtils::createAction(i18n("Merge Current File"), QIcon(QPixmap(startmerge)), i18n("Merge\nFile"), pKDiff3App, &KDiff3App::slotMergeCurrentFile, ac, "merge_current"); - d->m_pDirFoldAll = GuiUtils::createAction(i18n("Fold All Subdirs"), this, &DirectoryMergeWindow::collapseAll, ac, "dir_fold_all"); - d->m_pDirUnfoldAll = GuiUtils::createAction(i18n("Unfold All Subdirs"), this, &DirectoryMergeWindow::expandAll, ac, "dir_unfold_all"); + d->m_pDirFoldAll = GuiUtils::createAction(i18n("Fold All Subfolders"), this, &DirectoryMergeWindow::collapseAll, ac, "dir_fold_all"); + d->m_pDirUnfoldAll = GuiUtils::createAction(i18n("Unfold All Subfolders"), this, &DirectoryMergeWindow::expandAll, ac, "dir_unfold_all"); d->m_pDirRescan = GuiUtils::createAction(i18n("Rescan"), QKeySequence(Qt::SHIFT + Qt::Key_F5), this, &DirectoryMergeWindow::reload, ac, "dir_rescan"); d->m_pDirSaveMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Save Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotSaveMergeState, ac, "dir_save_merge_state"); d->m_pDirLoadMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Load Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotLoadMergeState, ac, "dir_load_merge_state"); d->m_pDirChooseAEverywhere = GuiUtils::createAction(i18n("Choose A for All Items"), this, &DirectoryMergeWindow::slotChooseAEverywhere, ac, "dir_choose_a_everywhere"); d->m_pDirChooseBEverywhere = GuiUtils::createAction(i18n("Choose B for All Items"), this, &DirectoryMergeWindow::slotChooseBEverywhere, ac, "dir_choose_b_everywhere"); d->m_pDirChooseCEverywhere = GuiUtils::createAction(i18n("Choose C for All Items"), this, &DirectoryMergeWindow::slotChooseCEverywhere, ac, "dir_choose_c_everywhere"); d->m_pDirAutoChoiceEverywhere = GuiUtils::createAction(i18n("Auto-Choose Operation for All Items"), this, &DirectoryMergeWindow::slotAutoChooseEverywhere, ac, "dir_autochoose_everywhere"); d->m_pDirDoNothingEverywhere = GuiUtils::createAction(i18n("No Operation for All Items"), this, &DirectoryMergeWindow::slotNoOpEverywhere, ac, "dir_nothing_everywhere"); // d->m_pDirSynchronizeDirectories = GuiUtils::createAction< KToggleAction >(i18n("Synchronize Directories"), 0, this, &DirectoryMergeWindow::slotSynchronizeDirectories, ac, "dir_synchronize_directories"); // d->m_pDirChooseNewerFiles = GuiUtils::createAction< KToggleAction >(i18n("Copy Newer Files Instead of Merging"), 0, this, &DirectoryMergeWindow::slotChooseNewerFiles, ac, "dir_choose_newer_files"); d->m_pDirShowIdenticalFiles = GuiUtils::createAction(i18n("Show Identical Files"), QIcon(QPixmap(showequalfiles)), i18n("Identical\nFiles"), this, &DirectoryMergeWindow::slotShowIdenticalFiles, ac, "dir_show_identical_files"); d->m_pDirShowDifferentFiles = GuiUtils::createAction(i18n("Show Different Files"), this, &DirectoryMergeWindow::slotShowDifferentFiles, ac, "dir_show_different_files"); d->m_pDirShowFilesOnlyInA = GuiUtils::createAction(i18n("Show Files only in A"), QIcon(QPixmap(showfilesonlyina)), i18n("Files\nonly in A"), this, &DirectoryMergeWindow::slotShowFilesOnlyInA, ac, "dir_show_files_only_in_a"); d->m_pDirShowFilesOnlyInB = GuiUtils::createAction(i18n("Show Files only in B"), QIcon(QPixmap(showfilesonlyinb)), i18n("Files\nonly in B"), this, &DirectoryMergeWindow::slotShowFilesOnlyInB, ac, "dir_show_files_only_in_b"); d->m_pDirShowFilesOnlyInC = GuiUtils::createAction(i18n("Show Files only in C"), QIcon(QPixmap(showfilesonlyinc)), i18n("Files\nonly in C"), this, &DirectoryMergeWindow::slotShowFilesOnlyInC, ac, "dir_show_files_only_in_c"); d->m_pDirShowIdenticalFiles->setChecked(d->m_pOptions->m_bDmShowIdenticalFiles); d->m_pDirCompareExplicit = GuiUtils::createAction(i18n("Compare Explicitly Selected Files"), this, &DirectoryMergeWindow::slotCompareExplicitlySelectedFiles, ac, "dir_compare_explicitly_selected_files"); d->m_pDirMergeExplicit = GuiUtils::createAction(i18n("Merge Explicitly Selected Files"), this, &DirectoryMergeWindow::slotMergeExplicitlySelectedFiles, ac, "dir_merge_explicitly_selected_files"); d->m_pDirCurrentDoNothing = GuiUtils::createAction(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_do_nothing"); d->m_pDirCurrentChooseA = GuiUtils::createAction(i18n("A"), this, &DirectoryMergeWindow::slotCurrentChooseA, ac, "dir_current_choose_a"); d->m_pDirCurrentChooseB = GuiUtils::createAction(i18n("B"), this, &DirectoryMergeWindow::slotCurrentChooseB, ac, "dir_current_choose_b"); d->m_pDirCurrentChooseC = GuiUtils::createAction(i18n("C"), this, &DirectoryMergeWindow::slotCurrentChooseC, ac, "dir_current_choose_c"); d->m_pDirCurrentMerge = GuiUtils::createAction(i18n("Merge"), this, &DirectoryMergeWindow::slotCurrentMerge, ac, "dir_current_merge"); d->m_pDirCurrentDelete = GuiUtils::createAction(i18n("Delete (if exists)"), this, &DirectoryMergeWindow::slotCurrentDelete, ac, "dir_current_delete"); d->m_pDirCurrentSyncDoNothing = GuiUtils::createAction(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_sync_do_nothing"); d->m_pDirCurrentSyncCopyAToB = GuiUtils::createAction(i18n("Copy A to B"), this, &DirectoryMergeWindow::slotCurrentCopyAToB, ac, "dir_current_sync_copy_a_to_b"); d->m_pDirCurrentSyncCopyBToA = GuiUtils::createAction(i18n("Copy B to A"), this, &DirectoryMergeWindow::slotCurrentCopyBToA, ac, "dir_current_sync_copy_b_to_a"); d->m_pDirCurrentSyncDeleteA = GuiUtils::createAction(i18n("Delete A"), this, &DirectoryMergeWindow::slotCurrentDeleteA, ac, "dir_current_sync_delete_a"); d->m_pDirCurrentSyncDeleteB = GuiUtils::createAction(i18n("Delete B"), this, &DirectoryMergeWindow::slotCurrentDeleteB, ac, "dir_current_sync_delete_b"); d->m_pDirCurrentSyncDeleteAAndB = GuiUtils::createAction(i18n("Delete A && B"), this, &DirectoryMergeWindow::slotCurrentDeleteAAndB, ac, "dir_current_sync_delete_a_and_b"); d->m_pDirCurrentSyncMergeToA = GuiUtils::createAction(i18n("Merge to A"), this, &DirectoryMergeWindow::slotCurrentMergeToA, ac, "dir_current_sync_merge_to_a"); d->m_pDirCurrentSyncMergeToB = GuiUtils::createAction(i18n("Merge to B"), this, &DirectoryMergeWindow::slotCurrentMergeToB, ac, "dir_current_sync_merge_to_b"); d->m_pDirCurrentSyncMergeToAAndB = GuiUtils::createAction(i18n("Merge to A && B"), this, &DirectoryMergeWindow::slotCurrentMergeToAAndB, ac, "dir_current_sync_merge_to_a_and_b"); } void DirectoryMergeWindow::updateAvailabilities(bool bDirCompare, bool bDiffWindowVisible, KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC) { d->m_pDirStartOperation->setEnabled(bDirCompare); d->m_pDirRunOperationForCurrentItem->setEnabled(bDirCompare); d->m_pDirFoldAll->setEnabled(bDirCompare); d->m_pDirUnfoldAll->setEnabled(bDirCompare); d->m_pDirCompareCurrent->setEnabled(bDirCompare && isVisible() && isFileSelected()); d->m_pDirMergeCurrent->setEnabled((bDirCompare && isVisible() && isFileSelected()) || bDiffWindowVisible); d->m_pDirRescan->setEnabled(bDirCompare); bool bThreeDirs = d->isThreeWay(); d->m_pDirAutoChoiceEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirDoNothingEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseAEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseBEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseCEverywhere->setEnabled(bDirCompare && isVisible() && bThreeDirs); MergeFileInfos* pMFI = d->getMFI(currentIndex()); bool bItemActive = bDirCompare && isVisible() && pMFI != nullptr; // && hasFocus(); bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : pMFI->conflictingFileTypes(); bool bDirWindowHasFocus = isVisible() && hasFocus(); d->m_pDirShowIdenticalFiles->setEnabled(bDirCompare && isVisible()); d->m_pDirShowDifferentFiles->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInA->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInB->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInC->setEnabled(bDirCompare && isVisible() && bThreeDirs); d->m_pDirCompareExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid()); d->m_pDirMergeExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid()); d->m_pDirCurrentDoNothing->setEnabled(bItemActive && bMergeMode); d->m_pDirCurrentChooseA->setEnabled(bItemActive && bMergeMode && pMFI->existsInA()); d->m_pDirCurrentChooseB->setEnabled(bItemActive && bMergeMode && pMFI->existsInB()); d->m_pDirCurrentChooseC->setEnabled(bItemActive && bMergeMode && pMFI->existsInC()); d->m_pDirCurrentMerge->setEnabled(bItemActive && bMergeMode && !bFTConflict); d->m_pDirCurrentDelete->setEnabled(bItemActive && bMergeMode); if(bDirWindowHasFocus) { chooseA->setEnabled(bItemActive && pMFI->existsInA()); chooseB->setEnabled(bItemActive && pMFI->existsInB()); chooseC->setEnabled(bItemActive && pMFI->existsInC()); chooseA->setChecked(false); chooseB->setChecked(false); chooseC->setChecked(false); } d->m_pDirCurrentSyncDoNothing->setEnabled(bItemActive && !bMergeMode); d->m_pDirCurrentSyncCopyAToB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA()); d->m_pDirCurrentSyncCopyBToA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB()); d->m_pDirCurrentSyncDeleteA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA()); d->m_pDirCurrentSyncDeleteB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB()); d->m_pDirCurrentSyncDeleteAAndB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA() && pMFI->existsInB()); d->m_pDirCurrentSyncMergeToA->setEnabled(bItemActive && !bMergeMode && !bFTConflict); d->m_pDirCurrentSyncMergeToB->setEnabled(bItemActive && !bMergeMode && !bFTConflict); d->m_pDirCurrentSyncMergeToAAndB->setEnabled(bItemActive && !bMergeMode && !bFTConflict); } //#include "directorymergewindow.moc" diff --git a/src/fileaccess.cpp b/src/fileaccess.cpp index fea4535..bffeb67 100644 --- a/src/fileaccess.cpp +++ b/src/fileaccess.cpp @@ -1,1249 +1,1249 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "fileaccess.h" #include "cvsignorelist.h" #include "common.h" #include "Logging.h" #include "progress.h" #include "ProgressProxyExtender.h" #include "Utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include FileAccess::FileAccess(const QString& name, bool bWantToWrite) { reset(); setFile(name, bWantToWrite); } FileAccess::FileAccess() { reset(); } void FileAccess::reset() { m_fileInfo = QFileInfo(); m_bExists = false; m_bFile = false; m_bDir = false; m_bSymLink = false; m_bWritable = false; m_bHidden = false; m_size = 0; m_modificationTime = QDateTime::fromMSecsSinceEpoch(0); m_url = QUrl(); m_bValidData = false; m_name = QString(); m_linkTarget = ""; //m_fileType = -1; tmpFile.clear(); tmpFile = QSharedPointer::create(); realFile = nullptr; } FileAccess::~FileAccess() { tmpFile.clear(); } /* Needed only during directory listing right now. */ void FileAccess::setFile(FileAccess* pParent, const QFileInfo& fi) { reset(); m_fileInfo = fi; m_url = QUrl::fromLocalFile(m_fileInfo.filePath()); if(!m_url.scheme().isEmpty()) m_url.setScheme(QLatin1Literal("file")); m_pParent = pParent; loadData(); } void FileAccess::setFile(const QString& name, bool bWantToWrite) { if(name.isEmpty()) return; QUrl url = QUrl::fromUserInput(name, QString(), QUrl::AssumeLocalFile); setFile(url, bWantToWrite); } void FileAccess::setFile(const QUrl& url, bool bWantToWrite) { reset(); Q_ASSERT(parent() == nullptr || url != parent()->url()); m_url = url; //QUrl::isLocalFile assumes the scheme is set. if(m_url.scheme().isEmpty()) m_url.setScheme(QLatin1Literal("file")); if(m_url.isLocalFile() || !m_url.isValid()) // Treat invalid urls as local files. { m_fileInfo.setFile(m_url.toLocalFile()); m_pParent = nullptr; loadData(); } else { m_name = m_url.fileName(); FileAccessJobHandler jh(this); // A friend, which writes to the parameters of this class! jh.stat(2 /*all details*/, bWantToWrite); // returns bSuccess, ignored m_bValidData = true; // After running stat() the variables are initialised // and valid even if the file doesn't exist and the stat // query failed. } } void FileAccess::loadData() { m_fileInfo.setCaching(true); if(parent() == nullptr) m_baseDir.setPath(m_fileInfo.absoluteFilePath()); else m_baseDir = m_pParent->m_baseDir; //convert to absolute path that doesn't depend on the current directory. m_fileInfo.makeAbsolute(); m_bSymLink = m_fileInfo.isSymLink(); m_bFile = m_fileInfo.isFile(); m_bDir = m_fileInfo.isDir(); m_bExists = m_fileInfo.exists(); m_size = m_fileInfo.size(); m_modificationTime = m_fileInfo.lastModified(); m_bHidden = m_fileInfo.isHidden(); m_bWritable = m_fileInfo.isWritable(); m_bReadable = m_fileInfo.isReadable(); m_bExecutable = m_fileInfo.isExecutable(); m_name = m_fileInfo.fileName(); if(isLocal() && m_name.isEmpty()) { m_name = m_fileInfo.absoluteDir().dirName(); } if(isLocal() && m_bSymLink) { m_linkTarget = m_fileInfo.symLinkTarget(); #ifndef Q_OS_WIN // Unfortunately Qt5 symLinkTarget/readLink always returns an absolute path, even if the link is relative char* s = (char*)malloc(PATH_MAX + 1); ssize_t len = readlink(QFile::encodeName(absoluteFilePath()).constData(), s, PATH_MAX); if(len > 0) { s[len] = '\0'; m_linkTarget = QFile::decodeName(s); } free(s); #endif } realFile = QSharedPointer::create(absoluteFilePath()); m_bValidData = true; } void FileAccess::addPath(const QString& txt) { if(!isLocal() && m_url.isValid()) { QUrl url = m_url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + txt); setFile(url); // reinitialise } else { QString slash = (txt.isEmpty() || txt[0] == '/') ? QLatin1String("") : QLatin1String("/"); setFile(absoluteFilePath() + slash + txt); } } /* Filetype: S_IFMT 0170000 bitmask for the file type bitfields S_IFSOCK 0140000 socket S_IFLNK 0120000 symbolic link S_IFREG 0100000 regular file S_IFBLK 0060000 block device S_IFDIR 0040000 directory S_IFCHR 0020000 character device S_IFIFO 0010000 fifo S_ISUID 0004000 set UID bit S_ISGID 0002000 set GID bit (see below) S_ISVTX 0001000 sticky bit (see below) Access: S_IRWXU 00700 mask for file owner permissions S_IRUSR 00400 owner has read permission S_IWUSR 00200 owner has write permission S_IXUSR 00100 owner has execute permission S_IRWXG 00070 mask for group permissions S_IRGRP 00040 group has read permission S_IWGRP 00020 group has write permission S_IXGRP 00010 group has execute permission S_IRWXO 00007 mask for permissions for others (not in group) S_IROTH 00004 others have read permission S_IWOTH 00002 others have write permission S_IXOTH 00001 others have execute permission */ void FileAccess::setFromUdsEntry(const KIO::UDSEntry& e, FileAccess *parent) { long acc = 0; long fileType = 0; QVector fields = e.fields(); QString filePath; m_pParent = parent; for(QVector::ConstIterator ei = fields.constBegin(); ei != fields.constEnd(); ++ei) { uint f = *ei; switch(f) { case KIO::UDSEntry::UDS_SIZE: m_size = e.numberValue(f); break; case KIO::UDSEntry::UDS_NAME: filePath = e.stringValue(f); break; // During listDir the relative path is given here. case KIO::UDSEntry::UDS_MODIFICATION_TIME: m_modificationTime = QDateTime::fromMSecsSinceEpoch(e.numberValue(f)); break; case KIO::UDSEntry::UDS_LINK_DEST: m_linkTarget = e.stringValue(f); break; case KIO::UDSEntry::UDS_ACCESS: { #ifndef Q_OS_WIN acc = e.numberValue(f); m_bReadable = (acc & S_IRUSR) != 0; m_bWritable = (acc & S_IWUSR) != 0; m_bExecutable = (acc & S_IXUSR) != 0; #endif break; } case KIO::UDSEntry::UDS_FILE_TYPE: { fileType = e.numberValue(f); m_bDir = (fileType & QT_STAT_MASK) == QT_STAT_DIR; m_bFile = (fileType & QT_STAT_MASK) == QT_STAT_REG; m_bSymLink = (fileType & QT_STAT_MASK) == QT_STAT_LNK; m_bExists = fileType != 0; //m_fileType = fileType; break; } case KIO::UDSEntry::UDS_URL: m_url = QUrl(e.stringValue(f)); break; case KIO::UDSEntry::UDS_MIME_TYPE: case KIO::UDSEntry::UDS_GUESSED_MIME_TYPE: case KIO::UDSEntry::UDS_XML_PROPERTIES: default: break; } } m_fileInfo = QFileInfo(filePath); m_fileInfo.setCaching(true); if(m_url.isEmpty()) { if(parent != nullptr) { m_url = parent->url().resolved(QUrl(filePath)); //Verify that the scheme doesn't change. Q_ASSERT(m_url.scheme() == parent->url().scheme()); } else { /* Invalid entry we don't know the full url because KIO didn't tell us and there is no parent node supplied. This is a bug if it happens and should be logged . However it is a recoverable error. */ qCWarning(kdiffFileAccess) << i18n("Unable to determine full url. No parent specified."); return; } } m_name = m_fileInfo.fileName(); if(isLocal() && m_name.isEmpty()) { m_name = m_fileInfo.absoluteDir().dirName(); } m_bExists = m_fileInfo.exists(); //insure modification time is initialized if it wasn't already. if(m_modificationTime == QDateTime::fromMSecsSinceEpoch(0)) m_modificationTime = m_fileInfo.lastModified(); m_bValidData = true; m_bSymLink = !m_linkTarget.isEmpty(); #ifndef Q_OS_WIN m_bHidden = m_name[0] == '.'; #endif } bool FileAccess::isValid() const { return m_bValidData; } bool FileAccess::isNormal() const { return !exists() || isFile() || isDir() || isSymLink(); } bool FileAccess::isFile() const { if(!isLocal()) return m_bFile; else return m_fileInfo.isFile(); } bool FileAccess::isDir() const { if(!isLocal()) return m_bDir; else return m_fileInfo.isDir(); } bool FileAccess::isSymLink() const { if(!isLocal()) return m_bSymLink; else return m_fileInfo.isSymLink(); } bool FileAccess::exists() const { if(!isLocal()) return m_bExists; else return m_fileInfo.exists(); } qint64 FileAccess::size() const { if(!isLocal()) return m_size; else return m_fileInfo.size(); } QUrl FileAccess::url() const { QUrl url = m_url; if(url.isLocalFile()) { url = QUrl::fromLocalFile(absoluteFilePath()); } return url; } bool FileAccess::isLocal() const { return m_url.isLocalFile() || !m_url.isValid(); } bool FileAccess::isReadable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bReadable; else return m_fileInfo.isReadable(); } bool FileAccess::isWritable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bWritable; else return m_fileInfo.isWritable(); } bool FileAccess::isExecutable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bExecutable; else return m_fileInfo.isExecutable(); } bool FileAccess::isHidden() const { if(!(isLocal())) return m_bHidden; else return m_fileInfo.isHidden(); } QString FileAccess::readLink() const { return m_linkTarget; } QString FileAccess::absoluteFilePath() const { if(!isLocal()) return m_url.url(); // return complete url return m_fileInfo.absoluteFilePath(); } // Full abs path // Just the name-part of the path, without parent directories QString FileAccess::fileName(bool needTmp) const { if(!isLocal()) return (needTmp) ? m_localCopy : m_name; else return m_name; } QString FileAccess::fileRelPath() const { QString path = m_baseDir.relativeFilePath(m_fileInfo.absoluteFilePath()); return path; } FileAccess* FileAccess::parent() const { return m_pParent; } QString FileAccess::prettyAbsPath() const { return isLocal() ? absoluteFilePath() : m_url.toDisplayString(); } QDateTime FileAccess::lastModified() const { Q_ASSERT(!m_modificationTime.isNull()); return m_modificationTime; } bool FileAccess::interruptableReadFile(void* pDestBuffer, qint64 maxLength) { ProgressProxy pp; const qint64 maxChunkSize = 100000; qint64 i = 0; pp.setMaxNofSteps(maxLength / maxChunkSize + 1); while(i < maxLength) { qint64 nextLength = std::min(maxLength - i, maxChunkSize); qint64 reallyRead = read((char*)pDestBuffer + i, nextLength); if(reallyRead != nextLength) { setStatusText(i18n("Failed to read file: %1", absoluteFilePath())); return false; } i += reallyRead; pp.setCurrent(qFloor(double(i) / maxLength * 100)); if(pp.wasCancelled()) return false; } return true; } bool FileAccess::readFile(void* pDestBuffer, qint64 maxLength) { bool success = false; //Avoid hang on linux for special files. if(!isNormal()) return true; if(isLocal() || !m_localCopy.isEmpty()) { if(open(QIODevice::ReadOnly))//krazy:exclude=syscalls { success = interruptableReadFile(pDestBuffer, maxLength); // maxLength == f.read( (char*)pDestBuffer, maxLength ) close(); } } else { FileAccessJobHandler jh(this); success = jh.get(pDestBuffer, maxLength); } close(); Q_ASSERT(!realFile->isOpen() && !tmpFile->isOpen()); return success; } bool FileAccess::writeFile(const void* pSrcBuffer, qint64 length) { ProgressProxy pp; if(isLocal()) { if(realFile->open(QIODevice::WriteOnly)) { const qint64 maxChunkSize = 100000; pp.setMaxNofSteps(length / maxChunkSize + 1); qint64 i = 0; while(i < length) { qint64 nextLength = std::min(length - i, maxChunkSize); qint64 reallyWritten = realFile->write((char*)pSrcBuffer + i, nextLength); if(reallyWritten != nextLength) { realFile->close(); return false; } i += reallyWritten; pp.step(); if(pp.wasCancelled()) { realFile->close(); return false; } } if(isExecutable()) // value is true if the old file was executable { // Preserve attributes realFile->setPermissions(realFile->permissions() | QFile::ExeUser); } realFile->close(); return true; } } else { FileAccessJobHandler jh(this); bool success = jh.put(pSrcBuffer, length, true /*overwrite*/); close(); Q_ASSERT(!realFile->isOpen() && !tmpFile->isOpen()); return success; } close(); Q_ASSERT(!realFile->isOpen() && !tmpFile->isOpen()); return false; } bool FileAccess::copyFile(const QString& dest) { FileAccessJobHandler jh(this); return jh.copyFile(dest); // Handles local and remote copying. } bool FileAccess::rename(const FileAccess& dest) { FileAccessJobHandler jh(this); return jh.rename(dest); } bool FileAccess::removeFile() { if(isLocal()) { return QDir().remove(absoluteFilePath()); } else { FileAccessJobHandler jh(this); return jh.removeFile(url()); } } bool FileAccess::listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore) { FileAccessJobHandler jh(this); return jh.listDir(pDirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, dirAntiPattern, bFollowDirLinks, bUseCvsIgnore); } QString FileAccess::getTempName() const { return m_localCopy; } const QString FileAccess::errorString() const { return getStatusText(); } bool FileAccess::open(const QFile::OpenMode flags) { bool result; result = createLocalCopy(); if(!result) { setStatusText(i18n("Creating temp copy of %1 failed.", absoluteFilePath())); return result; } if(m_localCopy.isEmpty() && realFile != nullptr) { bool r = realFile->open(flags); setStatusText(i18n("Opening %1 failed. %2", absoluteFilePath(), realFile->errorString())); return r; } bool r = tmpFile->open(); setStatusText(i18n("Opening %1 failed. %2", tmpFile->fileName(), tmpFile->errorString())); return r; } qint64 FileAccess::read(char* data, const qint64 maxlen) { if(!isNormal()) { //This is not an error special files should be skipped setStatusText(QString()); return 0; } qint64 len = 0; if(m_localCopy.isEmpty() && realFile != nullptr) { len = realFile->read(data, maxlen); if(len != maxlen) { setStatusText(i18n("Error reading from %1. %2", absoluteFilePath(), realFile->errorString())); } } else { len = tmpFile->read(data, maxlen); if(len != maxlen) { setStatusText(i18n("Error reading from %1. %2", absoluteFilePath(), tmpFile->errorString())); } } return len; } void FileAccess::close() { if(m_localCopy.isEmpty() && realFile != nullptr) { realFile->close(); } tmpFile->close(); } bool FileAccess::createLocalCopy() { if(isLocal() || !m_localCopy.isEmpty()) return true; tmpFile->setAutoRemove(true); tmpFile->open(); tmpFile->close(); m_localCopy = tmpFile->fileName(); return copyFile(tmpFile->fileName()); } //static tempfile Generator void FileAccess::createTempFile(QTemporaryFile& tmpFile) { tmpFile.setAutoRemove(true); tmpFile.open(); tmpFile.close(); } bool FileAccess::makeDir(const QString& dirName) { FileAccessJobHandler fh(nullptr); return fh.mkDir(dirName); } bool FileAccess::removeDir(const QString& dirName) { FileAccessJobHandler fh(nullptr); return fh.rmDir(dirName); } bool FileAccess::symLink(const QString& linkTarget, const QString& linkLocation) { if(linkTarget.isEmpty() || linkLocation.isEmpty()) return false; return QFile::link(linkTarget, linkLocation); //FileAccessJobHandler fh(0); //return fh.symLink( linkTarget, linkLocation ); } bool FileAccess::exists(const QString& name) { FileAccess fa(name); return fa.exists(); } // If the size couldn't be determined by stat() then the file is copied to a local temp file. qint64 FileAccess::sizeForReading() { if(!isLocal() && m_size == 0) { // Size couldn't be determined. Copy the file to a local temp place. createLocalCopy(); QString localCopy = tmpFile->fileName(); bool bSuccess = copyFile(localCopy); if(bSuccess) { QFileInfo fi(localCopy); m_size = fi.size(); m_localCopy = localCopy; return m_size; } else { return 0; } } else return size(); } QString FileAccess::getStatusText() const { return m_statusText; } void FileAccess::setStatusText(const QString& s) { m_statusText = s; } QString FileAccess::cleanPath(const QString& path) // static { FileAccess fa(path); if(fa.isLocal()) { return QDir::cleanPath(path); } else { return path; } } bool FileAccess::createBackup(const QString& bakExtension) { if(exists()) { // First rename the existing file to the bak-file. If a bak-file file exists, delete that. QString bakName = absoluteFilePath() + bakExtension; FileAccess bakFile(bakName, true /*bWantToWrite*/); if(bakFile.exists()) { bool bSuccess = bakFile.removeFile(); if(!bSuccess) { setStatusText(i18n("While trying to make a backup, deleting an older backup failed.\nFilename: %1", bakName)); return false; } } bool bSuccess = rename(bakFile); // krazy:exclude=syscalls if(!bSuccess) { setStatusText(i18n("While trying to make a backup, renaming failed.\nFilenames: %1 -> %2", absoluteFilePath(), bakName)); return false; } } return true; } void FileAccess::doError() { m_bExists = false; } void FileAccess::filterList(t_DirectoryList* pDirList, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, const bool bUseCvsIgnore) { CvsIgnoreList cvsIgnoreList; if(bUseCvsIgnore) { cvsIgnoreList.init(*this, pDirList); } //TODO: Ask os for this information don't hard code it. #if defined(Q_OS_WIN) bool bCaseSensitive = false; #else bool bCaseSensitive = true; #endif // Now remove all entries that should be ignored: t_DirectoryList::iterator i; for(i = pDirList->begin(); i != pDirList->end();) { t_DirectoryList::iterator i2 = i; ++i2; QString fileName = i->fileName(); if((i->isFile() && (!Utils::wildcardMultiMatch(filePattern, fileName, bCaseSensitive) || Utils::wildcardMultiMatch(fileAntiPattern, fileName, bCaseSensitive))) || (i->isDir() && Utils::wildcardMultiMatch(dirAntiPattern, fileName, bCaseSensitive)) || (bUseCvsIgnore && cvsIgnoreList.matches(fileName, bCaseSensitive))) { // Remove it pDirList->erase(i); i = i2; } else { ++i; } } } FileAccessJobHandler::FileAccessJobHandler(FileAccess* pFileAccess) { m_pFileAccess = pFileAccess; } bool FileAccessJobHandler::stat(short detail, bool bWantToWrite) { m_bSuccess = false; m_pFileAccess->setStatusText(QString()); KIO::StatJob* pStatJob = KIO::stat(m_pFileAccess->url(), bWantToWrite ? KIO::StatJob::DestinationSide : KIO::StatJob::SourceSide, detail, KIO::HideProgressInfo); connect(pStatJob, &KIO::StatJob::result, this, &FileAccessJobHandler::slotStatResult); ProgressProxy::enterEventLoop(pStatJob, i18n("Getting file status: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } void FileAccessJobHandler::slotStatResult(KJob* pJob) { if(pJob->error() != KJob::NoError) { //pJob->uiDelegate()->showErrorMessage(); m_pFileAccess->doError(); m_bSuccess = true; } else { m_bSuccess = true; const KIO::UDSEntry e = static_cast(pJob)->statResult(); m_pFileAccess->setFromUdsEntry(e, m_pFileAccess); } ProgressProxy::exitEventLoop(); } bool FileAccessJobHandler::get(void* pDestBuffer, long maxLength) { ProgressProxyExtender pp; // Implicitly used in slotPercent() if(maxLength > 0 && !pp.wasCancelled()) { KIO::TransferJob* pJob = KIO::get(m_pFileAccess->url(), KIO::NoReload); m_transferredBytes = 0; m_pTransferBuffer = (char*)pDestBuffer; m_maxLength = maxLength; m_bSuccess = false; m_pFileAccess->setStatusText(QString()); connect(pJob, &KIO::TransferJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, &KIO::TransferJob::data, this, &FileAccessJobHandler::slotGetData); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Reading file: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } else return true; } void FileAccessJobHandler::slotGetData(KJob* pJob, const QByteArray& newData) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { qint64 length = std::min(qint64(newData.size()), m_maxLength - m_transferredBytes); ::memcpy(m_pTransferBuffer + m_transferredBytes, newData.data(), newData.size()); m_transferredBytes += length; } } bool FileAccessJobHandler::put(const void* pSrcBuffer, long maxLength, bool bOverwrite, bool bResume, int permissions) { ProgressProxyExtender pp; // Implicitly used in slotPercent() if(maxLength > 0) { KIO::TransferJob* pJob = KIO::put(m_pFileAccess->url(), permissions, KIO::HideProgressInfo | (bOverwrite ? KIO::Overwrite : KIO::DefaultFlags) | (bResume ? KIO::Resume : KIO::DefaultFlags)); m_transferredBytes = 0; m_pTransferBuffer = (char*)pSrcBuffer; m_maxLength = maxLength; m_bSuccess = false; m_pFileAccess->setStatusText(QString()); connect(pJob, &KIO::TransferJob::result, this, &FileAccessJobHandler::slotPutJobResult); connect(pJob, &KIO::TransferJob::dataReq, this, &FileAccessJobHandler::slotPutData); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Writing file: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } else return true; } void FileAccessJobHandler::slotPutData(KIO::Job* pJob, QByteArray& data) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { /* Think twice before doing this in new code. The maxChunkSize must be able to fit a 32-bit int. Given that the fallowing is safe. */ qint64 maxChunkSize = 100000; qint64 length = std::min(maxChunkSize, m_maxLength - m_transferredBytes); data.resize((int)length); if(data.size() == (int)length) { if(length > 0) { ::memcpy(data.data(), m_pTransferBuffer + m_transferredBytes, data.size()); m_transferredBytes += length; } } else { KMessageBox::error(ProgressProxy::getDialog(), i18n("Out of memory")); data.resize(0); m_bSuccess = false; } } } void FileAccessJobHandler::slotPutJobResult(KJob* pJob) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { m_bSuccess = (m_transferredBytes == m_maxLength); // Special success condition } ProgressProxy::exitEventLoop(); // Close the dialog, return from exec() } bool FileAccessJobHandler::mkDir(const QString& dirName) { if(dirName.isEmpty()) return false; FileAccess dir(dirName); if(dir.isLocal()) { return QDir().mkdir(dir.absoluteFilePath()); } else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::mkdir(dir.url()); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); - ProgressProxy::enterEventLoop(pJob, i18n("Making directory: %1", dirName)); + ProgressProxy::enterEventLoop(pJob, i18n("Making folder: %1", dirName)); return m_bSuccess; } } bool FileAccessJobHandler::rmDir(const QString& dirName) { if(dirName.isEmpty()) return false; FileAccess fa(dirName); if(fa.isLocal()) { return QDir().rmdir(fa.absoluteFilePath()); } else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::rmdir(fa.url()); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); - ProgressProxy::enterEventLoop(pJob, i18n("Removing directory: %1", dirName)); + ProgressProxy::enterEventLoop(pJob, i18n("Removing folder: %1", dirName)); return m_bSuccess; } } bool FileAccessJobHandler::removeFile(const QUrl& fileName) { if(fileName.isEmpty()) return false; else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::file_delete(fileName, KIO::HideProgressInfo); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Removing file: %1", fileName.toDisplayString())); return m_bSuccess; } } bool FileAccessJobHandler::symLink(const QUrl& linkTarget, const QUrl& linkLocation) { if(linkTarget.isEmpty() || linkLocation.isEmpty()) return false; else { m_bSuccess = false; KIO::CopyJob* pJob = KIO::link(linkTarget, linkLocation, KIO::HideProgressInfo); connect(pJob, &KIO::CopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Creating symbolic link: %1 -> %2", linkLocation.toDisplayString(), linkTarget.toDisplayString())); return m_bSuccess; } } bool FileAccessJobHandler::rename(const FileAccess& destFile) { if(destFile.fileName().isEmpty()) return false; if(m_pFileAccess->isLocal() && destFile.isLocal()) { return QDir().rename(m_pFileAccess->absoluteFilePath(), destFile.absoluteFilePath()); } else { ProgressProxyExtender pp; int permissions = -1; m_bSuccess = false; KIO::FileCopyJob* pJob = KIO::file_move(m_pFileAccess->url(), destFile.url(), permissions, KIO::HideProgressInfo); connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Renaming file: %1 -> %2", m_pFileAccess->prettyAbsPath(), destFile.prettyAbsPath())); return m_bSuccess; } } void FileAccessJobHandler::slotSimpleJobResult(KJob* pJob) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { m_bSuccess = true; } ProgressProxy::exitEventLoop(); // Close the dialog, return from exec() } // Copy local or remote files. bool FileAccessJobHandler::copyFile(const QString& inDest) { ProgressProxyExtender pp; FileAccess dest; dest.setFile(inDest); m_pFileAccess->setStatusText(QString()); if(!m_pFileAccess->isNormal() || !dest.isNormal()) return false; int permissions = (m_pFileAccess->isExecutable() ? 0111 : 0) + (m_pFileAccess->isWritable() ? 0222 : 0) + (m_pFileAccess->isReadable() ? 0444 : 0); m_bSuccess = false; KIO::FileCopyJob* pJob = KIO::file_copy(m_pFileAccess->url(), dest.url(), permissions, KIO::HideProgressInfo|KIO::Overwrite); connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Copying file: %1 -> %2", m_pFileAccess->prettyAbsPath(), dest.prettyAbsPath())); return m_bSuccess; // Note that the KIO-slave preserves the original date, if this is supported. } bool FileAccessJobHandler::listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, const bool bUseCvsIgnore) { ProgressProxyExtender pp; m_pDirList = pDirList; m_pDirList->clear(); m_bFindHidden = bFindHidden; m_bRecursive = bRecursive; m_bFollowDirLinks = bFollowDirLinks; // Only relevant if bRecursive==true. m_fileAntiPattern = fileAntiPattern; m_filePattern = filePattern; m_dirAntiPattern = dirAntiPattern; if(pp.wasCancelled()) return true; // Cancelled is not an error. - pp.setInformation(i18n("Reading directory: %1", m_pFileAccess->absoluteFilePath()), 0, false); + pp.setInformation(i18n("Reading folder: %1", m_pFileAccess->absoluteFilePath()), 0, false); if(m_pFileAccess->isLocal()) { m_bSuccess = true; QDir dir(m_pFileAccess->absoluteFilePath()); dir.setSorting(QDir::Name | QDir::DirsFirst); if(bFindHidden) dir.setFilter(QDir::Files | QDir::Dirs | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); else dir.setFilter(QDir::Files | QDir::Dirs | QDir::System | QDir::NoDotAndDotDot); QFileInfoList fiList = dir.entryInfoList(); if(fiList.isEmpty()) { /* Sadly Qt provides no error information making this case ambigious. A readablity check is the best we can do. */ m_bSuccess = dir.isReadable(); } else { for(const QFileInfo& fi: fiList) // for each file... { if(pp.wasCancelled()) break; Q_ASSERT(fi.fileName() != "." && fi.fileName() != ".."); FileAccess fa; fa.setFile(m_pFileAccess, fi); pDirList->push_back(fa); } } } else { KIO::ListJob* pListJob = nullptr; pListJob = KIO::listDir(m_pFileAccess->url(), KIO::HideProgressInfo, true /*bFindHidden*/); m_bSuccess = false; if(pListJob != nullptr) { connect(pListJob, &KIO::ListJob::entries, this, &FileAccessJobHandler::slotListDirProcessNewEntries); connect(pListJob, &KIO::ListJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pListJob, &KIO::ListJob::infoMessage, &pp, &ProgressProxyExtender::slotListDirInfoMessage); // This line makes the transfer via fish unreliable.:-( /*if(m_pFileAccess->url().scheme() != QLatin1Literal("fish")){ connect( pListJob, static_cast(&KIO::ListJob::percent), &pp, &ProgressProxyExtender::slotPercent); }*/ ProgressProxy::enterEventLoop(pListJob, i18n("Listing directory: %1", m_pFileAccess->prettyAbsPath())); } } m_pFileAccess->filterList(pDirList, filePattern, fileAntiPattern, dirAntiPattern, bUseCvsIgnore); if(bRecursive) { t_DirectoryList::iterator i; t_DirectoryList subDirsList; for(i = m_pDirList->begin(); i != m_pDirList->end(); ++i) { if(i->isDir() && (!i->isSymLink() || m_bFollowDirLinks)) { t_DirectoryList dirList; i->listDir(&dirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, dirAntiPattern, bFollowDirLinks, bUseCvsIgnore); // append data onto the main list subDirsList.splice(subDirsList.end(), dirList); } } m_pDirList->splice(m_pDirList->end(), subDirsList); } return m_bSuccess; } void FileAccessJobHandler::slotListDirProcessNewEntries(KIO::Job*, const KIO::UDSEntryList& l) { //This function is called for non-local urls. Don't use QUrl::fromLocalFile here as it does not handle these. KIO::UDSEntryList::ConstIterator i; for(i = l.begin(); i != l.end(); ++i) { const KIO::UDSEntry& e = *i; FileAccess fa; fa.setFromUdsEntry(e, m_pFileAccess); //must be manually filtered KDE does not supply API for ignoring these. if(fa.fileName() != "." && fa.fileName() != "..") { //quick fix to preserve behavoir without creating invalid urls. TODO: look for altertive machanism for use with next major release. fa.setFile(fa.url()); m_pDirList->push_back(fa); } } } //#include "fileaccess.moc" diff --git a/src/kdiff3.cpp b/src/kdiff3.cpp index 81e9c3d..8a3ae3e 100644 --- a/src/kdiff3.cpp +++ b/src/kdiff3.cpp @@ -1,1048 +1,1048 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ // application specific includes #include "kdiff3.h" #include "directorymergewindow.h" #include "fileaccess.h" #include "guiutils.h" #include "kdiff3_part.h" #include "kdiff3_shell.h" #include "optiondialog.h" #include "progress.h" #include "smalldialogs.h" #include "difftextwindow.h" #include "mergeresultwindow.h" #include "RLPainter.h" #include "Utils.h" #ifndef Q_OS_WIN #include #endif // include files for QT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // include files for KDE #include #include #include #include #include #include #include #include #include #include #define ID_STATUS_MSG 1 #define MAIN_TOOLBAR_NAME QLatin1String("mainToolBar") KActionCollection* KDiff3App::actionCollection() { if(m_pKDiff3Shell == nullptr) return m_pKDiff3Part->actionCollection(); else return m_pKDiff3Shell->actionCollection(); } QStatusBar* KDiff3App::statusBar() { if(m_pKDiff3Shell == nullptr) return nullptr; else return m_pKDiff3Shell->statusBar(); } KToolBar* KDiff3App::toolBar(const QLatin1String toolBarId) { if(m_pKDiff3Shell == nullptr) return nullptr; else return m_pKDiff3Shell->toolBar(toolBarId); } bool KDiff3App::isPart() { return m_pKDiff3Shell == nullptr; } bool KDiff3App::isFileSaved() { return m_bFileSaved; } bool KDiff3App::isDirComparison() { return m_bDirCompare; } KDiff3App::KDiff3App(QWidget* pParent, const QString& name, KDiff3Part* pKDiff3Part) : QSplitter(pParent) //previously KMainWindow { setObjectName(name); m_pKDiff3Part = pKDiff3Part; m_pKDiff3Shell = qobject_cast(pParent); setWindowTitle("KDiff3"); setOpaqueResize(false); // faster resizing setUpdatesEnabled(false); KCrash::initialize(); // set Disabled to same color as enabled to prevent flicker in DirectoryMergeWindow QPalette pal; pal.setBrush(QPalette::Base, pal.brush(QPalette::Active, QPalette::Base)); pal.setColor(QPalette::Text, pal.color(QPalette::Active, QPalette::Text)); setPalette(pal); // Needed before any file operations via FileAccess happen. if(g_pProgressDialog == nullptr) { g_pProgressDialog = new ProgressDialog(this, statusBar()); g_pProgressDialog->setStayHidden(true); } // All default values must be set before calling readOptions(). m_pOptionDialog = new OptionDialog(m_pKDiff3Shell != nullptr, this); connect(m_pOptionDialog, &OptionDialog::applyDone, this, &KDiff3App::slotRefresh); // This is just a convenience variable to make code that accesses options more readable m_pOptions = m_pOptionDialog->getOptions(); m_pOptionDialog->readOptions(KSharedConfig::openConfig()); // Option handling: Only when pParent==0 (no parent) int argCount = KDiff3Shell::getParser()->optionNames().count() + KDiff3Shell::getParser()->positionalArguments().count(); bool hasArgs = !isPart() && argCount > 0; if(hasArgs) { QString s; QString title; if(KDiff3Shell::getParser()->isSet("confighelp")) { s = m_pOptionDialog->calcOptionHelp(); title = i18n("Current Configuration:"); } else { s = m_pOptionDialog->parseOptions(KDiff3Shell::getParser()->values("cs")); title = i18n("Config Option Error:"); } if(!s.isEmpty()) { //KMessageBox::information(0, s,i18n("KDiff3-Usage")); #ifndef Q_OS_WIN if(isatty(fileno(stderr)) != 1) #endif { QPointer pDialog = QPointer(new QDialog(this)); pDialog->setAttribute(Qt::WA_DeleteOnClose); pDialog->setModal(true); pDialog->setWindowTitle(title); QVBoxLayout* pVBoxLayout = new QVBoxLayout(pDialog); QPointer pTextEdit = QPointer(new KTextEdit(pDialog)); pTextEdit->setText(s); pTextEdit->setReadOnly(true); pTextEdit->setWordWrapMode(QTextOption::NoWrap); pVBoxLayout->addWidget(pTextEdit); pDialog->resize(600, 400); pDialog->exec(); } #if !defined(Q_OS_WIN) else { // Launched from a console QTextStream outStream(stdout); outStream << title << "\n"; outStream << s;//newline already appended by parseOptions } #endif exit(1); } } m_sd1->setOptions(m_pOptions); m_sd2->setOptions(m_pOptions); m_sd3->setOptions(m_pOptions); #ifdef ENABLE_AUTO m_bAutoFlag = hasArgs && KDiff3Shell::getParser()->isSet("auto"); #else m_bAutoFlag = false; #endif m_bAutoMode = m_bAutoFlag || m_pOptions->m_bAutoSaveAndQuitOnMergeWithoutConflicts; if(hasArgs) { m_outputFilename = KDiff3Shell::getParser()->value("output"); if(m_outputFilename.isEmpty()) m_outputFilename = KDiff3Shell::getParser()->value("out"); if(!m_outputFilename.isEmpty()) m_outputFilename = FileAccess(m_outputFilename, true).absoluteFilePath(); if(m_bAutoMode && m_outputFilename.isEmpty()) { if(m_bAutoFlag) { QTextStream(stderr) << i18n("Option --auto used, but no output file specified.") << "\n"; } m_bAutoMode = false; } if(m_outputFilename.isEmpty() && KDiff3Shell::getParser()->isSet("merge")) { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } else { m_bDefaultFilename = false; } g_bAutoSolve = !KDiff3Shell::getParser()->isSet("qall"); // Note that this is effective only once. QStringList args = KDiff3Shell::getParser()->positionalArguments(); m_sd1->setFilename(KDiff3Shell::getParser()->value("base")); if(m_sd1->isEmpty()) { if(args.count() > 0) m_sd1->setFilename(args[0]); // args->arg(0) if(args.count() > 1) m_sd2->setFilename(args[1]); if(args.count() > 2) m_sd3->setFilename(args[2]); } else { if(args.count() > 0) m_sd2->setFilename(args[0]); if(args.count() > 1) m_sd3->setFilename(args[1]); } //Set m_bDirCompare flag m_bDirCompare = FileAccess(m_sd1->getFilename()).isDir(); QStringList aliasList = KDiff3Shell::getParser()->values( "fname" ); QStringList::Iterator ali = aliasList.begin(); QString an1 = KDiff3Shell::getParser()->value("L1"); if(!an1.isEmpty()) { m_sd1->setAliasName(an1); } else if(ali != aliasList.end()) { m_sd1->setAliasName(*ali); ++ali; } QString an2 = KDiff3Shell::getParser()->value("L2"); if(!an2.isEmpty()) { m_sd2->setAliasName(an2); } else if(ali != aliasList.end()) { m_sd2->setAliasName(*ali); ++ali; } QString an3 = KDiff3Shell::getParser()->value("L3"); if(!an3.isEmpty()) { m_sd3->setAliasName(an3); } else if(ali != aliasList.end()) { m_sd3->setAliasName(*ali); ++ali; } } else { m_bDefaultFilename = false; g_bAutoSolve = false; } g_pProgressDialog->setStayHidden(m_bAutoMode); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts initActions(actionCollection()); //Warning: Call this before connecting KDiff3App::slotUpdateAvailabilities or calling KXMLGUIClient::setXMLFile MergeResultWindow::initActions(actionCollection()); initStatusBar(); m_pFindDialog = new FindDialog(this); connect(m_pFindDialog, &FindDialog::findNext, this, &KDiff3App::slotEditFindNext); autoAdvance->setChecked(m_pOptions->m_bAutoAdvance); showWhiteSpaceCharacters->setChecked(m_pOptions->m_bShowWhiteSpaceCharacters); showWhiteSpace->setChecked(m_pOptions->m_bShowWhiteSpace); showWhiteSpaceCharacters->setEnabled(m_pOptions->m_bShowWhiteSpace); showLineNumbers->setChecked(m_pOptions->m_bShowLineNumbers); wordWrap->setChecked(m_pOptions->m_bWordWrap); if(!isPart()) { viewStatusBar->setChecked(m_pOptions->isStatusBarVisable()); slotViewStatusBar(); KToolBar *mainToolBar = toolBar(MAIN_TOOLBAR_NAME); if(mainToolBar != nullptr){ mainToolBar->mainWindow()->addToolBar(m_pOptions->getToolbarPos(), mainToolBar); } // TODO restore window size/pos? /* QSize size = m_pOptions->m_geometry; QPoint pos = m_pOptions->m_position; if(!size.isEmpty()) { m_pKDiff3Shell->resize( size ); QRect visibleRect = QRect( pos, size ) & QApplication::desktop()->rect(); if ( visibleRect.width()>100 && visibleRect.height()>100 ) m_pKDiff3Shell->move( pos ); }*/ } slotRefresh(); m_pMainSplitter = this; m_pMainSplitter->setOrientation(Qt::Vertical); // setCentralWidget( m_pMainSplitter ); m_pDirectoryMergeSplitter = new QSplitter(m_pMainSplitter); m_pDirectoryMergeSplitter->setObjectName("DirectoryMergeSplitter"); m_pMainSplitter->addWidget(m_pDirectoryMergeSplitter); m_pDirectoryMergeSplitter->setOrientation(Qt::Horizontal); m_pDirectoryMergeWindow = new DirectoryMergeWindow(m_pDirectoryMergeSplitter, m_pOptions); m_pDirectoryMergeSplitter->addWidget(m_pDirectoryMergeWindow); m_pDirectoryMergeInfo = new DirectoryMergeInfo(m_pDirectoryMergeSplitter); m_pDirectoryMergeWindow->setDirectoryMergeInfo(m_pDirectoryMergeInfo); m_pDirectoryMergeSplitter->addWidget(m_pDirectoryMergeInfo); //Warning: Make sure DirectoryMergeWindow::initActions is called before this point or we can crash when selectionChanged is sent. connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::startDiffMerge, this, &KDiff3App::slotFileOpen2); connect(m_pDirectoryMergeWindow->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow->selectionModel(), &QItemSelectionModel::currentChanged, this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::checkIfCanContinue, this, &KDiff3App::slotCheckIfCanContinue); connect(m_pDirectoryMergeWindow, static_cast(&DirectoryMergeWindow::updateAvailabilities), this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::statusBarMessage, this, &KDiff3App::slotStatusMsg); connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &KDiff3App::slotClipboardChanged); connect(this, &KDiff3App::sigRecalcWordWrap, this, &KDiff3App::slotRecalcWordWrap, Qt::QueuedConnection); connect(this, &KDiff3App::checkIfCanContinue, this, &KDiff3App::slotCheckIfCanContinue); connect(this, &KDiff3App::finishDrop, this, &KDiff3App::slotFinishDrop); m_pDirectoryMergeWindow->initDirectoryMergeActions(this, actionCollection()); delete KDiff3Shell::getParser(); if(m_pKDiff3Shell == nullptr) { completeInit(QString()); } } void KDiff3App::completeInit(const QString& fn1, const QString& fn2, const QString& fn3) { if(m_pKDiff3Shell != nullptr) { QSize size = m_pOptions->getGeometry(); QPoint pos = m_pOptions->getPosition(); if(!size.isEmpty()) { m_pKDiff3Shell->resize(size); QRect visibleRect = QRect(pos, size) & QApplication::desktop()->rect(); if(visibleRect.width() > 100 && visibleRect.height() > 100) m_pKDiff3Shell->move(pos); if(!m_bAutoMode) { //Here we want the extra setup showMaximized does since the window has not be shown before if(m_pOptions->isMaximised()) m_pKDiff3Shell->showMaximized();// krazy:exclude=qmethods else m_pKDiff3Shell->show(); } } } if(!fn1.isEmpty()) { m_sd1->setFilename(fn1); } if(!fn2.isEmpty()) { m_sd2->setFilename(fn2); } if(!fn3.isEmpty()) { m_sd3->setFilename(fn3); } //should not happen now. Q_ASSERT(m_bDirCompare == FileAccess(m_sd1->getFilename()).isDir()); bool bSuccess = improveFilenames(false); if(m_bAutoFlag && m_bAutoMode && m_bDirCompare) { - QTextStream(stderr) << i18n("Option --auto ignored for directory comparison.") << "\n"; + QTextStream(stderr) << i18n("Option --auto ignored for folder comparison.") << "\n"; m_bAutoMode = false; } if(!m_bDirCompare) { m_pDirectoryMergeSplitter->hide(); mainInit(); if(m_bAutoMode) { QSharedPointer pSD = nullptr; if(m_sd3->isEmpty()) { if(m_totalDiffStatus.isBinaryEqualAB()) { pSD = m_sd1; } } else { if(m_totalDiffStatus.isBinaryEqualBC() || m_totalDiffStatus.isBinaryEqualAB()) { //if B==C (assume A is old), if A==B then C has changed pSD = m_sd3; } else if(m_totalDiffStatus.isBinaryEqualAC()) { pSD = m_sd2; // assuming B has changed } } if(pSD != nullptr) { // Save this file directly, not via the merge result window. FileAccess fa(m_outputFilename); if(m_pOptions->m_bDmCreateBakFiles && fa.exists()) { fa.createBackup(".orig"); } bSuccess = pSD->saveNormalDataAs(m_outputFilename); if(bSuccess) ::exit(0); else KMessageBox::error(this, i18n("Saving failed.")); } else if(m_pMergeResultWindow->getNrOfUnsolvedConflicts() == 0) { bSuccess = m_pMergeResultWindow->saveDocument(m_pMergeResultWindowTitle->getFileName(), m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) ::exit(0); } } } m_bAutoMode = false; if(m_pKDiff3Shell != nullptr) { if(m_pOptions->isMaximised()) //We want showMaximized here as the window has never been shown. m_pKDiff3Shell->showMaximized();// krazy:exclude=qmethods else m_pKDiff3Shell->show(); } g_pProgressDialog->setStayHidden(false); if(statusBar() != nullptr) statusBar()->setSizeGripEnabled(true); slotClipboardChanged(); // For initialisation. slotUpdateAvailabilities(); if(!m_bDirCompare && m_pKDiff3Shell != nullptr) { bool bFileOpenError = false; if((!m_sd1->isEmpty() && !m_sd1->hasData()) || (!m_sd2->isEmpty() && !m_sd2->hasData()) || (!m_sd3->isEmpty() && !m_sd3->hasData())) { QString text(i18n("Opening of these files failed:")); text += "\n\n"; if(!m_sd1->isEmpty() && !m_sd1->hasData()) text += " - " + m_sd1->getAliasName() + '\n'; if(!m_sd2->isEmpty() && !m_sd2->hasData()) text += " - " + m_sd2->getAliasName() + '\n'; if(!m_sd3->isEmpty() && !m_sd3->hasData()) text += " - " + m_sd3->getAliasName() + '\n'; KMessageBox::sorry(this, text, i18n("File Open Error")); bFileOpenError = true; } if(m_sd1->isEmpty() || m_sd2->isEmpty() || bFileOpenError) slotFileOpen(); } else if(!bSuccess) // Directory open failed { slotFileOpen(); } } KDiff3App::~KDiff3App() { } /** * Helper function used to create actions into the ac collection */ void KDiff3App::initActions(KActionCollection* ac) { if(ac == nullptr){ KMessageBox::error(nullptr, "actionCollection==0"); exit(-1);//we cannot recover from this. } fileOpen = KStandardAction::open(this, &KDiff3App::slotFileOpen, ac); fileOpen->setStatusTip(i18n("Opens documents for comparison...")); fileReload = GuiUtils::createAction(i18n("Reload"), QKeySequence(QKeySequence::Refresh), this, &KDiff3App::slotReload, ac, QLatin1String("file_reload")); fileSave = KStandardAction::save(this, &KDiff3App::slotFileSave, ac); fileSave->setStatusTip(i18n("Saves the merge result. All conflicts must be solved!")); fileSaveAs = KStandardAction::saveAs(this, &KDiff3App::slotFileSaveAs, ac); fileSaveAs->setStatusTip(i18n("Saves the current document as...")); #ifndef QT_NO_PRINTER filePrint = KStandardAction::print(this, &KDiff3App::slotFilePrint, ac); filePrint->setStatusTip(i18n("Print the differences")); #endif fileQuit = KStandardAction::quit(this, &KDiff3App::slotFileQuit, ac); fileQuit->setStatusTip(i18n("Quits the application")); editCut = KStandardAction::cut(this, &KDiff3App::slotEditCut, ac); editCut->setShortcuts(QKeySequence::Cut); editCut->setStatusTip(i18n("Cuts the selected section and puts it to the clipboard")); editCopy = KStandardAction::copy(this, &KDiff3App::slotEditCopy, ac); editCopy->setShortcut(QKeySequence::Copy); editCopy->setStatusTip(i18n("Copies the selected section to the clipboard")); editPaste = KStandardAction::paste(this, &KDiff3App::slotEditPaste, ac); editPaste->setStatusTip(i18n("Pastes the clipboard contents to current position")); editCut->setShortcut(QKeySequence::Paste); editSelectAll = KStandardAction::selectAll(this, &KDiff3App::slotEditSelectAll, ac); editSelectAll->setStatusTip(i18n("Select everything in current window")); editFind = KStandardAction::find(this, &KDiff3App::slotEditFind, ac); editFind->setShortcut(QKeySequence::Find); editFind->setStatusTip(i18n("Search for a string")); editFindNext = KStandardAction::findNext(this, &KDiff3App::slotEditFindNext, ac); editFindNext->setStatusTip(i18n("Search again for the string")); /* FIXME figure out how to implement this action viewToolBar = KStandardAction::showToolbar(this, &KDiff3App::slotViewToolBar, ac); viewToolBar->setStatusTip(i18n("Enables/disables the toolbar")); */ viewStatusBar = KStandardAction::showStatusbar(this, &KDiff3App::slotViewStatusBar, ac); viewStatusBar->setStatusTip(i18n("Enables/disables the statusbar")); KStandardAction::keyBindings(this, &KDiff3App::slotConfigureKeys, ac); QAction* pAction = KStandardAction::preferences(this, &KDiff3App::slotConfigure, ac); if(isPart()) pAction->setText(i18n("Configure KDiff3...")); #include "xpm/autoadvance.xpm" #include "xpm/currentpos.xpm" #include "xpm/down1arrow.xpm" #include "xpm/down2arrow.xpm" #include "xpm/downend.xpm" #include "xpm/iconA.xpm" #include "xpm/iconB.xpm" #include "xpm/iconC.xpm" #include "xpm/nextunsolved.xpm" #include "xpm/prevunsolved.xpm" #include "xpm/showlinenumbers.xpm" #include "xpm/showwhitespace.xpm" #include "xpm/showwhitespacechars.xpm" #include "xpm/up1arrow.xpm" #include "xpm/up2arrow.xpm" #include "xpm/upend.xpm" mGoCurrent = GuiUtils::createAction(i18n("Go to Current Delta"), QIcon(QPixmap(currentpos)), i18n("Current\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Space), this, &KDiff3App::slotGoCurrent, ac, "go_current"); mGoTop = GuiUtils::createAction(i18n("Go to First Delta"), QIcon(QPixmap(upend)), i18n("First\nDelta"), this, &KDiff3App::slotGoTop, ac, "go_top"); mGoBottom = GuiUtils::createAction(i18n("Go to Last Delta"), QIcon(QPixmap(downend)), i18n("Last\nDelta"), this, &KDiff3App::slotGoBottom, ac, "go_bottom"); QString omitsWhitespace = ".\n" + i18n("(Skips white space differences when \"Show White Space\" is disabled.)"); QString includeWhitespace = ".\n" + i18n("(Does not skip white space differences even when \"Show White Space\" is disabled.)"); mGoPrevDelta = GuiUtils::createAction(i18n("Go to Previous Delta"), QIcon(QPixmap(up1arrow)), i18n("Prev\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Up), this, &KDiff3App::slotGoPrevDelta, ac, "go_prev_delta"); mGoPrevDelta->setToolTip(mGoPrevDelta->text() + omitsWhitespace); mGoNextDelta = GuiUtils::createAction(i18n("Go to Next Delta"), QIcon(QPixmap(down1arrow)), i18n("Next\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Down), this, &KDiff3App::slotGoNextDelta, ac, "go_next_delta"); mGoNextDelta->setToolTip(mGoNextDelta->text() + omitsWhitespace); mGoPrevConflict = GuiUtils::createAction(i18n("Go to Previous Conflict"), QIcon(QPixmap(up2arrow)), i18n("Prev\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageUp), this, &KDiff3App::slotGoPrevConflict, ac, "go_prev_conflict"); mGoPrevConflict->setToolTip(mGoPrevConflict->text() + omitsWhitespace); mGoNextConflict = GuiUtils::createAction(i18n("Go to Next Conflict"), QIcon(QPixmap(down2arrow)), i18n("Next\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageDown), this, &KDiff3App::slotGoNextConflict, ac, "go_next_conflict"); mGoNextConflict->setToolTip(mGoNextConflict->text() + omitsWhitespace); mGoPrevUnsolvedConflict = GuiUtils::createAction(i18n("Go to Previous Unsolved Conflict"), QIcon(QPixmap(prevunsolved)), i18n("Prev\nUnsolved"), this, &KDiff3App::slotGoPrevUnsolvedConflict, ac, "go_prev_unsolved_conflict"); mGoPrevUnsolvedConflict->setToolTip(mGoPrevUnsolvedConflict->text() + includeWhitespace); mGoNextUnsolvedConflict = GuiUtils::createAction(i18n("Go to Next Unsolved Conflict"), QIcon(QPixmap(nextunsolved)), i18n("Next\nUnsolved"), this, &KDiff3App::slotGoNextUnsolvedConflict, ac, "go_next_unsolved_conflict"); mGoNextUnsolvedConflict->setToolTip(mGoNextUnsolvedConflict->text() + includeWhitespace); chooseA = GuiUtils::createAction(i18n("Select Line(s) From A"), QIcon(QPixmap(iconA)), i18n("Choose\nA"), QKeySequence(Qt::CTRL + Qt::Key_1), this, &KDiff3App::slotChooseA, ac, "merge_choose_a"); chooseB = GuiUtils::createAction(i18n("Select Line(s) From B"), QIcon(QPixmap(iconB)), i18n("Choose\nB"), QKeySequence(Qt::CTRL + Qt::Key_2), this, &KDiff3App::slotChooseB, ac, "merge_choose_b"); chooseC = GuiUtils::createAction(i18n("Select Line(s) From C"), QIcon(QPixmap(iconC)), i18n("Choose\nC"), QKeySequence(Qt::CTRL + Qt::Key_3), this, &KDiff3App::slotChooseC, ac, "merge_choose_c"); autoAdvance = GuiUtils::createAction(i18n("Automatically Go to Next Unsolved Conflict After Source Selection"), QIcon(QPixmap(autoadvance)), i18n("Auto\nNext"), this, &KDiff3App::slotAutoAdvanceToggled, ac, "merge_autoadvance"); showWhiteSpaceCharacters = GuiUtils::createAction(i18n("Show Space && Tabulator Characters"), QIcon(QPixmap(showwhitespacechars)), i18n("White\nCharacters"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace_characters"); showWhiteSpace = GuiUtils::createAction(i18n("Show White Space"), QIcon(QPixmap(showwhitespace)), i18n("White\nDeltas"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace"); showLineNumbers = GuiUtils::createAction(i18n("Show Line Numbers"), QIcon(QPixmap(showlinenumbers)), i18n("Line\nNumbers"), this, &KDiff3App::slotShowLineNumbersToggled, ac, "diff_showlinenumbers"); mAutoSolve = GuiUtils::createAction(i18n("Automatically Solve Simple Conflicts"), this, &KDiff3App::slotAutoSolve, ac, "merge_autosolve"); mUnsolve = GuiUtils::createAction(i18n("Set Deltas to Conflicts"), this, &KDiff3App::slotUnsolve, ac, "merge_autounsolve"); mergeRegExp = GuiUtils::createAction(i18n("Run Regular Expression Auto Merge"), this, &KDiff3App::slotRegExpAutoMerge, ac, "merge_regexp_automerge"); mMergeHistory = GuiUtils::createAction(i18n("Automatically Solve History Conflicts"), this, &KDiff3App::slotMergeHistory, ac, "merge_versioncontrol_history"); splitDiff = GuiUtils::createAction(i18n("Split Diff At Selection"), this, &KDiff3App::slotSplitDiff, ac, "merge_splitdiff"); joinDiffs = GuiUtils::createAction(i18n("Join Selected Diffs"), this, &KDiff3App::slotJoinDiffs, ac, "merge_joindiffs"); showWindowA = GuiUtils::createAction(i18n("Show Window A"), this, &KDiff3App::slotShowWindowAToggled, ac, "win_show_a"); showWindowB = GuiUtils::createAction(i18n("Show Window B"), this, &KDiff3App::slotShowWindowBToggled, ac, "win_show_b"); showWindowC = GuiUtils::createAction(i18n("Show Window C"), this, &KDiff3App::slotShowWindowCToggled, ac, "win_show_c"); overviewModeNormal = GuiUtils::createAction(i18n("Normal Overview"), this, &KDiff3App::slotOverviewNormal, ac, "diff_overview_normal"); overviewModeAB = GuiUtils::createAction(i18n("A vs. B Overview"), this, &KDiff3App::slotOverviewAB, ac, "diff_overview_ab"); overviewModeAC = GuiUtils::createAction(i18n("A vs. C Overview"), this, &KDiff3App::slotOverviewAC, ac, "diff_overview_ac"); overviewModeBC = GuiUtils::createAction(i18n("B vs. C Overview"), this, &KDiff3App::slotOverviewBC, ac, "diff_overview_bc"); wordWrap = GuiUtils::createAction(i18n("Word Wrap Diff Windows"), this, &KDiff3App::slotWordWrapToggled, ac, "diff_wordwrap"); addManualDiffHelp = GuiUtils::createAction(i18n("Add Manual Diff Alignment"), QKeySequence(Qt::CTRL + Qt::Key_Y), this, &KDiff3App::slotAddManualDiffHelp, ac, "diff_add_manual_diff_help"); clearManualDiffHelpList = GuiUtils::createAction(i18n("Clear All Manual Diff Alignments"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Y), this, &KDiff3App::slotClearManualDiffHelpList, ac, "diff_clear_manual_diff_help_list"); winFocusNext = GuiUtils::createAction(i18n("Focus Next Window"), QKeySequence(Qt::ALT + Qt::Key_Right), this, &KDiff3App::slotWinFocusNext, ac, "win_focus_next"); winFocusPrev = GuiUtils::createAction(i18n("Focus Prev Window"), QKeySequence(Qt::ALT + Qt::Key_Left), this, &KDiff3App::slotWinFocusPrev, ac, "win_focus_prev"); winToggleSplitOrientation = GuiUtils::createAction(i18n("Toggle Split Orientation"), this, &KDiff3App::slotWinToggleSplitterOrientation, ac, "win_toggle_split_orientation"); - dirShowBoth = GuiUtils::createAction(i18n("Dir && Text Split Screen View"), this, &KDiff3App::slotDirShowBoth, ac, "win_dir_show_both"); + dirShowBoth = GuiUtils::createAction(i18n("Folder && Text Split Screen View"), this, &KDiff3App::slotDirShowBoth, ac, "win_dir_show_both"); dirShowBoth->setChecked(true); - dirViewToggle = GuiUtils::createAction(i18n("Toggle Between Dir && Text View"), this, &KDiff3App::slotDirViewToggle, ac, "win_dir_view_toggle"); + dirViewToggle = GuiUtils::createAction(i18n("Toggle Between Folder && Text View"), this, &KDiff3App::slotDirViewToggle, ac, "win_dir_view_toggle"); m_pMergeEditorPopupMenu = new QMenu(this); /* chooseA->plug( m_pMergeEditorPopupMenu ); chooseB->plug( m_pMergeEditorPopupMenu ); chooseC->plug( m_pMergeEditorPopupMenu );*/ m_pMergeEditorPopupMenu->addAction(chooseA); m_pMergeEditorPopupMenu->addAction(chooseB); m_pMergeEditorPopupMenu->addAction(chooseC); } void KDiff3App::showPopupMenu(const QPoint& point) { m_pMergeEditorPopupMenu->popup(point); } void KDiff3App::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR if(statusBar() != nullptr) statusBar()->showMessage(i18n("Ready.")); } void KDiff3App::saveOptions(KSharedConfigPtr config) { if(!m_bAutoMode) { if(!isPart()) { m_pOptions->setMaximised(m_pKDiff3Shell->isMaximized()); if(!m_pKDiff3Shell->isMaximized() && m_pKDiff3Shell->isVisible()) { m_pOptions->setGeometry(m_pKDiff3Shell->size()); m_pOptions->setPosition(m_pKDiff3Shell->pos()); } /* TODO change this option as now KToolbar uses QToolbar positioning style if ( toolBar(MAIN_TOOLBAR_NAME)!=0 ) m_pOptionDialog->m_toolBarPos = (int) toolBar(MAIN_TOOLBAR_NAME)->allowedAreas();*/ } m_pOptionDialog->saveOptions(std::move(config)); } } bool KDiff3App::queryClose() { saveOptions(KSharedConfig::openConfig()); if(m_bOutputModified) { int result = KMessageBox::warningYesNoCancel(this, i18n("The merge result has not been saved."), i18n("Warning"), KGuiItem(i18n("Save && Quit")), KGuiItem(i18n("Quit Without Saving"))); if(result == KMessageBox::Cancel) return false; else if(result == KMessageBox::Yes) { slotFileSave(); if(m_bOutputModified) { KMessageBox::sorry(this, i18n("Saving the merge result failed."), i18n("Warning")); return false; } } } m_bOutputModified = false; if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, - i18n("You are currently doing a directory merge. Are you sure, you want to abort?"), + i18n("You are currently doing a folder merge. Are you sure, you want to abort?"), i18n("Warning"), KStandardGuiItem::quit(), KStandardGuiItem::cont() /* i18n("Continue Merging") */); if(result != KMessageBox::Yes) return false; } return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KDiff3App::slotFileSave() { if(m_bDefaultFilename) { slotFileSaveAs(); } else { slotStatusMsg(i18n("Saving file...")); bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) { m_bFileSaved = true; m_bOutputModified = false; if(m_bDirCompare) m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); } slotStatusMsg(i18n("Ready.")); } } void KDiff3App::slotFileSaveAs() { slotStatusMsg(i18n("Saving file with a new filename...")); QString s = QFileDialog::getSaveFileUrl(this, i18n("Save As..."), QUrl::fromLocalFile(QDir::currentPath())).url(QUrl::PreferLocalFile); if(!s.isEmpty()) { m_outputFilename = s; m_pMergeResultWindowTitle->setFileName(m_outputFilename); bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) { m_bOutputModified = false; if(m_bDirCompare) m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); } //setWindowTitle(url.fileName(),doc->isModified()); m_bDefaultFilename = false; } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFilePrint() { if(m_pDiffTextWindow1 == nullptr || m_pDiffTextWindow2 == nullptr) return; #ifdef QT_NO_PRINTER slotStatusMsg(i18n("Printing not implemented.")); #else QPrinter printer; QPointer printDialog=QPointer(new QPrintDialog(&printer, this)); LineRef firstSelectionD3LIdx; LineRef lastSelectionD3LIdx; m_pDiffTextWindow1->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); if(firstSelectionD3LIdx < 0) { m_pDiffTextWindow2->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindow3->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx >= 0) { printDialog->addEnabledOption(QPrintDialog::PrintSelection); //printer.setOptionEnabled(QPrinter::PrintSelection,true); printDialog->setPrintRange(QAbstractPrintDialog::Selection); } if(firstSelectionD3LIdx == -1) printDialog->setPrintRange(QAbstractPrintDialog::AllPages); //printDialog.setMinMax(0,0); printDialog->setFromTo(0, 0); int currentFirstLine = m_pDiffTextWindow1->getFirstLine(); int currentFirstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(currentFirstLine); // do some printer initialization printer.setFullPage(false); // initialize the printer using the print dialog if(printDialog->exec() == QDialog::Accepted) { slotStatusMsg(i18n("Printing...")); // create a painter to paint on the printer object RLPainter painter(&printer, m_pOptions->m_bRightToLeftLanguage, width(), Utils::getHorizontalAdvance(fontMetrics(),'W')); QPaintDevice* pPaintDevice = painter.device(); int dpiy = pPaintDevice->logicalDpiY(); int columnDistance = qRound((0.5 / 2.54) * dpiy); // 0.5 cm between the columns int columns = m_bTripleDiff ? 3 : 2; int columnWidth = (pPaintDevice->width() - (columns - 1) * columnDistance) / columns; QFont f = m_pOptions->m_font; f.setPointSizeF(f.pointSizeF() - 1); // Print with slightly smaller font. painter.setFont(f); QFontMetrics fm = painter.fontMetrics(); QString topLineText = i18n("Top line"); //int headerWidth = fm.width( m_sd1->getAliasName() + ", "+topLineText+": 01234567" ); int headerLines = Utils::getHorizontalAdvance(fm, m_sd1->getAliasName() + ", " + topLineText + ": 01234567") / columnWidth + 1; int headerMargin = headerLines * fm.height() + 3; // Text + one horizontal line int footerMargin = fm.height() + 3; QRect view(0, headerMargin, pPaintDevice->width(), pPaintDevice->height() - (headerMargin + footerMargin)); QRect view1(0 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); QRect view2(1 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); QRect view3(2 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); int linesPerPage = view.height() / fm.lineSpacing(); m_pEventLoopForPrinting = QPointer(new QEventLoop()); if(m_pOptions->m_bWordWrap) { // For printing the lines are wrapped differently (this invalidates the first line) recalcWordWrap(columnWidth); m_pEventLoopForPrinting->exec(); } LineCount totalNofLines = std::max(m_pDiffTextWindow1->getNofLines(), m_pDiffTextWindow2->getNofLines()); if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr) totalNofLines = std::max(totalNofLines, m_pDiffTextWindow3->getNofLines()); QList pageList; // = printer.pageList(); bool bPrintCurrentPage = false; bool bFirstPrintedPage = false; bool bPrintSelection = false; int totalNofPages = (totalNofLines + linesPerPage - 1) / linesPerPage; LineRef line; LineRef selectionEndLine; if(printer.printRange() == QPrinter::AllPages) { pageList.clear(); for(int i = 0; i < totalNofPages; ++i) { pageList.push_back(i + 1); } } else if(printer.printRange() == QPrinter::PageRange) { pageList.clear(); for(int i = printer.fromPage(); i <= printer.toPage(); ++i) { pageList.push_back(i); } } if(printer.printRange() == QPrinter::Selection) { bPrintSelection = true; if(firstSelectionD3LIdx >= 0) { line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(firstSelectionD3LIdx); selectionEndLine = m_pDiffTextWindow1->convertDiff3LineIdxToLine(lastSelectionD3LIdx + 1); totalNofPages = (selectionEndLine - line + linesPerPage - 1) / linesPerPage; } } int page = 1; ProgressProxy pp; pp.setMaxNofSteps(totalNofPages); QList::iterator pageListIt = pageList.begin(); for(;;) { pp.setInformation(i18n("Printing page %1 of %2", page, totalNofPages), false); pp.setCurrent(page - 1); if(pp.wasCancelled()) { printer.abort(); break; } if(!bPrintSelection) { if(pageListIt == pageList.end()) break; page = *pageListIt; line = (page - 1) * linesPerPage; if(page == 10000) { // This means "Print the current page" bPrintCurrentPage = true; // Detect the first visible line in the window. line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx); } } else { if(line >= selectionEndLine) { break; } else { if(selectionEndLine - line < linesPerPage) linesPerPage = selectionEndLine - line; } } if(line.isValid() && line < totalNofLines) { if(bFirstPrintedPage) printer.newPage(); painter.setClipping(true); painter.setPen(m_pOptions->m_colorA); QString headerText1 = m_sd1->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow1->calcTopLineInFile(line) + 1); m_pDiffTextWindow1->printWindow(painter, view1, headerText1, line, linesPerPage, m_pOptions->m_fgColor); painter.setPen(m_pOptions->m_colorB); QString headerText2 = m_sd2->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow2->calcTopLineInFile(line) + 1); m_pDiffTextWindow2->printWindow(painter, view2, headerText2, line, linesPerPage, m_pOptions->m_fgColor); if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr) { painter.setPen(m_pOptions->m_colorC); QString headerText3 = m_sd3->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow3->calcTopLineInFile(line) + 1); m_pDiffTextWindow3->printWindow(painter, view3, headerText3, line, linesPerPage, m_pOptions->m_fgColor); } painter.setClipping(false); painter.setPen(m_pOptions->m_fgColor); painter.drawLine(0, view.bottom() + 3, view.width(), view.bottom() + 3); QString s = bPrintCurrentPage ? QString("") : QString::number(page) + '/' + QString::number(totalNofPages); if(bPrintSelection) s += i18n(" (Selection)"); painter.drawText((view.right() - Utils::getHorizontalAdvance(painter.fontMetrics(), s)) / 2, view.bottom() + painter.fontMetrics().ascent() + 5, s); bFirstPrintedPage = true; } if(bPrintSelection) { line += linesPerPage; ++page; } else { ++pageListIt; } } painter.end(); if(m_pOptions->m_bWordWrap) { recalcWordWrap(); m_pEventLoopForPrinting->exec(); m_pDiffVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx)); } m_pEventLoopForPrinting.clear(); slotStatusMsg(i18n("Printing completed.")); } else { slotStatusMsg(i18n("Printing aborted.")); } #endif } void KDiff3App::slotFileQuit() { slotStatusMsg(i18n("Exiting...")); if(!queryClose()) return; // Don't quit QApplication::exit(isFileSaved() || isDirComparison() ? 0 : 1); } void KDiff3App::slotViewToolBar() { Q_ASSERT(viewToolBar != nullptr); slotStatusMsg(i18n("Toggling toolbar...")); m_pOptions->setToolbarState(viewToolBar->isChecked()); /////////////////////////////////////////////////////////////////// // turn Toolbar on or off if(toolBar(MAIN_TOOLBAR_NAME) != nullptr) { if(!m_pOptions->isToolBarVisable()) { toolBar(MAIN_TOOLBAR_NAME)->hide(); } else { toolBar(MAIN_TOOLBAR_NAME)->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotViewStatusBar() { slotStatusMsg(i18n("Toggle the statusbar...")); m_pOptions->setStatusBarState(viewStatusBar->isChecked()); /////////////////////////////////////////////////////////////////// //turn Statusbar on or off if(statusBar() != nullptr) { if(!viewStatusBar->isChecked()) { statusBar()->hide(); } else { statusBar()->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotStatusMsg(const QString& text) { /////////////////////////////////////////////////////////////////// // change status message permanently if(statusBar() != nullptr) { statusBar()->clearMessage(); statusBar()->showMessage(text); } } //#include "kdiff3.moc" diff --git a/src/kdiff3_shell.rc b/src/kdiff3_shell.rc index d805391..736d4cd 100644 --- a/src/kdiff3_shell.rc +++ b/src/kdiff3_shell.rc @@ -1,128 +1,128 @@ &File - &Directory + F&older Current Item Merge Operation Current Item Sync Operation M&ovement D&iffview M&erge &Window Main Toolbar diff --git a/src/main.cpp b/src/main.cpp index b2f17c3..51b78ee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,186 +1,186 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kdiff3_shell.h" #include "version.h" #include "Logging.h" #include // for fileno, stderr #include // for exit #ifndef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include #include #include #include void initialiseCmdLineArgs(QCommandLineParser* cmdLineParser) { QString configFileName = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, "kdiff3rc"); QFile configFile(configFileName); QString ignorableOptionsLine = "-u;-query;-html;-abort"; if(configFile.open(QIODevice::ReadOnly)) { QTextStream ts(&configFile); while(!ts.atEnd()) { QString line = ts.readLine(); if(line.startsWith(QLatin1String("IgnorableCmdLineOptions="))) { int pos = line.indexOf('='); if(pos >= 0) { ignorableOptionsLine = line.mid(pos + 1); } break; } } } //support our own old preferences this is obsolete QStringList sl = ignorableOptionsLine.split(','); if(!sl.isEmpty()) { const QStringList ignorableOptions = sl.front().split(';'); for(QString ignorableOption : ignorableOptions) { ignorableOption.remove('-'); if(!ignorableOption.isEmpty()) { if(ignorableOption.length() == 1) { cmdLineParser->addOption(QCommandLineOption({ignorableOption, QLatin1String("ignore")}, i18n("Ignored. (User defined.)"))); } else { cmdLineParser->addOption(QCommandLineOption(ignorableOption, i18n("Ignored. (User defined.)"))); } } } } } int main(int argc, char* argv[]) { const QLatin1String appName("kdiff3"); QApplication app(argc, argv); // KAboutData and QCommandLineParser depend on this being setup. KLocalizedString::setApplicationDomain(appName.data()); KCrash::initialize(); const QString i18nName = i18n("KDiff3"); QString appVersion(KDIFF3_VERSION_STRING); if(sizeof(void*) == 8) appVersion += i18n(" (64 bit)"); else if(sizeof(void*) == 4) appVersion += i18n(" (32 bit)"); - const QString description = i18n("Tool for Comparison and Merge of Files and Directories"); + const QString description = i18n("Tool for Comparison and Merge of Files and Folders"); const QString copyright = i18n("(c) 2002-2014 Joachim Eibl, (c) 2017 Michael Reeves KF5/Qt5 port"); const QString homePage = QStringLiteral("https://kde.org/applications/development/kdiff3"); KAboutData aboutData(appName, i18nName, appVersion, description, KAboutLicense::GPL_V2, copyright, QString(), homePage); KAboutData::setApplicationData(aboutData); QCommandLineParser* cmdLineParser = KDiff3Shell::getParser(); cmdLineParser->setApplicationDescription(aboutData.shortDescription()); aboutData.setupCommandLine(cmdLineParser); initialiseCmdLineArgs(cmdLineParser); // ignorable command options cmdLineParser->addOption(QCommandLineOption({QLatin1String("m"), QLatin1String("merge")}, i18n("Merge the input."))); cmdLineParser->addOption(QCommandLineOption({QLatin1String("b"), QLatin1String("base")}, i18n("Explicit base file. For compatibility with certain tools."), QLatin1String("file"))); cmdLineParser->addOption(QCommandLineOption({QLatin1String("o"), QLatin1String("output")}, i18n("Output file. Implies -m. E.g.: -o newfile.txt"), QLatin1String("file"))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("out"), i18n("Output file, again. (For compatibility with certain tools.)"), QLatin1String("file"))); #ifdef ENABLE_AUTO cmdLineParser->addOption(QCommandLineOption(QLatin1String("auto"), i18n("No GUI if all conflicts are auto-solvable. (Needs -o file)"))); #else cmdLineParser->addOption(QCommandLineOption(QLatin1String("auto"), i18n("Ignored."))); #endif cmdLineParser->addOption(QCommandLineOption(QLatin1String("qall"), i18n("Do not solve conflicts automatically."))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("L1"), i18n("Visible name replacement for input file 1 (base)."), QLatin1String("alias1"))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("L2"), i18n("Visible name replacement for input file 2."), QLatin1String("alias2"))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("L3"), i18n("Visible name replacement for input file 3."), QLatin1String("alias3"))); cmdLineParser->addOption(QCommandLineOption({QLatin1String("L"), QLatin1String("fname")}, i18n("Alternative visible name replacement. Supply this once for every input."), QLatin1String("alias"))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("cs"), i18n("Override a config setting. Use once for every setting. E.g.: --cs \"AutoAdvance=1\""), QLatin1String("string"))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("confighelp"), i18n("Show list of config settings and current values."))); cmdLineParser->addOption(QCommandLineOption(QLatin1String("config"), i18n("Use a different config file."), QLatin1String("file"))); // other command options cmdLineParser->addPositionalArgument(QLatin1String("[File1]"), i18n("file1 to open (base, if not specified via --base)")); cmdLineParser->addPositionalArgument(QLatin1String("[File2]"), i18n("file2 to open")); cmdLineParser->addPositionalArgument(QLatin1String("[File3]"), i18n("file3 to open")); bool isAtty = true; #ifndef Q_OS_WIN isAtty = isatty(fileno(stderr)) == 1;//will be true for redirected output as well #endif /* QCommandLineParser::process does what is expected on windows or when running from a commandline. However, it only accounts for a lack of terminal output on windows. */ if(isAtty) { cmdLineParser->process(QCoreApplication::arguments()); } else { /* There is no terminal connected so don't just exit mysteriously on error. */ if(!cmdLineParser->parse(QCoreApplication::arguments())) { QString errorMessage = cmdLineParser->errorText(); KMessageBox::error(nullptr, "

" + errorMessage + "

" + i18n("See kdiff3 --help for supported options.") + "
", aboutData.displayName()); exit(1); } if(cmdLineParser->isSet(QStringLiteral("version"))) { KMessageBox::information(nullptr, aboutData.displayName() + ' ' + aboutData.version(), aboutData.displayName()); exit(0); } if(cmdLineParser->isSet(QStringLiteral("help"))) { KMessageBox::information(nullptr, "
" + cmdLineParser->helpText() + "
", aboutData.displayName()); exit(0); } } aboutData.processCommandLine(cmdLineParser); KDiff3Shell* p = new KDiff3Shell(); p->show(); //p->setWindowState( p->windowState() | Qt::WindowActive ); // Patch for ubuntu: window not active on startup //app.installEventFilter( new CFilter ); int retVal = QApplication::exec(); /* if (QApplication::clipboard()->text().size() == 0) QApplication::clipboard()->clear(); // Patch for Ubuntu: Fix issue with Qt clipboard*/ return retVal; } diff --git a/src/optiondialog.cpp b/src/optiondialog.cpp index f1a89a2..6b6b2bb 100644 --- a/src/optiondialog.cpp +++ b/src/optiondialog.cpp @@ -1,1545 +1,1545 @@ /* * kdiff3 - Text Diff And Merge Tool * Copyright (C) 2002-2009 Joachim Eibl, joachim.eibl at gmx.de * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "optiondialog.h" #include "OptionItems.h" #include "common.h" #include "smalldialogs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include const QString OptionDialog::s_historyEntryStartRegExpToolTip = i18n("A version control history entry consists of several lines.\n" "Specify the regular expression to detect the first line (without the leading comment).\n" "Use parentheses to group the keys you want to use for sorting.\n" "If left empty, then KDiff3 assumes that empty lines separate history entries.\n" "See the documentation for details."); const QString OptionDialog::s_historyEntryStartSortKeyOrderToolTip = i18n("Each pair of parentheses used in the regular expression for the history start entry\n" "groups a key that can be used for sorting.\n" "Specify the list of keys (that are numbered in order of occurrence\n" "starting with 1) using ',' as separator (e.g. \"4,5,6,1,2,3,7\").\n" "If left empty, then no sorting will be done.\n" "See the documentation for details."); const QString OptionDialog::s_autoMergeRegExpToolTip = i18n("Regular expression for lines where KDiff3 should automatically choose one source.\n" "When a line with a conflict matches the regular expression then\n" "- if available - C, otherwise B will be chosen."); const QString OptionDialog::s_historyStartRegExpToolTip = i18n("Regular expression for the start of the version control history entry.\n" "Usually this line contains the \"$Log$\" keyword.\n" "Default value: \".*\\$Log.*\\$.*\""); class OptionCheckBox : public QCheckBox, public OptionBool { public: OptionCheckBox(const QString& text, bool bDefaultVal, const QString& saveName, bool* pbVar, QWidget* pParent) : QCheckBox(text, pParent), OptionBool(pbVar, bDefaultVal, saveName) {} void setToDefault() override { setChecked(getDefault()); } void setToCurrent() override { setChecked(getCurrent()); } using OptionBool::apply; void apply() override { apply(isChecked()); } private: Q_DISABLE_COPY(OptionCheckBox) }; class OptionRadioButton : public QRadioButton, public OptionBool { public: OptionRadioButton(const QString& text, bool bDefaultVal, const QString& saveName, bool* pbVar, QWidget* pParent) : QRadioButton(text, pParent), OptionBool(pbVar, bDefaultVal, saveName) {} void setToDefault() override { setChecked(getDefault()); } void setToCurrent() override { setChecked(getCurrent()); } using OptionBool::apply; void apply() override { apply(isChecked()); } private: Q_DISABLE_COPY(OptionRadioButton) }; FontChooser::FontChooser(QWidget* pParent) : QGroupBox(pParent) { QVBoxLayout* pLayout = new QVBoxLayout(this); m_pLabel = new QLabel(QString()); pLayout->addWidget(m_pLabel); QChar visualTab(0x2192); QChar visualSpace((ushort)0xb7); m_pExampleTextEdit = new QPlainTextEdit(QString("The quick brown fox jumps over the river\n" "but the little red hen escapes with a shiver.\n" ":-)") + visualTab + visualSpace, this); m_pExampleTextEdit->setFont(m_font); m_pExampleTextEdit->setReadOnly(true); pLayout->addWidget(m_pExampleTextEdit); m_pSelectFont = new QPushButton(i18n("Change Font")); m_pSelectFont->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(m_pSelectFont, &QPushButton::clicked, this, &FontChooser::slotSelectFont); pLayout->addWidget(m_pSelectFont); pLayout->setAlignment(m_pSelectFont, Qt::AlignRight); } QFont FontChooser::font() { return m_font; //QFont("courier",10); } void FontChooser::setFont(const QFont& font, bool) { m_font = font; m_pExampleTextEdit->setFont(m_font); m_pLabel->setText(i18n("Font: %1, %2, %3\n\nExample:", m_font.family(), m_font.styleName(), m_font.pointSize())); //update(); } void FontChooser::slotSelectFont() { bool bOk; m_font = QFontDialog::getFont(&bOk, m_font); m_pExampleTextEdit->setFont(m_font); m_pLabel->setText(i18n("Font: %1, %2, %3\n\nExample:", m_font.family(), m_font.styleName(), m_font.pointSize())); } class OptionFontChooser : public FontChooser, public OptionFont { public: OptionFontChooser(const QFont& defaultVal, const QString& saveName, QFont* pVar, QWidget* pParent) : FontChooser(pParent), OptionFont(pVar, defaultVal, saveName) {} void setToDefault() override { setFont(getDefault(), false); } void setToCurrent() override { setFont(getCurrent(), false); } using OptionFont::apply; void apply() override { apply(font()); } private: Q_DISABLE_COPY(OptionFontChooser) }; class OptionColorButton : public KColorButton, public OptionColor { public: OptionColorButton(const QColor &defaultVal, const QString& saveName, QColor* pVar, QWidget* pParent) : KColorButton(pParent), OptionColor(pVar, defaultVal, saveName) {} void setToDefault() override { setColor(getDefault()); } void setToCurrent() override { setColor(getCurrent()); } using OptionColor::apply; void apply() override { apply(color()); } private: Q_DISABLE_COPY(OptionColorButton) }; class OptionLineEdit : public QComboBox, public OptionString { public: OptionLineEdit(const QString& defaultVal, const QString& saveName, QString* pVar, QWidget* pParent) : QComboBox(pParent), OptionString(pVar, defaultVal, saveName) { setMinimumWidth(50); setEditable(true); m_list.push_back(defaultVal); insertText(); } void setToDefault() override { setEditText(getDefault()); } void setToCurrent() override { setEditText(getCurrent()); } using OptionString::apply; void apply() override { apply(currentText()); insertText(); } void write(ValueMap* config) override { config->writeEntry(m_saveName, m_list); } void read(ValueMap* config) override { m_list = config->readEntry(m_saveName, QStringList(m_defaultVal)); if(!m_list.empty()) setCurrent(m_list.front()); clear(); insertItems(0, m_list); } private: void insertText() { // Check if the text exists. If yes remove it and push it in as first element QString current = currentText(); m_list.removeAll(current); m_list.push_front(current); clear(); if(m_list.size() > 10) m_list.erase(m_list.begin() + 10, m_list.end()); insertItems(0, m_list); } Q_DISABLE_COPY(OptionLineEdit) QStringList m_list; }; class OptionIntEdit : public QLineEdit, public OptionNum { public: OptionIntEdit(int defaultVal, const QString& saveName, int* pVar, int rangeMin, int rangeMax, QWidget* pParent) : QLineEdit(pParent), OptionNum(pVar, defaultVal, saveName) { QIntValidator* v = new QIntValidator(this); v->setRange(rangeMin, rangeMax); setValidator(v); } void setToDefault() override { //QString::setNum does not account for locale settings setText(OptionNum::toString(getDefault())); } void setToCurrent() override { setText(getString()); } using OptionNum::apply; void apply() override { const QIntValidator* v = static_cast(validator()); setCurrent( qBound(v->bottom(), text().toInt(), v->top()) ); setText(getString()); } private: Q_DISABLE_COPY(OptionIntEdit) }; class OptionComboBox : public QComboBox, public OptionItemBase { public: OptionComboBox(int defaultVal, const QString& saveName, int* pVarNum, QWidget* pParent) : QComboBox(pParent), OptionItemBase(saveName) { setMinimumWidth(50); m_pVarNum = pVarNum; m_pVarStr = nullptr; m_defaultVal = defaultVal; setEditable(false); } OptionComboBox(int defaultVal, const QString& saveName, QString* pVarStr, QWidget* pParent) : QComboBox(pParent), OptionItemBase(saveName) { m_pVarNum = nullptr; m_pVarStr = pVarStr; m_defaultVal = defaultVal; setEditable(false); } void setToDefault() override { setCurrentIndex(m_defaultVal); if(m_pVarStr != nullptr) { *m_pVarStr = currentText(); } } void setToCurrent() override { if(m_pVarNum != nullptr) setCurrentIndex(*m_pVarNum); else setText(*m_pVarStr); } using OptionItemBase::apply; void apply() override { if(m_pVarNum != nullptr) { *m_pVarNum = currentIndex(); } else { *m_pVarStr = currentText(); } } void write(ValueMap* config) override { if(m_pVarStr != nullptr) config->writeEntry(m_saveName, *m_pVarStr); else config->writeEntry(m_saveName, *m_pVarNum); } void read(ValueMap* config) override { if(m_pVarStr != nullptr) setText(config->readEntry(m_saveName, currentText())); else *m_pVarNum = config->readEntry(m_saveName, *m_pVarNum); } void preserve() override { if(m_pVarStr != nullptr) { m_preservedStrVal = *m_pVarStr; } else { m_preservedNumVal = *m_pVarNum; } } void unpreserve() override { if(m_pVarStr != nullptr) { *m_pVarStr = m_preservedStrVal; } else { *m_pVarNum = m_preservedNumVal; } } private: Q_DISABLE_COPY(OptionComboBox) int* m_pVarNum; int m_preservedNumVal = 0; QString* m_pVarStr; QString m_preservedStrVal; int m_defaultVal; void setText(const QString& s) { // Find the string in the combobox-list, don't change the value if nothing fits. for(int i = 0; i < count(); ++i) { if(itemText(i) == s) { if(m_pVarNum != nullptr) *m_pVarNum = i; if(m_pVarStr != nullptr) *m_pVarStr = s; setCurrentIndex(i); return; } } } }; class OptionEncodingComboBox : public QComboBox, public OptionCodec { Q_OBJECT QVector m_codecVec; QTextCodec** m_ppVarCodec; public: OptionEncodingComboBox(const QString& saveName, QTextCodec** ppVarCodec, QWidget* pParent) : QComboBox(pParent), OptionCodec(saveName) { m_ppVarCodec = ppVarCodec; insertCodec(i18n("Unicode, 8 bit"), QTextCodec::codecForName("UTF-8")); insertCodec(i18n("Unicode"), QTextCodec::codecForName("iso-10646-UCS-2")); insertCodec(i18n("Latin1"), QTextCodec::codecForName("iso 8859-1")); // First sort codec names: std::map names; QList mibs = QTextCodec::availableMibs(); for(int i: mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) names[QString(QLatin1String(c->name())).toUpper()] = c; } std::map::iterator it; for(it = names.begin(); it != names.end(); ++it) { insertCodec("", it->second); } this->setToolTip(i18n( "Change this if non-ASCII characters are not displayed correctly.")); } void insertCodec(const QString& visibleCodecName, QTextCodec* c) { if(c != nullptr) { QByteArray nameArray = c->name(); QLatin1String codecName = QLatin1String(nameArray); for(int i = 0; i < m_codecVec.size(); ++i) { if(c == m_codecVec[i]) return; // don't insert any codec twice } // The m_codecVec.size will at this point return the value we need for the index. if(codecName == defaultName()) saveDefaultIndex(m_codecVec.size()); QString itemText = visibleCodecName.isEmpty() ? codecName : visibleCodecName + QLatin1String(" (") + codecName + QLatin1String(")"); addItem(itemText, m_codecVec.size()); m_codecVec.push_back(c); } } void setToDefault() override { int index = getDefaultIndex(); setCurrentIndex(index); if(m_ppVarCodec != nullptr) { *m_ppVarCodec = m_codecVec[index]; } } void setToCurrent() override { if(m_ppVarCodec != nullptr) { for(int i = 0; i < m_codecVec.size(); ++i) { if(*m_ppVarCodec == m_codecVec[i]) { setCurrentIndex(i); break; } } } } using OptionCodec::apply; void apply() override { if(m_ppVarCodec != nullptr) { *m_ppVarCodec = m_codecVec[currentIndex()]; } } void write(ValueMap* config) override { if(m_ppVarCodec != nullptr) config->writeEntry(m_saveName, (const char*)(*m_ppVarCodec)->name()); } void read(ValueMap* config) override { QString codecName = config->readEntry(m_saveName, (const char*)m_codecVec[currentIndex()]->name()); for(int i = 0; i < m_codecVec.size(); ++i) { if(codecName == QLatin1String(m_codecVec[i]->name())) { setCurrentIndex(i); if(m_ppVarCodec != nullptr) *m_ppVarCodec = m_codecVec[i]; break; } } } protected: void preserve() override { m_preservedVal = currentIndex(); } void unpreserve() override { setCurrentIndex(m_preservedVal); } int m_preservedVal; }; void OptionDialog::addOptionItem(OptionItemBase* p) { m_options->addOptionItem(p); } OptionDialog::OptionDialog(bool bShowDirMergeSettings, QWidget* parent) : KPageDialog(parent) { setFaceType(List); setWindowTitle(i18n("Configure")); setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Apply | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); setModal(true); //showButtonSeparator( true ); //setHelp( "kdiff3/index.html", QString::null ); m_options->init(); setupFontPage(); setupColorPage(); setupEditPage(); setupDiffPage(); setupMergePage(); setupOtherOptions(); if(bShowDirMergeSettings) setupDirectoryMergePage(); setupRegionalPage(); setupIntegrationPage(); // Initialize all values in the dialog resetToDefaults(); slotApply(); connect(button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &OptionDialog::slotApply); connect(button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &OptionDialog::slotOk); connect(button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &OptionDialog::slotDefault); connect(button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &QDialog::reject); connect(button(QDialogButtonBox::Help), &QPushButton::clicked, this, &OptionDialog::helpRequested); //connect(this, &OptionDialog::applyClicked, this, &OptionDialog::slotApply); //helpClicked() is connected in KDiff3App::KDiff3App -- Really where? //connect(this, &OptionDialog::defaultClicked, this, &OptionDialog::slotDefault); } void OptionDialog::helpRequested() { KHelpClient::invokeHelp(QStringLiteral("kdiff3/index.html")); } OptionDialog::~OptionDialog() { } void OptionDialog::setupOtherOptions() { //TODO move to Options class addOptionItem(new OptionToggleAction(false, "AutoAdvance", &m_options->m_bAutoAdvance)); addOptionItem(new OptionToggleAction(true, "ShowWhiteSpaceCharacters", &m_options->m_bShowWhiteSpaceCharacters)); addOptionItem(new OptionToggleAction(true, "ShowWhiteSpace", &m_options->m_bShowWhiteSpace)); addOptionItem(new OptionToggleAction(false, "ShowLineNumbers", &m_options->m_bShowLineNumbers)); addOptionItem(new OptionToggleAction(true, "HorizDiffWindowSplitting", &m_options->m_bHorizDiffWindowSplitting)); addOptionItem(new OptionToggleAction(false, "WordWrap", &m_options->m_bWordWrap)); addOptionItem(new OptionToggleAction(true, "ShowIdenticalFiles", &m_options->m_bDmShowIdenticalFiles)); addOptionItem(new OptionStringList(&m_options->m_recentAFiles, "RecentAFiles")); addOptionItem(new OptionStringList(&m_options->m_recentBFiles, "RecentBFiles")); addOptionItem(new OptionStringList(&m_options->m_recentCFiles, "RecentCFiles")); addOptionItem(new OptionStringList(&m_options->m_recentOutputFiles, "RecentOutputFiles")); addOptionItem(new OptionStringList(&m_options->m_recentEncodings, "RecentEncodings")); } void OptionDialog::setupFontPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Font")); pageItem->setHeader(i18n("Editor & Diff Output Font")); //not all themes have this icon if(QIcon::hasThemeIcon(QStringLiteral("font-select-symbolic"))) pageItem->setIcon(QIcon::fromTheme(QStringLiteral("font-select-symbolic"))); else pageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); //requires QT 5.2 or later. static const QFont defaultFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); static QFont defaultAppFont = QApplication::font(); OptionFontChooser* pAppFontChooser = new OptionFontChooser(defaultAppFont, "ApplicationFont", &m_options->m_appFont, page); addOptionItem(pAppFontChooser); topLayout->addWidget(pAppFontChooser); pAppFontChooser->setTitle(i18n("Application font")); OptionFontChooser* pFontChooser = new OptionFontChooser(defaultFont, "Font", &m_options->m_font, page); addOptionItem(pFontChooser); topLayout->addWidget(pFontChooser); pFontChooser->setTitle(i18n("File view font")); QGridLayout* gbox = new QGridLayout(); topLayout->addLayout(gbox); //int line=0; // This currently does not work (see rendering in class DiffTextWindow) //OptionCheckBox* pItalicDeltas = new OptionCheckBox( i18n("Italic font for deltas"), false, "ItalicForDeltas", &m_options->m_bItalicForDeltas, page, this ); //addOptionItem(pItalicDeltas); //gbox->addWidget( pItalicDeltas, line, 0, 1, 2 ); //pItalicDeltas->setToolTip( i18n( // "Selects the italic version of the font for differences.\n" // "If the font doesn't support italic characters, then this does nothing.") // ); } void OptionDialog::setupColorPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18nc("Title for color settings page","Color")); pageItem->setHeader(i18n("Colors Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("colormanagement"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); QLabel* label; int line = 0; int depth = QPixmap::defaultDepth(); bool bLowColor = depth <= 8; label = new QLabel(i18n("Editor and Diff Views:"), page); gbox->addWidget(label, line, 0); QFont f(label->font()); f.setBold(true); label->setFont(f); ++line; OptionColorButton* pFgColor = new OptionColorButton(Qt::black, "FgColor", &m_options->m_fgColor, page); label = new QLabel(i18n("Foreground color:"), page); label->setBuddy(pFgColor); addOptionItem(pFgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pFgColor, line, 1); ++line; OptionColorButton* pBgColor = new OptionColorButton(Qt::white, "BgColor", &m_options->m_bgColor, page); label = new QLabel(i18n("Background color:"), page); label->setBuddy(pBgColor); addOptionItem(pBgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pBgColor, line, 1); ++line; OptionColorButton* pDiffBgColor = new OptionColorButton( bLowColor ? QColor(Qt::lightGray) : qRgb(224, 224, 224), "DiffBgColor", &m_options->m_diffBgColor, page); label = new QLabel(i18n("Diff background color:"), page); label->setBuddy(pDiffBgColor); addOptionItem(pDiffBgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pDiffBgColor, line, 1); ++line; OptionColorButton* pColorA = new OptionColorButton( bLowColor ? qRgb(0, 0, 255) : qRgb(0, 0, 200) /*blue*/, "ColorA", &m_options->m_colorA, page); label = new QLabel(i18n("Color A:"), page); label->setBuddy(pColorA); addOptionItem(pColorA); gbox->addWidget(label, line, 0); gbox->addWidget(pColorA, line, 1); ++line; OptionColorButton* pColorB = new OptionColorButton( bLowColor ? qRgb(0, 128, 0) : qRgb(0, 150, 0) /*green*/, "ColorB", &m_options->m_colorB, page); label = new QLabel(i18n("Color B:"), page); label->setBuddy(pColorB); addOptionItem(pColorB); gbox->addWidget(label, line, 0); gbox->addWidget(pColorB, line, 1); ++line; OptionColorButton* pColorC = new OptionColorButton( bLowColor ? qRgb(128, 0, 128) : qRgb(150, 0, 150) /*magenta*/, "ColorC", &m_options->m_colorC, page); label = new QLabel(i18n("Color C:"), page); label->setBuddy(pColorC); addOptionItem(pColorC); gbox->addWidget(label, line, 0); gbox->addWidget(pColorC, line, 1); ++line; OptionColorButton* pColorForConflict = new OptionColorButton(Qt::red, "ColorForConflict", &m_options->m_colorForConflict, page); label = new QLabel(i18n("Conflict color:"), page); label->setBuddy(pColorForConflict); addOptionItem(pColorForConflict); gbox->addWidget(label, line, 0); gbox->addWidget(pColorForConflict, line, 1); ++line; OptionColorButton* pColor = new OptionColorButton( bLowColor ? qRgb(192, 192, 192) : qRgb(220, 220, 100), "CurrentRangeBgColor", &m_options->m_currentRangeBgColor, page); label = new QLabel(i18n("Current range background color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; pColor = new OptionColorButton( bLowColor ? qRgb(255, 255, 0) : qRgb(255, 255, 150), "CurrentRangeDiffBgColor", &m_options->m_currentRangeDiffBgColor, page); label = new QLabel(i18n("Current range diff background color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; pColor = new OptionColorButton(qRgb(0xff, 0xd0, 0x80), "ManualAlignmentRangeColor", &m_options->m_manualHelpRangeColor, page); label = new QLabel(i18n("Color for manually aligned difference ranges:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; - label = new QLabel(i18n("Directory Comparison View:"), page); + label = new QLabel(i18n("Folder Comparison View:"), page); gbox->addWidget(label, line, 0); label->setFont(f); ++line; pColor = new OptionColorButton(qRgb(0, 0xd0, 0), "NewestFileColor", &m_options->m_newestFileColor, page); label = new QLabel(i18n("Newest file color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); - QString dirColorTip = i18n("Changing this color will only be effective when starting the next directory comparison."); + QString dirColorTip = i18n("Changing this color will only be effective when starting the next folder comparison."); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0xf0, 0, 0), "OldestFileColor", &m_options->m_oldestFileColor, page); label = new QLabel(i18n("Oldest file color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0xc0, 0xc0, 0), "MidAgeFileColor", &m_options->m_midAgeFileColor, page); label = new QLabel(i18n("Middle age file color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0, 0, 0), "MissingFileColor", &m_options->m_missingFileColor, page); label = new QLabel(i18n("Color for missing files:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; topLayout->addStretch(10); } void OptionDialog::setupEditPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Editor")); pageItem->setHeader(i18n("Editor Behavior")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); QLabel* label; int line = 0; OptionCheckBox* pReplaceTabs = new OptionCheckBox(i18n("Tab inserts spaces"), false, "ReplaceTabs", &m_options->m_bReplaceTabs, page); addOptionItem(pReplaceTabs); gbox->addWidget(pReplaceTabs, line, 0, 1, 2); pReplaceTabs->setToolTip(i18n( "On: Pressing tab generates the appropriate number of spaces.\n" "Off: A tab character will be inserted.")); ++line; OptionIntEdit* pTabSize = new OptionIntEdit(8, "TabSize", &m_options->m_tabSize, 1, 100, page); label = new QLabel(i18n("Tab size:"), page); label->setBuddy(pTabSize); addOptionItem(pTabSize); gbox->addWidget(label, line, 0); gbox->addWidget(pTabSize, line, 1); ++line; OptionCheckBox* pAutoIndentation = new OptionCheckBox(i18n("Auto indentation"), true, "AutoIndentation", &m_options->m_bAutoIndentation, page); gbox->addWidget(pAutoIndentation, line, 0, 1, 2); addOptionItem(pAutoIndentation); pAutoIndentation->setToolTip(i18n( "On: The indentation of the previous line is used for a new line.\n")); ++line; OptionCheckBox* pAutoCopySelection = new OptionCheckBox(i18n("Auto copy selection"), false, "AutoCopySelection", &m_options->m_bAutoCopySelection, page); gbox->addWidget(pAutoCopySelection, line, 0, 1, 2); addOptionItem(pAutoCopySelection); pAutoCopySelection->setToolTip(i18n( "On: Any selection is immediately written to the clipboard.\n" "Off: You must explicitly copy e.g. via Ctrl-C.")); ++line; label = new QLabel(i18n("Line end style:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pLineEndStyle = new OptionComboBox(eLineEndStyleAutoDetect, "LineEndStyle", (int*)&m_options->m_lineEndStyle, page); gbox->addWidget(pLineEndStyle, line, 1); addOptionItem(pLineEndStyle); pLineEndStyle->insertItem(eLineEndStyleUnix, "Unix"); pLineEndStyle->insertItem(eLineEndStyleDos, "Dos/Windows"); pLineEndStyle->insertItem(eLineEndStyleAutoDetect, "Autodetect"); label->setToolTip(i18n( "Sets the line endings for when an edited file is saved.\n" "DOS/Windows: CR+LF; UNIX: LF; with CR=0D, LF=0A")); ++line; topLayout->addStretch(10); } void OptionDialog::setupDiffPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Diff")); pageItem->setHeader(i18n("Diff Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("text-x-patch"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label = nullptr; m_options->m_bPreserveCarriageReturn = false; /* OptionCheckBox* pPreserveCarriageReturn = new OptionCheckBox( i18n("Preserve carriage return"), false, "PreserveCarriageReturn", &m_options->m_bPreserveCarriageReturn, page, this ); addOptionItem(pPreserveCarriageReturn); gbox->addWidget( pPreserveCarriageReturn, line, 0, 1, 2 ); pPreserveCarriageReturn->setToolTip( i18n( "Show carriage return characters '\\r' if they exist.\n" "Helps to compare files that were modified under different operating systems.") ); ++line; */ OptionCheckBox* pIgnoreNumbers = new OptionCheckBox(i18n("Ignore numbers (treat as white space)"), false, "IgnoreNumbers", &m_options->m_bIgnoreNumbers, page); gbox->addWidget(pIgnoreNumbers, line, 0, 1, 2); addOptionItem(pIgnoreNumbers); pIgnoreNumbers->setToolTip(i18n( "Ignore number characters during line matching phase. (Similar to Ignore white space.)\n" "Might help to compare files with numeric data.")); ++line; OptionCheckBox* pIgnoreComments = new OptionCheckBox(i18n("Ignore C/C++ comments (treat as white space)"), false, "IgnoreComments", &m_options->m_bIgnoreComments, page); gbox->addWidget(pIgnoreComments, line, 0, 1, 2); addOptionItem(pIgnoreComments); pIgnoreComments->setToolTip(i18n("Treat C/C++ comments like white space.")); ++line; OptionCheckBox* pIgnoreCase = new OptionCheckBox(i18n("Ignore case (treat as white space)"), false, "IgnoreCase", &m_options->m_bIgnoreCase, page); gbox->addWidget(pIgnoreCase, line, 0, 1, 2); addOptionItem(pIgnoreCase); pIgnoreCase->setToolTip(i18n( "Treat case differences like white space changes. ('a'<=>'A')")); ++line; label = new QLabel(i18n("Preprocessor command:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pLE = new OptionLineEdit("", "PreProcessorCmd", &m_options->m_PreProcessorCmd, page); gbox->addWidget(pLE, line, 1); addOptionItem(pLE); label->setToolTip(i18n("User defined pre-processing. (See the docs for details.)")); ++line; label = new QLabel(i18n("Line-matching preprocessor command:"), page); gbox->addWidget(label, line, 0); pLE = new OptionLineEdit("", "LineMatchingPreProcessorCmd", &m_options->m_LineMatchingPreProcessorCmd, page); gbox->addWidget(pLE, line, 1); addOptionItem(pLE); label->setToolTip(i18n("This pre-processor is only used during line matching.\n(See the docs for details.)")); ++line; OptionCheckBox* pTryHard = new OptionCheckBox(i18n("Try hard (slower)"), true, "TryHard", &m_options->m_bTryHard, page); gbox->addWidget(pTryHard, line, 0, 1, 2); addOptionItem(pTryHard); pTryHard->setToolTip(i18n( "Enables the --minimal option for the external diff.\n" "The analysis of big files will be much slower.")); ++line; OptionCheckBox* pDiff3AlignBC = new OptionCheckBox(i18n("Align B and C for 3 input files"), false, "Diff3AlignBC", &m_options->m_bDiff3AlignBC, page); gbox->addWidget(pDiff3AlignBC, line, 0, 1, 2); addOptionItem(pDiff3AlignBC); pDiff3AlignBC->setToolTip(i18n( "Try to align B and C when comparing or merging three input files.\n" "Not recommended for merging because merge might get more complicated.\n" "(Default is off.)")); ++line; topLayout->addStretch(10); } void OptionDialog::setupMergePage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18nc("Settings page", "Merge")); pageItem->setHeader(i18n("Merge Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("merge"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label = nullptr; label = new QLabel(i18n("Auto advance delay (ms):"), page); gbox->addWidget(label, line, 0); OptionIntEdit* pAutoAdvanceDelay = new OptionIntEdit(500, "AutoAdvanceDelay", &m_options->m_autoAdvanceDelay, 0, 2000, page); gbox->addWidget(pAutoAdvanceDelay, line, 1); addOptionItem(pAutoAdvanceDelay); label->setToolTip(i18n( "When in Auto-Advance mode the result of the current selection is shown \n" "for the specified time, before jumping to the next conflict. Range: 0-2000 ms")); ++line; OptionCheckBox* pShowInfoDialogs = new OptionCheckBox(i18n("Show info dialogs"), true, "ShowInfoDialogs", &m_options->m_bShowInfoDialogs, page); gbox->addWidget(pShowInfoDialogs, line, 0, 1, 2); addOptionItem(pShowInfoDialogs); pShowInfoDialogs->setToolTip(i18n("Show a dialog with information about the number of conflicts.")); ++line; label = new QLabel(i18n("White space 2-file merge default:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pWhiteSpace2FileMergeDefault = new OptionComboBox(0, "WhiteSpace2FileMergeDefault", &m_options->m_whiteSpace2FileMergeDefault, page); gbox->addWidget(pWhiteSpace2FileMergeDefault, line, 1); addOptionItem(pWhiteSpace2FileMergeDefault); pWhiteSpace2FileMergeDefault->insertItem(0, i18n("Manual Choice")); pWhiteSpace2FileMergeDefault->insertItem(1, i18n("A")); pWhiteSpace2FileMergeDefault->insertItem(2, i18n("B")); label->setToolTip(i18n( "Allow the merge algorithm to automatically select an input for " "white-space-only changes.")); ++line; label = new QLabel(i18n("White space 3-file merge default:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pWhiteSpace3FileMergeDefault = new OptionComboBox(0, "WhiteSpace3FileMergeDefault", &m_options->m_whiteSpace3FileMergeDefault, page); gbox->addWidget(pWhiteSpace3FileMergeDefault, line, 1); addOptionItem(pWhiteSpace3FileMergeDefault); pWhiteSpace3FileMergeDefault->insertItem(0, i18n("Manual Choice")); pWhiteSpace3FileMergeDefault->insertItem(1, i18n("A")); pWhiteSpace3FileMergeDefault->insertItem(2, i18n("B")); pWhiteSpace3FileMergeDefault->insertItem(3, i18n("C")); label->setToolTip(i18n( "Allow the merge algorithm to automatically select an input for " "white-space-only changes.")); ++line; QGroupBox* pGroupBox = new QGroupBox(i18n("Automatic Merge Regular Expression")); gbox->addWidget(pGroupBox, line, 0, 1, 2); ++line; { gbox = new QGridLayout(pGroupBox); gbox->setColumnStretch(1, 10); line = 0; label = new QLabel(i18n("Auto merge regular expression:"), page); gbox->addWidget(label, line, 0); m_pAutoMergeRegExpLineEdit = new OptionLineEdit(".*\\$(Version|Header|Date|Author).*\\$.*", "AutoMergeRegExp", &m_options->m_autoMergeRegExp, page); gbox->addWidget(m_pAutoMergeRegExpLineEdit, line, 1); addOptionItem(m_pAutoMergeRegExpLineEdit); label->setToolTip(s_autoMergeRegExpToolTip); ++line; OptionCheckBox* pAutoMergeRegExp = new OptionCheckBox(i18n("Run regular expression auto merge on merge start"), false, "RunRegExpAutoMergeOnMergeStart", &m_options->m_bRunRegExpAutoMergeOnMergeStart, page); addOptionItem(pAutoMergeRegExp); gbox->addWidget(pAutoMergeRegExp, line, 0, 1, 2); pAutoMergeRegExp->setToolTip(i18n("Run the merge for auto merge regular expressions\n" "immediately when a merge starts.\n")); ++line; } pGroupBox = new QGroupBox(i18n("Version Control History Merging")); gbox->addWidget(pGroupBox, line, 0, 1, 2); ++line; { gbox = new QGridLayout(pGroupBox); gbox->setColumnStretch(1, 10); line = 0; label = new QLabel(i18n("History start regular expression:"), page); gbox->addWidget(label, line, 0); m_pHistoryStartRegExpLineEdit = new OptionLineEdit(".*\\$Log.*\\$.*", "HistoryStartRegExp", &m_options->m_historyStartRegExp, page); gbox->addWidget(m_pHistoryStartRegExpLineEdit, line, 1); addOptionItem(m_pHistoryStartRegExpLineEdit); label->setToolTip(s_historyStartRegExpToolTip); ++line; label = new QLabel(i18n("History entry start regular expression:"), page); gbox->addWidget(label, line, 0); // Example line: "** \main\rolle_fsp_dev_008\1 17 Aug 2001 10:45:44 rolle" QString historyEntryStartDefault = "\\s*\\\\main\\\\(\\S+)\\s+" // Start with "\main\" "([0-9]+) " // day "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) " //month "([0-9][0-9][0-9][0-9]) " // year "([0-9][0-9]:[0-9][0-9]:[0-9][0-9])\\s+(.*)"; // time, name m_pHistoryEntryStartRegExpLineEdit = new OptionLineEdit(historyEntryStartDefault, "HistoryEntryStartRegExp", &m_options->m_historyEntryStartRegExp, page); gbox->addWidget(m_pHistoryEntryStartRegExpLineEdit, line, 1); addOptionItem(m_pHistoryEntryStartRegExpLineEdit); label->setToolTip(s_historyEntryStartRegExpToolTip); ++line; m_pHistoryMergeSorting = new OptionCheckBox(i18n("History merge sorting"), false, "HistoryMergeSorting", &m_options->m_bHistoryMergeSorting, page); gbox->addWidget(m_pHistoryMergeSorting, line, 0, 1, 2); addOptionItem(m_pHistoryMergeSorting); m_pHistoryMergeSorting->setToolTip(i18n("Sort version control history by a key.")); ++line; //QString branch = newHistoryEntry.cap(1); //int day = newHistoryEntry.cap(2).toInt(); //int month = QString("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec").find(newHistoryEntry.cap(3))/4 + 1; //int year = newHistoryEntry.cap(4).toInt(); //QString time = newHistoryEntry.cap(5); //QString name = newHistoryEntry.cap(6); QString defaultSortKeyOrder = "4,3,2,5,1,6"; //QDate(year,month,day).toString(Qt::ISODate) +" "+ time + " " + branch + " " + name; label = new QLabel(i18n("History entry start sort key order:"), page); gbox->addWidget(label, line, 0); m_pHistorySortKeyOrderLineEdit = new OptionLineEdit(defaultSortKeyOrder, "HistoryEntryStartSortKeyOrder", &m_options->m_historyEntryStartSortKeyOrder, page); gbox->addWidget(m_pHistorySortKeyOrderLineEdit, line, 1); addOptionItem(m_pHistorySortKeyOrderLineEdit); label->setToolTip(s_historyEntryStartSortKeyOrderToolTip); m_pHistorySortKeyOrderLineEdit->setEnabled(false); connect(m_pHistoryMergeSorting, &OptionCheckBox::toggled, m_pHistorySortKeyOrderLineEdit, &OptionLineEdit::setEnabled); ++line; m_pHistoryAutoMerge = new OptionCheckBox(i18n("Merge version control history on merge start"), false, "RunHistoryAutoMergeOnMergeStart", &m_options->m_bRunHistoryAutoMergeOnMergeStart, page); addOptionItem(m_pHistoryAutoMerge); gbox->addWidget(m_pHistoryAutoMerge, line, 0, 1, 2); m_pHistoryAutoMerge->setToolTip(i18n("Run version control history automerge on merge start.")); ++line; OptionIntEdit* pMaxNofHistoryEntries = new OptionIntEdit(-1, "MaxNofHistoryEntries", &m_options->m_maxNofHistoryEntries, -1, 1000, page); label = new QLabel(i18n("Max number of history entries:"), page); gbox->addWidget(label, line, 0); gbox->addWidget(pMaxNofHistoryEntries, line, 1); addOptionItem(pMaxNofHistoryEntries); pMaxNofHistoryEntries->setToolTip(i18n("Cut off after specified number. Use -1 for infinite number of entries.")); ++line; } QPushButton* pButton = new QPushButton(i18n("Test your regular expressions"), page); gbox->addWidget(pButton, line, 0); connect(pButton, &QPushButton::clicked, this, &OptionDialog::slotHistoryMergeRegExpTester); ++line; label = new QLabel(i18n("Irrelevant merge command:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pLE = new OptionLineEdit("", "IrrelevantMergeCmd", &m_options->m_IrrelevantMergeCmd, page); gbox->addWidget(pLE, line, 1); addOptionItem(pLE); label->setToolTip(i18n("If specified this script is run after automerge\n" "when no other relevant changes were detected.\n" "Called with the parameters: filename1 filename2 filename3")); ++line; OptionCheckBox* pAutoSaveAndQuit = new OptionCheckBox(i18n("Auto save and quit on merge without conflicts"), false, "AutoSaveAndQuitOnMergeWithoutConflicts", &m_options->m_bAutoSaveAndQuitOnMergeWithoutConflicts, page); gbox->addWidget(pAutoSaveAndQuit, line, 0, 1, 2); addOptionItem(pAutoSaveAndQuit); pAutoSaveAndQuit->setToolTip(i18n("If KDiff3 was started for a file-merge from the command line and all\n" "conflicts are solvable without user interaction then automatically save and quit.\n" "(Similar to command line option \"--auto\".)")); ++line; topLayout->addStretch(10); } void OptionDialog::setupDirectoryMergePage() { QFrame* page = new QFrame(); - KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Directory")); - pageItem->setHeader(i18n("Directory")); + KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Folder")); + pageItem->setHeader(i18n("Folder")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("inode-directory"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; - OptionCheckBox* pRecursiveDirs = new OptionCheckBox(i18n("Recursive directories"), true, "RecursiveDirs", &m_options->m_bDmRecursiveDirs, page); + OptionCheckBox* pRecursiveDirs = new OptionCheckBox(i18n("Recursive folders"), true, "RecursiveDirs", &m_options->m_bDmRecursiveDirs, page); gbox->addWidget(pRecursiveDirs, line, 0, 1, 2); addOptionItem(pRecursiveDirs); - pRecursiveDirs->setToolTip(i18n("Whether to analyze subdirectories or not.")); + pRecursiveDirs->setToolTip(i18n("Whether to analyze subfolders or not.")); ++line; QLabel* label = new QLabel(i18n("File pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pFilePattern = new OptionLineEdit("*", "FilePattern", &m_options->m_DmFilePattern, page); gbox->addWidget(pFilePattern, line, 1); addOptionItem(pFilePattern); label->setToolTip(i18n( "Pattern(s) of files to be analyzed. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; label = new QLabel(i18n("File-anti-pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pFileAntiPattern = new OptionLineEdit("*.orig;*.o;*.obj;*.rej;*.bak", "FileAntiPattern", &m_options->m_DmFileAntiPattern, page); gbox->addWidget(pFileAntiPattern, line, 1); addOptionItem(pFileAntiPattern); label->setToolTip(i18n( "Pattern(s) of files to be excluded from analysis. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; - label = new QLabel(i18n("Dir-anti-pattern(s):"), page); + label = new QLabel(i18n("Folder-anti-pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pDirAntiPattern = new OptionLineEdit("CVS;.deps;.svn;.hg;.git", "DirAntiPattern", &m_options->m_DmDirAntiPattern, page); gbox->addWidget(pDirAntiPattern, line, 1); addOptionItem(pDirAntiPattern); label->setToolTip(i18n( - "Pattern(s) of directories to be excluded from analysis. \n" + "Pattern(s) of folders to be excluded from analysis. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; OptionCheckBox* pUseCvsIgnore = new OptionCheckBox(i18n("Use .cvsignore"), false, "UseCvsIgnore", &m_options->m_bDmUseCvsIgnore, page); gbox->addWidget(pUseCvsIgnore, line, 0, 1, 2); addOptionItem(pUseCvsIgnore); pUseCvsIgnore->setToolTip(i18n( "Extends the antipattern to anything that would be ignored by CVS.\n" - "Via local \".cvsignore\" files this can be directory specific.")); + "Via local \".cvsignore\" files this can be folder-specific.")); ++line; - OptionCheckBox* pFindHidden = new OptionCheckBox(i18n("Find hidden files and directories"), true, "FindHidden", &m_options->m_bDmFindHidden, page); + OptionCheckBox* pFindHidden = new OptionCheckBox(i18n("Find hidden files and folders"), true, "FindHidden", &m_options->m_bDmFindHidden, page); gbox->addWidget(pFindHidden, line, 0, 1, 2); addOptionItem(pFindHidden); - pFindHidden->setToolTip(i18n("Finds hidden files and directories.")); + pFindHidden->setToolTip(i18n("Finds hidden files and folders.")); ++line; OptionCheckBox* pFollowFileLinks = new OptionCheckBox(i18n("Follow file links"), false, "FollowFileLinks", &m_options->m_bDmFollowFileLinks, page); gbox->addWidget(pFollowFileLinks, line, 0, 1, 2); addOptionItem(pFollowFileLinks); pFollowFileLinks->setToolTip(i18n( "On: Compare the file the link points to.\n" "Off: Compare the links.")); ++line; - OptionCheckBox* pFollowDirLinks = new OptionCheckBox(i18n("Follow directory links"), false, "FollowDirLinks", &m_options->m_bDmFollowDirLinks, page); + OptionCheckBox* pFollowDirLinks = new OptionCheckBox(i18n("Follow folder links"), false, "FollowDirLinks", &m_options->m_bDmFollowDirLinks, page); gbox->addWidget(pFollowDirLinks, line, 0, 1, 2); addOptionItem(pFollowDirLinks); pFollowDirLinks->setToolTip(i18n( - "On: Compare the directory the link points to.\n" + "On: Compare the folder the link points to.\n" "Off: Compare the links.")); ++line; #if defined(Q_OS_WIN) bool bCaseSensitiveFilenameComparison = false; #else bool bCaseSensitiveFilenameComparison = true; #endif OptionCheckBox* pCaseSensitiveFileNames = new OptionCheckBox(i18n("Case sensitive filename comparison"), bCaseSensitiveFilenameComparison, "CaseSensitiveFilenameComparison", &m_options->m_bDmCaseSensitiveFilenameComparison, page); gbox->addWidget(pCaseSensitiveFileNames, line, 0, 1, 2); addOptionItem(pCaseSensitiveFileNames); pCaseSensitiveFileNames->setToolTip(i18n( - "The directory comparison will compare files or directories when their names match.\n" + "The folder comparison will compare files or folders when their names match.\n" "Set this option if the case of the names must match. (Default for Windows is off, otherwise on.)")); ++line; - OptionCheckBox* pUnfoldSubdirs = new OptionCheckBox(i18n("Unfold all subdirectories on load"), false, "UnfoldSubdirs", &m_options->m_bDmUnfoldSubdirs, page); + OptionCheckBox* pUnfoldSubdirs = new OptionCheckBox(i18n("Unfold all subfolders on load"), false, "UnfoldSubdirs", &m_options->m_bDmUnfoldSubdirs, page); gbox->addWidget(pUnfoldSubdirs, line, 0, 1, 2); addOptionItem(pUnfoldSubdirs); pUnfoldSubdirs->setToolTip(i18n( - "On: Unfold all subdirectories when starting a directory diff.\n" - "Off: Leave subdirectories folded.")); + "On: Unfold all subfolders when starting a folder diff.\n" + "Off: Leave subfolders folded.")); ++line; - OptionCheckBox* pSkipDirStatus = new OptionCheckBox(i18n("Skip directory status report"), false, "SkipDirStatus", &m_options->m_bDmSkipDirStatus, page); + OptionCheckBox* pSkipDirStatus = new OptionCheckBox(i18n("Skip folder status report"), false, "SkipDirStatus", &m_options->m_bDmSkipDirStatus, page); gbox->addWidget(pSkipDirStatus, line, 0, 1, 2); addOptionItem(pSkipDirStatus); pSkipDirStatus->setToolTip(i18n( - "On: Do not show the Directory Comparison Status.\n" + "On: Do not show the Folder Comparison Status.\n" "Off: Show the status dialog on start.")); ++line; QGroupBox* pBG = new QGroupBox(i18n("File Comparison Mode")); gbox->addWidget(pBG, line, 0, 1, 2); QVBoxLayout* pBGLayout = new QVBoxLayout(pBG); OptionRadioButton* pBinaryComparison = new OptionRadioButton(i18n("Binary comparison"), true, "BinaryComparison", &m_options->m_bDmBinaryComparison, pBG); addOptionItem(pBinaryComparison); pBinaryComparison->setToolTip(i18n("Binary comparison of each file. (Default)")); pBGLayout->addWidget(pBinaryComparison); OptionRadioButton* pFullAnalysis = new OptionRadioButton(i18n("Full analysis"), false, "FullAnalysis", &m_options->m_bDmFullAnalysis, pBG); addOptionItem(pFullAnalysis); pFullAnalysis->setToolTip(i18n("Do a full analysis and show statistics information in extra columns.\n" "(Slower than a binary comparison, much slower for binary files.)")); pBGLayout->addWidget(pFullAnalysis); OptionRadioButton* pTrustDate = new OptionRadioButton(i18n("Trust the size and modification date (unsafe)"), false, "TrustDate", &m_options->m_bDmTrustDate, pBG); addOptionItem(pTrustDate); pTrustDate->setToolTip(i18n("Assume that files are equal if the modification date and file length are equal.\n" "Files with equal contents but different modification dates will appear as different.\n" - "Useful for big directories or slow networks.")); + "Useful for big folders or slow networks.")); pBGLayout->addWidget(pTrustDate); OptionRadioButton* pTrustDateFallbackToBinary = new OptionRadioButton(i18n("Trust the size and date, but use binary comparison if date does not match (unsafe)"), false, "TrustDateFallbackToBinary", &m_options->m_bDmTrustDateFallbackToBinary, pBG); addOptionItem(pTrustDateFallbackToBinary); pTrustDateFallbackToBinary->setToolTip(i18n("Assume that files are equal if the modification date and file length are equal.\n" "If the dates are not equal but the sizes are, use binary comparison.\n" - "Useful for big directories or slow networks.")); + "Useful for big folders or slow networks.")); pBGLayout->addWidget(pTrustDateFallbackToBinary); OptionRadioButton* pTrustSize = new OptionRadioButton(i18n("Trust the size (unsafe)"), false, "TrustSize", &m_options->m_bDmTrustSize, pBG); addOptionItem(pTrustSize); pTrustSize->setToolTip(i18n("Assume that files are equal if their file lengths are equal.\n" - "Useful for big directories or slow networks when the date is modified during download.")); + "Useful for big folders or slow networks when the date is modified during download.")); pBGLayout->addWidget(pTrustSize); ++line; // Some two Dir-options: Affects only the default actions. - OptionCheckBox* pSyncMode = new OptionCheckBox(i18n("Synchronize directories"), false, "SyncMode", &m_options->m_bDmSyncMode, page); + OptionCheckBox* pSyncMode = new OptionCheckBox(i18n("Synchronize folders"), false, "SyncMode", &m_options->m_bDmSyncMode, page); addOptionItem(pSyncMode); gbox->addWidget(pSyncMode, line, 0, 1, 2); pSyncMode->setToolTip(i18n( - "Offers to store files in both directories so that\n" - "both directories are the same afterwards.\n" - "Works only when comparing two directories without specifying a destination.")); + "Offers to store files in both folders so that\n" + "both folders are the same afterwards.\n" + "Works only when comparing two folders without specifying a destination.")); ++line; // Allow white-space only differences to be considered equal OptionCheckBox* pWhiteSpaceDiffsEqual = new OptionCheckBox(i18n("White space differences considered equal"), true, "WhiteSpaceEqual", &m_options->m_bDmWhiteSpaceEqual, page); addOptionItem(pWhiteSpaceDiffsEqual); gbox->addWidget(pWhiteSpaceDiffsEqual, line, 0, 1, 2); pWhiteSpaceDiffsEqual->setToolTip(i18n( "If files differ only by white space consider them equal.\n" "This is only active when full analysis is chosen.")); connect(pFullAnalysis, &OptionRadioButton::toggled, pWhiteSpaceDiffsEqual, &OptionCheckBox::setEnabled); pWhiteSpaceDiffsEqual->setEnabled(false); ++line; OptionCheckBox* pCopyNewer = new OptionCheckBox(i18n("Copy newer instead of merging (unsafe)"), false, "CopyNewer", &m_options->m_bDmCopyNewer, page); addOptionItem(pCopyNewer); gbox->addWidget(pCopyNewer, line, 0, 1, 2); pCopyNewer->setToolTip(i18n( "Do not look inside, just take the newer file.\n" "(Use this only if you know what you are doing!)\n" - "Only effective when comparing two directories.")); + "Only effective when comparing two folders.")); ++line; OptionCheckBox* pCreateBakFiles = new OptionCheckBox(i18n("Backup files (.orig)"), true, "CreateBakFiles", &m_options->m_bDmCreateBakFiles, page); gbox->addWidget(pCreateBakFiles, line, 0, 1, 2); addOptionItem(pCreateBakFiles); pCreateBakFiles->setToolTip(i18n( "If a file would be saved over an old file, then the old file\n" "will be renamed with a '.orig' extension instead of being deleted.")); ++line; topLayout->addStretch(10); } void OptionDialog::setupRegionalPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Regional Settings")); pageItem->setHeader(i18n("Regional Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-locale"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label; m_pSameEncoding = new OptionCheckBox(i18n("Use the same encoding for everything:"), true, "SameEncoding", &m_options->m_bSameEncoding, page); addOptionItem(m_pSameEncoding); gbox->addWidget(m_pSameEncoding, line, 0, 1, 2); m_pSameEncoding->setToolTip(i18n( "Enable this allows to change all encodings by changing the first only.\n" "Disable this if different individual settings are needed.")); ++line; label = new QLabel(i18n("Note: Local Encoding is \"%1\"", QLatin1String(QTextCodec::codecForLocale()->name())), page); gbox->addWidget(label, line, 0); ++line; label = new QLabel(i18n("File Encoding for A:"), page); gbox->addWidget(label, line, 0); m_pEncodingAComboBox = new OptionEncodingComboBox("EncodingForA", &m_options->m_pEncodingA, page); addOptionItem(m_pEncodingAComboBox); gbox->addWidget(m_pEncodingAComboBox, line, 1); QString autoDetectToolTip = i18n( "If enabled then Unicode (UTF-16 or UTF-8) encoding will be detected.\n" "If the file is not Unicode then the selected encoding will be used as fallback.\n" "(Unicode detection depends on the first bytes of a file.)"); m_pAutoDetectUnicodeA = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeA", &m_options->m_bAutoDetectUnicodeA, page); gbox->addWidget(m_pAutoDetectUnicodeA, line, 2); addOptionItem(m_pAutoDetectUnicodeA); m_pAutoDetectUnicodeA->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for B:"), page); gbox->addWidget(label, line, 0); m_pEncodingBComboBox = new OptionEncodingComboBox("EncodingForB", &m_options->m_pEncodingB, page); addOptionItem(m_pEncodingBComboBox); gbox->addWidget(m_pEncodingBComboBox, line, 1); m_pAutoDetectUnicodeB = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeB", &m_options->m_bAutoDetectUnicodeB, page); addOptionItem(m_pAutoDetectUnicodeB); gbox->addWidget(m_pAutoDetectUnicodeB, line, 2); m_pAutoDetectUnicodeB->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for C:"), page); gbox->addWidget(label, line, 0); m_pEncodingCComboBox = new OptionEncodingComboBox("EncodingForC", &m_options->m_pEncodingC, page); addOptionItem(m_pEncodingCComboBox); gbox->addWidget(m_pEncodingCComboBox, line, 1); m_pAutoDetectUnicodeC = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeC", &m_options->m_bAutoDetectUnicodeC, page); addOptionItem(m_pAutoDetectUnicodeC); gbox->addWidget(m_pAutoDetectUnicodeC, line, 2); m_pAutoDetectUnicodeC->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for Merge Output and Saving:"), page); gbox->addWidget(label, line, 0); m_pEncodingOutComboBox = new OptionEncodingComboBox("EncodingForOutput", &m_options->m_pEncodingOut, page); addOptionItem(m_pEncodingOutComboBox); gbox->addWidget(m_pEncodingOutComboBox, line, 1); m_pAutoSelectOutEncoding = new OptionCheckBox(i18n("Auto Select"), true, "AutoSelectOutEncoding", &m_options->m_bAutoSelectOutEncoding, page); addOptionItem(m_pAutoSelectOutEncoding); gbox->addWidget(m_pAutoSelectOutEncoding, line, 2); m_pAutoSelectOutEncoding->setToolTip(i18n( "If enabled then the encoding from the input files is used.\n" "In ambiguous cases a dialog will ask the user to choose the encoding for saving.")); ++line; label = new QLabel(i18n("File Encoding for Preprocessor Files:"), page); gbox->addWidget(label, line, 0); m_pEncodingPPComboBox = new OptionEncodingComboBox("EncodingForPP", &m_options->m_pEncodingPP, page); addOptionItem(m_pEncodingPPComboBox); gbox->addWidget(m_pEncodingPPComboBox, line, 1); ++line; connect(m_pSameEncoding, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); connect(m_pEncodingAComboBox, static_cast(&OptionEncodingComboBox::activated), this, &OptionDialog::slotEncodingChanged); connect(m_pAutoDetectUnicodeA, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); connect(m_pAutoSelectOutEncoding, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); OptionCheckBox* pRightToLeftLanguage = new OptionCheckBox(i18n("Right To Left Language"), false, "RightToLeftLanguage", &m_options->m_bRightToLeftLanguage, page); addOptionItem(pRightToLeftLanguage); gbox->addWidget(pRightToLeftLanguage, line, 0, 1, 2); pRightToLeftLanguage->setToolTip(i18n( "Some languages are read from right to left.\n" "This setting will change the viewer and editor accordingly.")); ++line; topLayout->addStretch(10); } void OptionDialog::setupIntegrationPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Integration")); pageItem->setHeader(i18n("Integration Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(2, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label; label = new QLabel(i18n("Command line options to ignore:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pIgnorableCmdLineOptions = new OptionLineEdit("-u;-query;-html;-abort", "IgnorableCmdLineOptions", &m_options->m_ignorableCmdLineOptions, page); gbox->addWidget(pIgnorableCmdLineOptions, line, 1, 1, 2); addOptionItem(pIgnorableCmdLineOptions); label->setToolTip(i18n( "List of command line options that should be ignored when KDiff3 is used by other tools.\n" "Several values can be specified if separated via ';'\n" "This will suppress the \"Unknown option\" error.")); ++line; OptionCheckBox* pEscapeKeyQuits = new OptionCheckBox(i18n("Quit also via Escape key"), false, "EscapeKeyQuits", &m_options->m_bEscapeKeyQuits, page); gbox->addWidget(pEscapeKeyQuits, line, 0, 1, 2); addOptionItem(pEscapeKeyQuits); pEscapeKeyQuits->setToolTip(i18n( "Fast method to exit.\n" "For those who are used to using the Escape key.")); ++line; topLayout->addStretch(10); } void OptionDialog::slotEncodingChanged() { if(m_pSameEncoding->isChecked()) { m_pEncodingBComboBox->setEnabled(false); m_pEncodingBComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingCComboBox->setEnabled(false); m_pEncodingCComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingOutComboBox->setEnabled(false); m_pEncodingOutComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingPPComboBox->setEnabled(false); m_pEncodingPPComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pAutoDetectUnicodeB->setEnabled(false); m_pAutoDetectUnicodeB->setCheckState(m_pAutoDetectUnicodeA->checkState()); m_pAutoDetectUnicodeC->setEnabled(false); m_pAutoDetectUnicodeC->setCheckState(m_pAutoDetectUnicodeA->checkState()); m_pAutoSelectOutEncoding->setEnabled(false); m_pAutoSelectOutEncoding->setCheckState(m_pAutoDetectUnicodeA->checkState()); } else { m_pEncodingBComboBox->setEnabled(true); m_pEncodingCComboBox->setEnabled(true); m_pEncodingOutComboBox->setEnabled(true); m_pEncodingPPComboBox->setEnabled(true); m_pAutoDetectUnicodeB->setEnabled(true); m_pAutoDetectUnicodeC->setEnabled(true); m_pAutoSelectOutEncoding->setEnabled(true); m_pEncodingOutComboBox->setEnabled(m_pAutoSelectOutEncoding->checkState() == Qt::Unchecked); } } void OptionDialog::slotOk() { slotApply(); accept(); } /** Copy the values from the widgets to the public variables.*/ void OptionDialog::slotApply() { m_options->apply(); Q_EMIT applyDone(); } /** Set the default values in the widgets only, while the public variables remain unchanged. */ void OptionDialog::slotDefault() { int result = KMessageBox::warningContinueCancel(this, i18n("This resets all options. Not only those of the current topic.")); if(result == KMessageBox::Cancel) return; else resetToDefaults(); } void OptionDialog::resetToDefaults() { m_options->resetToDefaults(); slotEncodingChanged(); } /** Initialise the widgets using the values in the public varibles. */ void OptionDialog::setState() { m_options->setToCurrent(); slotEncodingChanged(); } void OptionDialog::saveOptions(KSharedConfigPtr config) { // No i18n()-Translations here! m_options->saveOptions(config); } void OptionDialog::readOptions(KSharedConfigPtr config) { // No i18n()-Translations here! m_options->readOptions(config); setState(); } const QString OptionDialog::parseOptions(const QStringList& optionList) { return m_options->parseOptions(optionList); } QString OptionDialog::calcOptionHelp() { return m_options->calcOptionHelp(); } void OptionDialog::slotHistoryMergeRegExpTester() { QPointer dlg=QPointer(new RegExpTester(this, s_autoMergeRegExpToolTip, s_historyStartRegExpToolTip, s_historyEntryStartRegExpToolTip, s_historyEntryStartSortKeyOrderToolTip)); dlg->init(m_pAutoMergeRegExpLineEdit->currentText(), m_pHistoryStartRegExpLineEdit->currentText(), m_pHistoryEntryStartRegExpLineEdit->currentText(), m_pHistorySortKeyOrderLineEdit->currentText()); if(dlg->exec()) { m_pAutoMergeRegExpLineEdit->setEditText(dlg->autoMergeRegExp()); m_pHistoryStartRegExpLineEdit->setEditText(dlg->historyStartRegExp()); m_pHistoryEntryStartRegExpLineEdit->setEditText(dlg->historyEntryStartRegExp()); m_pHistorySortKeyOrderLineEdit->setEditText(dlg->historySortKeyOrder()); } } #include "optiondialog.moc" diff --git a/src/org.kde.kdiff3.appdata.xml b/src/org.kde.kdiff3.appdata.xml index f17489e..d19ab6a 100644 --- a/src/org.kde.kdiff3.appdata.xml +++ b/src/org.kde.kdiff3.appdata.xml @@ -1,303 +1,303 @@ org.kde.kdiff3.desktop CC0-1.0 GPL-2.0+ KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 KDiff3 Kdiff3 KDiff3 xxKDiff3xx KDiff3 - A File And Directory Comparison And Merge Tool + A File And Folder Comparison And Merge Tool Una eina per a comparar i fusionar directoris i fitxers Una eina per a comparar i fusionar directoris i fitxers Nástroj pro porovnávání a slučování souborů a adresářů Programm zum Vergleichen und Zusammenführen von Dateien und Ordnern Ένα εργαλείο σύγκρισης και συγχώνευσης αρχείων και καταλόγων A File And Directory Comparison And Merge Tool Una herramienta para comparar y fusionar archivos y directorios Failide ja kataloogide võrdlemise ja ühendamise tööriist Tiedostojen ja kansioiden vertailu- ja yhdistämistyökalu Un outil de comparaison et de fusion de fichiers et de dossiers Unha ferramenta de comparación e fusión de ficheiros e directorios Sebuah Alat Penggabung dan Pembanding Direktori Dan File Uno strumento di confronto e di fusione di file e cartelle 파일 및 디렉터리 비교 및 병합 도구 Een hulpmiddel voor het vergelijken en samenvoegen van bestanden en mappen Narzędzie do porównywania i scalania plików i katalogów Uma Ferramenta de Comparação e Junção de Pastas e Ficheiros Uma ferramenta de comparação e junção de arquivos e pastas Nástroj na porovnanie a zlúčenie súborov a adresárov Ett verktyg för jämförelse och sammanfogning av filer och kataloger Програма для порівняння і об'єднання файлів та каталогів - xxA File And Directory Comparison And Merge Toolxx + xxA File And Folder Comparison And Merge Toolxx 文件和目录比较合并工具 KDE -

KDiff3 is a file and directory diff and merge tool which

+

KDiff3 is a file and folder diff and merge tool which

El KDiff3 és una eina per a la comparació i fusionat de fitxers i directoris

El KDiff3 és una eina per a la comparació i fusionat de fitxers i directoris

KDiff3 ist ein Programm zum Anzeigen von Unterschieden und zum Zusammenführen von Dateien und Ordnern.

Το KDiff3 είναι ένα εργαλείο σύγκρισης και συγχώνευσης αρχείων και καταλόγων, το οποίο

KDiff3 is a file and directory diff and merge tool which

KDiff es una herramienta de visualización de diferencias y de fusión de archivos y directorios que

KDiff3 on failide ja kataloogide võrdlemise ja ühendamise tööriist, mis

Kdiff3 on tiedostojen ja kansioiden vertailu- ja yhdistämistyökalu, joka:

KDiff3 est un outil de comparaison et de fusion de fichiers et de dossiers qui

KDiff3 é unha ferramenta de diferenzas e fusión de ficheiros e directorios que

KDiff3 adalah sebuah alat merge dan diff direktori dan file yang mana

KDiff3 è uno strumento per fondere file e cartelle che

KDiff3는 파일 및 디렉터리 diff 및 병합 도구입니다

KDdiff3 is een hulpmiddel om bestanden en mappen te vergelijken en samen te voegen dat

KDiff3 jest narzędziem do pokazywania różnicy i scalania plików i katalogów, które

O KDiff3 é uma ferramenta de detecção e junção das diferenças entre ficheiros e pastas que

O KDiff3 é uma ferramenta de diferenças e mesclagem de arquivos e pastas que

KDiff3 je nástroj na porovnávanie a zlučovanie súborov a adresárov

Kdiff3 är ett verktyg för jämförelse och sammanfogning av filer och kataloger, som

KDiff3 — програма для визначення відмінностей у файлах і каталогах та об'єднання цих відмінностей в один варіант, яка

-

xxKDiff3 is a file and directory diff and merge tool whichxx

+

xxKDiff3 is a file and folder diff and merge tool whichxx

KDiff3 是一个文件和目录比较合并工具,能够

    -
  • compares and merges two or three text input files or directories,
  • +
  • compares and merges two or three text input files or folders,
  • compara i fusiona dos o tres fitxers o directoris des de l'entrada,
  • compara i fusiona dos o tres fitxers o directoris des de l'entrada,
  • vergleicht zwei oder drei Textdateien bzw. Ordner und führt sie zusammen,
  • συγκρίνει και συγχωνεύει δύο ή τρία αρχεία κειμένου ή καταλόγους,
  • compares and merges two or three text input files or directories,
  • compara y fusiona dos o tres archivos de texto de entrada o directorios,
  • võrdleb ja ühendab kaht või kolme sisendfaili või -kataloogi,
  • vertaa kahta tai kolmea tekstitiedostoa tai kansiota ja osaa yhdistää ne
  • compare et fusionne deux ou trois fichiers ou dossiers en entrée,
  • compara e fusiona dous ou tres ficheiros ou directorios de entrada de texto,
  • membandingkan dan menggabungkan dua atau tiga input teks file atau direktori
  • confronta e fonde due o tre file di testo o cartelle in ingresso,
  • 두 개 또는 세 개의 텍스트 입력 파일 또는 디렉터리를 비교하고 병합하며,
  • twee of drie tekstbestanden of mappen vergelijkt en samenvoegt,
  • porównuje i scala dwa, trzy lub więcej plików wejściowych lub katalogów,
  • compara e junta dois ou três ficheiros de texto ou pastas de entrada,
  • compara e mescla dois ou três arquivos de texto ou pastas,
  • porovnáva a zlučuje dva alebo viac textových súborov alebo adresárov,
  • jämför och sammanfogar två eller tre textfiler eller kataloger,
  • порівнює і об’єднує два або три вхідних текстових файлів або каталогів.
  • -
  • xxcompares and merges two or three text input files or directories,xx
  • +
  • xxcompares and merges two or three text input files or folders,xx
  • 比较和合并2路、3路输入的文本文件或目录,
  • shows the differences line by line and character by character(!),
  • mostra les diferències línia per línia i caràcter per caràcter!,
  • mostra les diferències línia per línia i caràcter per caràcter!,
  • kann Unterschiede zeilenweise und sogar Zeichen für Zeichen anzeigen,
  • εμφανίζει τις διαφορές ανά γραμμή και ανά χαρακτήρα(!),
  • shows the differences line by line and character by character(!),
  • muestra las diferencias línea a línea y carácter a carácter (!),
  • näitab erinevusi ridade ja märkide (!) kaupa,
  • näyttää erot riveittäin ja merkeittäin (!)
  • affiche les différences ligne par ligne et caractère par caractère (!),
  • mostra as diferenzas liña por liña e carácter por carácter(!),
  • yang menampilkan perbedaan baris demi baris dan karakter demi karakter(!),
  • mostra le differenze riga per riga e carattere per carattere(!),
  • 줄과 문자 단위로(!)로 다른 줄을 표시하며,
  • toont de verschillen regel voor regel en teken voor teken(!),
  • pokazuje różnice wiersz po wierszu i znak po znaku(!),
  • mostra as diferenças linha-a-linha e carácter-a-carácter(!),
  • mostra as diferenças linha por linha e caractere por caractere(!),
  • zobrazí rozdiely riadok po riadku a znak po znaku,
  • visar skillnaderna rad för rad och tecken för tecken (!),
  • показує відмінності за рядками і за символами(!),
  • xxshows the differences line by line and character by character(!),xx
  • 按单行或单字符显示差异(!),
  • provides an automatic merge-facility,
  • proporciona la facilitat del fusionat automàtic,
  • proporciona la facilitat del fusionat automàtic,
  • hat eine Funktion zum automatischen Zusammenführen,
  • παρέχει ένα αυτόματο εεγαλείο συγχώνευσης,
  • provides an automatic merge-facility,
  • proporciona una utilidad de fusión automática,
  • pakub automaatse ühendamise võimalust,
  • tarjoaa automaattisen yhdistämistoiminnon
  • offre des fonctionnalités de fusion automatique,
  • fornece unha ferramenta de fusión automática,
  • menyediakan sebuah fasilitas penggabungan otomatis,
  • fornisce un servizio automatico per la fusione,
  • 자동 병합 기능을 제공하며,
  • een automatische samenvoegfunctie biedt,
  • zapewnia narzędzie do samoczynnego scalania,
  • oferece uma funcionalidade de junção automática,
  • fornecendo a facilidade da mesclagem automática,
  • poskytuje automatickú možnosť zlúčenia,
  • tillhandahåller en automatisk sammanfogingsfunktion,
  • надає можливість автоматичного об’єднання,
  • xxprovides an automatic merge-facility,xx
  • 提供自动合并功能,
  • has an editor for comfortable solving of merge-conflicts,
  • té un editor per a la solució còmoda dels conflictes de fusionat,
  • té un editor per a la solució còmoda dels conflictes de fusionat,
  • enthält einen speziellen komfortablen Editor zum Auflösen von Konflikten beim Zusammenführen,
  • έχει έναν επεξεργαστή για άνετη επίλυση των συγκρούσεων σε συγχωνεύσεις,
  • has an editor for comfortable solving of merge-conflicts,
  • posee un editor para resolver confortablemente los conflictos de fusión,
  • pakub redaktorit mugavaks ühendamisel tekkinud konfliktide lahendamiseks,
  • sisältää muokkaimen yhdistämisristiriitojen ratkaisemiseksi
  • dispose d'un éditeur pour résoudre confortablement les conflits lors de la fusion,
  • ten un editor para solucionar de maneira cómoda conflitos de fusión,
  • memiliki sebuah editor untuk pemecahan yang baik pada penggabungan yang bentrok
  • dispone di un editor per risolvere comodamente i conflitti di fusione,
  • 병합 충돌을 원만하게 해결할 수 있는 편집기가 있으며,
  • een editor heeft voor het comfortabel oplossen van samenvoegconflicten,
  • ma edytor do wygodnego rozwiązywania sprzeczności przy scalaniu,
  • tem um editor para resolver confortavelmente os conflitos de junção,
  • tem um editor para resolver confortavelmente os conflitos de mesclagem,
  • má editor na pohodlné riešenie konfliktov zlučovania,
  • har en editor för bekväm lösning av sammanfogningskonflikter,
  • містить редактор для полегшення розв’язання конфліктів під час об’єднання,
  • xxhas an editor for comfortable solving of merge-conflicts,xx
  • 有一个舒适的编辑器来解决合并冲突,
  • provides network transparency via KIO,
  • proporciona transparència de xarxa a través del KIO,
  • proporciona transparència de xarxa a través del KIO,
  • ist mit Hilfe von KIO Netzwerktransparent,
  • παρέχει δικτυακή διαφάνεια μέσα από το KIO,
  • provides network transparency via KIO,
  • proporciona transparencia de red mediante el uso de KIO,
  • võimaldab KIO vahendusel võrgu läbipaistvust,
  • tarjoaa läpinäkyvän verkon KIOn kautta
  • fournit une transparence réseau via KIO,
  • fornece transparencia de rede mediante KIO,
  • menyediakan transparan jaringan via KIO
  • fornisce trasparenza di rete tramite KIO,
  • KIO를 통한 네트워크 투명성을 제공하며,
  • netwerktransparantie biedt via KIO,
  • zapewnia przejrzystość sieciową przez KIO,
  • oferece a transparência na rede com o KIO,
  • fornece transparência de rede via KIO,
  • poskytuje sieťovú transparentnosť cez KIO,
  • tillhandahåller nätverkstransparens via KIO,
  • забезпечує мережеву прозорість за допомогою KIO,
  • xxprovides network transparency via KIO,xx
  • 通过 KIO 提供网络文件透明访问,
  • has options to highlight or hide changes in white-space or comments,
  • té opcions per a ressaltar o ocultar els canvis en els espais en blanc o els comentaris,
  • té opcions per a ressaltar o ocultar els canvis en els espais en blanc o els comentaris,
  • kann Unterschiede in Leerraum-Zeichen oder Kommentaren besonders hervorheben oder ausblenden,
  • έχει επιλογές τονισμού ή απόκρυψης αλλαγών σε κενά ή σχόλια,
  • has options to highlight or hide changes in white-space or comments,
  • contiene opciones para resaltar u ocultar cambios en espacios en blanco y en comentarios,
  • võib esile tõsta või peita erinevusi tühimärkides või kommentaarides,
  • osaa korostaa tai piilottaa muutokset tyhjemerkeissä ja kommenteissa
  • dispose d'options permettant de surligner ou de cacher les différences d'espaces ou de commentaires,
  • ten opcións para salientar ou agochar cambios en espazos en branco ou comentarios,
  • memiliki opsi untuk menyorot atau menyembunyikan perubahan dalam spasi putih atau komentar,
  • ha opzioni per evidenziare o per nascondere le modifiche negli spazi o nei commenti,
  • 공백이나 주석의 변경 사항을 강조 표시하거나 숨길 수 있는 옵션이 있고,
  • opties heeft voor het accentueren of verbergen van wijzigingen in witruimte of commentaar,
  • ma opcje do podświetlenia i ukrycia zmian w białych znakach lub komentarzach,
  • tem opções para realçar ou esconder as alterações nos espaços em branco ou comentários,
  • tem opção para realçar ou ocultar alterações nos espaços em branco ou comentários,
  • má voľby na zvýraznenie alebo skrytie zmien v bielych znakoch alebo komentároch,
  • har alternativ för att markera eller dölja ändringar av blanktecken eller kommentarer,
  • може підсвічувати або ховати зміни у пробілах або коментарях,
  • xxhas options to highlight or hide changes in white-space or comments,xx
  • 有选项可以设置高亮或隐藏空白字符和代码注释的更改,
  • supports Unicode, UTF-8 and other file encodings,
  • admet Unicode, UTF-8 i altres codificacions de fitxer,
  • admet Unicode, UTF-8 i altres codificacions de fitxer,
  • podporuje Unicode, UTF-8 a další kódování souborů.
  • unterstützt Unicode, UTF-8 und weitere Dateikodierungen,
  • υποστηρίζει Unicode, UTF-8 και άλλες κωδικοποιήσεις αρχείων,
  • supports Unicode, UTF-8 and other file encodings,
  • permite el uso de Unicode, UTF-8 y de otras codificaciones de archivos,
  • toetab Unicode'i, UTF-8 ja teisi failikodeeringuid,
  • tukee Unicodea, UTF-8:aa ja muita merkistökoodauksia
  • prend en charge Unicode, UTF-8 et d'autres encodages de fichiers,
  • é compatíbel con Unicode, UTF-8 e outras codificacións de ficheiros,s
  • dukungan Unicode, UTF-8 dqn pengenkodean file lain
  • supporta Unicode, UTF-8 e altre codifiche di file,
  • 유니코드, UTF-8 및 기타 파일 인코딩을 지원하며,
  • Unicode, UTF-8 en andere bestandscoderingen ondersteunt,
  • obsługuje Unikod, UTF-8 i inne kodowania plików,
  • suporta o Unicode, o UTF-8 e outras codificações de ficheiros,
  • suporta o Unicode, UTF-8 e outras codificações de arquivos,
  • podporuje Unicode, UTF-8 a iné kódovania súborov,
  • stöder Unicode, UTF-8 och andra filkodningar,
  • підтримує Unicode, UTF-8 та інші кодування текстів файлів,
  • xxsupports Unicode, UTF-8 and other file encodings,xx
  • 支持 Unicode, UTF-8 和其他文件编码,
  • prints differences,
  • imprimeix les diferències,
  • imprimeix les diferències,
  • druckt Abweichungen,
  • εκτυπώνει διαφορές,
  • prints differences,
  • imprime las diferencias,
  • trükib erinevusi,
  • tulostaa eroavuudet
  • affiche les différences,
  • imprime diferenzas,
  • cetakan yang berbeda
  • stampa le differenze,
  • 차이점을 인쇄하고,
  • verschillen in regels afdrukt,
  • drukuje różnice,
  • imprime as diferenças,
  • imprime as diferenças,
  • vytlačí rozdiely,
  • skriver ut skillnader,
  • може надсилати на друк різницю,
  • xxprints differences,xx
  • 打印差异,
  • supports version control keyword and history merging.
  • admet la paraula clau per al control de versions i un historial de les fusions.
  • admet la paraula clau per al control de versions i un historial de les fusions.
  • unterstützt Schlüsselwörter für Versionsverwaltung und Verlauf-Zusammenführung.
  • υποστηρίζει συγχώνευση ιστορικού και λέξεων κλειδιών σε έλεγχο εκδόσεων.
  • supports version control keyword and history merging.
  • permite el uso de palabras clave de control de versiones y de fusión de historial.
  • toetab versioonihalduse võtmesõnade ja ajaloo ühendamist.
  • tukee versionhallinnan avainsanoja ja historian yhdistämistä.
  • prend en charge les mots clés de contrôle de version et la fusion des historiques.
  • e permite fusionar historiais e palabras clave de control de versión.
  • dukungan katakunci kendali versi dan histori penggabungan.
  • supporta le parole chiave per il controllo di versione e la fusione della cronologia.
  • 버전 관리 키워드 및 기록 병합을 지원합니다.
  • versiebeheersleutelwoorden ondersteunt en samenvoegen van geschiedenis.
  • obsługuje scalanie słów kluczowych i historii zarządzania wersjami
  • suporta as palavras-chave de controlo de versões e de junção do histórico.
  • suporta palavras-chave de controle de versão e mesclagem de histórico.
  • podporuje kľúčové slová na správu verzií a zlučovanie histórie.
  • stöder nyckelord för versionskontroll och sammanfogning av historik.
  • підтримує об’єднання за ключовими словами і журналом інструментів керування версіями.
  • xxsupports version control keyword and history merging.xx
  • 支持版本控制关键词和历史合并。
https://kde.org/images/screenshots/kdiff3.png https://kde.org/applications/development/kdiff3 https://bugs.kde.org/enter_bug.cgi?format=guided&product=kdiff3 https://docs.kde.org/?application=kdiff3 https://www.kde.org/community/donations/?app=kdiff3 kdiff3
diff --git a/src/org.kde.kdiff3.desktop b/src/org.kde.kdiff3.desktop index e3cda5a..d644a0d 100755 --- a/src/org.kde.kdiff3.desktop +++ b/src/org.kde.kdiff3.desktop @@ -1,156 +1,156 @@ [Desktop Entry] Name=KDiff3 Name[be]=KDiff3 Name[bg]=KDiff3 Name[bs]=KDiff3 Name[ca]=KDiff3 Name[ca@valencia]=KDiff3 Name[cs]=KDiff3 Name[da]=KDiff3 Name[de]=KDiff3 Name[el]=KDiff3 Name[en_GB]=KDiff3 Name[es]=KDiff3 Name[et]=KDiff3 Name[eu]=KDiff3 Name[fi]=KDiff3 Name[fr]=KDiff3 Name[ga]=KDiff3 Name[gl]=KDiff3 Name[hi]=के-डिफ3 Name[hne]=के-डिफ3 Name[hr]=KDiff3 Name[hu]=KDiff3 Name[it]=KDiff3 Name[ja]=KDiff3 Name[km]=KDiff3 Name[ko]=KDiff3 Name[lt]=KDiff3 Name[ml]=കെഡിഫ്3 Name[mr]=के-डिफ3 Name[nb]=KDiff3 Name[nds]=KDiff3 Name[nl]=KDiff3 Name[nn]=KDiff3 Name[pl]=KDiff3 Name[pt]=KDiff3 Name[pt_BR]=KDiff3 Name[ro]=KDiff3 Name[ru]=KDiff3 Name[sk]=KDiff3 Name[sl]=KDiff3 Name[sr]=К‑диф3 Name[sr@ijekavian]=К‑диф3 Name[sr@ijekavianlatin]=KDiff3 Name[sr@latin]=KDiff3 Name[sv]=Kdiff3 Name[tr]=KDiff3 Name[ug]=KDiff3 Name[uk]=KDiff3 Name[x-test]=xxKDiff3xx Name[zh_CN]=KDiff3 Name[zh_TW]=KDiff3 GenericName=Diff/Patch Frontend GenericName[bg]=Интерфейс на Diff/Patch GenericName[bs]=Prikaz za Diff/Patch GenericName[ca]=Frontal del Diff/Patch GenericName[ca@valencia]=Frontal del Diff/Patch GenericName[cs]=Rozhraní pro Diff/Patch GenericName[da]=Brugerflade til diff/patch GenericName[de]=Grafische Oberfläche zu Diff/Patch GenericName[el]=Σύστημα υποστήριξης χρήστη για τα Diff/Patch GenericName[en_GB]=Diff/Patch Frontend GenericName[eo]=Fasado por la programoj "diff" kaj "patch" GenericName[es]=Interfaz para diff/patch GenericName[et]=Võrdlemise ja liitmise rakendus GenericName[eu]=Diff/Patch aurrealdekoa GenericName[fi]=Diff/Patch-käyttöliittymä GenericName[fr]=Interface graphique pour « Diff » / « Patch » GenericName[ga]=Comhéadan Diff/Patch GenericName[gl]=Interface para Diff e Patch GenericName[hi]=डिफ/पैच फ्रन्टएण्ड GenericName[hne]=डिफ/पैच फ्रन्टएन्ड GenericName[hu]=Diff/Patch Frontend GenericName[it]=Interfaccia per i comandi diff e patch GenericName[ja]=Diff/Patch フロントエンド GenericName[km]=Diff/Patch ខាង​មុខ GenericName[ko]=Diff/Patch 프론트엔드 GenericName[lt]=Diff/Patch naudotojo sąsaja GenericName[ml]=ഡിഫ്/പാച്ച് പുരോഭാഗം GenericName[mr]=डिफ/पेच फ्रंटएन्ड GenericName[nb]=Diff-/Patch-grensesnitt GenericName[nds]=Böversiet för "diff" un "patch" GenericName[nl]=Diff/Patch-hulpprogramma GenericName[nn]=Motor for diff- og patch-filer GenericName[pl]=Interfejs dla Diff/Patch GenericName[pt]=Interface do Diff/Patch GenericName[pt_BR]=Interface do diff/patch GenericName[ro]=Interfață Diferențiere/Peticire GenericName[ru]=Графический интерфейс Diff/Patch GenericName[sk]=Rozhranie Diff/Patch GenericName[sl]=Začelje za diff/patch GenericName[sr]=Прочеље за diff и patch GenericName[sr@ijekavian]=Прочеље за diff и patch GenericName[sr@ijekavianlatin]=Pročelje za diff i patch GenericName[sr@latin]=Pročelje za diff i patch GenericName[sv]=Jämförelse- och programfixgränssnitt GenericName[tr]=Diff/Patch Arayüzü GenericName[ug]=سېلىشتۇرۇش/ياماش(Diff/Patch) نىڭ ئالدى ئۇچى GenericName[uk]=Графічна оболонка Diff/Patch GenericName[x-test]=xxDiff/Patch Frontendxx GenericName[zh_CN]=Diff/Patch 前端 GenericName[zh_TW]=比較/修補程式前端介面 Exec=kdiff3 Icon=kdiff3 Type=Application X-DocPath=kdiff3/index.html -Comment=A File And Directory Comparison And Merge Tool +Comment=A File And Folder Comparison And Merge Tool Comment[bg]=Инструмент за сравняване и сливане на файлове и директории Comment[bs]=Alat za poređenje i spajanje direktorija i datoteka Comment[ca]=Una eina per a la comparació i fusió de fitxers i directoris Comment[ca@valencia]=Una eina per a la comparació i fusió de fitxers i directoris Comment[cs]=Nástroj pro porovnávání a slučování souborů a adresářů Comment[da]=Et værktøj til sammenfletning og sammenligning af filer og mapper Comment[de]=Programm zum Vergleichen und Zusammenführen von Dateien und Ordnern Comment[el]=Ένα εργαλείο σύγκρισης και συγχώνευσης αρχείων και καταλόγων Comment[en_GB]=A File And Directory Comparison And Merge Tool Comment[es]=Una herramienta para comparar y fusionar archivos y directorios Comment[et]=Failide ja kataloogide võrdlemise ja liitmise tööriist Comment[eu]=Fitxategiak eta direktorioak alderatzeko eta bateratzeko tresna Comment[fi]=Tiedostojen ja hakemistojen vertailu- ja yhdistämistyökalu Comment[fr]=Un outil de comparaison et de fusion de fichiers et de dossiers Comment[ga]=Uirlis a chuireann comhaid agus comhadlanna i gcomparáid agus a chumascann iad más gá Comment[gl]=Unha ferramenta de comparación e fusión de ficheiros e directorios Comment[hi]=फ़ाइल तथा डिरेक्ट्री तुलना करने व मिलाने का औजार Comment[hne]=फाइल अउ डिरेक्टरी तुलना करे अउ मिलाय के औजार Comment[hu]=Egy fájl és könyvtár összehasonlítási és egyesítési eszköz Comment[it]=Uno strumento di confronto e fusione di file e cartelle Comment[ja]=ファイルやディレクトリの比較/マージを行うツール Comment[km]=ការ​ប្រៀបធៀប​ថត និង​ឯកសារ និង​ឧបករណ៌​បញ្ចូល​គ្នា Comment[ko]=파일과 디렉터리 비교 및 병합 도구 Comment[lt]=Failų ir katalogų palyginimo ir suliejimo įrankis Comment[ml]=ഫയലും അറകളും താരതമ്യം ചെയ്യാനും ലയിപ്പിക്കാനുമുള്ള ഒരു ആയുധം Comment[nb]=Et verktøy for å sammelnlikne og flette filer og mapper Comment[nds]=En Warktüüch för't Verglieken un Tosamenföhren vun Dateien un Ornern Comment[nl]=Een hulpmiddel voor het vergelijken en samenvoegen van bestanden en mappen Comment[nn]=Eit program for samanlikning og fletting av filer og mapper Comment[pl]=Narzędzie do porównywania i scalania plików i katalogów Comment[pt]=Uma Ferramenta de Comparação e Junção de Ficheiros e Pastas Comment[pt_BR]=Uma ferramenta de comparação e junção de arquivos e pastas Comment[ro]=Un instrument de comparare și îmbinare a fișierelor și directoarelor Comment[ru]=Инструмент для сравнения и объединения файлов и каталогов Comment[sk]=Nástroj na porovnanie a zlúčenie súborov a adresárov Comment[sl]=Orodje za primerjavo in združevanje datotek in map Comment[sr]=Алатка за поређење и стапање фајлова и фасцикли Comment[sr@ijekavian]=Алатка за поређење и стапање фајлова и фасцикли Comment[sr@ijekavianlatin]=Alatka za poređenje i stapanje fajlova i fascikli Comment[sr@latin]=Alatka za poređenje i stapanje fajlova i fascikli Comment[sv]=Ett jämförelseverktyg för fil- och katalogjämförelser Comment[tr]=Bir Dosya Ve Dizin Karşılaştırma Ve Birleştirme Aracı Comment[ug]=ھۆججەت ۋە مۇندەرىجە سېلىشتۇرۇش ۋە بىرىكتۈرۈش قورالى Comment[uk]=Інструмент для порівняння та з’єднання файлів та тек -Comment[x-test]=xxA File And Directory Comparison And Merge Toolxx +Comment[x-test]=xxA File And Folder Comparison And Merge Toolxx Comment[zh_CN]=文件和文件夹比较合并工具 Comment[zh_TW]=一個檔案與目錄比較與合併的工具 Terminal=false Categories=Qt;KDE;Development; diff --git a/src/pdiff.cpp b/src/pdiff.cpp index 5b0ca15..a46f74a 100644 --- a/src/pdiff.cpp +++ b/src/pdiff.cpp @@ -1,2294 +1,2294 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "difftextwindow.h" #include "directorymergewindow.h" #include "fileaccess.h" #include "Logging.h" #include "kdiff3.h" #include "optiondialog.h" #include "progress.h" #include "Utils.h" #include "DirectoryInfo.h" #include "mergeresultwindow.h" #include "smalldialogs.h" #include #include #include #include #include #include #include #include #include // QKeyEvent, QDropEvent, QInputEvent #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool g_bIgnoreWhiteSpace = true; bool g_bIgnoreTrivialMatches = true; // Function uses setMinSize( sizeHint ) before adding the widget. // void addWidget(QBoxLayout* layout, QWidget* widget); template void addWidget(L* layout, W* widget) { QSize s = widget->sizeHint(); widget->setMinimumSize(QSize(std::max(s.width(), 0), std::max(s.height(), 0))); layout->addWidget(widget); } void KDiff3App::mainInit(TotalDiffStatus* pTotalDiffStatus, bool bLoadFiles, bool bUseCurrentEncoding) { ProgressProxy pp; QStringList errors; // When doing a full analysis in the directory-comparison, then the statistics-results // will be stored in the given TotalDiffStatus. Otherwise it will be 0. bool bGUI = pTotalDiffStatus == nullptr; if(pTotalDiffStatus == nullptr) pTotalDiffStatus = &m_totalDiffStatus; //bool bPreserveCarriageReturn = m_pOptions->m_bPreserveCarriageReturn; bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty(); if(bVisibleMergeResultWindow && bGUI) { //bPreserveCarriageReturn = false; QString msg; if(!m_pOptions->m_PreProcessorCmd.isEmpty()) { msg += "- " + i18n("PreprocessorCmd: ") + m_pOptions->m_PreProcessorCmd + '\n'; } if(!msg.isEmpty()) { int result = KMessageBox::warningYesNo(this, i18n("The following option(s) you selected might change data:\n") + msg + i18n("\nMost likely this is not wanted during a merge.\n" "Do you want to disable these settings or continue with these settings active?"), i18n("Option Unsafe for Merging"), KGuiItem(i18n("Use These Options During Merge")), KGuiItem(i18n("Disable Unsafe Options"))); if(result == KMessageBox::No) { m_pOptions->m_PreProcessorCmd = ""; } } } // Because of the progressdialog paintevents can occur, but data is invalid, // so painting must be suppressed if(bGUI) setLockPainting(true); //insure merge result window never has stale iterators. if(m_pMergeResultWindow) m_pMergeResultWindow->clearMergeList(); m_diff3LineList.clear(); m_diff3LineVector.clear(); if(bLoadFiles) { m_manualDiffHelpList.clear(); if(m_sd3->isEmpty()) pp.setMaxNofSteps(4); // Read 2 files, 1 comparison, 1 finediff else pp.setMaxNofSteps(9); // Read 3 files, 3 comparisons, 3 finediffs // First get all input data. pp.setInformation(i18n("Loading A")); qCInfo(kdiffMain) << i18n("Loading A: %1", m_sd1->getFilename()); if(bUseCurrentEncoding) errors = m_sd1->readAndPreprocess(m_sd1->getEncoding(), false); else errors = m_sd1->readAndPreprocess(m_pOptions->m_pEncodingA, m_pOptions->m_bAutoDetectUnicodeA); if(!errors.isEmpty()) { KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file A."), errors); } pp.step(); pp.setInformation(i18n("Loading B")); qCInfo(kdiffMain) << i18n("Loading B: %1", m_sd2->getFilename()); if(bUseCurrentEncoding) errors = m_sd2->readAndPreprocess(m_sd2->getEncoding(), false); else errors = m_sd2->readAndPreprocess(m_pOptions->m_pEncodingB, m_pOptions->m_bAutoDetectUnicodeB); if(!errors.isEmpty()) KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file B."), errors); pp.step(); } else { if(m_sd3->isEmpty()) pp.setMaxNofSteps(2); // 1 comparison, 1 finediff else pp.setMaxNofSteps(6); // 3 comparisons, 3 finediffs } if(pTotalDiffStatus) pTotalDiffStatus->reset(); if(errors.isEmpty()) { // Run the diff. if(m_sd3->isEmpty()) { pTotalDiffStatus->bBinaryAEqB = m_sd1->isBinaryEqualWith(*m_sd2); if(m_sd1->isText() && m_sd2->isText()) { pp.setInformation(i18n("Diff: A <-> B")); qCInfo(kdiffMain) << i18n("Diff: A <-> B") ; m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_diffList12, A, B, m_pOptionDialog->getOptions()); pp.step(); pp.setInformation(i18n("Linediff: A <-> B")); qCInfo(kdiffMain) << i18n("Linediff: A <-> B"); m_diff3LineList.calcDiff3LineListUsingAB(&m_diffList12); pTotalDiffStatus->bTextAEqB = m_diff3LineList.fineDiff(A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay()); if(m_sd1->getSizeBytes() == 0) pTotalDiffStatus->bTextAEqB = false; pp.step(); } else { pp.step(); pp.step(); } } else { if(bLoadFiles) { pp.setInformation(i18n("Loading C")); qCInfo(kdiffMain) << i18n("Loading C: %1", m_sd2->getFilename()); if(bUseCurrentEncoding) errors = m_sd3->readAndPreprocess(m_sd3->getEncoding(), false); else errors = m_sd3->readAndPreprocess(m_pOptions->m_pEncodingC, m_pOptions->m_bAutoDetectUnicodeC); if(!errors.isEmpty()) KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file C."), errors); pp.step(); } pTotalDiffStatus->bBinaryAEqB = m_sd1->isBinaryEqualWith(*m_sd2); pTotalDiffStatus->bBinaryAEqC = m_sd1->isBinaryEqualWith(*m_sd3); pTotalDiffStatus->bBinaryBEqC = m_sd3->isBinaryEqualWith(*m_sd2); pp.setInformation(i18n("Diff: A <-> B")); qCInfo(kdiffMain) << i18n("Diff: A <-> B"); if(m_sd1->isText() && m_sd2->isText()) { m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_diffList12, A, B, m_pOptionDialog->getOptions()); m_diff3LineList.calcDiff3LineListUsingAB(&m_diffList12); } pp.step(); pp.setInformation(i18n("Diff: A <-> C")); qCInfo(kdiffMain) << i18n("Diff: A <-> C"); if(m_sd1->isText() && m_sd3->isText()) { m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines(), m_diffList13, A, C, m_pOptionDialog->getOptions()); m_diff3LineList.calcDiff3LineListUsingAC(&m_diffList13); m_diff3LineList.correctManualDiffAlignment(&m_manualDiffHelpList); m_diff3LineList.calcDiff3LineListTrim(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff(), &m_manualDiffHelpList); } pp.step(); pp.setInformation(i18n("Diff: B <-> C")); qCInfo(kdiffMain) << i18n("Diff: B <-> C"); if(m_sd2->isText() && m_sd3->isText()) { m_manualDiffHelpList.runDiff(m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines(), m_diffList23, B, C, m_pOptionDialog->getOptions()); if(m_pOptions->m_bDiff3AlignBC) { m_diff3LineList.calcDiff3LineListUsingBC(&m_diffList23); m_diff3LineList.correctManualDiffAlignment(&m_manualDiffHelpList); m_diff3LineList.calcDiff3LineListTrim(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff(), &m_manualDiffHelpList); } } pp.step(); m_diff3LineList.debugLineCheck(m_sd1->getSizeLines(), A); m_diff3LineList.debugLineCheck(m_sd2->getSizeLines(), B); m_diff3LineList.debugLineCheck(m_sd3->getSizeLines(), C); pp.setInformation(i18n("Linediff: A <-> B")); qCInfo(kdiffMain) << i18n("Linediff: A <-> B"); if(m_sd1->hasData() && m_sd2->hasData() && m_sd1->isText() && m_sd2->isText()) pTotalDiffStatus->bTextAEqB = m_diff3LineList.fineDiff(A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay()); pp.step(); pp.setInformation(i18n("Linediff: B <-> C")); qCInfo(kdiffMain) << i18n("Linediff: B <-> C"); if(m_sd2->hasData() && m_sd3->hasData() && m_sd2->isText() && m_sd3->isText()) pTotalDiffStatus->bTextBEqC = m_diff3LineList.fineDiff(B, m_sd2->getLineDataForDisplay(), m_sd3->getLineDataForDisplay()); pp.step(); pp.setInformation(i18n("Linediff: A <-> C")); qCInfo(kdiffMain) << i18n("Linediff: A <-> C"); if(m_sd1->hasData() && m_sd3->hasData() && m_sd1->isText() && m_sd3->isText()) pTotalDiffStatus->bTextAEqC = m_diff3LineList.fineDiff(C, m_sd3->getLineDataForDisplay(), m_sd1->getLineDataForDisplay()); m_diff3LineList.debugLineCheck(m_sd2->getSizeLines(), B); m_diff3LineList.debugLineCheck(m_sd3->getSizeLines(), C); pp.setInformation(i18n("Linediff: A <-> B")); if(m_sd1->hasData() && m_sd2->hasData() && m_sd1->isText() && m_sd2->isText()) pTotalDiffStatus->bTextAEqB = m_diff3LineList.fineDiff(A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay()); pp.step(); pp.setInformation(i18n("Linediff: B <-> C")); if(m_sd3->hasData() && m_sd2->hasData() && m_sd3->isText() && m_sd2->isText()) pTotalDiffStatus->bTextBEqC = m_diff3LineList.fineDiff(B, m_sd2->getLineDataForDisplay(), m_sd3->getLineDataForDisplay()); pp.step(); pp.setInformation(i18n("Linediff: A <-> C")); if(m_sd1->hasData() && m_sd3->hasData() && m_sd1->isText() && m_sd3->isText()) pTotalDiffStatus->bTextAEqC = m_diff3LineList.fineDiff(C, m_sd3->getLineDataForDisplay(), m_sd1->getLineDataForDisplay()); pp.step(); if(m_sd1->getSizeBytes() == 0) { pTotalDiffStatus->bTextAEqB = false; pTotalDiffStatus->bTextAEqC = false; } if(m_sd2->getSizeBytes() == 0) { pTotalDiffStatus->bTextAEqB = false; pTotalDiffStatus->bTextBEqC = false; } } } else { pp.clear(); } if(errors.isEmpty() && m_sd1->isText() && m_sd2->isText()) { m_diffBufferInfo->init(&m_diff3LineList, &m_diff3LineVector, m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines()); Diff3Line::m_pDiffBufferInfo = m_diffBufferInfo; m_diff3LineList.calcWhiteDiff3Lines(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff()); m_diff3LineList.calcDiff3LineVector(m_diff3LineVector); } // Calc needed lines for display m_neededLines = m_diff3LineList.size(); QList oldHeights; if(m_pDirectoryMergeSplitter->isVisible()) oldHeights = m_pMainSplitter->sizes(); initView(); m_pMergeResultWindow->connectActions(); if(m_pDirectoryMergeSplitter->isVisible()) { if(oldHeights.count() < 2) oldHeights.append(0); if(oldHeights[1] == 0) // Distribute the available space evenly between the two widgets. { oldHeights[1] = oldHeights[0] / 2; oldHeights[0] -= oldHeights[1]; } if(oldHeights[0] == 0 && oldHeights[1] == 0) { oldHeights[1] = 100; oldHeights[0] = 100; } m_pMainSplitter->setSizes(oldHeights); } m_pMainWidget->setVisible(bGUI); m_bTripleDiff = !m_sd3->isEmpty(); m_pMergeResultWindowTitle->setEncodings(m_sd1->getEncoding(), m_sd2->getEncoding(), m_sd3->getEncoding()); if(!m_pOptions->m_bAutoSelectOutEncoding) m_pMergeResultWindowTitle->setEncoding(m_pOptions->m_pEncodingOut); m_pMergeResultWindowTitle->setLineEndStyles(m_sd1->getLineEndStyle(), m_sd2->getLineEndStyle(), m_sd3->getLineEndStyle()); if(bGUI) { const ManualDiffHelpList* pMDHL = &m_manualDiffHelpList; m_pDiffTextWindow1->init(m_sd1->getAliasName(), m_sd1->getEncoding(), m_sd1->getLineEndStyle(), m_sd1->getLineDataForDisplay(), m_sd1->getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindowFrame1->init(); m_pDiffTextWindow2->init(m_sd2->getAliasName(), m_sd2->getEncoding(), m_sd2->getLineEndStyle(), m_sd2->getLineDataForDisplay(), m_sd2->getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindowFrame2->init(); m_pDiffTextWindow3->init(m_sd3->getAliasName(), m_sd3->getEncoding(), m_sd3->getLineEndStyle(), m_sd3->getLineDataForDisplay(), m_sd3->getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindowFrame3->init(); m_pDiffTextWindowFrame3->setVisible(m_bTripleDiff); } m_bOutputModified = bVisibleMergeResultWindow; m_pMergeResultWindow->init( m_sd1->getLineDataForDisplay(), m_sd1->getSizeLines(), m_sd2->getLineDataForDisplay(), m_sd2->getSizeLines(), m_bTripleDiff ? m_sd3->getLineDataForDisplay() : nullptr, m_sd3->getSizeLines(), &m_diff3LineList, pTotalDiffStatus); m_pMergeResultWindowTitle->setFileName(m_outputFilename.isEmpty() ? QString("unnamed.txt") : m_outputFilename); if(!bGUI) { // We now have all needed information. The rest below is only for GUI-activation. m_sd1->reset(); m_sd2->reset(); m_sd3->reset(); } else { m_pOverview->init(&m_diff3LineList, m_bTripleDiff); m_pDiffVScrollBar->setValue(0); m_pHScrollBar->setValue(0); m_pMergeVScrollBar->setValue(0); setLockPainting(false); if(!bVisibleMergeResultWindow) m_pMergeWindowFrame->hide(); else m_pMergeWindowFrame->show(); // Try to create a meaningful but not too long caption if(!isPart() && errors.isEmpty()) { createCaption(); } //initialize wheel tracking to zero m_iCumulativeWheelDelta = 0; m_bFinishMainInit = true; // call slotFinishMainInit after finishing the word wrap m_bLoadFiles = bLoadFiles; postRecalcWordWrap(); } } void KDiff3App::setLockPainting(bool bLock) { if(m_pDiffTextWindow1) m_pDiffTextWindow1->setPaintingAllowed(!bLock); if(m_pDiffTextWindow2) m_pDiffTextWindow2->setPaintingAllowed(!bLock); if(m_pDiffTextWindow3) m_pDiffTextWindow3->setPaintingAllowed(!bLock); if(m_pOverview) m_pOverview->setPaintingAllowed(!bLock); if(m_pMergeResultWindow) m_pMergeResultWindow->setPaintingAllowed(!bLock); } void KDiff3App::createCaption() { // Try to create a meaningful but not too long caption // 1. If the filenames are equal then show only one filename QString caption; QString f1 = m_sd1->getAliasName(); QString f2 = m_sd2->getAliasName(); QString f3 = m_sd3->getAliasName(); int p; if((p = f1.lastIndexOf('/')) >= 0 || (p = f1.lastIndexOf('\\')) >= 0) f1 = f1.mid(p + 1); if((p = f2.lastIndexOf('/')) >= 0 || (p = f2.lastIndexOf('\\')) >= 0) f2 = f2.mid(p + 1); if((p = f3.lastIndexOf('/')) >= 0 || (p = f3.lastIndexOf('\\')) >= 0) f3 = f3.mid(p + 1); if(!f1.isEmpty()) { if((f2.isEmpty() && f3.isEmpty()) || (f2.isEmpty() && f1 == f3) || (f3.isEmpty() && f1 == f2) || (f1 == f2 && f1 == f3)) caption = f1; } else if(!f2.isEmpty()) { if(f3.isEmpty() || f2 == f3) caption = f2; } else if(!f3.isEmpty()) caption = f3; // 2. If the files don't have the same name then show all names if(caption.isEmpty() && (!f1.isEmpty() || !f2.isEmpty() || !f3.isEmpty())) { caption = (f1.isEmpty() ? QString("") : f1); caption += QLatin1String(caption.isEmpty() || f2.isEmpty() ? "" : " <-> ") + (f2.isEmpty() ? QString("") : f2); caption += QLatin1String(caption.isEmpty() || f3.isEmpty() ? "" : " <-> ") + (f3.isEmpty() ? QString("") : f3); } m_pKDiff3Shell->setWindowTitle(caption.isEmpty() ? QString("KDiff3") : caption + QString(" - KDiff3")); } void KDiff3App::setHScrollBarRange() { int w1 = m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->isVisible() ? m_pDiffTextWindow1->getMaxTextWidth() : 0; int w2 = m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->isVisible() ? m_pDiffTextWindow2->getMaxTextWidth() : 0; int w3 = m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->isVisible() ? m_pDiffTextWindow3->getMaxTextWidth() : 0; int wm = m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() ? m_pMergeResultWindow->getMaxTextWidth() : 0; int v1 = m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->isVisible() ? m_pDiffTextWindow1->getVisibleTextAreaWidth() : 0; int v2 = m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->isVisible() ? m_pDiffTextWindow2->getVisibleTextAreaWidth() : 0; int v3 = m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->isVisible() ? m_pDiffTextWindow3->getVisibleTextAreaWidth() : 0; int vm = m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() ? m_pMergeResultWindow->getVisibleTextAreaWidth() : 0; // Find the minimum, but don't consider 0. int pageStep = 0; if((pageStep == 0 || pageStep > v1) && v1 > 0) pageStep = v1; if((pageStep == 0 || pageStep > v2) && v2 > 0) pageStep = v2; if((pageStep == 0 || pageStep > v3) && v3 > 0) pageStep = v3; if((pageStep == 0 || pageStep > vm) && vm > 0) pageStep = vm; int rangeMax = 0; if(w1 > v1 && w1 - v1 > rangeMax && v1 > 0) rangeMax = w1 - v1; if(w2 > v2 && w2 - v2 > rangeMax && v2 > 0) rangeMax = w2 - v2; if(w3 > v3 && w3 - v3 > rangeMax && v3 > 0) rangeMax = w3 - v3; if(wm > vm && wm - vm > rangeMax && vm > 0) rangeMax = wm - vm; m_pHScrollBar->setRange(0, rangeMax); m_pHScrollBar->setPageStep(pageStep); } void KDiff3App::resizeDiffTextWindowHeight(int newHeight) { m_DTWHeight = newHeight; m_pDiffVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - newHeight)); m_pDiffVScrollBar->setPageStep(newHeight); m_pOverview->setRange(m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep()); setHScrollBarRange(); } void KDiff3App::resizeMergeResultWindow() { MergeResultWindow* p = m_pMergeResultWindow; m_pMergeVScrollBar->setRange(0, std::max(0, p->getNofLines() - p->getNofVisibleLines())); m_pMergeVScrollBar->setPageStep(p->getNofVisibleLines()); setHScrollBarRange(); } void KDiff3App::scrollDiffTextWindow(int deltaX, int deltaY) { if(deltaY != 0 && m_pDiffVScrollBar != nullptr) { m_pDiffVScrollBar->setValue(m_pDiffVScrollBar->value() + deltaY); m_pOverview->setRange(m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep()); } if(deltaX != 0 && m_pHScrollBar != nullptr) m_pHScrollBar->QScrollBar::setValue(m_pHScrollBar->value() + deltaX); } void KDiff3App::scrollMergeResultWindow(int deltaX, int deltaY) { if(deltaY != 0) m_pMergeVScrollBar->setValue(m_pMergeVScrollBar->value() + deltaY); if(deltaX != 0) m_pHScrollBar->setValue(m_pHScrollBar->value() + deltaX); } void KDiff3App::setDiff3Line(int line) { m_pDiffVScrollBar->setValue(line); } void KDiff3App::sourceMask(int srcMask, int enabledMask) { chooseA->blockSignals(true); chooseB->blockSignals(true); chooseC->blockSignals(true); chooseA->setChecked((srcMask & 1) != 0); chooseB->setChecked((srcMask & 2) != 0); chooseC->setChecked((srcMask & 4) != 0); chooseA->blockSignals(false); chooseB->blockSignals(false); chooseC->blockSignals(false); chooseA->setEnabled((enabledMask & 1) != 0); chooseB->setEnabled((enabledMask & 2) != 0); chooseC->setEnabled((enabledMask & 4) != 0); } void KDiff3App::initView() { // set the main widget here if(m_pMainWidget != nullptr) { return; //delete m_pMainWidget; } m_pMainWidget = new QWidget(); // Contains vertical splitter and horiz scrollbar m_pMainSplitter->addWidget(m_pMainWidget); m_pMainWidget->setObjectName("MainWidget"); QVBoxLayout* pVLayout = new QVBoxLayout(m_pMainWidget); pVLayout->setMargin(0); pVLayout->setSpacing(0); QSplitter* pVSplitter = new QSplitter(); pVSplitter->setObjectName("VSplitter"); pVSplitter->setOpaqueResize(false); pVSplitter->setOrientation(Qt::Vertical); pVLayout->addWidget(pVSplitter); QWidget* pDiffWindowFrame = new QWidget(); // Contains diff windows, overview and vert scrollbar pDiffWindowFrame->setObjectName("DiffWindowFrame"); QHBoxLayout* pDiffHLayout = new QHBoxLayout(pDiffWindowFrame); pDiffHLayout->setMargin(0); pDiffHLayout->setSpacing(0); pVSplitter->addWidget(pDiffWindowFrame); m_pDiffWindowSplitter = new QSplitter(); m_pDiffWindowSplitter->setObjectName("DiffWindowSplitter"); m_pDiffWindowSplitter->setOpaqueResize(false); m_pDiffWindowSplitter->setOrientation(m_pOptions->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical); pDiffHLayout->addWidget(m_pDiffWindowSplitter); m_pOverview = new Overview(m_pOptionDialog->getOptions()); m_pOverview->setObjectName("Overview"); pDiffHLayout->addWidget(m_pOverview); connect(m_pOverview, &Overview::setLine, this, &KDiff3App::setDiff3Line); m_pDiffVScrollBar = new QScrollBar(Qt::Vertical, pDiffWindowFrame); pDiffHLayout->addWidget(m_pDiffVScrollBar); m_pDiffTextWindowFrame1 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), m_pOptionDialog->getOptions(), A, m_sd1); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame1); m_pDiffTextWindowFrame2 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), m_pOptionDialog->getOptions(), B, m_sd2); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame2); m_pDiffTextWindowFrame3 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), m_pOptionDialog->getOptions(), C, m_sd3); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame3); m_pDiffTextWindow1 = m_pDiffTextWindowFrame1->getDiffTextWindow(); m_pDiffTextWindow2 = m_pDiffTextWindowFrame2->getDiffTextWindow(); m_pDiffTextWindow3 = m_pDiffTextWindowFrame3->getDiffTextWindow(); m_pDiffTextWindowFrame1->setupConnections(this); m_pDiffTextWindowFrame2->setupConnections(this); m_pDiffTextWindowFrame3->setupConnections(this); // Merge window m_pMergeWindowFrame = new QWidget(pVSplitter); m_pMergeWindowFrame->setObjectName("MergeWindowFrame"); pVSplitter->addWidget(m_pMergeWindowFrame); QHBoxLayout* pMergeHLayout = new QHBoxLayout(m_pMergeWindowFrame); pMergeHLayout->setMargin(0); pMergeHLayout->setSpacing(0); QVBoxLayout* pMergeVLayout = new QVBoxLayout(); pMergeHLayout->addLayout(pMergeVLayout, 1); m_pMergeResultWindowTitle = new WindowTitleWidget(m_pOptionDialog->getOptions()); pMergeVLayout->addWidget(m_pMergeResultWindowTitle); m_pMergeResultWindow = new MergeResultWindow(m_pMergeWindowFrame, m_pOptionDialog->getOptions(), statusBar()); pMergeVLayout->addWidget(m_pMergeResultWindow, 1); m_pMergeVScrollBar = new QScrollBar(Qt::Vertical, m_pMergeWindowFrame); pMergeHLayout->addWidget(m_pMergeVScrollBar); m_pMainSplitter->addWidget(m_pMainWidget); autoAdvance->setEnabled(true); QList sizes = pVSplitter->sizes(); int total = sizes[0] + sizes[1]; if(total < 10) total = 100; sizes[0] = total / 2; sizes[1] = total / 2; pVSplitter->setSizes(sizes); QList hSizes; hSizes << 1 << 1 << 1; m_pDiffWindowSplitter->setSizes(hSizes); m_pMergeResultWindow->installEventFilter(m_pMergeResultWindowTitle); // for focus tracking QHBoxLayout* pHScrollBarLayout = new QHBoxLayout(); pVLayout->addLayout(pHScrollBarLayout); m_pHScrollBar = new ReversibleScrollBar(Qt::Horizontal, &m_pOptions->m_bRightToLeftLanguage); pHScrollBarLayout->addWidget(m_pHScrollBar); m_pCornerWidget = new QWidget(m_pMainWidget); pHScrollBarLayout->addWidget(m_pCornerWidget); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pOverview, &Overview::setFirstLine); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow1, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow1, &DiffTextWindow::setHorizScrollOffset); m_pDiffTextWindow1->setupConnections(this); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow2, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow2, &DiffTextWindow::setHorizScrollOffset); m_pDiffTextWindow2->setupConnections(this); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow3, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow3, &DiffTextWindow::setHorizScrollOffset); m_pDiffTextWindow3->setupConnections(this); MergeResultWindow* p = m_pMergeResultWindow; connect(m_pMergeVScrollBar, &QScrollBar::valueChanged, p, &MergeResultWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, p, &MergeResultWindow::setHorizScrollOffset); connect(p, &MergeResultWindow::modifiedChanged, m_pMergeResultWindowTitle, &WindowTitleWidget::slotSetModified); p->setupConnections(this); sourceMask(0, 0); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow1, &DiffTextWindow::setFastSelectorRange); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow2, &DiffTextWindow::setFastSelectorRange); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow3, &DiffTextWindow::setFastSelectorRange); connect(m_pDiffTextWindow1, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow2, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow3, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow1, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow2, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow3, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDirectoryMergeInfo, &DirectoryMergeInfo::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow1, &DiffTextWindow::resizeHeightChangedSignal, this, &KDiff3App::resizeDiffTextWindowHeight); // The following two connects cause the wordwrap to be recalced thrice, just to make sure. Better than forgetting one. connect(m_pDiffTextWindow1, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); connect(m_pDiffTextWindow2, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); connect(m_pDiffTextWindow3, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); m_pDiffTextWindow1->setFocus(); m_pMainWidget->setMinimumSize(50, 50); m_pCornerWidget->setFixedSize(m_pDiffVScrollBar->width(), m_pHScrollBar->height()); showWindowA->setChecked(true); showWindowB->setChecked(true); showWindowC->setChecked(true); } // called after word wrap is complete void KDiff3App::slotFinishMainInit() { Q_ASSERT(m_pDiffTextWindow1 != nullptr && m_pDiffVScrollBar != nullptr); setHScrollBarRange(); int newHeight = m_pDiffTextWindow1->getNofVisibleLines(); /*int newWidth = m_pDiffTextWindow1->getNofVisibleColumns();*/ m_DTWHeight = newHeight; m_pDiffVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - newHeight)); m_pDiffVScrollBar->setPageStep(newHeight); m_pOverview->setRange(m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep()); int d3l = -1; if(!m_manualDiffHelpList.empty()) d3l = m_manualDiffHelpList.front().calcManualDiffFirstDiff3LineIdx(m_diff3LineVector); if(d3l >= 0 && m_pDiffTextWindow1) { int line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(d3l); m_pDiffVScrollBar->setValue(std::max(0, line - 1)); } else { m_pMergeResultWindow->slotGoTop(); if(!m_outputFilename.isEmpty() && !m_pMergeResultWindow->isUnsolvedConflictAtCurrent()) m_pMergeResultWindow->slotGoNextUnsolvedConflict(); } if(m_pCornerWidget) m_pCornerWidget->setFixedSize(m_pDiffVScrollBar->width(), m_pHScrollBar->height()); slotUpdateAvailabilities(); setUpdatesEnabled(true); bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty(); TotalDiffStatus* pTotalDiffStatus = &m_totalDiffStatus; if(m_bLoadFiles) { if(bVisibleMergeResultWindow) m_pMergeResultWindow->showNrOfConflicts(); else if( // Avoid showing this message during startup without parameters. !(m_sd1->getAliasName().isEmpty() && m_sd2->getAliasName().isEmpty() && m_sd3->getAliasName().isEmpty()) && (m_sd1->isValid() && m_sd2->isValid() && m_sd3->isValid())) { QString totalInfo; if(pTotalDiffStatus->bBinaryAEqB && pTotalDiffStatus->bBinaryAEqC) totalInfo += i18n("All input files are binary equal."); else if(pTotalDiffStatus->bTextAEqB && pTotalDiffStatus->bTextAEqC) totalInfo += i18n("All input files contain the same text, but are not binary equal."); else { if(pTotalDiffStatus->bBinaryAEqB) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B")); else if(pTotalDiffStatus->bTextAEqB) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("A"), i18n("B")); if(pTotalDiffStatus->bBinaryAEqC) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C")); else if(pTotalDiffStatus->bTextAEqC) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("A"), i18n("C")); if(pTotalDiffStatus->bBinaryBEqC) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C")); else if(pTotalDiffStatus->bTextBEqC) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("B"), i18n("C")); } if(!totalInfo.isEmpty()) KMessageBox::information(this, totalInfo); } if(bVisibleMergeResultWindow && (!m_sd1->isText() || !m_sd2->isText() || !m_sd3->isText())) { KMessageBox::information(this, i18n( "Some input files do not seem to be pure text files.\n" "Note that the KDiff3 merge was not meant for binary data.\n" "Continue at your own risk.")); } if(m_sd1->isIncompleteConversion() || m_sd2->isIncompleteConversion() || m_sd3->isIncompleteConversion()) { QString files; if(m_sd1->isIncompleteConversion()) files += i18n("A"); if(m_sd2->isIncompleteConversion()) files += files.isEmpty() ? i18n("B") : i18n(", B"); if(m_sd3->isIncompleteConversion()) files += files.isEmpty() ? i18n("C") : i18n(", C"); KMessageBox::information(this, i18n("Some input characters could not be converted to valid unicode.\n" "You might be using the wrong codec. (e.g. UTF-8 for non UTF-8 files).\n" "Do not save the result if unsure. Continue at your own risk.\n" "Affected input files are in %1.", files)); } } if(bVisibleMergeResultWindow && m_pMergeResultWindow) { m_pMergeResultWindow->setFocus(); } else if(m_pDiffTextWindow1) { m_pDiffTextWindow1->setFocus(); } } void KDiff3App::resizeEvent(QResizeEvent* e) { QSplitter::resizeEvent(e); if(m_pCornerWidget) m_pCornerWidget->setFixedSize(m_pDiffVScrollBar->width(), m_pHScrollBar->height()); } void KDiff3App::wheelEvent(QWheelEvent* pWheelEvent) { pWheelEvent->accept(); int deltaX = 0; int d = pWheelEvent->delta(); //As per QT documentation, some mice/OS combos send delta values //less than 120 units(15 degrees) d = d + m_iCumulativeWheelDelta; if(d > -120 && d < 120) { //not enough for a full step in either direction, add it up //to use on a successive call m_iCumulativeWheelDelta = d; } else { //reset cumulative tracking of the wheel since we have enough //for a 15 degree movement m_iCumulativeWheelDelta = 0; } int deltaY = -d / 120 * QApplication::wheelScrollLines(); scrollDiffTextWindow(deltaX, deltaY); } void KDiff3App::keyPressEvent(QKeyEvent* keyEvent) { if(keyEvent->key() == Qt::Key_Escape && m_pKDiff3Shell && m_pOptions->m_bEscapeKeyQuits) { m_pKDiff3Shell->close(); return; } //FIXME: Move use QAction int deltaX = 0; int deltaY = 0; int pageSize = m_DTWHeight; bool bCtrl = (keyEvent->QInputEvent::modifiers() & Qt::ControlModifier) != 0; switch(keyEvent->key()) { case Qt::Key_Down: if(!bCtrl) ++deltaY; break; case Qt::Key_Up: if(!bCtrl) --deltaY; break; case Qt::Key_PageDown: if(!bCtrl) deltaY += pageSize; break; case Qt::Key_PageUp: if(!bCtrl) deltaY -= pageSize; break; case Qt::Key_Left: if(!bCtrl) --deltaX; break; case Qt::Key_Right: if(!bCtrl) ++deltaX; break; case Qt::Key_Home: if(bCtrl) { if(m_pDiffVScrollBar != nullptr) m_pDiffVScrollBar->setValue(0); } else { if(m_pHScrollBar != nullptr) m_pHScrollBar->setValue(0); } break; case Qt::Key_End: if(bCtrl) { if(m_pDiffVScrollBar != nullptr) m_pDiffVScrollBar->setValue(m_pDiffVScrollBar->maximum()); } else { if(m_pHScrollBar != nullptr) m_pHScrollBar->setValue(m_pHScrollBar->maximum()); } break; default: break; } scrollDiffTextWindow(deltaX, deltaY); } void KDiff3App::slotFinishDrop() { raise(); mainInit(); } void KDiff3App::slotFileOpen() { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; //create dummy DirectoryInfo record for first run so we don't crash. if(m_dirinfo == nullptr) m_dirinfo = QSharedPointer::create(); if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, - i18n("You are currently doing a directory merge. Are you sure, you want to abort?"), + i18n("You are currently doing a folder merge. Are you sure, you want to abort?"), i18n("Warning"), KGuiItem(i18n("Abort")), KGuiItem(i18n("Continue Merging"))); if(result != KMessageBox::Yes) return; } slotStatusMsg(i18n("Opening files...")); for(;;) { QPointer d = QPointer(new OpenDialog(this, QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirA().prettyAbsPath() : m_sd1->isFromBuffer() ? QString("") : m_sd1->getAliasName()), QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirB().prettyAbsPath() : m_sd2->isFromBuffer() ? QString("") : m_sd2->getAliasName()), QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirC().prettyAbsPath() : m_sd3->isFromBuffer() ? QString("") : m_sd3->getAliasName()), m_bDirCompare ? !m_dirinfo->destDir().prettyAbsPath().isEmpty() : !m_outputFilename.isEmpty(), QDir::toNativeSeparators(m_bDefaultFilename ? QString("") : m_outputFilename), m_pOptionDialog->getOptions())); int status = d->exec(); if(status == QDialog::Accepted) { m_sd1->setFilename(d->m_pLineA->currentText()); m_sd2->setFilename(d->m_pLineB->currentText()); m_sd3->setFilename(d->m_pLineC->currentText()); if(d->m_pMerge->isChecked()) { if(d->m_pLineOut->currentText().isEmpty()) { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } else { m_outputFilename = d->m_pLineOut->currentText(); m_bDefaultFilename = false; } } else m_outputFilename = ""; m_bDirCompare = FileAccess(m_sd1->getFilename()).isDir(); bool bSuccess = improveFilenames(false); if(!bSuccess) continue; if(m_bDirCompare) { m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) { m_pMainWidget->hide(); } break; } else { m_pDirectoryMergeSplitter->hide(); mainInit(); if((!m_sd1->isEmpty() && !m_sd1->hasData()) || (!m_sd2->isEmpty() && !m_sd2->hasData()) || (!m_sd3->isEmpty() && !m_sd3->hasData())) { QString text(i18n("Opening of these files failed:")); text += "\n\n"; if(!m_sd1->isEmpty() && !m_sd1->hasData()) text += " - " + m_sd1->getAliasName() + '\n'; if(!m_sd2->isEmpty() && !m_sd2->hasData()) text += " - " + m_sd2->getAliasName() + '\n'; if(!m_sd3->isEmpty() && !m_sd3->hasData()) text += " - " + m_sd3->getAliasName() + '\n'; KMessageBox::sorry(this, text, i18n("File open error")); continue; } } } break; } slotUpdateAvailabilities(); slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFileOpen2(const QString& fn1, const QString& fn2, const QString& fn3, const QString& ofn, const QString& an1, const QString& an2, const QString& an3, TotalDiffStatus* pTotalDiffStatus) { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; if(fn1.isEmpty() && fn2.isEmpty() && fn3.isEmpty() && ofn.isEmpty() && m_pMainWidget != nullptr) { m_pMainWidget->hide(); return; } slotStatusMsg(i18n("Opening files...")); m_sd1->setFilename(fn1); m_sd2->setFilename(fn2); m_sd3->setFilename(fn3); m_sd1->setAliasName(an1); m_sd2->setAliasName(an2); m_sd3->setAliasName(an3); if(!ofn.isEmpty()) { m_outputFilename = ofn; m_bDefaultFilename = false; } else { m_outputFilename = ""; m_bDefaultFilename = true; } improveFilenames(true); // Create new window for KDiff3 for directory comparison. if(!FileAccess(m_sd1->getFilename()).isDir()) { mainInit(pTotalDiffStatus); if(pTotalDiffStatus != nullptr) return; if(!((!m_sd1->isEmpty() && !m_sd1->hasData()) || (!m_sd2->isEmpty() && !m_sd2->hasData()) || (!m_sd3->isEmpty() && !m_sd3->hasData()))) { if(m_pDirectoryMergeWindow != nullptr && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFileNameChanged(const QString& fileName, e_SrcSelector winIdx) { QString fn1 = m_sd1->getFilename(); QString an1 = m_sd1->getAliasName(); QString fn2 = m_sd2->getFilename(); QString an2 = m_sd2->getAliasName(); QString fn3 = m_sd3->getFilename(); QString an3 = m_sd3->getAliasName(); if(winIdx == A) { fn1 = fileName; an1 = ""; } if(winIdx == B) { fn2 = fileName; an2 = ""; } if(winIdx == C) { fn3 = fileName; an3 = ""; } slotFileOpen2(fn1, fn2, fn3, m_outputFilename, an1, an2, an3, nullptr); } void KDiff3App::slotEditCut() { slotStatusMsg(i18n("Cutting selection...")); QString s; if(m_pMergeResultWindow != nullptr) { s = m_pMergeResultWindow->getSelection(); m_pMergeResultWindow->deleteSelection(); m_pMergeResultWindow->update(); } if(!s.isEmpty()) { QApplication::clipboard()->setText(s, QClipboard::Clipboard); } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditCopy() { slotStatusMsg(i18n("Copying selection to clipboard...")); QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty()) { QApplication::clipboard()->setText(s, QClipboard::Clipboard); } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditPaste() { slotStatusMsg(i18n("Inserting clipboard contents...")); if(m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible()) { m_pMergeResultWindow->pasteClipboard(false); } else { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(bShouldConintue) { QString error; bool do_init = false; if(m_pDiffTextWindow1->hasFocus()) { error = m_sd1->setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } else if(m_pDiffTextWindow2->hasFocus()) { error = m_sd2->setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } else if(m_pDiffTextWindow3->hasFocus()) { error = m_sd3->setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } if(!error.isEmpty()) { KMessageBox::error(m_pOptionDialog, error); } if(do_init) { mainInit(); } } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditSelectAll() { LineRef l = 0; int p = 0; // needed as dummy return values if(m_pMergeResultWindow && m_pMergeResultWindow->hasFocus()) { m_pMergeResultWindow->setSelection(0, 0, m_pMergeResultWindow->getNofLines(), 0); } else if(m_pDiffTextWindow1 && m_pDiffTextWindow1->hasFocus()) { m_pDiffTextWindow1->setSelection(0, 0, m_pDiffTextWindow1->getNofLines(), 0, l, p); } else if(m_pDiffTextWindow2 && m_pDiffTextWindow2->hasFocus()) { m_pDiffTextWindow2->setSelection(0, 0, m_pDiffTextWindow2->getNofLines(), 0, l, p); } else if(m_pDiffTextWindow3 && m_pDiffTextWindow3->hasFocus()) { m_pDiffTextWindow3->setSelection(0, 0, m_pDiffTextWindow3->getNofLines(), 0, l, p); } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotGoCurrent() { Q_EMIT goCurrent(); } void KDiff3App::slotGoTop() { Q_EMIT goTop(); } void KDiff3App::slotGoBottom() { Q_EMIT goBottom(); } void KDiff3App::slotGoPrevUnsolvedConflict() { Q_EMIT goPrevUnsolvedConflict(); } void KDiff3App::slotGoNextUnsolvedConflict() { m_bTimerBlock = false; Q_EMIT goNextUnsolvedConflict(); } void KDiff3App::slotGoPrevConflict() { Q_EMIT goPrevConflict(); } void KDiff3App::slotGoNextConflict() { m_bTimerBlock = false; Q_EMIT goNextConflict(); } void KDiff3App::slotGoPrevDelta() { Q_EMIT goPrevDelta(); } void KDiff3App::slotGoNextDelta() { Q_EMIT goNextDelta(); } void KDiff3App::choose(e_SrcSelector choice) { if(!m_bTimerBlock) { if(m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->hasFocus()) { if(choice == A) m_pDirectoryMergeWindow->slotCurrentChooseA(); if(choice == B) m_pDirectoryMergeWindow->slotCurrentChooseB(); if(choice == C) m_pDirectoryMergeWindow->slotCurrentChooseC(); chooseA->setChecked(false); chooseB->setChecked(false); chooseC->setChecked(false); } else if(m_pMergeResultWindow) { m_pMergeResultWindow->choose(choice); if(autoAdvance->isChecked()) { m_bTimerBlock = true; QTimer::singleShot(m_pOptions->m_autoAdvanceDelay, this, &KDiff3App::slotGoNextUnsolvedConflict); } } } } void KDiff3App::slotChooseA() { choose(A); } void KDiff3App::slotChooseB() { choose(B); } void KDiff3App::slotChooseC() { choose(C); } void KDiff3App::slotAutoSolve() { Q_EMIT autoSolve(); slotUpdateAvailabilities(); } void KDiff3App::slotUnsolve() { Q_EMIT unsolve(); } void KDiff3App::slotMergeHistory() { Q_EMIT mergeHistory(); } void KDiff3App::slotRegExpAutoMerge() { Q_EMIT regExpAutoMerge(); } void KDiff3App::slotSplitDiff() { LineRef firstLine; LineRef lastLine; DiffTextWindow* pDTW = nullptr; if(m_pDiffTextWindow1) { pDTW = m_pDiffTextWindow1; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow2) { pDTW = m_pDiffTextWindow2; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow3) { pDTW = m_pDiffTextWindow3; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(pDTW && firstLine.isValid() && m_pMergeResultWindow) { pDTW->resetSelection(); m_pMergeResultWindow->slotSplitDiff(firstLine, lastLine); } } void KDiff3App::slotJoinDiffs() { LineRef firstLine; LineRef lastLine; DiffTextWindow* pDTW = nullptr; if(m_pDiffTextWindow1) { pDTW = m_pDiffTextWindow1; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow2) { pDTW = m_pDiffTextWindow2; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow3) { pDTW = m_pDiffTextWindow3; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(pDTW && firstLine.isValid() && m_pMergeResultWindow) { pDTW->resetSelection(); m_pMergeResultWindow->slotJoinDiffs(firstLine, lastLine); } } void KDiff3App::slotConfigure() { m_pOptionDialog->setState(); m_pOptionDialog->setMinimumHeight(m_pOptionDialog->minimumHeight() + 40); m_pOptionDialog->exec(); slotRefresh(); } void KDiff3App::slotConfigureKeys() { KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); } void KDiff3App::slotRefresh() { QApplication::setFont(m_pOptions->m_appFont); Q_EMIT doRefresh(); if(m_pHScrollBar != nullptr) { m_pHScrollBar->setAgain(); } if(m_pDiffWindowSplitter != nullptr) { m_pDiffWindowSplitter->setOrientation(m_pOptions->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical); } if(m_pDirectoryMergeWindow) { m_pDirectoryMergeWindow->updateFileVisibilities(); } } void KDiff3App::slotSelectionStart() { //editCopy->setEnabled( false ); //editCut->setEnabled( false ); const QObject* s = sender(); if(m_pDiffTextWindow1 && s != m_pDiffTextWindow1) m_pDiffTextWindow1->resetSelection(); if(m_pDiffTextWindow2 && s != m_pDiffTextWindow2) m_pDiffTextWindow2->resetSelection(); if(m_pDiffTextWindow3 && s != m_pDiffTextWindow3) m_pDiffTextWindow3->resetSelection(); if(m_pMergeResultWindow && s != m_pMergeResultWindow) m_pMergeResultWindow->resetSelection(); } void KDiff3App::slotSelectionEnd() { //const QObject* s = sender(); //editCopy->setEnabled(true); //editCut->setEnabled( s==m_pMergeResultWindow ); if(m_pOptions->m_bAutoCopySelection) { slotEditCopy(); } else { QClipboard* clipBoard = QApplication::clipboard(); if(clipBoard->supportsSelection()) { QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty()) { clipBoard->setText(s, QClipboard::Selection); } } } } void KDiff3App::slotClipboardChanged() { const QClipboard* clipboard = QApplication::clipboard(); const QMimeData* mimeData = clipboard->mimeData(); if(mimeData->hasText()) { QString s = clipboard->text(); editPaste->setEnabled(!s.isEmpty()); } else { editPaste->setEnabled(false); } } void KDiff3App::slotOutputModified(bool bModified) { if(bModified && !m_bOutputModified) { m_bOutputModified = true; slotUpdateAvailabilities(); } } void KDiff3App::slotAutoAdvanceToggled() { m_pOptions->m_bAutoAdvance = autoAdvance->isChecked(); } void KDiff3App::slotWordWrapToggled() { m_pOptions->m_bWordWrap = wordWrap->isChecked(); postRecalcWordWrap(); } // Enable or disable all widgets except the status bar widget. void KDiff3App::mainWindowEnable(bool bEnable) { if(QMainWindow* pWindow = dynamic_cast(window())) { QWidget* pStatusBarWidget = pWindow->statusBar(); pWindow->setEnabled(bEnable); pStatusBarWidget->setEnabled(true); } } void KDiff3App::postRecalcWordWrap() { if(!m_bRecalcWordWrapPosted) { m_bRecalcWordWrapPosted = true; m_firstD3LIdx = -1; Q_EMIT sigRecalcWordWrap(); } else { g_pProgressDialog->cancel(ProgressDialog::eResize); } } void KDiff3App::slotRecalcWordWrap() { recalcWordWrap(); } // visibleTextWidthForPrinting is >=0 only for printing, otherwise the really visible width is used void KDiff3App::recalcWordWrap(int visibleTextWidthForPrinting) { m_bRecalcWordWrapPosted = true; mainWindowEnable(false); if(m_firstD3LIdx < 0) { m_firstD3LIdx = 0; if(m_pDiffTextWindow1) m_firstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(m_pDiffTextWindow1->getFirstLine()); } // Convert selection to D3L-coords (converting back happens in DiffTextWindow::recalcWordWrap() if(m_pDiffTextWindow1) m_pDiffTextWindow1->convertSelectionToD3LCoords(); if(m_pDiffTextWindow2) m_pDiffTextWindow2->convertSelectionToD3LCoords(); if(m_pDiffTextWindow3) m_pDiffTextWindow3->convertSelectionToD3LCoords(); g_pProgressDialog->clearCancelState(); // clear cancelled state if previously set if(!m_diff3LineList.empty()) { if(m_pOptions->m_bWordWrap) { m_diff3LineList.recalcWordWrap(true); // Let every window calc how many lines will be needed. if(m_pDiffTextWindow1) { m_pDiffTextWindow1->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } if(m_pDiffTextWindow2) { m_pDiffTextWindow2->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } if(m_pDiffTextWindow3) { m_pDiffTextWindow3->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } } else { m_neededLines = m_diff3LineVector.size(); if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(false, 0, 0); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(false, 0, 0); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(false, 0, 0); } bool bRunnablesStarted = DiffTextWindow::startRunnables(); if(!bRunnablesStarted) slotFinishRecalcWordWrap(visibleTextWidthForPrinting); else { g_pProgressDialog->setInformation(m_pOptions->m_bWordWrap ? i18n("Word wrap (Cancel disables word wrap)") : i18n("Calculating max width for horizontal scrollbar"), false); } } else { //don't leave proccessing incomplete if m_diff3LineList isEmpty as when an error occures during reading. slotFinishRecalcWordWrap(visibleTextWidthForPrinting); } } void KDiff3App::slotFinishRecalcWordWrap(int visibleTextWidthForPrinting) { g_pProgressDialog->pop(); if(m_pOptions->m_bWordWrap && g_pProgressDialog->wasCancelled()) { if(g_pProgressDialog->cancelReason() == ProgressDialog::eUserAbort) { wordWrap->setChecked(false); m_pOptions->m_bWordWrap = wordWrap->isChecked(); } Q_EMIT sigRecalcWordWrap(); return; } else { m_bRecalcWordWrapPosted = false; } g_pProgressDialog->setStayHidden(false); bool bPrinting = visibleTextWidthForPrinting >= 0; if(!m_diff3LineList.empty()) { if(m_pOptions->m_bWordWrap) { LineCount sumOfLines = m_diff3LineList.recalcWordWrap(false); // Finish the word wrap if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); m_neededLines = sumOfLines; } else { if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(false, 1, 0); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(false, 1, 0); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(false, 1, 0); } slotStatusMsg(QString()); } if(!bPrinting) { if(m_pOverview) m_pOverview->slotRedraw(); if(m_pDiffVScrollBar) m_pDiffVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - m_DTWHeight)); if(m_pDiffTextWindow1) { if(m_pDiffVScrollBar) m_pDiffVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(m_firstD3LIdx)); setHScrollBarRange(); m_pHScrollBar->setValue(0); } } mainWindowEnable(true); if(m_bFinishMainInit) { m_bFinishMainInit = false; slotFinishMainInit(); } if(m_pEventLoopForPrinting) m_pEventLoopForPrinting->quit(); } void KDiff3App::slotShowWhiteSpaceToggled() { m_pOptions->m_bShowWhiteSpaceCharacters = showWhiteSpaceCharacters->isChecked(); m_pOptions->m_bShowWhiteSpace = showWhiteSpace->isChecked(); Q_EMIT showWhiteSpaceToggled(); if(m_pOverview != nullptr) m_pOverview->slotRedraw(); } void KDiff3App::slotShowLineNumbersToggled() { m_pOptions->m_bShowLineNumbers = showLineNumbers->isChecked(); if(wordWrap->isChecked()) recalcWordWrap(); Q_EMIT showLineNumbersToggled(); } /// Return true for success, else false bool KDiff3App::improveFilenames(bool bCreateNewInstance) { FileAccess f1(m_sd1->getFilename()); FileAccess f2(m_sd2->getFilename()); FileAccess f3(m_sd3->getFilename()); FileAccess f4(m_outputFilename); if(f1.isFile() && f1.exists()) { if(f2.isDir()) { f2.addPath(f1.fileName()); if(f2.isFile() && f2.exists()) m_sd2->setFileAccess(f2); } if(f3.isDir()) { f3.addPath(f1.fileName()); if(f3.isFile() && f3.exists()) m_sd3->setFileAccess(f3); } if(f4.isDir()) { f4.addPath(f1.fileName()); if(f4.isFile() && f4.exists()) m_outputFilename = f4.absoluteFilePath(); } } else if(f1.isDir()) { if(bCreateNewInstance) { Q_EMIT createNewInstance(f1.absoluteFilePath(), f2.absoluteFilePath(), f3.absoluteFilePath()); } else { bool bDirCompare = m_bDirCompare; FileAccess destDir; if(!m_bDefaultFilename) destDir = f4; m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) m_pMainWidget->hide(); setUpdatesEnabled(true); m_dirinfo = QSharedPointer::create(f1, f2, f3, destDir); bool bSuccess = m_pDirectoryMergeWindow->init( m_dirinfo, !m_outputFilename.isEmpty()); //This is a bug if it still happens. Q_ASSERT(m_bDirCompare == bDirCompare); if(bSuccess) { m_sd1->reset(); if(m_pDiffTextWindow1 != nullptr) { m_pDiffTextWindow1->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_pDiffTextWindowFrame1->init(); } m_sd2->reset(); if(m_pDiffTextWindow2 != nullptr) { m_pDiffTextWindow2->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_pDiffTextWindowFrame2->init(); } m_sd3->reset(); if(m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindow3->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_pDiffTextWindowFrame3->init(); } } slotUpdateAvailabilities(); return bSuccess; } } return true; } void KDiff3App::slotReload() { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; mainInit(); } bool KDiff3App::canContinue() { // First test if anything must be saved. if(m_bOutputModified) { int result = KMessageBox::warningYesNoCancel(this, i18n("The merge result has not been saved."), i18n("Warning"), KGuiItem(i18n("Save && Continue")), KGuiItem(i18n("Continue Without Saving"))); if(result == KMessageBox::Cancel) return false; else if(result == KMessageBox::Yes) { slotFileSave(); if(m_bOutputModified) { KMessageBox::sorry(this, i18n("Saving the merge result failed."), i18n("Warning")); return false; } } } m_bOutputModified = false; return true; } /* Translate canContinue return value to out param because Qt does not support return values on slots. */ void KDiff3App::slotCheckIfCanContinue(bool& pbContinue) { pbContinue = canContinue(); } void KDiff3App::slotDirShowBoth() { if(dirShowBoth->isChecked()) { if(m_pDirectoryMergeSplitter) m_pDirectoryMergeSplitter->setVisible(m_bDirCompare); if(m_pMainWidget != nullptr) m_pMainWidget->show(); } else { bool bTextDataAvailable = (m_sd1->hasData() || m_sd2->hasData() || m_sd3->hasData()); if(m_pMainWidget != nullptr && bTextDataAvailable) { m_pMainWidget->show(); m_pDirectoryMergeSplitter->hide(); } else if(m_bDirCompare) { m_pDirectoryMergeSplitter->show(); } } slotUpdateAvailabilities(); } void KDiff3App::slotDirViewToggle() { if(m_bDirCompare) { if(!m_pDirectoryMergeSplitter->isVisible()) { m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) m_pMainWidget->hide(); } else { if(m_pMainWidget != nullptr) { m_pDirectoryMergeSplitter->hide(); m_pMainWidget->show(); } } } slotUpdateAvailabilities(); } void KDiff3App::slotShowWindowAToggled() { if(m_pDiffTextWindow1 != nullptr) { m_pDiffTextWindowFrame1->setVisible(showWindowA->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotShowWindowBToggled() { if(m_pDiffTextWindow2 != nullptr) { m_pDiffTextWindowFrame2->setVisible(showWindowB->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotShowWindowCToggled() { if(m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindowFrame3->setVisible(showWindowC->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotEditFind() { m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; m_pFindDialog->currentWindow = 1; // Use currently selected text: QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty() && !s.contains('\n')) { m_pFindDialog->m_pSearchString->setText(s); } if(QDialog::Accepted == m_pFindDialog->exec()) { slotEditFindNext(); } } void KDiff3App::slotEditFindNext() { QString s = m_pFindDialog->m_pSearchString->text(); if(s.isEmpty()) { slotEditFind(); return; } bool bDirDown = true; bool bCaseSensitive = m_pFindDialog->m_pCaseSensitive->isChecked(); LineRef d3vLine = m_pFindDialog->currentLine; int posInLine = m_pFindDialog->currentPos; LineRef l = 0; int p = 0; if(m_pFindDialog->currentWindow == 1) { if(m_pFindDialog->m_pSearchInA->isChecked() && m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow1->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); m_pDiffVScrollBar->setValue(l - m_pDiffVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 2; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 2) { if(m_pFindDialog->m_pSearchInB->isChecked() && m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow2->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); m_pDiffVScrollBar->setValue(l - m_pDiffVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 3; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 3) { if(m_pFindDialog->m_pSearchInC->isChecked() && m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow3->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); m_pDiffVScrollBar->setValue(l - m_pDiffVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 4; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 4) { if(m_pFindDialog->m_pSearchInOutput->isChecked() && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() && m_pMergeResultWindow->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pMergeResultWindow->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length()); m_pMergeVScrollBar->setValue(d3vLine - m_pMergeVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, posInLine + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 5; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } KMessageBox::information(this, i18n("Search complete."), i18n("Search Complete")); m_pFindDialog->currentWindow = 1; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } void KDiff3App::slotMergeCurrentFile() { if(m_bDirCompare && m_pDirectoryMergeWindow->isVisible() && m_pDirectoryMergeWindow->isFileSelected()) { m_pDirectoryMergeWindow->mergeCurrentFile(); } else if(m_pMainWidget != nullptr && m_pMainWidget->isVisible()) { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; if(m_outputFilename.isEmpty()) { if(!m_sd3->isEmpty() && !m_sd3->isFromBuffer()) { m_outputFilename = m_sd3->getFilename(); } else if(!m_sd2->isEmpty() && !m_sd2->isFromBuffer()) { m_outputFilename = m_sd2->getFilename(); } else if(!m_sd1->isEmpty() && !m_sd1->isFromBuffer()) { m_outputFilename = m_sd1->getFilename(); } else { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } } mainInit(); } } void KDiff3App::slotWinFocusNext() { QWidget* focus = qApp->focusWidget(); if(focus == m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } std::list visibleWidgetList; if(m_pDiffTextWindow1 && m_pDiffTextWindow1->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow1); if(m_pDiffTextWindow2 && m_pDiffTextWindow2->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow2); if(m_pDiffTextWindow3 && m_pDiffTextWindow3->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow3); if(m_pMergeResultWindow && m_pMergeResultWindow->isVisible()) visibleWidgetList.push_back(m_pMergeResultWindow); if(m_bDirCompare /*m_pDirectoryMergeWindow->isVisible()*/) visibleWidgetList.push_back(m_pDirectoryMergeWindow); //if ( m_pDirectoryMergeInfo->isVisible() ) visibleWidgetList.push_back(m_pDirectoryMergeInfo->getInfoList()); std::list::iterator i = std::find(visibleWidgetList.begin(), visibleWidgetList.end(), focus); ++i; if(i == visibleWidgetList.end()) i = visibleWidgetList.begin(); if(i != visibleWidgetList.end()) { if(*i == m_pDirectoryMergeWindow && !dirShowBoth->isChecked()) { slotDirViewToggle(); } (*i)->setFocus(); } } void KDiff3App::slotWinFocusPrev() { QWidget* focus = qApp->focusWidget(); if(focus == m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } std::list visibleWidgetList; if(m_pDiffTextWindow1 && m_pDiffTextWindow1->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow1); if(m_pDiffTextWindow2 && m_pDiffTextWindow2->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow2); if(m_pDiffTextWindow3 && m_pDiffTextWindow3->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow3); if(m_pMergeResultWindow && m_pMergeResultWindow->isVisible()) visibleWidgetList.push_back(m_pMergeResultWindow); if(m_bDirCompare /* m_pDirectoryMergeWindow->isVisible() */) visibleWidgetList.push_back(m_pDirectoryMergeWindow); //if ( m_pDirectoryMergeInfo->isVisible() ) visibleWidgetList.push_back(m_pDirectoryMergeInfo->getInfoList()); std::list::iterator i = std::find(visibleWidgetList.begin(), visibleWidgetList.end(), focus); if(i == visibleWidgetList.begin()) i = visibleWidgetList.end(); --i; if(i != visibleWidgetList.end()) { if(*i == m_pDirectoryMergeWindow && !dirShowBoth->isChecked()) { slotDirViewToggle(); } (*i)->setFocus(); } } void KDiff3App::slotWinToggleSplitterOrientation() { if(m_pDiffWindowSplitter != nullptr) { m_pDiffWindowSplitter->setOrientation( m_pDiffWindowSplitter->orientation() == Qt::Vertical ? Qt::Horizontal : Qt::Vertical); m_pOptions->m_bHorizDiffWindowSplitting = m_pDiffWindowSplitter->orientation() == Qt::Horizontal; } } void KDiff3App::slotOverviewNormal() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMNormal); if(m_pMergeResultWindow != nullptr) m_pMergeResultWindow->setOverviewMode(Overview::eOMNormal); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewAB() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMAvsB); m_pMergeResultWindow->setOverviewMode(Overview::eOMAvsB); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewAC() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMAvsC); if(m_pMergeResultWindow != nullptr) m_pMergeResultWindow->setOverviewMode(Overview::eOMAvsC); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewBC() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMBvsC); if(m_pMergeResultWindow != nullptr) m_pMergeResultWindow->setOverviewMode(Overview::eOMBvsC); slotUpdateAvailabilities(); } void KDiff3App::slotNoRelevantChangesDetected() { if(m_bTripleDiff && !m_outputFilename.isEmpty()) { //KMessageBox::information( this, "No relevant changes detected", "KDiff3" ); if(!m_pOptions->m_IrrelevantMergeCmd.isEmpty()) { /* QProcess doesn't check for single quotes and uses non-standard escaping syntax for double quotes. The distinction between single and double quotes is purely a command shell issue. So we split the command string ourselves. */ QStringList args; QString program; Utils::getArguments(m_pOptions->m_IrrelevantMergeCmd, program, args); QProcess process; process.start(program, args); process.waitForFinished(-1); } } } void KDiff3App::slotAddManualDiffHelp() { LineRef firstLine; LineRef lastLine; e_SrcSelector winIdx = Invalid; if(m_pDiffTextWindow1) { m_pDiffTextWindow1->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = A; } if(firstLine < 0 && m_pDiffTextWindow2) { m_pDiffTextWindow2->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = B; } if(firstLine < 0 && m_pDiffTextWindow3) { m_pDiffTextWindow3->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = C; } if(firstLine < 0 || lastLine < 0 || lastLine < firstLine) KMessageBox::information(this, i18n("Nothing is selected in either diff input window."), i18n("Error while adding manual diff range")); else { m_manualDiffHelpList.insertEntry(winIdx, firstLine, lastLine); mainInit(nullptr, false); // Init without reload slotRefresh(); } } void KDiff3App::slotClearManualDiffHelpList() { m_manualDiffHelpList.clear(); mainInit(nullptr, false); // Init without reload slotRefresh(); } void KDiff3App::slotEncodingChanged(QTextCodec* c) { Q_UNUSED(c); mainInit(nullptr, true, true); // Init with reload slotRefresh(); } void KDiff3App::slotUpdateAvailabilities() { if(m_pMainSplitter == nullptr || m_pDiffTextWindow2 == nullptr || m_pDiffTextWindow1 == nullptr || m_pDiffTextWindow3 == nullptr) return; bool bTextDataAvailable = (m_sd1->hasData() || m_sd2->hasData() || m_sd3->hasData()); if(dirShowBoth->isChecked()) { if(m_pDirectoryMergeSplitter != nullptr) m_pDirectoryMergeSplitter->setVisible(m_bDirCompare); if(m_pMainWidget != nullptr && !m_pMainWidget->isVisible() && bTextDataAvailable && !m_pDirectoryMergeWindow->isScanning()) m_pMainWidget->show(); } bool bDiffWindowVisible = m_pMainWidget != nullptr && m_pMainWidget->isVisible(); bool bMergeEditorVisible = m_pMergeWindowFrame != nullptr && m_pMergeWindowFrame->isVisible() && m_pMergeResultWindow != nullptr; m_pDirectoryMergeWindow->updateAvailabilities(m_bDirCompare, bDiffWindowVisible, chooseA, chooseB, chooseC); dirShowBoth->setEnabled(m_bDirCompare); dirViewToggle->setEnabled( m_bDirCompare && ((m_pDirectoryMergeSplitter != nullptr && m_pMainWidget != nullptr) && ((!m_pDirectoryMergeSplitter->isVisible() && m_pMainWidget->isVisible()) || (m_pDirectoryMergeSplitter->isVisible() && !m_pMainWidget->isVisible() && bTextDataAvailable)))); bool bDirWindowHasFocus = m_pDirectoryMergeSplitter != nullptr && m_pDirectoryMergeSplitter->isVisible() && m_pDirectoryMergeWindow->hasFocus(); showWhiteSpaceCharacters->setEnabled(bDiffWindowVisible); autoAdvance->setEnabled(bMergeEditorVisible); mAutoSolve->setEnabled(bMergeEditorVisible && m_bTripleDiff); mUnsolve->setEnabled(bMergeEditorVisible); if(!bDirWindowHasFocus) { chooseA->setEnabled(bMergeEditorVisible); chooseB->setEnabled(bMergeEditorVisible); chooseC->setEnabled(bMergeEditorVisible && m_bTripleDiff); } if(m_pMergeResultWindow != nullptr) { m_pMergeResultWindow->slotUpdateAvailabilities(bMergeEditorVisible, m_bTripleDiff); } mMergeHistory->setEnabled(bMergeEditorVisible); mergeRegExp->setEnabled(bMergeEditorVisible); showWindowA->setEnabled(bDiffWindowVisible && (m_pDiffTextWindow2->isVisible() || m_pDiffTextWindow3->isVisible())); showWindowB->setEnabled(bDiffWindowVisible && (m_pDiffTextWindow1->isVisible() || m_pDiffTextWindow3->isVisible())); showWindowC->setEnabled(bDiffWindowVisible && m_bTripleDiff && (m_pDiffTextWindow1->isVisible() || m_pDiffTextWindow2->isVisible())); editFind->setEnabled(bDiffWindowVisible); editFindNext->setEnabled(bDiffWindowVisible); m_pFindDialog->m_pSearchInC->setEnabled(m_bTripleDiff); m_pFindDialog->m_pSearchInOutput->setEnabled(bMergeEditorVisible); bool bSavable = bMergeEditorVisible && m_pMergeResultWindow->getNrOfUnsolvedConflicts() == 0; fileSave->setEnabled(m_bOutputModified && bSavable); fileSaveAs->setEnabled(bSavable); mGoTop->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaAboveCurrent()); mGoBottom->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaBelowCurrent()); mGoCurrent->setEnabled(bDiffWindowVisible); mGoPrevUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictAboveCurrent()); mGoNextUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictBelowCurrent()); mGoPrevConflict->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isConflictAboveCurrent()); mGoNextConflict->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isConflictBelowCurrent()); mGoPrevDelta->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaAboveCurrent()); mGoNextDelta->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaBelowCurrent()); overviewModeNormal->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeAB->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeAC->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeBC->setEnabled(m_bTripleDiff && bDiffWindowVisible); Overview::e_OverviewMode overviewMode = m_pOverview == nullptr ? Overview::eOMNormal : m_pOverview->getOverviewMode(); overviewModeNormal->setChecked(overviewMode == Overview::eOMNormal); overviewModeAB->setChecked(overviewMode == Overview::eOMAvsB); overviewModeAC->setChecked(overviewMode == Overview::eOMAvsC); overviewModeBC->setChecked(overviewMode == Overview::eOMBvsC); winToggleSplitOrientation->setEnabled(bDiffWindowVisible && m_pDiffWindowSplitter != nullptr); } diff --git a/src/smalldialogs.cpp b/src/smalldialogs.cpp index e18781d..226a89e 100644 --- a/src/smalldialogs.cpp +++ b/src/smalldialogs.cpp @@ -1,625 +1,625 @@ /*************************************************************************** * Copyright (C) 2005-2007 by Joachim Eibl * * joachim.eibl at gmx.de * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "smalldialogs.h" #include "diff.h" #include "options.h" #include "kdiff3.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // OpenDialog ************************************************************** OpenDialog::OpenDialog( KDiff3App* pParent, const QString& n1, const QString& n2, const QString& n3, bool bMerge, const QString& outputName, const QSharedPointer &pOptions) : QDialog(pParent) { setObjectName("OpenDialog"); setModal(true); m_pOptions = pOptions; QVBoxLayout* v = new QVBoxLayout(this); v->setMargin(5); QGridLayout* h = new QGridLayout(); v->addLayout(h); h->setSpacing(5); h->setColumnStretch(1, 10); QLabel* label = new QLabel(i18n("A (Base):"), this); m_pLineA = new QComboBox(); m_pLineA->setEditable(true); m_pLineA->insertItems(0, m_pOptions->m_recentAFiles); m_pLineA->setEditText(QUrl(n1).toDisplayString()); m_pLineA->setMinimumWidth(200); QPushButton* button = new QPushButton(QIcon::fromTheme("document-new"), i18n("File..."), this); connect(button, &QPushButton::clicked, this, &OpenDialog::selectFileA); - QPushButton* button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Dir..."), this); + QPushButton* button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Folder..."), this); connect(button2, &QPushButton::clicked, this, &OpenDialog::selectDirA); connect(m_pLineA, &QComboBox::editTextChanged, this, &OpenDialog::inputFilenameChanged); h->addWidget(label, 0, 0); h->addWidget(m_pLineA, 0, 1); h->addWidget(button, 0, 2); h->addWidget(button2, 0, 3); label = new QLabel("B:", this); m_pLineB = new QComboBox(); m_pLineB->setEditable(true); m_pLineB->insertItems(0, m_pOptions->m_recentBFiles); m_pLineB->setEditText(QUrl(n2).toDisplayString()); m_pLineB->setMinimumWidth(200); button = new QPushButton(QIcon::fromTheme("document-new"), i18n("File..."), this); connect(button, &QPushButton::clicked, this, &OpenDialog::selectFileB); - button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Dir..."), this); + button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Folder..."), this); connect(button2, &QPushButton::clicked, this, &OpenDialog::selectDirB); connect(m_pLineB, &QComboBox::editTextChanged, this, &OpenDialog::inputFilenameChanged); h->addWidget(label, 1, 0); h->addWidget(m_pLineB, 1, 1); h->addWidget(button, 1, 2); h->addWidget(button2, 1, 3); label = new QLabel(i18n("C (Optional):"), this); m_pLineC = new QComboBox(); m_pLineC->setEditable(true); m_pLineC->insertItems(0, m_pOptions->m_recentCFiles); m_pLineC->setEditText(QUrl(n3).toDisplayString()); m_pLineC->setMinimumWidth(200); button = new QPushButton(QIcon::fromTheme("document-new"), i18n("File..."), this); connect(button, &QPushButton::clicked, this, &OpenDialog::selectFileC); - button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Dir..."), this); + button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Folder..."), this); connect(button2, &QPushButton::clicked, this, &OpenDialog::selectDirC); connect(m_pLineC, &QComboBox::editTextChanged, this, &OpenDialog::inputFilenameChanged); h->addWidget(label, 2, 0); h->addWidget(m_pLineC, 2, 1); h->addWidget(button, 2, 2); h->addWidget(button2, 2, 3); m_pMerge = new QCheckBox(i18n("Merge"), this); h->addWidget(m_pMerge, 3, 0); QHBoxLayout* hl = new QHBoxLayout(); h->addLayout(hl, 3, 1); hl->addStretch(2); button = new QPushButton(i18n("Swap/Copy Names..."), this); //button->setToggleButton(false); hl->addWidget(button); QMenu* m = new QMenu(this); m->addAction(i18n("Swap %1<->%2", i18n("A"), i18n("B"))); m->addAction(i18n("Swap %1<->%2", i18n("B"), i18n("C"))); m->addAction(i18n("Swap %1<->%2", i18n("C"), i18n("A"))); m->addAction(i18n("Copy %1->Output", i18n("A"))); m->addAction(i18n("Copy %1->Output", i18n("B"))); m->addAction(i18n("Copy %1->Output", i18n("C"))); m->addAction(i18n("Swap %1<->Output", i18n("A"))); m->addAction(i18n("Swap %1<->Output", i18n("B"))); m->addAction(i18n("Swap %1<->Output", i18n("C"))); connect(m, &QMenu::triggered, this, &OpenDialog::slotSwapCopyNames); button->setMenu(m); hl->addStretch(2); label = new QLabel(i18n("Output (optional):"), this); m_pLineOut = new QComboBox(); m_pLineOut->setEditable(true); m_pLineOut->insertItems(0, m_pOptions->m_recentOutputFiles); m_pLineOut->setEditText(QUrl(outputName).toDisplayString()); m_pLineOut->setMinimumWidth(200); button = new QPushButton(QIcon::fromTheme("document-new"), i18n("File..."), this); connect(button, &QPushButton::clicked, this, &OpenDialog::selectOutputName); - button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Dir..."), this); + button2 = new QPushButton(QIcon::fromTheme("document-open-folder"), i18n("Folder..."), this); connect(button2, &QPushButton::clicked, this, &OpenDialog::selectOutputDir); connect(m_pMerge, &QCheckBox::stateChanged, this, &OpenDialog::internalSlot); connect(this, &OpenDialog::internalSignal, m_pLineOut, &QComboBox::setEnabled); connect(this, &OpenDialog::internalSignal, button, &QPushButton::setEnabled); connect(this, &OpenDialog::internalSignal, button2, &QPushButton::setEnabled); m_pMerge->setChecked(!bMerge); m_pMerge->setChecked(bMerge); // m_pLineOutput->setEnabled( bMerge ); // button->setEnabled( bMerge ); h->addWidget(label, 4, 0); h->addWidget(m_pLineOut, 4, 1); h->addWidget(button, 4, 2); h->addWidget(button2, 4, 3); h->addItem(new QSpacerItem(200, 0), 0, 1); QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); v->addWidget(box); button = box->addButton(i18n("Configure..."), QDialogButtonBox::ActionRole); button->setIcon(QIcon::fromTheme("configure")); connect(button, &QPushButton::clicked, pParent, &KDiff3App::slotConfigure); connect(box, &QDialogButtonBox::accepted, this, &OpenDialog::accept); connect(box, &QDialogButtonBox::rejected, this, &OpenDialog::reject); QSize sh = sizeHint(); setFixedHeight(sh.height()); m_bInputFileNameChanged = false; #ifdef Q_OS_WIN m_pLineA->lineEdit()->installEventFilter(this); m_pLineB->lineEdit()->installEventFilter(this); m_pLineC->lineEdit()->installEventFilter(this); m_pLineOut->lineEdit()->installEventFilter(this); #endif } // Eventfilter: Only needed under Windows. // Without this, files dropped in the line edit have URL-encoding. // This eventfilter decodes the filenames as needed by KDiff3. bool OpenDialog::eventFilter(QObject* o, QEvent* e) { if(e->type() == QEvent::DragEnter) { QDragEnterEvent* d = static_cast(e); d->setAccepted(d->mimeData()->hasUrls()); return true; } if(e->type() == QEvent::Drop) { QDropEvent* d = static_cast(e); if(!d->mimeData()->hasUrls()) return false; QList lst = d->mimeData()->urls(); if(lst.count() > 0) { static_cast(o)->setText(QDir::toNativeSeparators(lst[0].toLocalFile())); static_cast(o)->setFocus(); } return true; } return false; } void OpenDialog::selectURL(QComboBox* pLine, bool bDir, int i, bool bSave) { QString current = pLine->currentText(); QUrl currentUrl; if(current.isEmpty() && i > 3) { current = m_pLineC->currentText(); } if(current.isEmpty()) { current = m_pLineB->currentText(); } if(current.isEmpty()) { current = m_pLineA->currentText(); } currentUrl = QUrl::fromUserInput(current, QString(), QUrl::AssumeLocalFile); - QUrl newURL = bDir ? QFileDialog::getExistingDirectoryUrl(this, i18n("Open Directory"), currentUrl) + QUrl newURL = bDir ? QFileDialog::getExistingDirectoryUrl(this, i18n("Open Folder"), currentUrl) : bSave ? QFileDialog::getSaveFileUrl(this, i18n("Select Output File"), currentUrl, i18n("All files (*)")) : QFileDialog::getOpenFileUrl(this, i18n("Open File"), currentUrl, i18n("All files (*)")); if(!newURL.isEmpty()) { /* Since we are selecting a directory open in the parent directory not the one selected. */ //QFileDialog::setStartDir( KIO::upUrl( newURL ) ); pLine->setEditText(newURL.url()); } // newURL won't be modified if nothing was selected. } void OpenDialog::selectFileA() { selectURL(m_pLineA, false, 1, false); } void OpenDialog::selectFileB() { selectURL(m_pLineB, false, 2, false); } void OpenDialog::selectFileC() { selectURL(m_pLineC, false, 3, false); } void OpenDialog::selectOutputName() { selectURL(m_pLineOut, false, 4, true); } void OpenDialog::selectDirA() { selectURL(m_pLineA, true, 1, false); } void OpenDialog::selectDirB() { selectURL(m_pLineB, true, 2, false); } void OpenDialog::selectDirC() { selectURL(m_pLineC, true, 3, false); } void OpenDialog::selectOutputDir() { selectURL(m_pLineOut, true, 4, true); } void OpenDialog::internalSlot(int i) { Q_EMIT internalSignal(i != 0); } // Clear the output-filename when any input-filename changed, // because users forgot to change the output and accidentally overwrote it with // wrong data during a merge. void OpenDialog::inputFilenameChanged() { if(!m_bInputFileNameChanged) { m_bInputFileNameChanged = true; m_pLineOut->clearEditText(); } } void OpenDialog::fixCurrentText(QComboBox* pCB) { QString s = pCB->currentText(); int pos = s.indexOf('\n'); if(pos >= 0) s = s.left(pos); pos = s.indexOf('\r'); if(pos >= 0) s = s.left(pos); pCB->setEditText(s); } void OpenDialog::accept() { int maxNofRecentFiles = 10; fixCurrentText(m_pLineA); QString s = m_pLineA->currentText(); s = QUrl::fromUserInput(s, QString(), QUrl::AssumeLocalFile).toLocalFile(); QStringList* sl = &m_pOptions->m_recentAFiles; // If an item exist, remove it from the list and reinsert it at the beginning. sl->removeAll(s); if(!s.isEmpty()) sl->prepend(s); if(sl->count() > maxNofRecentFiles) sl->erase(sl->begin() + maxNofRecentFiles, sl->end()); fixCurrentText(m_pLineB); s = m_pLineB->currentText(); s = QUrl::fromUserInput(s, QString(), QUrl::AssumeLocalFile).toLocalFile(); sl = &m_pOptions->m_recentBFiles; sl->removeAll(s); if(!s.isEmpty()) sl->prepend(s); if(sl->count() > maxNofRecentFiles) sl->erase(sl->begin() + maxNofRecentFiles, sl->end()); fixCurrentText(m_pLineC); s = m_pLineC->currentText(); s = QUrl::fromUserInput(s, QString(), QUrl::AssumeLocalFile).toLocalFile(); sl = &m_pOptions->m_recentCFiles; sl->removeAll(s); if(!s.isEmpty()) sl->prepend(s); if(sl->count() > maxNofRecentFiles) sl->erase(sl->begin() + maxNofRecentFiles, sl->end()); fixCurrentText(m_pLineOut); s = m_pLineOut->currentText(); s = QUrl::fromUserInput(s, QString(), QUrl::AssumeLocalFile).toLocalFile(); sl = &m_pOptions->m_recentOutputFiles; sl->removeAll(s); if(!s.isEmpty()) sl->prepend(s); if(sl->count() > maxNofRecentFiles) sl->erase(sl->begin() + maxNofRecentFiles, sl->end()); QDialog::accept(); } void OpenDialog::slotSwapCopyNames(QAction* pAction) const // id selected in the popup menu { int id = pAction->parentWidget()->actions().indexOf(pAction); QComboBox* cb1 = nullptr; QComboBox* cb2 = nullptr; switch(id) { case 0: cb1 = m_pLineA; cb2 = m_pLineB; break; case 1: cb1 = m_pLineB; cb2 = m_pLineC; break; case 2: cb1 = m_pLineC; cb2 = m_pLineA; break; case 3: cb1 = m_pLineA; cb2 = m_pLineOut; break; case 4: cb1 = m_pLineB; cb2 = m_pLineOut; break; case 5: cb1 = m_pLineC; cb2 = m_pLineOut; break; case 6: cb1 = m_pLineA; cb2 = m_pLineOut; break; case 7: cb1 = m_pLineB; cb2 = m_pLineOut; break; case 8: cb1 = m_pLineC; cb2 = m_pLineOut; break; } if(cb1 && cb2) { QString t1 = cb1->currentText(); QString t2 = cb2->currentText(); cb2->setEditText(t1); if(id <= 2 || id >= 6) { cb1->setEditText(t2); } } } // FindDialog ********************************************* FindDialog::FindDialog(QWidget* pParent) : QDialog(pParent) { QGridLayout* layout = new QGridLayout(this); layout->setMargin(5); layout->setSpacing(5); int line = 0; layout->addWidget(new QLabel(i18n("Search text:"), this), line, 0, 1, 2); ++line; m_pSearchString = new QLineEdit(this); layout->addWidget(m_pSearchString, line, 0, 1, 2); ++line; m_pCaseSensitive = new QCheckBox(i18n("Case sensitive"), this); layout->addWidget(m_pCaseSensitive, line, 1); m_pSearchInA = new QCheckBox(i18n("Search A"), this); layout->addWidget(m_pSearchInA, line, 0); m_pSearchInA->setChecked(true); ++line; m_pSearchInB = new QCheckBox(i18n("Search B"), this); layout->addWidget(m_pSearchInB, line, 0); m_pSearchInB->setChecked(true); ++line; m_pSearchInC = new QCheckBox(i18n("Search C"), this); layout->addWidget(m_pSearchInC, line, 0); m_pSearchInC->setChecked(true); ++line; m_pSearchInOutput = new QCheckBox(i18n("Search output"), this); layout->addWidget(m_pSearchInOutput, line, 0); m_pSearchInOutput->setChecked(true); ++line; QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Cancel, this); layout->addWidget(box, line, 0, 1, 2); box->addButton(i18n("&Search"), QDialogButtonBox::AcceptRole); connect(box, &QDialogButtonBox::accepted, this, &FindDialog::accept); connect(box, &QDialogButtonBox::rejected, this, &FindDialog::reject); hide(); } void FindDialog::setVisible(bool bVisible) { QDialog::setVisible(bVisible); m_pSearchString->selectAll(); m_pSearchString->setFocus(); } RegExpTester::RegExpTester(QWidget* pParent, const QString& autoMergeRegExpToolTip, const QString& historyStartRegExpToolTip, const QString& historyEntryStartRegExpToolTip, const QString& historySortKeyOrderToolTip) : QDialog(pParent) { int line = 0; setWindowTitle(i18n("Regular Expression Tester")); QGridLayout* pGrid = new QGridLayout(this); pGrid->setSpacing(5); pGrid->setMargin(5); QLabel* l = new QLabel(i18n("Auto merge regular expression:"), this); pGrid->addWidget(l, line, 0); l->setToolTip(autoMergeRegExpToolTip); m_pAutoMergeRegExpEdit = new QLineEdit(this); pGrid->addWidget(m_pAutoMergeRegExpEdit, line, 1); connect(m_pAutoMergeRegExpEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Example auto merge line:"), this); pGrid->addWidget(l, line, 0); l->setToolTip(i18n("To test auto merge, copy a line as used in your files.")); m_pAutoMergeExampleEdit = new QLineEdit(this); pGrid->addWidget(m_pAutoMergeExampleEdit, line, 1); connect(m_pAutoMergeExampleEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Match result:"), this); pGrid->addWidget(l, line, 0); m_pAutoMergeMatchResult = new QLineEdit(this); m_pAutoMergeMatchResult->setReadOnly(true); pGrid->addWidget(m_pAutoMergeMatchResult, line, 1); ++line; pGrid->addItem(new QSpacerItem(100, 20), line, 0); pGrid->setRowStretch(line, 5); ++line; l = new QLabel(i18n("History start regular expression:"), this); pGrid->addWidget(l, line, 0); l->setToolTip(historyStartRegExpToolTip); m_pHistoryStartRegExpEdit = new QLineEdit(this); pGrid->addWidget(m_pHistoryStartRegExpEdit, line, 1); connect(m_pHistoryStartRegExpEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Example history start line (with leading comment):"), this); pGrid->addWidget(l, line, 0); l->setToolTip(i18n("Copy a history start line as used in your files,\n" "including the leading comment.")); m_pHistoryStartExampleEdit = new QLineEdit(this); pGrid->addWidget(m_pHistoryStartExampleEdit, line, 1); connect(m_pHistoryStartExampleEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Match result:"), this); pGrid->addWidget(l, line, 0); m_pHistoryStartMatchResult = new QLineEdit(this); m_pHistoryStartMatchResult->setReadOnly(true); pGrid->addWidget(m_pHistoryStartMatchResult, line, 1); ++line; pGrid->addItem(new QSpacerItem(100, 20), line, 0); pGrid->setRowStretch(line, 5); ++line; l = new QLabel(i18n("History entry start regular expression:"), this); pGrid->addWidget(l, line, 0); l->setToolTip(historyEntryStartRegExpToolTip); m_pHistoryEntryStartRegExpEdit = new QLineEdit(this); pGrid->addWidget(m_pHistoryEntryStartRegExpEdit, line, 1); connect(m_pHistoryEntryStartRegExpEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("History sort key order:"), this); pGrid->addWidget(l, line, 0); l->setToolTip(historySortKeyOrderToolTip); m_pHistorySortKeyOrderEdit = new QLineEdit(this); pGrid->addWidget(m_pHistorySortKeyOrderEdit, line, 1); connect(m_pHistorySortKeyOrderEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Example history entry start line (without leading comment):"), this); pGrid->addWidget(l, line, 0); l->setToolTip(i18n("Copy a history entry start line as used in your files,\n" "but omit the leading comment.")); m_pHistoryEntryStartExampleEdit = new QLineEdit(this); pGrid->addWidget(m_pHistoryEntryStartExampleEdit, line, 1); connect(m_pHistoryEntryStartExampleEdit, &QLineEdit::textChanged, this, &RegExpTester::slotRecalc); ++line; l = new QLabel(i18n("Match result:"), this); pGrid->addWidget(l, line, 0); m_pHistoryEntryStartMatchResult = new QLineEdit(this); m_pHistoryEntryStartMatchResult->setReadOnly(true); pGrid->addWidget(m_pHistoryEntryStartMatchResult, line, 1); ++line; l = new QLabel(i18n("Sort key result:"), this); pGrid->addWidget(l, line, 0); m_pHistorySortKeyResult = new QLineEdit(this); m_pHistorySortKeyResult->setReadOnly(true); pGrid->addWidget(m_pHistorySortKeyResult, line, 1); ++line; QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); pGrid->addWidget(box, line, 0, 1, 2); connect(box, &QDialogButtonBox::accepted, this, &RegExpTester::accept); connect(box, &QDialogButtonBox::rejected, this, &RegExpTester::reject); resize(800, sizeHint().height()); } void RegExpTester::init(const QString& autoMergeRegExp, const QString& historyStartRegExp, const QString& historyEntryStartRegExp, const QString& historySortKeyOrder) { m_pAutoMergeRegExpEdit->setText(autoMergeRegExp); m_pHistoryStartRegExpEdit->setText(historyStartRegExp); m_pHistoryEntryStartRegExpEdit->setText(historyEntryStartRegExp); m_pHistorySortKeyOrderEdit->setText(historySortKeyOrder); } QString RegExpTester::autoMergeRegExp() { return m_pAutoMergeRegExpEdit->text(); } QString RegExpTester::historyStartRegExp() { return m_pHistoryStartRegExpEdit->text(); } QString RegExpTester::historyEntryStartRegExp() { return m_pHistoryEntryStartRegExpEdit->text(); } QString RegExpTester::historySortKeyOrder() { return m_pHistorySortKeyOrderEdit->text(); } void RegExpTester::slotRecalc() { QRegExp autoMergeRegExp(m_pAutoMergeRegExpEdit->text()); if(autoMergeRegExp.exactMatch(m_pAutoMergeExampleEdit->text())) { m_pAutoMergeMatchResult->setText(i18n("Match success.")); } else { m_pAutoMergeMatchResult->setText(i18n("Match failed.")); } QRegExp historyStartRegExp(m_pHistoryStartRegExpEdit->text()); if(historyStartRegExp.exactMatch(m_pHistoryStartExampleEdit->text())) { m_pHistoryStartMatchResult->setText(i18n("Match success.")); } else { m_pHistoryStartMatchResult->setText(i18n("Match failed.")); } QStringList parenthesesGroups; bool bSuccess = findParenthesesGroups(m_pHistoryEntryStartRegExpEdit->text(), parenthesesGroups); if(!bSuccess) { m_pHistoryEntryStartMatchResult->setText(i18n("Opening and closing parentheses do not match in regular expression.")); m_pHistorySortKeyResult->setText(""); return; } QRegExp historyEntryStartRegExp(m_pHistoryEntryStartRegExpEdit->text()); QString s = m_pHistoryEntryStartExampleEdit->text(); if(historyEntryStartRegExp.exactMatch(s)) { m_pHistoryEntryStartMatchResult->setText(i18n("Match success.")); QString key = calcHistorySortKey(m_pHistorySortKeyOrderEdit->text(), historyEntryStartRegExp, parenthesesGroups); m_pHistorySortKeyResult->setText(key); } else { m_pHistoryEntryStartMatchResult->setText(i18n("Match failed.")); m_pHistorySortKeyResult->setText(""); } } //#include "smalldialogs.moc" diff --git a/test/generate_testdata_from_git_merges.py b/test/generate_testdata_from_git_merges.py index 38d2cfb..2ddb59e 100755 --- a/test/generate_testdata_from_git_merges.py +++ b/test/generate_testdata_from_git_merges.py @@ -1,89 +1,89 @@ #!/usr/bin/env python import argparse import glob import os import subprocess as sp import sys parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description='Generate input files for alignmenttest from the files merged for each merge commit in a git repository.\n\n' + 'This script finds all merge commits in the clone where it is run, checks which files were modified in both\n' + 'parents of the merge commit and then finds the common ancestor of these files to get the merge base.\n\n' 'Example:\n' ' cd ~/git/linux\n' ' ~/kdiff3/test/%s -d ~/kdiff3/test/testdata/linux\n' % os.path.basename(sys.argv[0])) parser.add_argument('-d', metavar='destination_path', nargs=1, default=['testdata_from_git/'], - help='specify the directory where to save the test input files. If the directory does not exist it will be created.') + help='specify the folder where to save the test input files. If the folder does not exist it will be created.') args = parser.parse_args() dirname=args.d[0] print 'Generating input files in %s ...' % dirname sys.stdout.flush() if not os.path.exists(dirname): os.makedirs(dirname) merges = sp.check_output('git rev-list --merges --parents master'.split()).strip() for entry in merges.splitlines(): fields = entry.split() if len(fields) > 3: print 'merge %s had more than 2 parents: %s' % (fields[0], fields) merge, contrib1, contrib2 = fields[:3] if glob.glob('%s/%s_*' % (dirname, merge)): print 'skipping merge %s because files for this merge already present' % merge continue base = sp.check_output(('git merge-base %s %s' % (contrib1, contrib2)).split()).strip() fileschanged1 = sp.check_output(('git diff --name-only %s %s' % (base, contrib1)).split()).strip().splitlines() fileschanged2 = sp.check_output(('git diff --name-only %s %s' % (base, contrib2)).split()).strip().splitlines() fileschangedboth = set(fileschanged1) & set(fileschanged2) if not fileschangedboth: print 'No files overlapped for merge %s' % merge else: print 'Overlapping files for merge %s with base %s: %s' % (merge, base, fileschangedboth) for filename in fileschangedboth: simplified_filename = filename.replace('/', '_').replace('.', '_') try: base_content = sp.check_output(('git show %s:%s' % (base, filename)).split()) contrib1_content = sp.check_output(('git show %s:%s' % (contrib1, filename)).split()) contrib2_content = sp.check_output(('git show %s:%s' % (contrib2, filename)).split()) if base_content == contrib1_content or \ base_content == contrib2_content or \ contrib1_content == contrib2_content: print 'this merge was trivial. Skipping.' else: basefilename = '%s/%s_%s_base.txt' % (dirname, merge, simplified_filename) contrib1filename = '%s/%s_%s_contrib1.txt' % (dirname, merge, simplified_filename) contrib2filename = '%s/%s_%s_contrib2.txt' % (dirname, merge, simplified_filename) for filename, content in [(basefilename, base_content), (contrib1filename, contrib1_content), (contrib2filename, contrib2_content)]: with open(filename, 'wb') as f: f.write(content) with open('%s/%s_%s_expected_result.txt' % (dirname, merge, simplified_filename), 'a') as f: pass except sp.CalledProcessError: print 'error from git show, continuing with next file' print 'Input files generated.' print '' print 'To create a reference set of expected_result.txt files, run alignmenttest and copy/move all %s/*_actual_result.txt files to %s/*_expected_result.txt:' % (dirname, dirname) print ' ./alignmenttest > /dev/null' print ' cd %s' % dirname print ' for file in *_actual_result.txt; do mv ${file} ${file/actual/expected}; done' print 'If you\'ve already modified the algorithm, you can run the alignment test of an older version of kdiff3 and copy those result files over'