diff options
author | Daniel Wilhelm <shieldwed@outlook.com> | 2018-05-09 00:04:33 +0200 |
---|---|---|
committer | Daniel Wilhelm <shieldwed@outlook.com> | 2018-05-09 00:04:33 +0200 |
commit | 017e56b81ba735c39c43701f737ac7dde55da7b4 (patch) | |
tree | ea7aaaee13a06a702701e2f74f5d390e10ae303e | |
parent | 9.4 (diff) | |
download | FreeFileSync-017e56b81ba735c39c43701f737ac7dde55da7b4.tar.gz FreeFileSync-017e56b81ba735c39c43701f737ac7dde55da7b4.tar.bz2 FreeFileSync-017e56b81ba735c39c43701f737ac7dde55da7b4.zip |
9.5
139 files changed, 2344 insertions, 1937 deletions
diff --git a/Changelog.txt b/Changelog.txt index 897f7a5d..30904ccb 100755 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,20 @@ +FreeFileSync 9.5 [2017-11-05] +----------------------------- +Allow to change error handling option on progress dialogs +Set up shutdown behavior during sync (summary, exit, sleep, shutdown) +Conditional execution of the post sync command line +Directly use native shutdown/sleep API (Windows and macOS) +Run post sync command even when fail on first error was set +Merged batch and GUI error handling options +Write post sync command to log file +Update GUI-specific options when saving as batch job +Progress graph area matches processed data ratio +Delete files permanently with Shift+Del +Apply correct quotation for CSV-exported folder list +Replace Unicode arrow chars with ASCII for variant description +Updated libcurl, OpenSSL to latest builds + + FreeFileSync 9.4 [2017-10-05] ----------------------------- Fixed copying files with locked byte ranges using VSS diff --git a/FreeFileSync/Build/Help/html/base.css b/FreeFileSync/Build/Help/html/base.css index 001b1ae9..5175281a 100755 --- a/FreeFileSync/Build/Help/html/base.css +++ b/FreeFileSync/Build/Help/html/base.css @@ -87,3 +87,8 @@ table td font-weight: bold; margin-bottom: 5px; } + +.screen-snippet +{ + box-shadow: 1px 1px 2px #888; +} diff --git a/FreeFileSync/Build/Help/html/command-line.html b/FreeFileSync/Build/Help/html/command-line.html index c2e1ac95..c58d5248 100755 --- a/FreeFileSync/Build/Help/html/command-line.html +++ b/FreeFileSync/Build/Help/html/command-line.html @@ -10,14 +10,14 @@ <h1>Command Line Usage</h1> <p> - FreeFileSync supports additional synchronization scenarios via a command line interface. + FreeFileSync supports additional synchronization scenarios via a command line interface. To get a syntax overview, open the console, go to the directory where FreeFileSync is installed and type: </p> - + <div class="greybox"> <span class="command-line">FreeFileSync.exe -h</span> <span style="margin: 0 40px">or</span> <span class="command-line">FreeFileSync.exe --help</span> </div> - + <br> <img src="../images/command-line-syntax.png" alt="Command line syntax"> <br> @@ -64,12 +64,12 @@ If you are running the batch job unattended, make sure your script is not blocked showing a notification dialog. Consider the following options when setting up the FreeFileSync batch job: <div class="half-line"> </div> - + <ul style="margin: 0"> <li>Enable checkbox <b>Run minimized</b> or have <b>On completion</b> automatically close the results dialog after synchronization. - + <li>Set error handling to <b>Stop</b> or <b>Ignore</b> instead of <i>Pop-up</i>. - </ul> + </ul> </div> <br> @@ -104,8 +104,8 @@ <div class="command-line">FreeFileSync.exe "D:\Manual Backup.ffs_gui" "D:\Backup Projects.ffs_batch"</div> </div> <br> - - + + <h2>5. Use a different GlobalSettings.xml file</h2> <p> By default, FreeFileSync uses a single GlobalSettings.xml file containing options that apply to all synchronization tasks; @@ -117,4 +117,4 @@ <div class="command-line">FreeFileSync.exe "D:\My GlobalSettings.xml"</div> </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/comparison-settings.html b/FreeFileSync/Build/Help/html/comparison-settings.html index 951efdef..eb885f4b 100755 --- a/FreeFileSync/Build/Help/html/comparison-settings.html +++ b/FreeFileSync/Build/Help/html/comparison-settings.html @@ -18,12 +18,12 @@ When comparing two folders, FreeFileSync analyses the <b>paths relative to the left and right base folders</b> of the contained files. If the relative path matches, FreeFileSync decides how the file pair is categorized by considering the selected comparison variant: </p> - + <b>I. Compare by <i>File time and size</i></b> <p> This variant considers two files equal when both <b>modification time and file size</b> match. It should be selected when synchronizing files with a backup location. - Whenever a file is changed, its file modification time is also updated. + Whenever a file is changed, its file modification time is also updated. Therefore, a comparison by <i>File Time and size</i> will detect all files that should be synchronized. The following categories are distinguished: </p> @@ -33,7 +33,7 @@ <li>left only <li>right only </ul> - + <li><b>file exists on both sides</b> <ol style="list-style: lower-roman"> <li><b>different date</b> @@ -49,14 +49,14 @@ </ol> </ol> <br> - + <b>II. Compare by <i>File content</i></b> <p> Two files are marked as equal if they have <b>identical content</b>. This variant should be selected when doing consistency checks to see if the files on both sides are bit-wise identical. Naturally, it is the slowest of all comparison variants, so its usefulness for the purpose of synchronization is limited. If used for synchronization, it can serve as a fallback when modification times are not reliable. For example - certain mobile phones and legacy FTP servers do not preserve modification times, so the only way to detect different files when the + certain mobile phones and legacy FTP servers do not preserve modification times, so the only way to detect different files when the file sizes are the same is by reading their content. </p> <ol style="list-style: upper-roman"> @@ -65,7 +65,7 @@ <li>left only <li>right only </ul> - + <li><b>file exists on both sides</b> <ul> <li>equal @@ -86,7 +86,7 @@ <li>left only <li>right only </ul> - + <li><b>file exists on both sides</b> <ul> <li>equal @@ -97,7 +97,7 @@ <h2>Symbolic Link Handling</h2> <p> - FreeFileSync lets you choose to include symbolic links (also called symlinks or soft links) + FreeFileSync lets you choose to include symbolic links (also called symlinks or soft links) when scanning directories rather than skipping over them. When included, you can select between two ways to handle them: </p> @@ -105,8 +105,8 @@ <li><b>Follow:</b> Treat symbolic links like the object they are pointing to. Links pointing to directories are traversed like ordinary directories and - the target of each link is copied during synchronization.<br> - + the target of each link is copied during synchronization.<br> + <li><b>Direct:</b> Evaluate the symbolic link object directly. Symbolic links will be shown as separate entities. @@ -118,10 +118,10 @@ <div class="bluebox"> <b>Note</b> <ul style="margin: 0"> - <li>Under Windows the symbolic link options apply to symbolic links, volume mount points and NTFS junction points. + <li>Under Windows the symbolic link options apply to symbolic links, volume mount points and NTFS junction points. <li>Copying symbolic links requires FreeFileSync to be started with administrator rights. - </ul> + </ul> </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/daylight-saving-time.html b/FreeFileSync/Build/Help/html/daylight-saving-time.html index 70574316..148275e4 100755 --- a/FreeFileSync/Build/Help/html/daylight-saving-time.html +++ b/FreeFileSync/Build/Help/html/daylight-saving-time.html @@ -49,17 +49,17 @@ If the differences are caused by changing the time zone, enter one or more time shifts as needed. <br> </p> - <img src="../images/ignore-time-shift.png" alt="Ignore daylight saving time shift"><br> + <img src="../images/ignore-time-shift.png" class="screen-snippet" alt="Ignore daylight saving time shift"><br> <br> <div class="bluebox"> <b>Note</b><br> - File times have to be equal or differ by exactly the time shift entered to be considered the same. + File times have to be equal or differ by exactly the time shift entered to be considered the same. Therefore, the time shift setting should not be confused with a time interval or tolerance. </div> <br> - + <li>Alternatively, you can avoid the problem in first place by only synchronizing from FAT to FAT or NTFS to NTFS file systems. - Since most local disks are formatted with NTFS and USB memory sticks with FAT, this situation could be handled by formatting the USB stick with NTFS as well. + Since most local disks are formatted with NTFS and USB memory sticks with FAT, this situation could be handled by formatting the USB stick with NTFS as well. </ol> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/exclude-items.html b/FreeFileSync/Build/Help/html/exclude-items.html index 563d4271..7e12a70c 100755 --- a/FreeFileSync/Build/Help/html/exclude-items.html +++ b/FreeFileSync/Build/Help/html/exclude-items.html @@ -20,15 +20,15 @@ <div class="greybox"> <ul style="margin: 0"> <li>Each list item must be a file or directory path <b>relative</b> to synchronization base directories. - + <li>Multiple items must be separated by <b>|</b> or a <b>new line</b>. - + <li>Wild cards <b>*</b> and <b>?</b> may be used: <b>*</b> means zero or more characters while <b>?</b> represents exactly one character. </ul> </div> <br> - <h2>Example: <span style="font-weight:normal">Exclude items for mirror-sync from <span class="file-path">C:\Source</span> to <span class="file-path">D:\Target</span></span></h2> + <h2>Example: <span style="font-weight:normal">Exclude specific items from a mirror-sync, <span class="file-path">C:\Source</span> to <span class="file-path">D:\Target</span></span></h2> <table style="border-spacing:0;"> <tr> @@ -66,30 +66,39 @@ <tr> <td><span class="file-path">*.txt</span> files located in subdirectories of base directories</td> <td><span class="file-path">\*\*.txt</span></td> - </tr> + </tr> </table> <br> - + <h2>Example: <span style="font-weight:normal">Exclude a sub folder except for certain files</span></h2> <p> - Set up <b>two folder pairs</b> with the same source and target paths but with distinct local filters:<br> + Set up <b>two folder pairs</b> with the same source and target paths but with <b>distinct local filters</b>:<br> Folder pair 1; local <em>exclude</em> filter: <span class="file-path">\SubFolder\</span><br> Folder pair 2; local <em>include</em> filter: <span class="file-path">\SubFolder\*.txt</span> </p> <br> - + + <h2>Example: <span style="font-weight:normal">Exclude empty folders</span></h2> + <p> + <img src="../images/filter-zero-file-size.png" class="screen-snippet" alt="Filter with file size zero" style="float: right; margin-left: 10px"> + Set up a <b>file size filter</b> with a lower limit of <b>0 bytes</b>. Both the <em>time span</em> + and <em>file size</em> filters match files only, so this will exclude all folders. + During synchronization however some excluded folders will still be synchronized if they contain at least one non-excluded item, i.e. when they are not empty.<br> + </p> + <br style="clear:both"> + <div class="bluebox"> <b>Note</b> <ul style="margin: 0"> <li>For simple exclusions, just right-click and exclude one item or a list of items directly via the context menu on main dialog. - + <li>A filter phrase is compared against <b>both</b> file and directory paths. If you want to consider directories only, you can give a hint by appending a path separator. - + <li>On Windows both slash (<b>/</b>) and backslash (<b>\</b>) may be used as the path separator character. - </ul> + </ul> </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/expert-settings.html b/FreeFileSync/Build/Help/html/expert-settings.html index dfe76447..4620f863 100755 --- a/FreeFileSync/Build/Help/html/expert-settings.html +++ b/FreeFileSync/Build/Help/html/expert-settings.html @@ -16,8 +16,8 @@ Therefore, you should <b>apply manual changes only while FreeFileSync is not running.</b><br> <br> To locate this file on Windows, enter <b><span class="command-line">%appdata%\FreeFileSync</span></b> in the Windows Explorer address bar or go to the FreeFileSync - installation folder if you are using the portable installation. - On Linux, you can find the file in <b><span class="command-line">~/.FreeFileSync</span></b> for the Launchpad release and in the installation folder for the portable version. + installation folder if you are using the portable installation. + On Linux, you can find the file in <b><span class="command-line">~/.FreeFileSync</span></b> for the Launchpad release and in the installation folder for the portable version. On macOS, go to <b><span class="command-line">~/Library/Application Support/FreeFileSync</span></b>. </p> @@ -64,9 +64,9 @@ In order to prevent multiple synchronization tasks from reading and writing the same files, FreeFileSync instances are serialized with lock files (<span class="file-path">sync.ffs_lock</span>). The lock files are only recognized by FreeFileSync and make sure that at most, - a single synchronization is running against a certain folder at a time while + a single synchronization is running against a certain folder at a time while other instances are queued to wait. - This ensures that only consistent sets of files are subject to synchronization. + This ensures that only consistent sets of files are subject to synchronization. The primary use case are network synchronization scenarios where multiple users run FreeFileSync concurrently against a shared network folder. </p> @@ -88,10 +88,10 @@ The progress logs of the most recent synchronizations (for both GUI and batch jobs) are collected automatically in the file <span class="file-path">LastSyncs.log</span>. The maximum size of this log file can be set here. </p> - + <p> <b>NotificationSound:</b><br> Select sound files from the FreeFileSync installation directory to be played after comparison or synchronization. Set empty names if no sound should be played. - </p> + </p> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/external-applications.html b/FreeFileSync/Build/Help/html/external-applications.html index 3fb8ad5b..5655ff7c 100755 --- a/FreeFileSync/Build/Help/html/external-applications.html +++ b/FreeFileSync/Build/Help/html/external-applications.html @@ -10,18 +10,18 @@ <h1>External Applications</h1> <p> - When you double-click on one of the rows on the main dialog, FreeFileSync opens the operating system's file browser - by default. On Windows, it calls <span class="command-line">explorer /select, "%local_path%"</span>, on + When you double-click on one of the rows on the main dialog, FreeFileSync opens the operating system's file browser + by default. On Windows, it calls <span class="command-line">explorer /select, "%local_path%"</span>, on Linux <span class="command-line">xdg-open "%folder_path%"</span> and on macOS <span class="command-line">open -R "%local_path%"</span>. - To customize this behavior or integrate other external applications into FreeFileSync, + To customize this behavior or integrate other external applications into FreeFileSync, navigate to <b>Menu → Tools → Options → Customize context menu</b> and add or replace a command. </p> - + <p> The <b>first entry</b> will be executed when <b>double-clicking</b> a row on the main grid or when pressing <b>ENTER</b>. All other entries can be accessed quickly by pressing the associated <b>numeric keys</b> or via the context menu that is shown after a right mouse click. </p> - + <p> In addition to regular <a href="macros.html">Macros</a>, the following special macros are available: </p> @@ -45,7 +45,7 @@ <td>Creates a temporary local copy for files located on SFTP and MTP storage. Identical to %item_path% for files on local disks and network shares.</td> </tr> </table> - + <p> <b>Note:</b> To refer to the item on the opposite side, append "2" to the macro name: e.g. <span class="command-line">%item_path2%, %folder_path2%, %local_path2%</span>. @@ -59,16 +59,16 @@ <ul> <li>Start file content comparison (Diff) tool:<br> <div class="command-line">"C:\Program Files (x86)\WinMerge\WinMergeU.exe" "%local_path%" "%local_path2%"</div><br> - + <li>Show file in Windows Explorer:<br> <div class="command-line">explorer /select, "%local_path%"</div><br> - + <li>Open file with associated application:<br> <div class="command-line">"%local_path%"</div><br> - + <li>Open Command Prompt for selected item:<br> <div class="command-line">cmd /k cd /D "%folder_path%"</div><br> - + <li>Write list of selected file paths to a text file:<br> <div class="command-line">cmd /c echo %item_path% >> %csidl_Desktop%\file_list.txt</div><br> @@ -78,7 +78,7 @@ <div class="bluebox"> <b>Note</b><br> - Macros need to be protected with quotation marks if they can resolve to file paths containing whitespace characters. + Macros need to be protected with quotation marks if they can resolve to file paths containing whitespace characters. </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/freefilesync.html b/FreeFileSync/Build/Help/html/freefilesync.html index f9ea1b0f..cd3d648a 100755 --- a/FreeFileSync/Build/Help/html/freefilesync.html +++ b/FreeFileSync/Build/Help/html/freefilesync.html @@ -16,16 +16,16 @@ <ol> <li>Choose left and right folders.<br> - <img src="../images/basic-step-choose-folders.png" alt="Choose left and right directories"> - + <img src="../images/basic-step-choose-folders.png" class="screen-snippet" alt="Choose left and right directories"> + <li><b>Compare</b> them.<br> - <img src="../images/basic-step-compare.png" alt="Start comparison"> - + <img src="../images/basic-step-compare.png" class="screen-snippet" alt="Start comparison"> + <li>Select synchronization settings.<br> - <img src="../images/basic-step-sync-config.png" alt="Select synchronization settings"> - + <img src="../images/basic-step-sync-config.png" class="screen-snippet" alt="Select synchronization settings"> + <li>Press <b>Synchronize</b> to begin synchronization.<br> - <img src="../images/basic-step-start-sync.png" alt="Press Synchronize to begin synchronization"> + <img src="../images/basic-step-start-sync.png" class="screen-snippet" alt="Press Synchronize to begin synchronization"> </ol> <br> @@ -43,11 +43,11 @@ <li>Select left and right folders <li>Save/load configuration <li>Tree overview panel - <li>Synchronization preview + <li>Synchronization preview <li>Select categories to show on grid <li>Synchronization statistics </ol> </div> - <div style="clear:both"></div> + <div style="clear:both"></div> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/macros.html b/FreeFileSync/Build/Help/html/macros.html index a563ce39..b173aeb4 100755 --- a/FreeFileSync/Build/Help/html/macros.html +++ b/FreeFileSync/Build/Help/html/macros.html @@ -31,7 +31,7 @@ <tr> <td><span class="command-line">%time%</span></td> <td><span class="file-path">112233 </span>format: [hhmmss]</td> - </tr> + </tr> <tr> <td><span class="command-line">%timestamp%</span></td> <td><span class="file-path">2016-12-31 112233 </span>format: [YYYY-MM-DD hhmmss]</td> @@ -143,7 +143,7 @@ <tr> <th>Macro</th> <th>Example</th> - </tr> + </tr> <tr> <td><span class="command-line">%csidl_Desktop%</span></td> <td><span class="file-path">C:\Users\Zenju\Desktop</span></td> @@ -201,7 +201,7 @@ <td><span class="file-path">C:\Users\Zenju\AppData\Roaming\Microsoft\Windows\Templates</span></td> </tr> </table> - + <p> <b>Note:</b> Most macros listed here also have a variant for public folders, e.g. <span class="command-line">%csidl_Documents%</span> has <span class="command-line">%csidl_PublicDocuments%</span>. @@ -217,7 +217,7 @@ <h2>Example:</h2> <p> - The FreeFileSync batch file <span class="file-path">C:\SyncJob.ffs_batch</span> contains + The FreeFileSync batch file <span class="file-path">C:\SyncJob.ffs_batch</span> contains macro <span class="command-line">%MyVar%</span> instead of an absolute target folder and is invoked by a cmd file: </p> @@ -235,4 +235,4 @@ FreeFileSync executable directly. Using <span class="command-line">start /wait</span> would create a new program context without these temporary variables. </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/realtimesync.html b/FreeFileSync/Build/Help/html/realtimesync.html index 59bf1389..6053ba62 100755 --- a/FreeFileSync/Build/Help/html/realtimesync.html +++ b/FreeFileSync/Build/Help/html/realtimesync.html @@ -16,16 +16,16 @@ The primary purpose of RealTimeSync is to execute a command line each time it <b>detects changes</b> in one of the monitored directories, or when a <b>directory becomes available</b> (e. g. insert of a USB-stick). Usually this command line will trigger a FreeFileSync batch job.<br> <br> - + RealTimeSync will register to receive change notifications directly from the operating system in order to avoid the overhead of repeatedly polling for changes. - Each time a file or folder is created/updated/deleted in the monitored directories or their sub directories, RealTimeSync will run the command line. + Each time a file or folder is created/updated/deleted in the monitored directories or their sub directories, RealTimeSync will run the command line. <br><br> </p> - + <h2>Example: <span style="font-weight:normal">Real time synchronization - in combination with FreeFileSync</span></h2> <p> - Start RealTimeSync.exe located in FreeFileSync's installation directory and + Start RealTimeSync.exe located in FreeFileSync's installation directory and enter the folders you want to monitor. Instead of doing this manually you can import an ffs_batch file via <b>Menu → File → Open</b> or simply via <b>drag and drop</b>. RealTimeSync will not only extract all directories relevant for synchronization, @@ -43,14 +43,14 @@ <li>The command should <b>not</b> <b>block</b> progress. If you call a FreeFileSync batch job, make sure it won't show any popup dialogs. See notes in <a href="command-line.html">Command Line Usage</a>. <br> - - <li>RealTimeSync will skip showing the main dialog and begin monitoring immediately if - you pass an ffs_real configuration file <b>or</b> a FreeFileSync ffs_batch file as the first - command line argument to RealTimeSync.exe. This can be used to integrate RealTimeSync into the operating system's auto start:<br> + + <li>RealTimeSync will skip showing the main dialog and begin monitoring immediately if + you pass an ffs_real configuration file <b>or</b> a FreeFileSync ffs_batch file as the first + command line argument to RealTimeSync.exe. This can be used to integrate RealTimeSync into the operating system's auto start:<br> <div class="command-line"> "C:\Program Files\FreeFileSync\RealTimeSync.exe" "D:\Backup Projects.ffs_real"</div> <div class="command-line"> "C:\Program Files\FreeFileSync\RealTimeSync.exe" "D:\Backup Projects.ffs_batch"</div> <br> - + <li>RealTimeSync is not required to start FreeFileSync. It can also be used in other scenarios, like sending an email whenever a certain directory is modified. </ul> </div> @@ -58,16 +58,16 @@ <h2>Example: <span style="font-weight:normal">Automatic synchronization when a USB stick is inserted</span></h2> <p> - Save an ffs_batch configuration in the USB stick's root directory, + Save an ffs_batch configuration in the USB stick's root directory, e.g. <span class="file-path">H:\</span> and let FreeFileSync run it when the stick is mounted. - But, instead of hard coding the USB drive letter <span class="file-path">H:\</span> (which may change occasionally), + But, instead of hard coding the USB drive letter <span class="file-path">H:\</span> (which may change occasionally), refer to the USB stick via its <a href="variable-drive-letters.html">volume name</a> instead.<br> <br> Configure RealTimeSync as follows:<br> </p> <div style="display:inline-block; margin-left: 1.3cm; text-align: center;"> <img src="../images/realtimesync-monitor-usb.png" alt="Monitor USB stick insert"><br> - + <i>"Backup" is the volume name of the USB stick in our example.</i> </div> @@ -77,7 +77,7 @@ </p> <div class="bluebox"> - <b>Note</b><br> + <b>Note</b><br> The full path of the last changed file and the action that triggered the change notification (create, update or delete) are written to the environment variables <b><span class="command-line">%change_path%</span></b> and <b><span class="command-line">%change_action%</span></b>. @@ -87,13 +87,13 @@ <h2>Example: <span style="font-weight:normal">Log names of changed files and directories (Windows)</span></h2> <p> <div class="greybox"> - Show which file or directory has triggered a change. Enter command line:<br> + Show which file or directory has triggered a change. Enter command line:<br> <div class="command-line"> cmd /c echo %change_action% "%change_path%" & pause </div> <br> - - Write a list of all changes to a log file:<br> + + Write a list of all changes to a log file:<br> <div class="command-line"> cmd /c echo %change_action% "%change_path%" >> %csidl_Desktop%\log.txt </div> @@ -121,4 +121,4 @@ This makes sure the monitored folders are not in heavy use. In any case, files changed during the execution of FreeFileSync will be synchronized the next time FreeFileSync runs. </p> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/run-as-service.html b/FreeFileSync/Build/Help/html/run-as-service.html index 6175ee7b..27fc39ee 100755 --- a/FreeFileSync/Build/Help/html/run-as-service.html +++ b/FreeFileSync/Build/Help/html/run-as-service.html @@ -32,12 +32,12 @@ RealTimeSync should start monitoring when a user logs in:<br> Create a new shortcut, enter the command line from above as target and place it into the Windows autostart folder. (Enter <span class="command-line"><b>shell:startup</b></span> in the Windows Explorer address bar to find the folder quickly.) - + </p> <img src="../images/realtimesync-create-shortcut.png" alt="Create shortcut"><br><br> <img src="../images/realtimesync-shortcut-properties.png" alt="Shortcut properties"> <br> - + <li><p> RealTimeSync should be monitoring while Windows is running, irrespective of currently logged in users:<br> Create a new task in your operating systems's task scheduler and have it execute the command line above when the system starts. @@ -47,4 +47,4 @@ <img src="../images/realtimesync-schedule.png" alt="Schedule RealTimeSync"> </ol> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/schedule-a-batch-job.html b/FreeFileSync/Build/Help/html/schedule-a-batch-job.html index 8f99875a..17f5db64 100755 --- a/FreeFileSync/Build/Help/html/schedule-a-batch-job.html +++ b/FreeFileSync/Build/Help/html/schedule-a-batch-job.html @@ -14,25 +14,25 @@ <br> <img src="../images/setup-batch-job.png" alt="Setup a FreeFileSync batch job"> <br><br> - - + + <li>By default, FreeFileSync will show a progress dialog during synchronization and will wait while the results dialog is shown. If the progress dialog is not needed, enable checkbox <b>Run minimized</b>. This will also skip the results dialog at the end. - <br><br> + <br><br> Alternatively, if you want to see the progress, but not to wait at the results dialog, it's sufficient to only select the <i>On completion</i> action <b>Close progress dialog</b>. <br><br> - + <div class="bluebox"> <b>Note</b><br> - Even if the progress dialog is not shown at the beginning, you can make it visible later <b>during</b> + Even if the progress dialog is not shown at the beginning, you can make it visible later <b>during</b> synchronization by double-clicking the FreeFileSync icon in the notification area. </div> <br> <li>If you don't want error or warning messages to interrupt synchronization, set <b>Handle errors</b> to either <b>Ignore</b> or <b>Stop</b>.<br> - - <li>If log files are required, enable <b>Save log</b> and enter a folder path. + + <li>If log files are required, enable <b>Save log</b> and enter a folder path. If the path is left empty, the logs will be saved under the current user's roaming profile, <span class="file-path">%appdata%\FreeFileSync\Logs</span>.<br> Additionally, FreeFileSync always stores the result of the last @@ -40,23 +40,23 @@ <li>Set up the FreeFileSync batch job in your operating system's scheduler:<br> </ol> - + <br> <hr/> - + <h2>A. Windows Task Scheduler:</h2> <ul> <li>Open the Task Scheduler either via the start menu, or enter <span class="command-line"><b>taskschd.msc</b></span> in the run dialog (keyboard shortcut: Windows + R). - - <li>Create a new <b>basic task</b> and follow the wizard. - + + <li>Create a new <b>basic task</b> and follow the wizard. + <li>Make <b>Program/script</b> point to the location of FreeFileSync.exe and insert the ffs_batch file into <b>Add arguments</b>. - + <li>Use quotation marks to protect spaces in path names, e.g. <span class="file-path">"D:\Backup Projects.ffs_batch"</span><br> <br> <img src="../images/windows-scheduler.png" alt="Windows Task Scheduler"> - </ul> - + </ul> + <div class="bluebox"> <b>Note</b><br> <ul> @@ -64,63 +64,63 @@ when the ffs_batch file association is registered. If an ffs_batch file was entered instead, the task would return with error code 2147942593 (0x800700C1), "%1 is not a valid Win32 application".<br> For Windows 8 and later this limitation does not apply and you may enter the ffs_batch file path directly into <i>Program/script</i> and leave out <i>Add arguments</i>. - + <li>If you schedule FreeFileSync to run under a different user account, note that settings (e.g. <span class="file-path">GlobalSettings.xml</span>) will also be read from a different path, <span class="file-path">C:\Users\<username>\AppData\Roaming\FreeFileSync</span>, or in the case of the SYSTEM account from <span class="file-path">C:\Windows\System32\config\systemprofile\AppData\Roaming\FreeFileSync</span>. </ul> </div> <br> - + <hr/> - + <h2>B. macOS Automator and Calendar:</h2> <ul> <li>Open Launchpad and run <b>Automator</b>.<br> - <img src="../images/launch-automator.png" alt="Launch macOS Automator"><br> - + <img src="../images/launch-automator.png" class="screen-snippet" alt="Launch macOS Automator"><br> + <li>Create a new <b>Calendar Alarm</b>.<br> <img src="../images/new-calendar-alarm.png" alt="Create Calendar Alarm"><br> - + <li>Drag and drop the ffs_batch file on the workflow panel.<br> - <img src="../images/automator-file-dropped.png" alt="Drop FreeFileSync batch file in Automator"><br> - + <img src="../images/automator-file-dropped.png" class="screen-snippet" alt="Drop FreeFileSync batch file in Automator"><br> + <li>Drag and drop action <i>Files & Folders/Open Finder Items</i> and add it to the workflow.<br> <img src="../images/open-finder-items.png" alt="Add open Finder items"><br> - + <li>Go to <b>File → Save...</b> and save the Automator job.<br> - <img src="../images/save-automator.png" alt="Save Automator job"><br> - + <img src="../images/save-automator.png" class="screen-snippet" alt="Save Automator job"><br> + <li>The Calendar app will start automatically with the Automator job scheduled to the current day. You can now select a different time for synchronization or make it a recurring task.<br> - <img src="../images/calendar-job-added.png" alt="Edit batch job in Calendar"><br> + <img src="../images/calendar-job-added.png" class="screen-snippet" alt="Edit batch job in Calendar"><br> </ul> <hr/> - + <h2>C. Windows XP Scheduled Tasks:</h2> <ul> <li>Go to <b>Start → Control Panel → Scheduled Tasks</b> and select <b>Add Scheduled Task</b>. - + <li>Follow the wizard and select <span class="file-path">FreeFileSync.exe</span> as program to run. - + <li>Fill the input field <b>Run:</b> <span class="command-line"><FreeFileSync installation folder>\FreeFileSync.exe <job name>.ffs_batch</span><br> <br> <img src="../images/xp-scheduler.png" alt="Windows XP Task Scheduler"><br> - </ul> + </ul> <hr/> - + <h2>D. Ubuntu Linux Gnome Scheduled Tasks:</h2> <ul> <li>Install Gnome-schedule if necessary: <span class="command-line">sudo apt-get install gnome-schedule</span> - + <li>Search the Ubuntu Unity Dash for <b>Scheduled tasks</b> - + <li>Enter the command: <span class="command-line"><FreeFileSync installation folder>/FreeFileSync <job name>.ffs_batch</span><br> - + <li>Select <b>X application</b> since FreeFileSync requires access to GUI <br> - <img src="../images/gnome-scheduler.png" alt="Gnome Scheduler"> + <img src="../images/gnome-scheduler.png" class="screen-snippet" alt="Gnome Scheduler"> </ul> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/synchronization-settings.html b/FreeFileSync/Build/Help/html/synchronization-settings.html index dfe31444..0f4f4fe7 100755 --- a/FreeFileSync/Build/Help/html/synchronization-settings.html +++ b/FreeFileSync/Build/Help/html/synchronization-settings.html @@ -33,7 +33,7 @@ the database files are available to determine moved files. <li>Detection is not supported by all file systems. Most notably, certain file moves on the FAT file system cannot be detected. Also virtualized file systems, e.g. a mounted WebDAV drive, might not support move detection. In these cases FreeFileSync will automatically fall back to copy and delete. - </ul> + </ul> </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/synchronize-with-sftp.html b/FreeFileSync/Build/Help/html/synchronize-with-sftp.html index 50b119b2..f3d853bf 100755 --- a/FreeFileSync/Build/Help/html/synchronize-with-sftp.html +++ b/FreeFileSync/Build/Help/html/synchronize-with-sftp.html @@ -15,15 +15,15 @@ <br> <img src="../images/sftp-login.png" alt="Enter SFTP login data"> </p> - + <div class="bluebox"> - <b>Note</b><br>In case the SFTP server sets file modification times to the current time + <b>Note</b><br>In case the SFTP server sets file modification times to the current time you can do a <a href="comparison-settings.html">Compare by File Size</a> as a workaround. Another solution is to set up the <i>Two way</i> variant and have the files with the newer dates be copied back from the server during the next synchronization. - </div> + </div> <br> - + <h2>Set up SFTP for best performance</h2> <p> By default, FreeFileSync creates one connection to the server and uses one SFTP channel, i.e. only a single SFTP command can be sent and received at a time. @@ -34,19 +34,19 @@ <br><br> <b>Example</b>: 2 connections using 10 channels each can yield a <b>20</b> times faster directory reading. <br><br> - <img src="../images/sftp-performance.png" alt="Set up SFTP for best performance"> - + <img src="../images/sftp-performance.png" class="screen-snippet" alt="Set up SFTP for best performance"> + <ul style="margin: 0"> <li>The creation of additional connections and channels takes time. If you are only scanning a small remote directory, setting up too many connections and channels might actually slow the overall process down. Creating extra connections is slower than creating extra channels.<br> - + <li>SFTP servers have internal limits on the number of allowed connections and channels. Generally, servers expect one connection per user, so this number should be kept rather low. If too many connections and channels are used, the server may decide to stop responding. </ul> </p> - + <div class="bluebox"> <b>Advice</b><br>Start with low numbers and make tests with different combinations of connections and channels for your particular SFTP synchronization scenario to see what gives the highest speed. @@ -54,7 +54,7 @@ Therefore, you should <b>restart</b> FreeFileSync before measuring SFTP speed. </div> <br> - + <h1>Synchronize with SFTP <span style="font-weight: normal">(Linux)</span></h1> <p>An SFTP share can be mapped to a local folder for use with FreeFileSync:</p> @@ -63,13 +63,13 @@ <ul style="margin: 0"> <li>Install: <div class="command-line">sudo apt-get install sshfs</div><br> - + <li>Mount SFTP share: <div class="command-line">sshfs ssh-account@ssh-server:<path> mountpoint</div><br> - + <li>Unmount:<br> <div class="command-line">fusermount -u mountpoint</div> </ul> </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/tips-and-tricks.html b/FreeFileSync/Build/Help/html/tips-and-tricks.html index ca72cfc7..06b69e33 100755 --- a/FreeFileSync/Build/Help/html/tips-and-tricks.html +++ b/FreeFileSync/Build/Help/html/tips-and-tricks.html @@ -110,4 +110,4 @@ <img src="../images/synchronization-variant-double-click.png" alt="Double-click synchronization variant"> <br> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/variable-drive-letters.html b/FreeFileSync/Build/Help/html/variable-drive-letters.html index 7acc19a0..008b32c4 100755 --- a/FreeFileSync/Build/Help/html/variable-drive-letters.html +++ b/FreeFileSync/Build/Help/html/variable-drive-letters.html @@ -26,7 +26,7 @@ <div class="bluebox"> <b>Note</b><br> It is not required to look up and enter the volume name manually. Just select the corresponding entry in the drop down menu.<br> - <img src="../images/path-by-volume-name.png" alt="Drive letter by volume name"> + <img src="../images/path-by-volume-name.png" class="screen-snippet" alt="Drive letter by volume name"> </div> <br> @@ -35,14 +35,14 @@ <div class="greybox"> <ul style="margin: 0"> <li>Use <span class="file-path">\folder</span> instead of <span class="file-path">E:\folder</span> - + <li>Save and copy synchronization settings to the USB stick: <span class="file-path">E:\Backup.ffs_gui</span> - + <li>Start FreeFileSync by double-clicking on <span class="file-path">E:\Backup.ffs_gui</span><br> </ul> <br> The working directory is then automatically set to <span class="file-path">E:\</span> by the operating system so that the - relative path <span class="file-path">\folder</span> will be resolved as <span class="file-path">E:\folder</span> during synchronization. + relative path <span class="file-path">\folder</span> will be resolved as <span class="file-path">E:\folder</span> during synchronization. </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/versioning.html b/FreeFileSync/Build/Help/html/versioning.html index acc22fa7..4a1d1f99 100755 --- a/FreeFileSync/Build/Help/html/versioning.html +++ b/FreeFileSync/Build/Help/html/versioning.html @@ -23,10 +23,10 @@ <b>Replace</b>. Deleted files will be moved to the specified folder without any decoration and will replace already existing older versions.<br> - <img src="../images/versioning.png" alt="Versioning"> + <img src="../images/versioning.png" class="screen-snippet" alt="Versioning"> <br><br> </p> - + <h2>2. Keep all versions of old files</h2> <p> Set deletion handling to <b>Versioning</b> @@ -46,7 +46,7 @@ C:\Revisions\Folder\File.txt <b>2012-12-12 111111</b>.txt<br> C:\Revisions\Folder\File.txt <b>2012-12-12 122222</b>.txt<br> C:\Revisions\Folder\File.txt <b>2012-12-12 133333</b>.txt - </div> + </div> </div> <br> @@ -61,10 +61,10 @@ <p><b>Example:</b> Using the dynamically generated folder name <span class="file-path">C:\Revisions\%timestamp%</span></p> <div class="greybox"> - <div class="file-path"> + <div class="file-path"> C:\Revisions\<b>2012-12-12 111111</b>\Folder\File.txt<br> C:\Revisions\<b>2012-12-12 122222</b>\Folder\File.txt<br> - C:\Revisions\<b>2012-12-12 133333</b>\Folder\File.txt + C:\Revisions\<b>2012-12-12 133333</b>\Folder\File.txt </div> </div> <p> @@ -74,4 +74,4 @@ to days and weeks. </p> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/html/volume-shadow-copy.html b/FreeFileSync/Build/Help/html/volume-shadow-copy.html index cc897e3b..c9004c35 100755 --- a/FreeFileSync/Build/Help/html/volume-shadow-copy.html +++ b/FreeFileSync/Build/Help/html/volume-shadow-copy.html @@ -19,7 +19,7 @@ <ul style="margin: 0"> <li>The volume snapshot created by the Volume Shadow Copy Service is only used for copying files that are actually locked. <li>Accessing the Volume Shadow Copy Service requires FreeFileSync to be started with administrator rights. - </ul> + </ul> </div> <br> @@ -52,4 +52,4 @@ Reference: <a rel="nofollow" target="_blank" href="http://support.microsoft.com/kb/940032">http://support.microsoft.com/kb/940032</a> </p> </body> -</html>
\ No newline at end of file +</html> diff --git a/FreeFileSync/Build/Help/images/add-folder-pair.png b/FreeFileSync/Build/Help/images/add-folder-pair.png Binary files differindex ee525bcb..eb4236c0 100755 --- a/FreeFileSync/Build/Help/images/add-folder-pair.png +++ b/FreeFileSync/Build/Help/images/add-folder-pair.png diff --git a/FreeFileSync/Build/Help/images/basic-step-choose-folders.png b/FreeFileSync/Build/Help/images/basic-step-choose-folders.png Binary files differindex 27c897b2..e0887b0a 100755 --- a/FreeFileSync/Build/Help/images/basic-step-choose-folders.png +++ b/FreeFileSync/Build/Help/images/basic-step-choose-folders.png diff --git a/FreeFileSync/Build/Help/images/com-settings-context.png b/FreeFileSync/Build/Help/images/com-settings-context.png Binary files differindex 8024d4bf..690d7bbd 100755 --- a/FreeFileSync/Build/Help/images/com-settings-context.png +++ b/FreeFileSync/Build/Help/images/com-settings-context.png diff --git a/FreeFileSync/Build/Help/images/command-line-syntax.png b/FreeFileSync/Build/Help/images/command-line-syntax.png Binary files differindex 4a96a267..53f47122 100755 --- a/FreeFileSync/Build/Help/images/command-line-syntax.png +++ b/FreeFileSync/Build/Help/images/command-line-syntax.png diff --git a/FreeFileSync/Build/Help/images/comparison-settings.png b/FreeFileSync/Build/Help/images/comparison-settings.png Binary files differindex 4ea72934..371b28ed 100755 --- a/FreeFileSync/Build/Help/images/comparison-settings.png +++ b/FreeFileSync/Build/Help/images/comparison-settings.png diff --git a/FreeFileSync/Build/Help/images/comparison-variant-double-click.png b/FreeFileSync/Build/Help/images/comparison-variant-double-click.png Binary files differindex 8af50d42..eade94b5 100755 --- a/FreeFileSync/Build/Help/images/comparison-variant-double-click.png +++ b/FreeFileSync/Build/Help/images/comparison-variant-double-click.png diff --git a/FreeFileSync/Build/Help/images/copy-alternative-path.png b/FreeFileSync/Build/Help/images/copy-alternative-path.png Binary files differindex a934019e..bded2b56 100755 --- a/FreeFileSync/Build/Help/images/copy-alternative-path.png +++ b/FreeFileSync/Build/Help/images/copy-alternative-path.png diff --git a/FreeFileSync/Build/Help/images/donate.png b/FreeFileSync/Build/Help/images/donate.png Binary files differdeleted file mode 100755 index e620d363..00000000 --- a/FreeFileSync/Build/Help/images/donate.png +++ /dev/null diff --git a/FreeFileSync/Build/Help/images/filter-zero-file-size.png b/FreeFileSync/Build/Help/images/filter-zero-file-size.png Binary files differnew file mode 100755 index 00000000..59ce29fe --- /dev/null +++ b/FreeFileSync/Build/Help/images/filter-zero-file-size.png diff --git a/FreeFileSync/Build/Help/images/filter.png b/FreeFileSync/Build/Help/images/filter.png Binary files differindex d2ad979d..9e688264 100755 --- a/FreeFileSync/Build/Help/images/filter.png +++ b/FreeFileSync/Build/Help/images/filter.png diff --git a/FreeFileSync/Build/Help/images/ignore-time-shift.png b/FreeFileSync/Build/Help/images/ignore-time-shift.png Binary files differindex 4c19e893..fe9361be 100755 --- a/FreeFileSync/Build/Help/images/ignore-time-shift.png +++ b/FreeFileSync/Build/Help/images/ignore-time-shift.png diff --git a/FreeFileSync/Build/Help/images/main-window.png b/FreeFileSync/Build/Help/images/main-window.png Binary files differindex b4e5b034..2b51d3a6 100755 --- a/FreeFileSync/Build/Help/images/main-window.png +++ b/FreeFileSync/Build/Help/images/main-window.png diff --git a/FreeFileSync/Build/Help/images/realtimesync-monitor-usb.png b/FreeFileSync/Build/Help/images/realtimesync-monitor-usb.png Binary files differindex 68413b6e..9f83fba3 100755 --- a/FreeFileSync/Build/Help/images/realtimesync-monitor-usb.png +++ b/FreeFileSync/Build/Help/images/realtimesync-monitor-usb.png diff --git a/FreeFileSync/Build/Help/images/remove-drop-down-path.png b/FreeFileSync/Build/Help/images/remove-drop-down-path.png Binary files differindex 2d41fdbb..863349ca 100755 --- a/FreeFileSync/Build/Help/images/remove-drop-down-path.png +++ b/FreeFileSync/Build/Help/images/remove-drop-down-path.png diff --git a/FreeFileSync/Build/Help/images/remove-local-settings.png b/FreeFileSync/Build/Help/images/remove-local-settings.png Binary files differindex ac39ad83..5acd9962 100755 --- a/FreeFileSync/Build/Help/images/remove-local-settings.png +++ b/FreeFileSync/Build/Help/images/remove-local-settings.png diff --git a/FreeFileSync/Build/Help/images/select-time-span.png b/FreeFileSync/Build/Help/images/select-time-span.png Binary files differindex ea6be45f..ea0cb37c 100755 --- a/FreeFileSync/Build/Help/images/select-time-span.png +++ b/FreeFileSync/Build/Help/images/select-time-span.png diff --git a/FreeFileSync/Build/Help/images/setup-batch-job.png b/FreeFileSync/Build/Help/images/setup-batch-job.png Binary files differindex ef7b5b9d..2545af21 100755 --- a/FreeFileSync/Build/Help/images/setup-batch-job.png +++ b/FreeFileSync/Build/Help/images/setup-batch-job.png diff --git a/FreeFileSync/Build/Help/images/sftp-login.png b/FreeFileSync/Build/Help/images/sftp-login.png Binary files differindex 1e5f05ca..b1d3f60b 100755 --- a/FreeFileSync/Build/Help/images/sftp-login.png +++ b/FreeFileSync/Build/Help/images/sftp-login.png diff --git a/FreeFileSync/Build/Help/images/sftp-performance.png b/FreeFileSync/Build/Help/images/sftp-performance.png Binary files differindex 309e373e..4e2d1528 100755 --- a/FreeFileSync/Build/Help/images/sftp-performance.png +++ b/FreeFileSync/Build/Help/images/sftp-performance.png diff --git a/FreeFileSync/Build/Help/images/synchronization-settings.png b/FreeFileSync/Build/Help/images/synchronization-settings.png Binary files differindex cd778caf..c5aded2a 100755 --- a/FreeFileSync/Build/Help/images/synchronization-settings.png +++ b/FreeFileSync/Build/Help/images/synchronization-settings.png diff --git a/FreeFileSync/Build/Help/images/two-folder-drop.png b/FreeFileSync/Build/Help/images/two-folder-drop.png Binary files differindex 81e1f12e..fe63d164 100755 --- a/FreeFileSync/Build/Help/images/two-folder-drop.png +++ b/FreeFileSync/Build/Help/images/two-folder-drop.png diff --git a/FreeFileSync/Build/Help/images/versioning.png b/FreeFileSync/Build/Help/images/versioning.png Binary files differindex 1bc4643c..f9fd5b56 100755 --- a/FreeFileSync/Build/Help/images/versioning.png +++ b/FreeFileSync/Build/Help/images/versioning.png diff --git a/FreeFileSync/Build/Help/images/view-filter-default.png b/FreeFileSync/Build/Help/images/view-filter-default.png Binary files differindex fdc6ca49..1a725dec 100755 --- a/FreeFileSync/Build/Help/images/view-filter-default.png +++ b/FreeFileSync/Build/Help/images/view-filter-default.png diff --git a/FreeFileSync/Build/Languages/arabic.lng b/FreeFileSync/Build/Languages/arabic.lng index 36435abb..ed471da1 100755 --- a/FreeFileSync/Build/Languages/arabic.lng +++ b/FreeFileSync/Build/Languages/arabic.lng @@ -1198,11 +1198,11 @@ The command is triggered if: <source>Time elapsed:</source> <target>الوقت المنقضي:</target> -<source>Bytes:</source> -<target>بايت:</target> +<source>Bytes</source> +<target>بايت</target> -<source>Items:</source> -<target>العناصر:</target> +<source>Items</source> +<target>العناصر</target> <source>Synchronizing...</source> <target>مزامنة...</target> diff --git a/FreeFileSync/Build/Languages/bulgarian.lng b/FreeFileSync/Build/Languages/bulgarian.lng index 9e4160b1..9aba3ab4 100755 --- a/FreeFileSync/Build/Languages/bulgarian.lng +++ b/FreeFileSync/Build/Languages/bulgarian.lng @@ -1174,11 +1174,11 @@ The command is triggered if: <source>Time elapsed:</source> <target>Изминало време:</target> -<source>Bytes:</source> -<target>Байтове:</target> +<source>Bytes</source> +<target>Байтове</target> -<source>Items:</source> -<target>Елементи:</target> +<source>Items</source> +<target>Елементи</target> <source>Synchronizing...</source> <target>Синхронизация...</target> diff --git a/FreeFileSync/Build/Languages/chinese_simple.lng b/FreeFileSync/Build/Languages/chinese_simple.lng index bceddef0..8559381d 100755 --- a/FreeFileSync/Build/Languages/chinese_simple.lng +++ b/FreeFileSync/Build/Languages/chinese_simple.lng @@ -1168,11 +1168,11 @@ The command is triggered if: <source>Time elapsed:</source> <target>已用时间:</target> -<source>Bytes:</source> -<target>字节:</target> +<source>Bytes</source> +<target>字节</target> -<source>Items:</source> -<target>项目:</target> +<source>Items</source> +<target>项目</target> <source>Synchronizing...</source> <target>同步中...</target> diff --git a/FreeFileSync/Build/Languages/chinese_traditional.lng b/FreeFileSync/Build/Languages/chinese_traditional.lng index fb5dcc2e..800f38c1 100755 --- a/FreeFileSync/Build/Languages/chinese_traditional.lng +++ b/FreeFileSync/Build/Languages/chinese_traditional.lng @@ -1168,11 +1168,11 @@ The command is triggered if: <source>Time elapsed:</source> <target>經過時間:</target> -<source>Bytes:</source> -<target>位元組:</target> +<source>Bytes</source> +<target>位元組</target> -<source>Items:</source> -<target>項目:</target> +<source>Items</source> +<target>項目</target> <source>Synchronizing...</source> <target>正在同步…</target> diff --git a/FreeFileSync/Build/Languages/croatian.lng b/FreeFileSync/Build/Languages/croatian.lng index 67ffe110..c30f7c13 100755 --- a/FreeFileSync/Build/Languages/croatian.lng +++ b/FreeFileSync/Build/Languages/croatian.lng @@ -1180,11 +1180,11 @@ Naredba će biti pokrenuta ako se: <source>Time elapsed:</source> <target>Proteklo vremena:</target> -<source>Bytes:</source> -<target>Bajta:</target> +<source>Bytes</source> +<target>Bajta</target> -<source>Items:</source> -<target>Stavke:</target> +<source>Items</source> +<target>Stavke</target> <source>Synchronizing...</source> <target>Sinkroniziranje...</target> diff --git a/FreeFileSync/Build/Languages/czech.lng b/FreeFileSync/Build/Languages/czech.lng index f03cae26..48c27dec 100755 --- a/FreeFileSync/Build/Languages/czech.lng +++ b/FreeFileSync/Build/Languages/czech.lng @@ -1180,11 +1180,11 @@ Příkaz je spuštěn když: <source>Time elapsed:</source> <target>Uplynulý čas:</target> -<source>Bytes:</source> -<target>Bajtů:</target> +<source>Bytes</source> +<target>Bajtů</target> -<source>Items:</source> -<target>Položek:</target> +<source>Items</source> +<target>Položek</target> <source>Synchronizing...</source> <target>Synchronizuji...</target> diff --git a/FreeFileSync/Build/Languages/danish.lng b/FreeFileSync/Build/Languages/danish.lng index 3400b751..3e64b5be 100755 --- a/FreeFileSync/Build/Languages/danish.lng +++ b/FreeFileSync/Build/Languages/danish.lng @@ -1174,11 +1174,11 @@ Kommandoen udføres hvis: <source>Time elapsed:</source> <target>Brugt tid:</target> -<source>Bytes:</source> -<target>Bytes:</target> +<source>Bytes</source> +<target>Bytes</target> -<source>Items:</source> -<target>Emner:</target> +<source>Items</source> +<target>Emner</target> <source>Synchronizing...</source> <target>Synkroniserer...</target> diff --git a/FreeFileSync/Build/Languages/dutch.lng b/FreeFileSync/Build/Languages/dutch.lng index c28c07aa..572441f3 100755 --- a/FreeFileSync/Build/Languages/dutch.lng +++ b/FreeFileSync/Build/Languages/dutch.lng @@ -1174,11 +1174,11 @@ De opdracht wordt geactiveerd als: <source>Time elapsed:</source> <target>Verstreken tijd:</target> -<source>Bytes:</source> -<target>Bytes:</target> +<source>Bytes</source> +<target>Bytes</target> -<source>Items:</source> -<target>Objecten:</target> +<source>Items</source> +<target>Objecten</target> <source>Synchronizing...</source> <target>Synchroniseren...</target> diff --git a/FreeFileSync/Build/Languages/english_uk.lng b/FreeFileSync/Build/Languages/english_uk.lng index 14ab1a32..3162a2d3 100755 --- a/FreeFileSync/Build/Languages/english_uk.lng +++ b/FreeFileSync/Build/Languages/english_uk.lng @@ -7,13 +7,28 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> +<source>The FreeFileSync portable version cannot install into a subfolder of %x.</source> +<target></target> + <source>Please enter a file path.</source> <target></target> +<source>Recommended range:</source> +<target></target> + +<source>Sleep</source> +<target></target> + +<source>FreeFileSync %x is available!</source> +<target></target> + <source>Support with a donation</source> <target></target> -<source>4 connections = 4 times faster directory reading</source> +<source>&Cancel</source> +<target></target> + +<source>Error dialog:</source> <target></target> <source>Connections for directory reading:</source> @@ -22,12 +37,21 @@ <source>&SSH agent</source> <target></target> +<source>Stop on errors</source> +<target></target> + <source>Ignore &all</source> <target></target> <source>The %x protocol does not support directory monitoring:</source> <target></target> +<source>Open the selected configuration for editing only without executing it.</source> +<target></target> + +<source>The recycle bin is not supported by the following folders. Deleted or overwritten files will not be able to be restored:</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>Both sides have changed since last synchronisation.</target> @@ -73,9 +97,6 @@ <source>Checking recycle bin availability for folder %x...</source> <target>Checking recycle bin availability for folder %x...</target> -<source>The recycle bin is not available for the following folders. Files will be deleted permanently instead:</source> -<target>The recycle bin is not available for the following folders. Files will be deleted permanently instead:</target> - <source>An exception occurred</source> <target>An exception occurred</target> @@ -124,9 +145,6 @@ <source>Any number of alternative directory pairs for at most one config file.</source> <target>Any number of alternative directory pairs for at most one config file.</target> -<source>Open configuration for editing without executing it.</source> -<target>Open configuration for editing without executing it.</target> - <source>Path to an alternate GlobalSettings.xml file.</source> <target>Path to an alternate GlobalSettings.xml file.</target> @@ -989,6 +1007,9 @@ The command is triggered if: <source>Select a variant:</source> <target>Select a variant:</target> +<source>Detect synchronization directions with the help of database files</source> +<target>Detect synchronisation directions with the help of database files</target> + <source>Include &symbolic links:</source> <target>Include &symbolic links:</target> @@ -1040,9 +1061,6 @@ The command is triggered if: <source>C&lear</source> <target>C&lear</target> -<source>Detect synchronization directions with the help of database files</source> -<target>Detect synchronisation directions with the help of database files</target> - <source>Detect moved files</source> <target>Detect moved files</target> @@ -1063,36 +1081,18 @@ The command is triggered if: <source>&Recycle bin</source> <target>&Recycle bin</target> -<source>Back up deleted and overwritten files in the recycle bin</source> -<target>Back up deleted and overwritten files in the recycle bin</target> - <source>&Permanent</source> <target>&Permanent</target> -<source>Delete or overwrite files permanently</source> -<target>Delete or overwrite files permanently</target> - <source>&Versioning</source> <target>&Versioning</target> -<source>Move files to a user-defined folder</source> -<target>Move files to a user-defined folder</target> - <source>Naming convention:</source> <target>Naming convention:</target> -<source>Handle errors:</source> -<target>Handle errors:</target> - -<source>&Pop-up</source> -<target>&Pop-up</target> - <source>Show pop-up on errors or warnings</source> <target>Show pop-up on errors or warnings</target> -<source>Hide all error and warning messages</source> -<target>Hide all error and warning messages</target> - <source>On completion:</source> <target>On completion:</target> @@ -1150,18 +1150,12 @@ The command is triggered if: <source>How to get best performance?</source> <target>How to get best performance?</target> -<source>Suggested range: [1 - 10]</source> -<target>Suggested range: [1 - 10]</target> - <source>SFTP channels per connection:</source> <target>SFTP channels per connection:</target> <source>Detect server limit</source> <target>Detect server limit</target> -<source>2 connections x 10 channels = 20 times faster directory reading</source> -<target>2 connections x 10 channels = 20 times faster directory reading</target> - <source>Select a directory on the server:</source> <target>Select a directory on the server:</target> @@ -1186,11 +1180,11 @@ The command is triggered if: <source>Time elapsed:</source> <target>Time elapsed:</target> -<source>Bytes:</source> -<target>Bytes:</target> +<source>Bytes</source> +<target>Bytes</target> -<source>Items:</source> -<target>Items:</target> +<source>Items</source> +<target>Items</target> <source>Synchronizing...</source> <target>Synchronising...</target> @@ -1213,8 +1207,8 @@ The command is triggered if: <source>Create a batch file for unattended synchronization. To start, double-click this file or schedule in a task planner: %x</source> <target>Create a batch file for unattended synchronisation. To start, double-click this file or schedule in a task planner: %x</target> -<source>&Stop</source> -<target>&Stop</target> +<source>&Show</source> +<target>&Show</target> <source>Stop synchronization at first error</source> <target>Stop synchronisation at first error</target> @@ -1378,9 +1372,6 @@ This guarantees a consistent state even in case of a serious error. <source>&Show details</source> <target>&Show details</target> -<source>A new version of FreeFileSync is available:</source> -<target>A new version of FreeFileSync is available:</target> - <source>Local path not available for %x.</source> <target>Local path not available for %x.</target> @@ -1573,12 +1564,6 @@ This guarantees a consistent state even in case of a serious error. <source>Close progress dialog</source> <target>Close progress dialogue</target> -<source>Log off</source> -<target>Log off</target> - -<source>Standby</source> -<target>Standby</target> - <source>Shut down</source> <target>Shut down</target> @@ -1678,9 +1663,6 @@ This guarantees a consistent state even in case of a serious error. <source>Show hidden dialogs and warning messages again?</source> <target>Show hidden dialogues and warning messages again?</target> -<source>&Show</source> -<target>&Show</target> - <source>Downloading update...</source> <target>Downloading update...</target> @@ -1738,6 +1720,15 @@ This guarantees a consistent state even in case of a serious error. <source>MB</source> <target>MB</target> +<source>Back up deleted and overwritten files in the recycle bin</source> +<target>Back up deleted and overwritten files in the recycle bin</target> + +<source>Delete or overwrite files permanently</source> +<target>Delete or overwrite files permanently</target> + +<source>Move files to a user-defined folder</source> +<target>Move files to a user-defined folder</target> + <source>Replace</source> <target>Replace</target> @@ -1981,9 +1972,6 @@ This guarantees a consistent state even in case of a serious error. <source>Edit with FreeFileSync</source> <target>Edit with FreeFileSync</target> -<source>The portable version cannot install into the selected folder.</source> -<target>The portable version cannot install into the selected folder.</target> - <source>Please choose the local installation type or select a different folder for installation.</source> <target>Please choose the local installation type or select a different folder for installation.</target> diff --git a/FreeFileSync/Build/Languages/finnish.lng b/FreeFileSync/Build/Languages/finnish.lng index 5ec18071..ab8ea668 100755 --- a/FreeFileSync/Build/Languages/finnish.lng +++ b/FreeFileSync/Build/Languages/finnish.lng @@ -1174,11 +1174,11 @@ Käsky suoritetaan jos: <source>Time elapsed:</source> <target>Aikaa kulunut:</target> -<source>Bytes:</source> -<target>Tavua:</target> +<source>Bytes</source> +<target>Tavua</target> -<source>Items:</source> -<target>Kohteita:</target> +<source>Items</source> +<target>Kohteita</target> <source>Synchronizing...</source> <target>Täsmäytetään...</target> diff --git a/FreeFileSync/Build/Languages/french.lng b/FreeFileSync/Build/Languages/french.lng index 21846c1e..ad4e712b 100755 --- a/FreeFileSync/Build/Languages/french.lng +++ b/FreeFileSync/Build/Languages/french.lng @@ -1174,11 +1174,11 @@ La commande est déclenchée si : <source>Time elapsed:</source> <target>Temps écoulé :</target> -<source>Bytes:</source> -<target>Octets :</target> +<source>Bytes</source> +<target>Octets</target> -<source>Items:</source> -<target>Eléments :</target> +<source>Items</source> +<target>Eléments</target> <source>Synchronizing...</source> <target>Synchronisation en cours ...</target> diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index f404476d..d40f7b5d 100755 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -7,9 +7,6 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> -<source>Support with a donation</source> -<target>Mit einer Spende unterstützen</target> - <source>Both sides have changed since last synchronization.</source> <target>Beide Seiten wurden seit der letzten Synchronisation verändert.</target> @@ -55,8 +52,8 @@ <source>Checking recycle bin availability for folder %x...</source> <target>Prüfe Verfügbarkeit des Papierkorbs für Ordner %x...</target> -<source>The recycle bin is not available for the following folders. Files will be deleted permanently instead:</source> -<target>Der Papierkorb ist für die folgenden Ordner nicht verfügbar. Dateien werden stattdessen permanent gelöscht:</target> +<source>The recycle bin is not supported by the following folders. Deleted or overwritten files will not be able to be restored:</source> +<target>Der Papierkorb wird von den folgenden Ordnern nicht unterstützt. Gelöschte oder überschriebene Dateien werden nicht wiederhergestellt werden können:</target> <source>An exception occurred</source> <target>Eine Ausnahme ist aufgetreten</target> @@ -106,8 +103,8 @@ <source>Any number of alternative directory pairs for at most one config file.</source> <target>Beliebige Anzahl von alternativen Verzeichnispaaren für maximal eine Konfigurationsdatei.</target> -<source>Open configuration for editing without executing it.</source> -<target>Konfiguration zum Editieren öffnen ohne sie auszuführen.</target> +<source>Open the selected configuration for editing only without executing it.</source> +<target>Ausgewählte Konfiguration nur zum Editieren öffnen ohne sie auszuführen.</target> <source>Path to an alternate GlobalSettings.xml file.</source> <target>Pfad zu alternativer GlobalSettings.xml Datei.</target> @@ -731,6 +728,15 @@ Die Befehlszeile wird ausgelöst, wenn: <source>job name</source> <target>Auftragsname</target> +<source>Show summary</source> +<target>Zusammenfassung zeigen</target> + +<source>Sleep</source> +<target>Energie sparen</target> + +<source>Shut down</source> +<target>Herunterfahren</target> + <source>Synchronization stopped</source> <target>Synchronisation unterbrochen</target> @@ -752,6 +758,9 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Synchronization completed successfully</source> <target>Synchronisation erfolgreich abgeschlossen</target> +<source>Executing command %x</source> +<target>Führe Befehl aus: %x</target> + <source>Cleaning up old log files...</source> <target>Bereinige alte Protokolldateien...</target> @@ -1028,9 +1037,6 @@ Die Befehlszeile wird ausgelöst, wenn: <source>C&lear</source> <target>&Löschen</target> -<source>Detect synchronization directions with the help of database files</source> -<target>Ermittle die Synchronisationsrichtungen mit Hilfe von Datenbankdateien</target> - <source>Detect moved files</source> <target>Verschobene Dateien erkennen</target> @@ -1051,38 +1057,23 @@ Die Befehlszeile wird ausgelöst, wenn: <source>&Recycle bin</source> <target>Papier&korb</target> -<source>Back up deleted and overwritten files in the recycle bin</source> -<target>Gelöschte und überschriebene Dateien im Papierkorb sichern</target> - <source>&Permanent</source> <target>&Permanent</target> -<source>Delete or overwrite files permanently</source> -<target>Dateien endgültig löschen oder überschreiben</target> - <source>&Versioning</source> <target>&Versionierung</target> -<source>Move files to a user-defined folder</source> -<target>Dateien in einen benutzerdefinierten Ordner verschieben</target> - <source>Naming convention:</source> <target>Namenskonvention:</target> -<source>Handle errors:</source> -<target>Fehlerbehandlung:</target> - -<source>&Pop-up</source> -<target>&Nachfragen</target> +<source>&Ignore errors</source> +<target>&Fehler ignorieren</target> <source>Show pop-up on errors or warnings</source> <target>Ein Auswahlfenster bei Fehlern oder Warnungen anzeigen</target> -<source>Hide all error and warning messages</source> -<target>Alle Fehler- und Warnmeldungen unterdrücken</target> - -<source>On completion:</source> -<target>Nach Abschluss:</target> +<source>Run command after synchronization:</source> +<target>Befehl nach Synchronisation ausführen:</target> <source>OK</source> <target>OK</target> @@ -1144,21 +1135,12 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Connections for directory reading:</source> <target>Verbindungen zum Verzeichnislesen:</target> -<source>Suggested range: [1 - 10]</source> -<target>Empfohlener Bereich: [1 - 10]</target> - <source>SFTP channels per connection:</source> <target>SFTP Kanäle je Verbindung:</target> <source>Detect server limit</source> <target>Ermittle Serverlimit</target> -<source>2 connections x 10 channels = 20 times faster directory reading</source> -<target>2 Verbindungen x 10 Kanäle = 20 mal schnelleres Lesen des Verzeichnisses</target> - -<source>4 connections = 4 times faster directory reading</source> -<target>4 Verbindungen = 4 mal schnelleres Verzeichnislesen</target> - <source>Select a directory on the server:</source> <target>Verzeichnis auf dem Server auswählen:</target> @@ -1183,11 +1165,11 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Time elapsed:</source> <target>Vergangene Zeit:</target> -<source>Bytes:</source> -<target>Bytes:</target> +<source>Bytes</source> +<target>Bytes</target> -<source>Items:</source> -<target>Elemente:</target> +<source>Items</source> +<target>Elemente</target> <source>Synchronizing...</source> <target>Synchronisiere...</target> @@ -1198,6 +1180,9 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Bytes copied:</source> <target>Kopierte Datenmenge:</target> +<source>When finished:</source> +<target>Am Ende:</target> + <source>Close</source> <target>Schließen</target> @@ -1210,15 +1195,18 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Create a batch file for unattended synchronization. To start, double-click this file or schedule in a task planner: %x</source> <target>Erstellt eine Batchdatei für die unbeaufsichtigte Synchronisation. Zum Starten die Datei doppelklicken oder in einen Taskplaner eintragen: %x</target> -<source>&Stop</source> -<target>&Stop</target> +<source>Run minimized</source> +<target>Minimiert ausführen</target> + +<source>&Show error dialog</source> +<target>&Zeige Fehlerdialog</target> + +<source>&Cancel</source> +<target>&Abbrechen</target> <source>Stop synchronization at first error</source> <target>Synchronisation beim ersten Fehler unterbrechen</target> -<source>Run minimized</source> -<target>Minimiert ausführen</target> - <source>Save log:</source> <target>Protokoll speichern:</target> @@ -1288,12 +1276,15 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Source code written in C++ using:</source> <target>Der Quellcode wurde in C++ geschrieben mit:</target> -<source>Donation details</source> -<target>Spendendetails</target> - <source>If you like FreeFileSync:</source> <target>Wenn Sie FreeFileSync mögen:</target> +<source>Support with a donation</source> +<target>Mit einer Spende unterstützen</target> + +<source>Donation details</source> +<target>Spendendetails</target> + <source>Feedback and suggestions are welcome</source> <target>Feedback und Vorschläge sind willkommen</target> @@ -1375,8 +1366,8 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>&Show details</source> <target>&Zeige Details</target> -<source>A new version of FreeFileSync is available:</source> -<target>Eine neue Version von FreeFileSync ist verfügbar:</target> +<source>FreeFileSync %x is available!</source> +<target>FreeFileSync %x ist verfügbar!</target> <source>Local path not available for %x.</source> <target>Lokaler Pfad ist nicht verfügbar für %x.</target> @@ -1567,18 +1558,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Searching for program updates...</source> <target>Suche nach aktualisierten Programmversionen...</target> -<source>Close progress dialog</source> -<target>Schließe Verlaufsdialog</target> - -<source>Log off</source> -<target>Abmelden</target> - -<source>Standby</source> -<target>Energie sparen</target> - -<source>Shut down</source> -<target>Herunterfahren</target> - <source>Paused</source> <target>Angehalten</target> @@ -1615,6 +1594,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Thank you, %x, for your donation and support!</source> <target>Danke %x für die Spende und Unterstützung!</target> +<source>Recommended range:</source> +<target>Empfohlener Bereich:</target> + <source>Password:</source> <target>Passwort:</target> @@ -1738,6 +1720,15 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>MB</source> <target>MB</target> +<source>Back up deleted and overwritten files in the recycle bin</source> +<target>Gelöschte und überschriebene Dateien im Papierkorb sichern</target> + +<source>Delete or overwrite files permanently</source> +<target>Dateien endgültig löschen oder überschreiben</target> + +<source>Move files to a user-defined folder</source> +<target>Dateien in einen benutzerdefinierten Ordner verschieben</target> + <source>Replace</source> <target>Ersetzen</target> @@ -1750,6 +1741,15 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Append a time stamp to each file name</source> <target>Einen Zeitstempel an jeden Dateinamen anhängen</target> +<source>On completion:</source> +<target>Nach Abschluss:</target> + +<source>On errors:</source> +<target>Bei Fehlern:</target> + +<source>On success:</source> +<target>Bei Erfolg:</target> + <source>Main config</source> <target>Hauptkonfiguration</target> @@ -1771,9 +1771,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Name</source> <target>Name</target> -<source>Items</source> -<target>Elemente</target> - <source>Percentage</source> <target>Prozent</target> @@ -1903,6 +1900,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Cannot change process I/O priorities.</source> <target>Die Eingabe/Ausgabe Prioritäten für den Prozess können nicht geändert werden.</target> +<source>Unable to shut down system.</source> +<target>Das System kann nicht heruntergefahren werden.</target> + <source>Checking recycle bin failed for folder %x.</source> <target>Die Prüfung des Papierkorbs für Ordner %x ist fehlgeschlagen.</target> @@ -1981,8 +1981,8 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Edit with FreeFileSync</source> <target>Mit FreeFileSync editieren</target> -<source>The portable version cannot install into the selected folder.</source> -<target>Die portable Version kann nicht in den gewählten Ordner installiert werden.</target> +<source>The FreeFileSync portable version cannot install into a subfolder of %x.</source> +<target>Die portable Version von FreeFileSync kann nicht in einen Unterordner von %x installiert werden.</target> <source>Please choose the local installation type or select a different folder for installation.</source> <target>Bitte wählen Sie den lokalen Installationstyp oder einen anderen Ordner für die Installation.</target> diff --git a/FreeFileSync/Build/Languages/greek.lng b/FreeFileSync/Build/Languages/greek.lng index f98646cc..9522e703 100755 --- a/FreeFileSync/Build/Languages/greek.lng +++ b/FreeFileSync/Build/Languages/greek.lng @@ -1174,11 +1174,11 @@ The command is triggered if: <source>Time elapsed:</source> <target>Πέρασε χρόνος:</target> -<source>Bytes:</source> -<target>Bytes:</target> +<source>Bytes</source> +<target>Bytes</target> -<source>Items:</source> -<target>Στοιχεία:</target> +<source>Items</source> +<target>Στοιχεία</target> <source>Synchronizing...</source> <target>Γίνεται συγχρονισμός...</target> diff --git a/FreeFileSync/Build/Languages/hebrew.lng b/FreeFileSync/Build/Languages/hebrew.lng index 146e71ca..bd8d2e1e 100755 --- a/FreeFileSync/Build/Languages/hebrew.lng +++ b/FreeFileSync/Build/Languages/hebrew.lng @@ -1171,11 +1171,11 @@ The command is triggered if: <source>Time elapsed:</source> <target>זמן שעבר:</target> -<source>Bytes:</source> -<target>בתים:</target> +<source>Bytes</source> +<target>בתים</target> -<source>Items:</source> -<target>פריטים:</target> +<source>Items</source> +<target>פריטים</target> <source>Synchronizing...</source> <target>מסנכרן...</target> diff --git a/FreeFileSync/Build/Languages/hungarian.lng b/FreeFileSync/Build/Languages/hungarian.lng index d069319a..bb838559 100755 --- a/FreeFileSync/Build/Languages/hungarian.lng +++ b/FreeFileSync/Build/Languages/hungarian.lng @@ -1174,11 +1174,11 @@ A parancs végrehajtódik, ha: <source>Time elapsed:</source> <target>Eltelt idő:</target> -<source>Bytes:</source> -<target>Bájt:</target> +<source>Bytes</source> +<target>Bájt</target> -<source>Items:</source> -<target>Tétel:</target> +<source>Items</source> +<target>Tétel</target> <source>Synchronizing...</source> <target>Szinkronizálás folyamatban...</target> diff --git a/FreeFileSync/Build/Languages/italian.lng b/FreeFileSync/Build/Languages/italian.lng index 4515dd40..2f455e9b 100755 --- a/FreeFileSync/Build/Languages/italian.lng +++ b/FreeFileSync/Build/Languages/italian.lng @@ -1174,11 +1174,11 @@ Il comando è attivato se: <source>Time elapsed:</source> <target>Tempo trascorso:</target> -<source>Bytes:</source> -<target>Byte:</target> +<source>Bytes</source> +<target>Byte</target> -<source>Items:</source> -<target>Elementi:</target> +<source>Items</source> +<target>Elementi</target> <source>Synchronizing...</source> <target>Sincronizzazione...</target> diff --git a/FreeFileSync/Build/Languages/japanese.lng b/FreeFileSync/Build/Languages/japanese.lng index 6821425f..ecfdde41 100755 --- a/FreeFileSync/Build/Languages/japanese.lng +++ b/FreeFileSync/Build/Languages/japanese.lng @@ -1168,11 +1168,11 @@ The command is triggered if: <source>Time elapsed:</source> <target>経過時間:</target> -<source>Bytes:</source> -<target>バイト:</target> +<source>Bytes</source> +<target>バイト</target> -<source>Items:</source> -<target>項目:</target> +<source>Items</source> +<target>項目</target> <source>Synchronizing...</source> <target>同期処理中...</target> diff --git a/FreeFileSync/Build/Languages/korean.lng b/FreeFileSync/Build/Languages/korean.lng index aa9d6a9b..c877df9c 100755 --- a/FreeFileSync/Build/Languages/korean.lng +++ b/FreeFileSync/Build/Languages/korean.lng @@ -1168,11 +1168,11 @@ The command is triggered if: <source>Time elapsed:</source> <target>경과 시간:</target> -<source>Bytes:</source> -<target>바이트:</target> +<source>Bytes</source> +<target>바이트</target> -<source>Items:</source> -<target>항목:</target> +<source>Items</source> +<target>항목</target> <source>Synchronizing...</source> <target>동기화 작업 중...</target> diff --git a/FreeFileSync/Build/Languages/lithuanian.lng b/FreeFileSync/Build/Languages/lithuanian.lng index e3369b1b..572cf40a 100755 --- a/FreeFileSync/Build/Languages/lithuanian.lng +++ b/FreeFileSync/Build/Languages/lithuanian.lng @@ -1180,11 +1180,11 @@ Komanda inicijuojama jei: <source>Time elapsed:</source> <target>Praėjęs laikas:</target> -<source>Bytes:</source> -<target>Baitai:</target> +<source>Bytes</source> +<target>Baitai</target> -<source>Items:</source> -<target>Elementai:</target> +<source>Items</source> +<target>Elementai</target> <source>Synchronizing...</source> <target>Suvienodinama...</target> diff --git a/FreeFileSync/Build/Languages/norwegian.lng b/FreeFileSync/Build/Languages/norwegian.lng index d8f6de80..942d98c7 100755 --- a/FreeFileSync/Build/Languages/norwegian.lng +++ b/FreeFileSync/Build/Languages/norwegian.lng @@ -1174,11 +1174,11 @@ Kommandoen utføres hvis: <source>Time elapsed:</source> <target>Brukt tid:</target> -<source>Bytes:</source> -<target>Bytes:</target> +<source>Bytes</source> +<target>Bytes</target> -<source>Items:</source> -<target>Elementer:</target> +<source>Items</source> +<target>Elementer</target> <source>Synchronizing...</source> <target>Synkroniserer...</target> diff --git a/FreeFileSync/Build/Languages/polish.lng b/FreeFileSync/Build/Languages/polish.lng index 9dba88b5..16609a65 100755 --- a/FreeFileSync/Build/Languages/polish.lng +++ b/FreeFileSync/Build/Languages/polish.lng @@ -1180,11 +1180,11 @@ Komenda jest wykonywana gdy: <source>Time elapsed:</source> <target>Upłynęło:</target> -<source>Bytes:</source> -<target>Bajty:</target> +<source>Bytes</source> +<target>Bajty</target> -<source>Items:</source> -<target>Elementy:</target> +<source>Items</source> +<target>Elementy</target> <source>Synchronizing...</source> <target>Synchronizuję...</target> diff --git a/FreeFileSync/Build/Languages/portuguese.lng b/FreeFileSync/Build/Languages/portuguese.lng index a0a691d3..8c388cfe 100755 --- a/FreeFileSync/Build/Languages/portuguese.lng +++ b/FreeFileSync/Build/Languages/portuguese.lng @@ -1174,11 +1174,11 @@ O comando é executado se: <source>Time elapsed:</source> <target>Tempo decorrido:</target> -<source>Bytes:</source> -<target>Bytes:</target> +<source>Bytes</source> +<target>Bytes</target> -<source>Items:</source> -<target>Itens:</target> +<source>Items</source> +<target>Itens</target> <source>Synchronizing...</source> <target>A sincronizar...</target> diff --git a/FreeFileSync/Build/Languages/portuguese_br.lng b/FreeFileSync/Build/Languages/portuguese_br.lng index 648d8cb5..cc1c602f 100755 --- a/FreeFileSync/Build/Languages/portuguese_br.lng +++ b/FreeFileSync/Build/Languages/portuguese_br.lng @@ -1174,11 +1174,11 @@ O comando é disparado se: <source>Time elapsed:</source> <target>Tempo decorrido:</target> -<source>Bytes:</source> -<target>Bytes:</target> +<source>Bytes</source> +<target>Bytes</target> -<source>Items:</source> -<target>Itens:</target> +<source>Items</source> +<target>Itens</target> <source>Synchronizing...</source> <target>Sincronizando...</target> diff --git a/FreeFileSync/Build/Languages/romanian.lng b/FreeFileSync/Build/Languages/romanian.lng index 17ae5545..c6caab97 100755 --- a/FreeFileSync/Build/Languages/romanian.lng +++ b/FreeFileSync/Build/Languages/romanian.lng @@ -1180,11 +1180,11 @@ Comanda este declanșată dacă: <source>Time elapsed:</source> <target>Timp Scurs:</target> -<source>Bytes:</source> -<target>Baiți:</target> +<source>Bytes</source> +<target>Baiți</target> -<source>Items:</source> -<target>Elemente:</target> +<source>Items</source> +<target>Elemente</target> <source>Synchronizing...</source> <target>Sincronizare Aflată în Curs...</target> diff --git a/FreeFileSync/Build/Languages/slovenian.lng b/FreeFileSync/Build/Languages/slovenian.lng index 66d0b16b..88604387 100755 --- a/FreeFileSync/Build/Languages/slovenian.lng +++ b/FreeFileSync/Build/Languages/slovenian.lng @@ -1186,11 +1186,11 @@ Ukaz se sproži če: <source>Time elapsed:</source> <target>Pretečeni čas:</target> -<source>Bytes:</source> -<target>Bitov:</target> +<source>Bytes</source> +<target>Bitov</target> -<source>Items:</source> -<target>Objektov:</target> +<source>Items</source> +<target>Objektov</target> <source>Synchronizing...</source> <target>Sinhroniziram...</target> diff --git a/FreeFileSync/Build/Languages/spanish.lng b/FreeFileSync/Build/Languages/spanish.lng index 4c34cb2a..c94c975b 100755 --- a/FreeFileSync/Build/Languages/spanish.lng +++ b/FreeFileSync/Build/Languages/spanish.lng @@ -1174,11 +1174,11 @@ El comando es disparado si: <source>Time elapsed:</source> <target>Tiempo transcurrido:</target> -<source>Bytes:</source> -<target>Bytes:</target> +<source>Bytes</source> +<target>Bytes</target> -<source>Items:</source> -<target>Elementos:</target> +<source>Items</source> +<target>Elementos</target> <source>Synchronizing...</source> <target>Sincronizando...</target> diff --git a/FreeFileSync/Build/Languages/swedish.lng b/FreeFileSync/Build/Languages/swedish.lng index b90d7114..85aeef75 100755 --- a/FreeFileSync/Build/Languages/swedish.lng +++ b/FreeFileSync/Build/Languages/swedish.lng @@ -1174,11 +1174,11 @@ Kommandot triggas om: <source>Time elapsed:</source> <target>Förfluten tid:</target> -<source>Bytes:</source> -<target>Byte:</target> +<source>Bytes</source> +<target>Byte</target> -<source>Items:</source> -<target>Objekt:</target> +<source>Items</source> +<target>Objekt</target> <source>Synchronizing...</source> <target>Synkroniserar...</target> diff --git a/FreeFileSync/Build/Languages/turkish.lng b/FreeFileSync/Build/Languages/turkish.lng index f6651925..d4425d99 100755 --- a/FreeFileSync/Build/Languages/turkish.lng +++ b/FreeFileSync/Build/Languages/turkish.lng @@ -1174,11 +1174,11 @@ Komut şu durumlarda yürütülür: <source>Time elapsed:</source> <target>Geçen süre:</target> -<source>Bytes:</source> -<target>Bayt:</target> +<source>Bytes</source> +<target>Bayt</target> -<source>Items:</source> -<target>Öge:</target> +<source>Items</source> +<target>Öge</target> <source>Synchronizing...</source> <target>Eşitleniyor...</target> diff --git a/FreeFileSync/Build/Languages/ukrainian.lng b/FreeFileSync/Build/Languages/ukrainian.lng index ba2434d7..1eacd45f 100755 --- a/FreeFileSync/Build/Languages/ukrainian.lng +++ b/FreeFileSync/Build/Languages/ukrainian.lng @@ -1180,11 +1180,11 @@ The command is triggered if: <source>Time elapsed:</source> <target>Пройшло часу:</target> -<source>Bytes:</source> -<target>Байт:</target> +<source>Bytes</source> +<target>Байт</target> -<source>Items:</source> -<target>Елементи:</target> +<source>Items</source> +<target>Елементи</target> <source>Synchronizing...</source> <target>Синхронізація...</target> diff --git a/FreeFileSync/Build/Resources.zip b/FreeFileSync/Build/Resources.zip Binary files differindex 18d7907c..39f43566 100755 --- a/FreeFileSync/Build/Resources.zip +++ b/FreeFileSync/Build/Resources.zip diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile index c0a9d597..46e18617 100755..100644 --- a/FreeFileSync/Source/Makefile +++ b/FreeFileSync/Source/Makefile @@ -39,7 +39,7 @@ CPP_LIST+=fs/native.cpp CPP_LIST+=file_hierarchy.cpp CPP_LIST+=ui/custom_grid.cpp CPP_LIST+=ui/folder_history_box.cpp -CPP_LIST+=ui/on_completion_box.cpp +CPP_LIST+=ui/command_box.cpp CPP_LIST+=ui/folder_selector.cpp CPP_LIST+=ui/batch_config.cpp CPP_LIST+=ui/batch_status_handler.cpp @@ -78,6 +78,7 @@ CPP_LIST+=../../zen/file_traverser.cpp CPP_LIST+=../../zen/zstring.cpp CPP_LIST+=../../zen/format_unit.cpp CPP_LIST+=../../zen/process_priority.cpp +CPP_LIST+=../../zen/shutdown.cpp CPP_LIST+=../../wx+/file_drop.cpp CPP_LIST+=../../wx+/grid.cpp CPP_LIST+=../../wx+/image_tools.cpp diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp index 043c91a9..72fbcc37 100755 --- a/FreeFileSync/Source/RealTimeSync/application.cpp +++ b/FreeFileSync/Source/RealTimeSync/application.cpp @@ -124,7 +124,9 @@ int Application::OnRun() catch (const std::bad_alloc& e) //the only kind of exception we don't want crash dumps for { logFatalError(e.what()); //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! - wxSafeShowMessage(L"RealTimeSync - " + _("An exception occurred"), e.what()); + + const auto title = copyStringTo<std::wstring>(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); + wxSafeShowMessage(title, e.what()); return FFS_RC_EXCEPTION; } //catch (...) -> let it crash and create mini dump!!! diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index a54a93f1..6ba72874 100755 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -20,6 +20,7 @@ #include "../lib/help_provider.h" #include "../lib/process_xml.h" #include "../lib/ffs_paths.h" +#include "../version/version.h" #include <gtk/gtk.h> @@ -158,8 +159,7 @@ void MainDialog::OnShowHelp(wxCommandEvent& event) void MainDialog::OnMenuAbout(wxCommandEvent& event) { - wxString build = formatTime<std::wstring>(FORMAT_DATE, getCompileTime()); - build += L" - Unicode"; + wxString build = formatTime<std::wstring>(FORMAT_DATE, getCompileTime()) + SPACED_DASH + L"Unicode"; #ifndef wxUSE_UNICODE #error what is going on? #endif @@ -271,7 +271,7 @@ void MainDialog::setLastUsedConfig(const Zstring& filepath) //set title if (filepath == lastRunConfigPath_) { - SetTitle(L"RealTimeSync - " + _("Automated Synchronization")); + SetTitle(wxString(L"RealTimeSync ") + zen::ffsVersion + SPACED_DASH + _("Automated Synchronization")); currentConfigFileName_.clear(); } else diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index 6ed0426c..395ae1d3 100755 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -114,7 +114,8 @@ int Application::OnRun() { logFatalError(e.what()); //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! - wxSafeShowMessage(L"FreeFileSync - " + _("An exception occurred"), e.what()); + const auto title = copyStringTo<std::wstring>(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); + wxSafeShowMessage(title, e.what()); return FFS_RC_EXCEPTION; } //catch (...) -> let it crash and create mini dump!!! @@ -146,13 +147,11 @@ void Application::launch(const std::vector<Zstring>& commandArgs) auto notifyFatalError = [&](const std::wstring& msg, const std::wstring& title) { - auto titleTmp = copyStringTo<std::wstring>(wxTheApp->GetAppDisplayName()) + L" - " + title; - - //error handling strategy unknown and no sync log output available at this point! => show message box - logFatalError(utfTo<std::string>(msg)); - wxSafeShowMessage(title, msg); + //error handling strategy unknown and no sync log output available at this point! => show message box + auto titleFmt = copyStringTo<std::wstring>(wxTheApp->GetAppDisplayName()) + SPACED_DASH + title; + wxSafeShowMessage(titleFmt, msg); raiseReturnCode(returnCode, FFS_RC_ABORTED); }; @@ -429,9 +428,11 @@ void showSyntaxHelp() void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& batchCfg, const Zstring& referenceFile, FfsReturnCode& returnCode) { + const bool showPopupAllowed = !batchCfg.mainCfg.ignoreErrors && batchCfg.batchExCfg.batchErrorDialog == BatchErrorDialog::SHOW; + auto notifyError = [&](const std::wstring& msg, FfsReturnCode rc) { - if (batchCfg.handleError == ON_ERROR_POPUP) + if (showPopupAllowed) showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(msg)); else //"exit" or "ignore" logFatalError(utfTo<std::string>(msg)); @@ -475,42 +476,33 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat const std::chrono::system_clock::time_point batchStartTime = std::chrono::system_clock::now(); //class handling status updates and error messages - BatchStatusHandler statusHandler(!batchCfg.runMinimized, //throw BatchAbortProcess, BatchRequestSwitchToMainDialog + BatchStatusHandler statusHandler(!batchCfg.batchExCfg.runMinimized, //throw AbortProcess, BatchRequestSwitchToMainDialog extractJobName(referenceFile), globalCfg.soundFileSyncFinished, batchStartTime, - batchCfg.logFolderPathPhrase, - batchCfg.logfilesCountLimit, + batchCfg.batchExCfg.logFolderPathPhrase, + batchCfg.batchExCfg.logfilesCountLimit, globalCfg.lastSyncsLogFileSizeMax, - batchCfg.handleError, + batchCfg.mainCfg.ignoreErrors, + batchCfg.batchExCfg.batchErrorDialog, globalCfg.automaticRetryCount, globalCfg.automaticRetryDelay, returnCode, - batchCfg.mainCfg.onCompletion, - globalCfg.gui.onCompletionHistory); + batchCfg.mainCfg.postSyncCommand, + batchCfg.mainCfg.postSyncCondition, + batchCfg.batchExCfg.postSyncAction); logNonDefaultSettings(globalCfg, statusHandler); //inform about (important) non-default global settings const std::vector<FolderPairCfg> cmpConfig = extractCompareCfg(batchCfg.mainCfg); - bool allowPwPrompt = false; - switch (batchCfg.handleError) - { - case ON_ERROR_POPUP: - allowPwPrompt = true; - break; - case ON_ERROR_IGNORE: - case ON_ERROR_STOP: - break; - } - //batch mode: place directory locks on directories during both comparison AND synchronization std::unique_ptr<LockHolder> dirLocks; //COMPARE DIRECTORIES FolderComparison cmpResult = compare(globalCfg.optDialogs, globalCfg.fileTimeTolerance, - allowPwPrompt, //allowUserInteraction + showPopupAllowed, //allowUserInteraction globalCfg.runWithBackgroundPriority, globalCfg.folderAccessTimeout, globalCfg.createLockFile, @@ -535,7 +527,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat globalCfg.optDialogs, statusHandler); //throw ? } - catch (BatchAbortProcess&) {} //exit used by statusHandler + catch (AbortProcess&) {} //exit used by statusHandler catch (BatchRequestSwitchToMainDialog&) { //open new toplevel window *after* progress dialog is gone => run on main event loop diff --git a/FreeFileSync/Source/comparison.cpp b/FreeFileSync/Source/comparison.cpp index 48b65588..a8dba8fd 100755 --- a/FreeFileSync/Source/comparison.cpp +++ b/FreeFileSync/Source/comparison.cpp @@ -196,15 +196,15 @@ template <SelectedSide side, class FileOrLinkPair> inline std::wstring getConflictInvalidDate(const FileOrLinkPair& file) { return replaceCpy(_("File %x has an invalid date."), L"%x", fmtPath(AFS::getDisplayPath(file.template getAbstractPath<side>()))) + L"\n" + - _("Date:") + L" " + utcToLocalTimeString(file.template getLastWriteTime<side>()); + _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime<side>()); } std::wstring getConflictSameDateDiffSize(const FilePair& file) { return _("Files have the same date but a different size.") + L"\n" + - arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(file.getLastWriteTime< LEFT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(file.getFileSize<LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(file.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(file.getFileSize<RIGHT_SIDE>()); + arrowLeft + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L" " + _("Size:") + L" " + formatNumber(file.getFileSize<LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + formatNumber(file.getFileSize<RIGHT_SIDE>()); } @@ -226,8 +226,8 @@ template <class FileOrLinkPair> std::wstring getDescrDiffMetaDate(const FileOrLinkPair& file) { return _("Items differ in attributes only") + L"\n" + - arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(file.template getLastWriteTime< LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(file.template getLastWriteTime<RIGHT_SIDE>()); + arrowLeft + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime< LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime<RIGHT_SIDE>()); } //----------------------------------------------------------------------------- diff --git a/FreeFileSync/Source/file_hierarchy.cpp b/FreeFileSync/Source/file_hierarchy.cpp index 4d0330a3..64f36bd1 100755 --- a/FreeFileSync/Source/file_hierarchy.cpp +++ b/FreeFileSync/Source/file_hierarchy.cpp @@ -140,9 +140,6 @@ bool hasDirectChild(const ContainerObject& hierObj, Predicate p) std::any_of(hierObj.refSubLinks ().begin(), hierObj.refSubLinks ().end(), p) || std::any_of(hierObj.refSubFolders().begin(), hierObj.refSubFolders().end(), p); } - -const wchar_t arrowLeft [] = L"<-"; -const wchar_t arrowRight[] = L"->"; } @@ -310,6 +307,13 @@ std::wstring zen::getCategoryDescription(CompareFilesResult cmpRes) } +namespace +{ +const wchar_t arrowLeft [] = L"<-"; +const wchar_t arrowRight[] = L"->"; +} + + std::wstring zen::getCategoryDescription(const FileSystemObject& fsObj) { const std::wstring footer = L"\n[" + utfTo<std::wstring>(fsObj. getPairItemName()) + L"]"; @@ -332,14 +336,14 @@ std::wstring zen::getCategoryDescription(const FileSystemObject& fsObj) [&](const FilePair& file) { descr += std::wstring(L"\n") + - arrowLeft + L" " + zen::utcToLocalTimeString(file.getLastWriteTime< LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + zen::utcToLocalTimeString(file.getLastWriteTime<RIGHT_SIDE>()); + arrowLeft + L" " + zen::formatUtcToLocalTime(file.getLastWriteTime< LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + zen::formatUtcToLocalTime(file.getLastWriteTime<RIGHT_SIDE>()); }, [&](const SymlinkPair& symlink) { descr += std::wstring(L"\n") + - arrowLeft + L" " + zen::utcToLocalTimeString(symlink.getLastWriteTime< LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + zen::utcToLocalTimeString(symlink.getLastWriteTime<RIGHT_SIDE>()); + arrowLeft + L" " + zen::formatUtcToLocalTime(symlink.getLastWriteTime< LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + zen::formatUtcToLocalTime(symlink.getLastWriteTime<RIGHT_SIDE>()); }); return descr + footer; } diff --git a/FreeFileSync/Source/file_hierarchy.h b/FreeFileSync/Source/file_hierarchy.h index 59a76fcd..c91354df 100755 --- a/FreeFileSync/Source/file_hierarchy.h +++ b/FreeFileSync/Source/file_hierarchy.h @@ -345,20 +345,20 @@ public: inline friend bool operator==(const DerefIter& lhs, const DerefIter& rhs) { return lhs.it_ == rhs.it_; } inline friend bool operator!=(const DerefIter& lhs, const DerefIter& rhs) { return !(lhs == rhs); } V& operator* () const { return **it_; } - V* operator->() const { return &**it_; } + V* operator->() const { return &** it_; } private: IterImpl it_; }; /* C++17: specialize std::iterator_traits instead of inherting from std::iterator -namespace std +namespace std { -template <class IterImpl, class V> -struct iterator_traits<zen::DerefIter<IterImpl, V>> +template <class IterImpl, class V> +struct iterator_traits<zen::DerefIter<IterImpl, V>> { - using iterator_category = std::bidirectional_iterator_tag; - using value_type = V; + using iterator_category = std::bidirectional_iterator_tag; + using value_type = V; }; } */ diff --git a/FreeFileSync/Source/lib/db_file.cpp b/FreeFileSync/Source/lib/db_file.cpp index a41690bc..2d26b32d 100755 --- a/FreeFileSync/Source/lib/db_file.cpp +++ b/FreeFileSync/Source/lib/db_file.cpp @@ -584,12 +584,12 @@ private: //create or update new "in-sync" state InSyncFile& dbFile = mapAddOrUpdate(dbFiles, file.getPairItemName(), - InSyncFile(InSyncDescrFile(file.getLastWriteTime< LEFT_SIDE>(), - file.getFileId < LEFT_SIDE>()), - InSyncDescrFile(file.getLastWriteTime<RIGHT_SIDE>(), - file.getFileId <RIGHT_SIDE>()), - activeCmpVar_, - file.getFileSize<LEFT_SIDE>())); + InSyncFile(InSyncDescrFile(file.getLastWriteTime< LEFT_SIDE>(), + file.getFileId < LEFT_SIDE>()), + InSyncDescrFile(file.getLastWriteTime<RIGHT_SIDE>(), + file.getFileId <RIGHT_SIDE>()), + activeCmpVar_, + file.getFileSize<LEFT_SIDE>())); toPreserve.insert(&dbFile); } else //not in sync: preserve last synchronous state @@ -625,9 +625,9 @@ private: //create or update new "in-sync" state InSyncSymlink& dbSymlink = mapAddOrUpdate(dbSymlinks, symlink.getPairItemName(), - InSyncSymlink(InSyncDescrLink(symlink.getLastWriteTime<LEFT_SIDE>()), - InSyncDescrLink(symlink.getLastWriteTime<RIGHT_SIDE>()), - activeCmpVar_)); + InSyncSymlink(InSyncDescrLink(symlink.getLastWriteTime<LEFT_SIDE>()), + InSyncDescrLink(symlink.getLastWriteTime<RIGHT_SIDE>()), + activeCmpVar_)); toPreserve.insert(&dbSymlink); } else //not in sync: preserve last synchronous state @@ -749,7 +749,7 @@ struct StreamStatusNotifier void operator()(int64_t bytesDelta) //throw X { bytesTotal_ += bytesDelta; - if (notifyStatus_) notifyStatus_(msgPrefix_ + L" (" + filesizeToShortString(bytesTotal_) + L")"); //throw X + if (notifyStatus_) notifyStatus_(msgPrefix_ + L" (" + formatFilesizeShort(bytesTotal_) + L")"); //throw X } private: diff --git a/FreeFileSync/Source/lib/dir_lock.cpp b/FreeFileSync/Source/lib/dir_lock.cpp index 0419a36e..13c0e744 100755 --- a/FreeFileSync/Source/lib/dir_lock.cpp +++ b/FreeFileSync/Source/lib/dir_lock.cpp @@ -16,6 +16,7 @@ #include <zen/file_io.h> #include <zen/optional.h> #include <wx/log.h> +#include <wx/app.h> #include <fcntl.h> //open() #include <sys/stat.h> // @@ -45,7 +46,7 @@ public: void operator()() const //throw ThreadInterruption { - setCurrentThreadName("Folder Lock Life Signs"); + setCurrentThreadName("DirLock: Life Signs"); try { @@ -59,7 +60,8 @@ public: } catch (const std::exception& e) //exceptions must be catched per thread { - wxSafeShowMessage(L"FreeFileSync - " + _("An exception occurred"), utfTo<wxString>(e.what()) + L" (Dirlock)"); //simple wxMessageBox won't do for threads + const auto title = copyStringTo<std::wstring>(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred"); + wxSafeShowMessage(title, utfTo<wxString>(e.what()) + L" (Dirlock)"); //simple wxMessageBox won't do for threads } } @@ -325,7 +327,7 @@ void waitOnDirLock(const Zstring& lockFilePath, DirLockCallback* callback) //thr if (now >= lastLifeSign + seconds(EMIT_LIFE_SIGN_INTERVAL + 1)) { const int remainingSeconds = std::max<int>(0, DETECT_ABANDONED_INTERVAL - duration_cast<seconds>(steady_clock::now() - lastLifeSign).count()); - const std::wstring remSecMsg = replaceCpy(_P("1 sec", "%x sec", remainingSeconds), L"%x", toGuiString(remainingSeconds)); + const std::wstring remSecMsg = replaceCpy(_P("1 sec", "%x sec", remainingSeconds), L"%x", formatNumber(remainingSeconds)); callback->reportStatus(infoMsg + L" | " + _("Detecting abandoned lock...") + L' ' + remSecMsg); } else @@ -392,13 +394,13 @@ public: while (!::tryLock(lockFilePath)) //throw FileError ::waitOnDirLock(lockFilePath, callback); // - lifeSignthread = InterruptibleThread(LifeSigns(lockFilePath)); + lifeSignthread_ = InterruptibleThread(LifeSigns(lockFilePath)); } ~SharedDirLock() { - lifeSignthread.interrupt(); //thread lifetime is subset of this instances's life - lifeSignthread.join(); + lifeSignthread_.interrupt(); //thread lifetime is subset of this instances's life + lifeSignthread_.join(); ::releaseLock(lockFilePath_); //throw () } @@ -408,7 +410,7 @@ private: SharedDirLock& operator=(const DirLock&) = delete; const Zstring lockFilePath_; - InterruptibleThread lifeSignthread; + InterruptibleThread lifeSignthread_; }; @@ -429,8 +431,8 @@ public: tidyUp(); //optimization: check if we already own a lock for this path - auto iterGuid = fileToGuid.find(lockFilePath); - if (iterGuid != fileToGuid.end()) + auto iterGuid = fileToGuid_.find(lockFilePath); + if (iterGuid != fileToGuid_.end()) if (const std::shared_ptr<SharedDirLock>& activeLock = getActiveLock(iterGuid->second)) //returns null-lock if not found return activeLock; //SharedDirLock is still active -> enlarge circle of shared ownership @@ -439,7 +441,7 @@ public: const std::string lockId = retrieveLockId(lockFilePath); //throw FileError if (const std::shared_ptr<SharedDirLock>& activeLock = getActiveLock(lockId)) //returns null-lock if not found { - fileToGuid[lockFilePath] = lockId; //found an alias for one of our active locks + fileToGuid_[lockFilePath] = lockId; //found an alias for one of our active locks return activeLock; } } @@ -450,8 +452,8 @@ public: const std::string& newLockGuid = retrieveLockId(lockFilePath); //throw FileError //update registry - fileToGuid[lockFilePath] = newLockGuid; //throw() - guidToLock[newLockGuid] = newLock; // + fileToGuid_[lockFilePath] = newLockGuid; //throw() + guidToLock_[newLockGuid] = newLock; // return newLock; } @@ -467,18 +469,18 @@ private: std::shared_ptr<SharedDirLock> getActiveLock(const UniqueId& lockId) //returns null if none found { - auto it = guidToLock.find(lockId); - return it != guidToLock.end() ? it->second.lock() : nullptr; //try to get shared_ptr; throw() + auto it = guidToLock_.find(lockId); + return it != guidToLock_.end() ? it->second.lock() : nullptr; //try to get shared_ptr; throw() } void tidyUp() //remove obsolete entries { - erase_if(guidToLock, [ ](const GuidToLockMap::value_type& v) { return !v.second.lock(); }); - erase_if(fileToGuid, [&](const FileToGuidMap::value_type& v) { return guidToLock.find(v.second) == guidToLock.end(); }); + erase_if(guidToLock_, [ ](const GuidToLockMap::value_type& v) { return !v.second.lock(); }); + erase_if(fileToGuid_, [&](const FileToGuidMap::value_type& v) { return guidToLock_.find(v.second) == guidToLock_.end(); }); } - FileToGuidMap fileToGuid; //lockname |-> GUID; locks can be referenced by a lockFilePath or alternatively a GUID - GuidToLockMap guidToLock; //GUID |-> "shared lock ownership" + FileToGuidMap fileToGuid_; //lockname |-> GUID; locks can be referenced by a lockFilePath or alternatively a GUID + GuidToLockMap guidToLock_; //GUID |-> "shared lock ownership" }; @@ -488,5 +490,5 @@ DirLock::DirLock(const Zstring& lockFilePath, DirLockCallback* callback) //throw callback->reportStatus(replaceCpy(_("Creating file %x"), L"%x", fmtPath(lockFilePath))); - sharedLock = LockAdmin::instance().retrieve(lockFilePath, callback); //throw FileError + sharedLock_ = LockAdmin::instance().retrieve(lockFilePath, callback); //throw FileError } diff --git a/FreeFileSync/Source/lib/dir_lock.h b/FreeFileSync/Source/lib/dir_lock.h index 4ae4fe5d..7ed26849 100755 --- a/FreeFileSync/Source/lib/dir_lock.h +++ b/FreeFileSync/Source/lib/dir_lock.h @@ -39,7 +39,7 @@ public: private: class LockAdmin; class SharedDirLock; - std::shared_ptr<SharedDirLock> sharedLock; + std::shared_ptr<SharedDirLock> sharedLock_; }; } diff --git a/FreeFileSync/Source/lib/generate_logfile.h b/FreeFileSync/Source/lib/generate_logfile.h index 716c3fcb..b99a0b78 100755 --- a/FreeFileSync/Source/lib/generate_logfile.h +++ b/FreeFileSync/Source/lib/generate_logfile.h @@ -49,7 +49,7 @@ struct OnUpdateLogfileStatusNoThrow void operator()(int64_t bytesDelta) { bytesWritten += bytesDelta; - try { pc_.reportStatus(msg + L" (" + filesizeToShortString(bytesWritten) + L")"); /*throw X*/ } + try { pc_.reportStatus(msg + L" (" + formatFilesizeShort(bytesWritten) + L")"); /*throw X*/ } catch (...) {} } @@ -84,16 +84,16 @@ std::wstring generateLogHeader(const SummaryInfo& s) const wchar_t tabSpace[] = L" "; - std::wstring itemsProc = tabSpace + _("Items processed:") + L" " + toGuiString(s.itemsProcessed); //show always, even if 0! + std::wstring itemsProc = tabSpace + _("Items processed:") + L" " + formatNumber(s.itemsProcessed); //show always, even if 0! if (s.itemsProcessed != 0 || s.bytesProcessed != 0) //[!] don't show 0 bytes processed if 0 items were processed - itemsProc += + L" (" + filesizeToShortString(s.bytesProcessed) + L")"; + itemsProc += + L" (" + formatFilesizeShort(s.bytesProcessed) + L")"; results.push_back(itemsProc); if (s.itemsTotal != 0 || s.bytesTotal != 0) //=: sync phase was reached and there were actual items to sync { if (s.itemsProcessed != s.itemsTotal || s.bytesProcessed != s.bytesTotal) - results.push_back(tabSpace + _("Items remaining:") + L" " + toGuiString(s.itemsTotal - s.itemsProcessed) + L" (" + filesizeToShortString(s.bytesTotal - s.bytesProcessed) + L")"); + results.push_back(tabSpace + _("Items remaining:") + L" " + formatNumber(s.itemsTotal - s.itemsProcessed) + L" (" + formatFilesizeShort(s.bytesTotal - s.bytesProcessed) + L")"); } results.push_back(tabSpace + _("Total time:") + L" " + copyStringTo<std::wstring>(wxTimeSpan::Seconds(s.totalTime).Format())); diff --git a/FreeFileSync/Source/lib/icon_buffer.cpp b/FreeFileSync/Source/lib/icon_buffer.cpp index 792e5b43..97eb8f15 100755 --- a/FreeFileSync/Source/lib/icon_buffer.cpp +++ b/FreeFileSync/Source/lib/icon_buffer.cpp @@ -330,17 +330,17 @@ struct IconBuffer::Impl }; -IconBuffer::IconBuffer(IconSize sz) : pimpl(std::make_unique<Impl>()), iconSizeType(sz) +IconBuffer::IconBuffer(IconSize sz) : pimpl_(std::make_unique<Impl>()), iconSizeType(sz) { - pimpl->worker = InterruptibleThread(WorkerThread(pimpl->workload, pimpl->buffer, sz)); + pimpl_->worker = InterruptibleThread(WorkerThread(pimpl_->workload, pimpl_->buffer, sz)); } IconBuffer::~IconBuffer() { setWorkload({}); //make sure interruption point is always reached! //needed??? - pimpl->worker.interrupt(); - pimpl->worker.join(); + pimpl_->worker.interrupt(); + pimpl_->worker.join(); } @@ -363,18 +363,18 @@ int IconBuffer::getSize(IconSize sz) bool IconBuffer::readyForRetrieval(const AbstractPath& filePath) { - return pimpl->buffer->hasIcon(filePath); + return pimpl_->buffer->hasIcon(filePath); } Opt<wxBitmap> IconBuffer::retrieveFileIcon(const AbstractPath& filePath) { - if (Opt<wxBitmap> ico = pimpl->buffer->retrieve(filePath)) + if (Opt<wxBitmap> ico = pimpl_->buffer->retrieve(filePath)) return ico; //since this icon seems important right now, we don't want to wait until next setWorkload() to start retrieving - pimpl->workload->addToWorkload(filePath); - pimpl->buffer->limitSize(); + pimpl_->workload->addToWorkload(filePath); + pimpl_->buffer->limitSize(); return NoValue(); } @@ -383,8 +383,8 @@ void IconBuffer::setWorkload(const std::vector<AbstractPath>& load) { assert(load.size() < BUFFER_SIZE_MAX / 2); - pimpl->workload->setWorkload(load); //since buffer can only increase due to new workload, - pimpl->buffer->limitSize(); //this is the place to impose the limit from main thread! + pimpl_->workload->setWorkload(load); //since buffer can only increase due to new workload, + pimpl_->buffer->limitSize(); //this is the place to impose the limit from main thread! } @@ -394,13 +394,13 @@ wxBitmap IconBuffer::getIconByExtension(const Zstring& filePath) assert(std::this_thread::get_id() == mainThreadId); - auto it = pimpl->extensionIcons.find(ext); - if (it == pimpl->extensionIcons.end()) + auto it = pimpl_->extensionIcons.find(ext); + if (it == pimpl_->extensionIcons.end()) { const Zstring& templateName(ext.empty() ? Zstr("file") : Zstr("file.") + ext); //don't pass actual file name to getIconByTemplatePath(), e.g. "AUTHORS" has own mime type on Linux!!! //=> we want to buffer by extension only to minimize buffer-misses! - it = pimpl->extensionIcons.emplace(ext, extractWxBitmap(getIconByTemplatePath(templateName, IconBuffer::getSize(iconSizeType)))).first; + it = pimpl_->extensionIcons.emplace(ext, extractWxBitmap(getIconByTemplatePath(templateName, IconBuffer::getSize(iconSizeType)))).first; } //need buffer size limit??? return it->second; diff --git a/FreeFileSync/Source/lib/icon_buffer.h b/FreeFileSync/Source/lib/icon_buffer.h index 8523d956..6721497b 100755 --- a/FreeFileSync/Source/lib/icon_buffer.h +++ b/FreeFileSync/Source/lib/icon_buffer.h @@ -45,7 +45,7 @@ public: private: struct Impl; - const std::unique_ptr<Impl> pimpl; + const std::unique_ptr<Impl> pimpl_; const IconSize iconSizeType; }; diff --git a/FreeFileSync/Source/lib/localization.cpp b/FreeFileSync/Source/lib/localization.cpp index b936c4e4..81ad1565 100755 --- a/FreeFileSync/Source/lib/localization.cpp +++ b/FreeFileSync/Source/lib/localization.cpp @@ -51,9 +51,9 @@ public: { const size_t formNo = pluralParser->getForm(n); if (formNo < it->second.size()) - return replaceCpy(it->second[formNo], L"%x", toGuiString(n)); + return replaceCpy(it->second[formNo], L"%x", formatNumber(n)); } - return replaceCpy(std::abs(n) == 1 ? singular : plural, L"%x", toGuiString(n)); //fallback + return replaceCpy(std::abs(n) == 1 ? singular : plural, L"%x", formatNumber(n)); //fallback } private: @@ -112,12 +112,10 @@ std::vector<TranslationInfo> loadTranslations() { std::vector<TranslationInfo> locMapping; { - const wchar_t ltrMark = L'\u200E'; //UTF-8: E2 80 8E - //default entry: TranslationInfo newEntry; newEntry.languageID = wxLANGUAGE_ENGLISH_US; - newEntry.languageName = std::wstring(L"English (US)") + ltrMark; //handle weak ")" for bidi-algorithm + newEntry.languageName = std::wstring(L"English (US)") + LTR_MARK; //handle weak ")" for bidi-algorithm newEntry.translatorName = L"Zenju"; newEntry.languageFlag = L"flag_usa.png"; newEntry.langFilePath = Zstr(""); diff --git a/FreeFileSync/Source/lib/perf_check.cpp b/FreeFileSync/Source/lib/perf_check.cpp index 87fc10ee..c2cd9b01 100755 --- a/FreeFileSync/Source/lib/perf_check.cpp +++ b/FreeFileSync/Source/lib/perf_check.cpp @@ -102,7 +102,7 @@ zen::Opt<std::wstring> PerfCheck::getBytesPerSecond() const const double bytesDelta = itemBack.second.bytes_ - itemFront.second.bytes_; if (timeDeltaMs != 0) - return filesizeToShortString(static_cast<int64_t>(bytesDelta * 1000.0 / timeDeltaMs)) + _("/sec"); + return formatFilesizeShort(static_cast<int64_t>(bytesDelta * 1000.0 / timeDeltaMs)) + _("/sec"); } return NoValue(); } diff --git a/FreeFileSync/Source/lib/process_xml.cpp b/FreeFileSync/Source/lib/process_xml.cpp index 31cf7120..3165a108 100755 --- a/FreeFileSync/Source/lib/process_xml.cpp +++ b/FreeFileSync/Source/lib/process_xml.cpp @@ -23,9 +23,9 @@ using namespace std::rel_ops; namespace { //------------------------------------------------------------------------------------------------------------------------------- -const int XML_FORMAT_VER_GLOBAL = 4; -const int XML_FORMAT_VER_FFS_GUI = 7; //2017-02-16 -const int XML_FORMAT_VER_FFS_BATCH = 7; // +const int XML_FORMAT_VER_GLOBAL = 5; // +const int XML_FORMAT_VER_FFS_GUI = 8; //2017-10-24 +const int XML_FORMAT_VER_FFS_BATCH = 8; // //------------------------------------------------------------------------------------------------------------------------------- } @@ -88,44 +88,19 @@ Zstring xmlAccess::getGlobalConfigFile() } -xmlAccess::XmlGuiConfig xmlAccess::convertBatchToGui(const xmlAccess::XmlBatchConfig& batchCfg) //noexcept +XmlGuiConfig xmlAccess::convertBatchToGui(const XmlBatchConfig& batchCfg) //noexcept { XmlGuiConfig output; output.mainCfg = batchCfg.mainCfg; - - switch (batchCfg.handleError) - { - case ON_ERROR_POPUP: - case ON_ERROR_STOP: - output.handleError = ON_GUIERROR_POPUP; - break; - case ON_ERROR_IGNORE: - output.handleError = ON_GUIERROR_IGNORE; - break; - } return output; } -xmlAccess::XmlBatchConfig xmlAccess::convertGuiToBatch(const xmlAccess::XmlGuiConfig& guiCfg, const XmlBatchConfig* referenceBatchCfg) //noexcept +XmlBatchConfig xmlAccess::convertGuiToBatch(const XmlGuiConfig& guiCfg, const BatchExclusiveConfig& batchExCfg) //noexcept { XmlBatchConfig output; - - //try to take over batch-specific settings from reference if available - if (referenceBatchCfg) - output = *referenceBatchCfg; - else - switch (guiCfg.handleError) - { - case ON_GUIERROR_POPUP: - output.handleError = ON_ERROR_POPUP; - break; - case ON_GUIERROR_IGNORE: - output.handleError = ON_ERROR_IGNORE; - break; - } - output.mainCfg = guiCfg.mainCfg; + output.batchExCfg = batchExCfg; return output; } @@ -244,32 +219,60 @@ bool readText(const std::string& input, SyncDirection& value) template <> inline -void writeText(const OnError& value, std::string& output) +void writeText(const BatchErrorDialog& value, std::string& output) +{ + switch (value) + { + case BatchErrorDialog::SHOW: + output = "Show"; + break; + case BatchErrorDialog::CANCEL: + output = "Cancel"; + break; + } +} + +template <> inline +bool readText(const std::string& input, BatchErrorDialog& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Show") + value = BatchErrorDialog::SHOW; + else if (tmp == "Cancel") + value = BatchErrorDialog::CANCEL; + else + return false; + return true; +} + + +template <> inline +void writeText(const PostSyncCondition& value, std::string& output) { switch (value) { - case ON_ERROR_IGNORE: - output = "Ignore"; + case PostSyncCondition::COMPLETION: + output = "Completion"; break; - case ON_ERROR_POPUP: - output = "Popup"; + case PostSyncCondition::ERRORS: + output = "Errors"; break; - case ON_ERROR_STOP: - output = "Stop"; + case PostSyncCondition::SUCCESS: + output = "Success"; break; } } template <> inline -bool readText(const std::string& input, OnError& value) +bool readText(const std::string& input, PostSyncCondition& value) { const std::string tmp = trimCpy(input); - if (tmp == "Ignore") - value = ON_ERROR_IGNORE; - else if (tmp == "Popup") - value = ON_ERROR_POPUP; - else if (tmp == "Stop") - value = ON_ERROR_STOP; + if (tmp == "Completion") + value = PostSyncCondition::COMPLETION; + else if (tmp == "Errors") + value = PostSyncCondition::ERRORS; + else if (tmp == "Success") + value = PostSyncCondition::SUCCESS; else return false; return true; @@ -277,27 +280,37 @@ bool readText(const std::string& input, OnError& value) template <> inline -void writeText(const OnGuiError& value, std::string& output) +void writeText(const PostSyncAction& value, std::string& output) { switch (value) { - case ON_GUIERROR_IGNORE: - output = "Ignore"; + case PostSyncAction::SUMMARY: + output = "Summary"; + break; + case PostSyncAction::EXIT: + output = "Exit"; + break; + case PostSyncAction::SLEEP: + output = "Sleep"; break; - case ON_GUIERROR_POPUP: - output = "Popup"; + case PostSyncAction::SHUTDOWN: + output = "Shutdown"; break; } } template <> inline -bool readText(const std::string& input, OnGuiError& value) +bool readText(const std::string& input, PostSyncAction& value) { const std::string tmp = trimCpy(input); - if (tmp == "Ignore") - value = ON_GUIERROR_IGNORE; - else if (tmp == "Popup") - value = ON_GUIERROR_POPUP; + if (tmp == "Summary") + value = PostSyncAction::SUMMARY; + else if (tmp == "Exit") + value = PostSyncAction::EXIT; + else if (tmp == "Sleep") + value = PostSyncAction::SLEEP; + else if (tmp == "Shutdown") + value = PostSyncAction::SHUTDOWN; else return false; return true; @@ -321,7 +334,6 @@ void writeText(const FileIconSize& value, std::string& output) } } - template <> inline bool readText(const std::string& input, FileIconSize& value) { @@ -974,39 +986,107 @@ void readConfig(const XmlIn& in, MainConfiguration& mainCfg, int formatVer) mainCfg.additionalPairs.push_back(newPair); //set additional folder pairs } - inMain["OnCompletion"](mainCfg.onCompletion); + //TODO: remove if parameter migration after some time! 2017-10-24 + if (formatVer < 8) + inMain["OnCompletion"](mainCfg.postSyncCommand); + else + { + inMain["IgnoreErrors"](mainCfg.ignoreErrors); + inMain["PostSyncCommand"](mainCfg.postSyncCommand); + inMain["PostSyncCommand"].attribute("Condition", mainCfg.postSyncCondition); + } } -void readConfig(const XmlIn& in, xmlAccess::XmlGuiConfig& config, int formatVer) +void readConfig(const XmlIn& in, XmlGuiConfig& config, int formatVer) { - readConfig(in, config.mainCfg, formatVer); //read main config + //read main config + readConfig(in, config.mainCfg, formatVer); //read GUI specific config data XmlIn inGuiCfg = in["GuiConfig"]; - inGuiCfg["HandleError"](config.handleError); - std::string val; if (inGuiCfg["MiddleGridView"](val)) //refactor into enum!? config.highlightSyncAction = val == "Action"; + + //TODO: remove if clause after migration! 2017-10-24 + if (formatVer < 8) + { + std::string str; + if (inGuiCfg["HandleError"](str)) + config.mainCfg.ignoreErrors = str == "Ignore"; + + str = trimCpy(utfTo<std::string>(config.mainCfg.postSyncCommand)); + if (str == "Close progress dialog") + config.mainCfg.postSyncCommand.clear(); + } } -void readConfig(const XmlIn& in, xmlAccess::XmlBatchConfig& config, int formatVer) +void readConfig(const XmlIn& in, BatchExclusiveConfig& config, int formatVer) { - readConfig(in, config.mainCfg, formatVer); //read main config - - //read GUI specific config data XmlIn inBatchCfg = in["BatchConfig"]; - inBatchCfg["HandleError" ](config.handleError); + //TODO: remove if clause after migration! 2017-10-24 + if (formatVer < 8) + { + std::string str; + if (inBatchCfg["HandleError"](str)) + config.batchErrorDialog = str == "Stop" ? BatchErrorDialog::CANCEL : BatchErrorDialog::SHOW; + } + else + { + inBatchCfg["ErrorDialog"](config.batchErrorDialog); + inBatchCfg["PostSyncAction"](config.postSyncAction); + } + inBatchCfg["RunMinimized" ](config.runMinimized); inBatchCfg["LogfileFolder"](config.logFolderPathPhrase); inBatchCfg["LogfileFolder"].attribute("Limit", config.logfilesCountLimit); } +void readConfig(const XmlIn& in, XmlBatchConfig& config, int formatVer) +{ + readConfig(in, config.mainCfg, formatVer); + readConfig(in, config.batchExCfg, formatVer); + + //TODO: remove if clause after migration! 2017-10-24 + if (formatVer < 8) + { + std::string str; + if (in["BatchConfig"]["HandleError"](str)) + config.mainCfg.ignoreErrors = str == "Ignore"; + + str = trimCpy(utfTo<std::string>(config.mainCfg.postSyncCommand)); + if (str == "Close progress dialog") + { + config.batchExCfg.postSyncAction = PostSyncAction::EXIT; + config.mainCfg.postSyncCommand.clear(); + } + else if (str == "rundll32.exe powrprof.dll,SetSuspendState Sleep" || + str == "rundll32.exe powrprof.dll,SetSuspendState" || + str == "systemctl suspend" || + str == "osascript -e \'tell application \"System Events\" to sleep\'") + { + config.batchExCfg.postSyncAction = PostSyncAction::SLEEP; + config.mainCfg.postSyncCommand.clear(); + } + else if (str == "shutdown /s /t 60" || + str == "shutdown -s -t 60" || + str == "systemctl poweroff" || + str == "osascript -e \'tell application \"System Events\" to shut down\'") + { + config.batchExCfg.postSyncAction = PostSyncAction::SHUTDOWN; + config.mainCfg.postSyncCommand.clear(); + } + else if (config.batchExCfg.runMinimized) + config.batchExCfg.postSyncAction = PostSyncAction::EXIT; + } +} + + void readConfig(const XmlIn& in, XmlGlobalSettings& config, int formatVer) { XmlIn inGeneral = in["General"]; @@ -1067,9 +1147,6 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config, int formatVer) inCopyToHistory.attribute("LastUsedPath", config.gui.mainDlg.copyToCfg.lastUsedPath); inCopyToHistory.attribute("MaxSize", config.gui.mainDlg.copyToCfg.historySizeMax); - XmlIn inManualDel = inWnd["ManualDeletion"]; - inManualDel.attribute("UseRecycler", config.gui.mainDlg.manualDeletionUseRecycler); - inWnd["CaseSensitiveSearch"].attribute("Enabled", config.gui.mainDlg.textSearchRespectCase); inWnd["FolderPairsVisible" ].attribute("Max", config.gui.mainDlg.maxFolderPairsVisible); @@ -1121,8 +1198,17 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config, int formatVer) inGui["FolderHistoryRight"](config.gui.folderHistoryRight); inGui["FolderHistoryLeft"].attribute("MaxSize", config.gui.folderHistMax); - inGui["OnCompletionHistory"](config.gui.onCompletionHistory); - inGui["OnCompletionHistory"].attribute("MaxSize", config.gui.onCompletionHistoryMax); + //TODO: remove if clause after migration! 2017-10-24 + if (formatVer < 5) + { + inGui["OnCompletionHistory"](config.gui.commandHistory); + inGui["OnCompletionHistory"].attribute("MaxSize", config.gui.commandHistoryMax); + } + else + { + inGui["CommandHistory"](config.gui.commandHistory); + inGui["CommandHistory"].attribute("MaxSize", config.gui.commandHistoryMax); + } //external applications //TODO: remove old parameter after migration! 2016-05-28 @@ -1174,7 +1260,6 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config, int formatVer) int getConfigFormatVersion(const XmlDoc& doc) { - //(try to) migrate old configuration if needed int xmlFormatVer = 0; /*bool success = */doc.root().getAttribute("XmlFormat", xmlFormatVer); return xmlFormatVer; @@ -1198,7 +1283,7 @@ void readConfig(const Zstring& filepath, XmlType type, ConfigType& cfg, int curr { checkForMappingErrors(in, filepath); //throw FileError - //(try to) migrate old configuration if needed + //(try to) migrate old configuration automatically if (formatVer< currentXmlFormatVer) try { xmlAccess::writeConfig(cfg, filepath); /*throw FileError*/ } catch (FileError&) { assert(false); } //don't bother user! @@ -1211,19 +1296,19 @@ void readConfig(const Zstring& filepath, XmlType type, ConfigType& cfg, int curr } -void xmlAccess::readConfig(const Zstring& filepath, xmlAccess::XmlGuiConfig& cfg, std::wstring& warningMsg) +void xmlAccess::readConfig(const Zstring& filepath, XmlGuiConfig& cfg, std::wstring& warningMsg) { ::readConfig(filepath, XML_TYPE_GUI, cfg, XML_FORMAT_VER_FFS_GUI, warningMsg); //throw FileError } -void xmlAccess::readConfig(const Zstring& filepath, xmlAccess::XmlBatchConfig& cfg, std::wstring& warningMsg) +void xmlAccess::readConfig(const Zstring& filepath, XmlBatchConfig& cfg, std::wstring& warningMsg) { ::readConfig(filepath, XML_TYPE_BATCH, cfg, XML_FORMAT_VER_FFS_BATCH, warningMsg); //throw FileError } -void xmlAccess::readConfig(const Zstring& filepath, xmlAccess::XmlGlobalSettings& cfg, std::wstring& warningMsg) +void xmlAccess::readConfig(const Zstring& filepath, XmlGlobalSettings& cfg, std::wstring& warningMsg) { ::readConfig(filepath, XML_TYPE_GLOBAL, cfg, XML_FORMAT_VER_GLOBAL, warningMsg); //throw FileError } @@ -1422,7 +1507,9 @@ void writeConfig(const MainConfiguration& mainCfg, XmlOut& out) for (const FolderPairEnh& fp : mainCfg.additionalPairs) writeConfig(fp, outFp); - outMain["OnCompletion"](mainCfg.onCompletion); + outMain["IgnoreErrors"](mainCfg.ignoreErrors); + outMain["PostSyncCommand"](mainCfg.postSyncCommand); + outMain["PostSyncCommand"].attribute("Condition", mainCfg.postSyncCondition); } @@ -1433,25 +1520,29 @@ void writeConfig(const XmlGuiConfig& config, XmlOut& out) //write GUI specific config data XmlOut outGuiCfg = out["GuiConfig"]; - outGuiCfg["HandleError" ](config.handleError); outGuiCfg["MiddleGridView"](config.highlightSyncAction ? "Action" : "Category"); //refactor into enum!? } -void writeConfig(const XmlBatchConfig& config, XmlOut& out) -{ - - writeConfig(config.mainCfg, out); //write main config - //write GUI specific config data +void writeConfig(const BatchExclusiveConfig& config, XmlOut& out) +{ XmlOut outBatchCfg = out["BatchConfig"]; - outBatchCfg["HandleError" ](config.handleError); + outBatchCfg["ErrorDialog" ](config.batchErrorDialog); + outBatchCfg["PostSyncAction"](config.postSyncAction); outBatchCfg["RunMinimized" ](config.runMinimized); outBatchCfg["LogfileFolder"](config.logFolderPathPhrase); outBatchCfg["LogfileFolder"].attribute("Limit", config.logfilesCountLimit); } +void writeConfig(const XmlBatchConfig& config, XmlOut& out) +{ + writeConfig(config.mainCfg, out); + writeConfig(config.batchExCfg, out); +} + + void writeConfig(const XmlGlobalSettings& config, XmlOut& out) { XmlOut outGeneral = out["General"]; @@ -1508,9 +1599,6 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) outCopyToHistory.attribute("LastUsedPath", config.gui.mainDlg.copyToCfg.lastUsedPath); outCopyToHistory.attribute("MaxSize", config.gui.mainDlg.copyToCfg.historySizeMax); - XmlOut outManualDel = outWnd["ManualDeletion"]; - outManualDel.attribute("UseRecycler", config.gui.mainDlg.manualDeletionUseRecycler); - outWnd["CaseSensitiveSearch"].attribute("Enabled", config.gui.mainDlg.textSearchRespectCase); outWnd["FolderPairsVisible" ].attribute("Max", config.gui.mainDlg.maxFolderPairsVisible); @@ -1556,8 +1644,8 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) outGui["FolderHistoryRight"](config.gui.folderHistoryRight); outGui["FolderHistoryLeft" ].attribute("MaxSize", config.gui.folderHistMax); - outGui["OnCompletionHistory"](config.gui.onCompletionHistory); - outGui["OnCompletionHistory"].attribute("MaxSize", config.gui.onCompletionHistoryMax); + outGui["CommandHistory"](config.gui.commandHistory); + outGui["CommandHistory"].attribute("MaxSize", config.gui.commandHistoryMax); //external applications outGui["ExternalApps"](config.gui.externelApplications); diff --git a/FreeFileSync/Source/lib/process_xml.h b/FreeFileSync/Source/lib/process_xml.h index d7f45543..30e05fab 100755 --- a/FreeFileSync/Source/lib/process_xml.h +++ b/FreeFileSync/Source/lib/process_xml.h @@ -27,17 +27,19 @@ enum XmlType XmlType getXmlType(const Zstring& filepath); //throw FileError -enum OnError +enum class BatchErrorDialog { - ON_ERROR_IGNORE, - ON_ERROR_POPUP, - ON_ERROR_STOP + SHOW, + CANCEL }; -enum OnGuiError + +enum class PostSyncAction { - ON_GUIERROR_POPUP, - ON_GUIERROR_IGNORE + SUMMARY, + EXIT, + SLEEP, + SHUTDOWN }; using Description = std::wstring; @@ -49,7 +51,6 @@ struct XmlGuiConfig { zen::MainConfiguration mainCfg; - OnGuiError handleError = ON_GUIERROR_POPUP; //reaction on error situation during synchronization bool highlightSyncAction = true; }; @@ -57,20 +58,25 @@ struct XmlGuiConfig inline bool operator==(const XmlGuiConfig& lhs, const XmlGuiConfig& rhs) { - return lhs.mainCfg == rhs.mainCfg && - lhs.handleError == rhs.handleError && + return lhs.mainCfg == rhs.mainCfg && lhs.highlightSyncAction == rhs.highlightSyncAction; } -struct XmlBatchConfig +struct BatchExclusiveConfig { - zen::MainConfiguration mainCfg; - + BatchErrorDialog batchErrorDialog = BatchErrorDialog::SHOW; bool runMinimized = false; + PostSyncAction postSyncAction = PostSyncAction::SUMMARY; Zstring logFolderPathPhrase; int logfilesCountLimit = -1; //max logfiles; 0 := don't save logfiles; < 0 := no limit - OnError handleError = ON_ERROR_POPUP; //reaction on error situation during synchronization +}; + + +struct XmlBatchConfig +{ + zen::MainConfiguration mainCfg; + BatchExclusiveConfig batchExCfg; }; @@ -177,7 +183,6 @@ struct XmlGlobalSettings size_t historySizeMax = 15; } copyToCfg; - bool manualDeletionUseRecycler = true; bool textSearchRespectCase = false; //good default for Linux, too! int maxFolderPairsVisible = 6; @@ -214,8 +219,8 @@ struct XmlGlobalSettings std::vector<Zstring> folderHistoryRight; size_t folderHistMax = 15; - std::vector<Zstring> onCompletionHistory; - size_t onCompletionHistoryMax = 8; + std::vector<Zstring> commandHistory; + size_t commandHistoryMax = 8; ExternalApps externelApplications { @@ -245,7 +250,7 @@ void readAnyConfig(const std::vector<Zstring>& filepaths, XmlGuiConfig& config, //config conversion utilities XmlGuiConfig convertBatchToGui(const XmlBatchConfig& batchCfg); //noexcept -XmlBatchConfig convertGuiToBatch(const XmlGuiConfig& guiCfg, const XmlBatchConfig* referenceBatchCfg); // +XmlBatchConfig convertGuiToBatch(const XmlGuiConfig& guiCfg, const BatchExclusiveConfig& batchExCfg); // std::wstring extractJobName(const Zstring& configFilename); } diff --git a/FreeFileSync/Source/lib/resolve_path.cpp b/FreeFileSync/Source/lib/resolve_path.cpp index c4c45779..1c67bd78 100755 --- a/FreeFileSync/Source/lib/resolve_path.cpp +++ b/FreeFileSync/Source/lib/resolve_path.cpp @@ -80,8 +80,6 @@ Zstring resolveRelativePath(const Zstring& relativePath) - - //returns value if resolved Opt<Zstring> tryResolveMacro(const Zstring& macro) //macro without %-characters { diff --git a/FreeFileSync/Source/lib/status_handler.h b/FreeFileSync/Source/lib/status_handler.h index 6e53a1e1..64abced3 100755 --- a/FreeFileSync/Source/lib/status_handler.h +++ b/FreeFileSync/Source/lib/status_handler.h @@ -24,11 +24,21 @@ Updating GUI is fast! - Synchronization 0.74 ms (despite complex graph control!) */ +//Exception class used to abort the "compare" and "sync" process +class AbortProcess {}; + + +enum class AbortTrigger +{ + USER, + PROGRAM, +}; + //gui may want to abort process struct AbortCallback { virtual ~AbortCallback() {} - virtual void requestAbortion() = 0; + virtual void userRequestAbort() = 0; }; @@ -57,73 +67,90 @@ public: numbersCurrent_(4), //init with phase count numbersTotal_ (4) {} // -protected: //implement parts of ProcessCallback void initNewPhase(int itemsTotal, int64_t bytesTotal, Phase phaseId) override //may throw { currentPhase_ = phaseId; - refNumbers(numbersTotal_, currentPhase_) = std::make_pair(itemsTotal, bytesTotal); + refNumbers(numbersTotal_, currentPhase_) = { itemsTotal, bytesTotal }; } void updateProcessedData(int itemsDelta, int64_t bytesDelta) override { updateData(numbersCurrent_, itemsDelta, bytesDelta); } //note: these methods MUST NOT throw in order - void updateTotalData (int itemsDelta, int64_t bytesDelta) override { updateData(numbersTotal_, itemsDelta, bytesDelta); } //to properly allow undoing setting of statistics! + void updateTotalData (int itemsDelta, int64_t bytesDelta) override { updateData(numbersTotal_, itemsDelta, bytesDelta); } //to properly allow undoing setting of statistics! void requestUiRefresh() override //throw X { - if (abortRequested) //triggered by requestAbortion() + if (abortRequested_) //triggered by requestAbortion() { forceUiRefresh(); - abortProcessNow(); //throw X + throw AbortProcess(); } - else if (updateUiIsAllowed()) //test if specific time span between ui updates is over + if (updateUiIsAllowed()) forceUiRefresh(); } void reportStatus(const std::wstring& text) override //throw X { - //assert(!text.empty()); -> possible, start of parallel scan - if (!abortRequested) statusText_ = text; + //assert(!text.empty()); -> possible: start of parallel scan + if (!abortRequested_) statusText_ = text; requestUiRefresh(); //throw X } void reportInfo(const std::wstring& text) override //throw X { assert(!text.empty()); - if (!abortRequested) statusText_ = text; + if (!abortRequested_) statusText_ = text; requestUiRefresh(); //throw X //log text in derived class } + void abortProcessNow() override + { + if (!abortRequested_) abortRequested_ = AbortTrigger::PROGRAM; + throw AbortProcess(); + } + + void userAbortProcessNow() + { + if (!abortRequested_) abortRequested_ = AbortTrigger::USER; + throw AbortProcess(); + } + //implement AbortCallback - void requestAbortion() override + void userRequestAbort() override { - abortRequested = true; + if (!abortRequested_) abortRequested_ = AbortTrigger::USER; statusText_ = _("Stop requested: Waiting for current operation to finish..."); } //called from GUI code: this does NOT call abortProcessNow() immediately, but later when we're out of the C GUI call stack //implement Statistics Phase currentPhase() const override { return currentPhase_; } - int getItemsCurrent(Phase phaseId) const override { return refNumbers(numbersCurrent_, phaseId).first; } - int getItemsTotal (Phase phaseId) const override { assert(phaseId != PHASE_SCANNING); return refNumbers(numbersTotal_, phaseId).first; } + int getItemsCurrent(Phase phaseId) const override { return refNumbers(numbersCurrent_, phaseId).items; } + int getItemsTotal (Phase phaseId) const override { assert(phaseId != PHASE_SCANNING); return refNumbers(numbersTotal_, phaseId).items; } - int64_t getBytesCurrent(Phase phaseId) const override { assert(phaseId != PHASE_SCANNING); return refNumbers(numbersCurrent_, phaseId).second; } - int64_t getBytesTotal (Phase phaseId) const override { assert(phaseId != PHASE_SCANNING); return refNumbers(numbersTotal_, phaseId).second; } + int64_t getBytesCurrent(Phase phaseId) const override { assert(phaseId != PHASE_SCANNING); return refNumbers(numbersCurrent_, phaseId).bytes; } + int64_t getBytesTotal (Phase phaseId) const override { assert(phaseId != PHASE_SCANNING); return refNumbers(numbersTotal_, phaseId).bytes; } const std::wstring& currentStatusText() const override { return statusText_; } - bool abortIsRequested() const { return abortRequested; } +protected: + Opt<AbortTrigger> getAbortStatus() const { return abortRequested_; } private: - using StatNumbers = std::vector<std::pair<int, int64_t>>; + struct StatNumber + { + int items = 0; + int64_t bytes = 0; + }; + using StatNumbers = std::vector<StatNumber>; void updateData(StatNumbers& num, int itemsDelta, int64_t bytesDelta) { auto& st = refNumbers(num, currentPhase_); - st.first += itemsDelta; - st.second += bytesDelta; + st.items += itemsDelta; + st.bytes += bytesDelta; } - static const std::pair<int, int64_t>& refNumbers(const StatNumbers& num, Phase phaseId) + static const StatNumber& refNumbers(const StatNumbers& num, Phase phaseId) { switch (phaseId) { @@ -140,14 +167,14 @@ private: return num[3]; //dummy entry! } - static std::pair<int, int64_t>& refNumbers(StatNumbers& num, Phase phaseId) { return const_cast<std::pair<int, int64_t>&>(refNumbers(static_cast<const StatNumbers&>(num), phaseId)); } + static StatNumber& refNumbers(StatNumbers& num, Phase phaseId) { return const_cast<StatNumber&>(refNumbers(static_cast<const StatNumbers&>(num), phaseId)); } Phase currentPhase_ = PHASE_NONE; StatNumbers numbersCurrent_; StatNumbers numbersTotal_; std::wstring statusText_; - bool abortRequested = false; + Opt<AbortTrigger> abortRequested_; }; } diff --git a/FreeFileSync/Source/structures.cpp b/FreeFileSync/Source/structures.cpp index fb8e2824..fb605b9a 100755 --- a/FreeFileSync/Source/structures.cpp +++ b/FreeFileSync/Source/structures.cpp @@ -69,13 +69,22 @@ std::wstring zen::getVariantName(CompareVariant var) std::wstring zen::getVariantName(DirectionConfig::Variant var) { - //const wchar_t arrowLeft [] = L"\u2190"; - //const wchar_t arrowRight[] = L"\u2192"; unicode arrows -> too small + const wchar_t arrowLeft [] = L"<-"; + const wchar_t arrowRight[] = L"->"; + const wchar_t angleRight[] = L">"; +#if 0 + //const wchar_t arrowLeft [] = L"\u2190"; unicode arrows -> too small + //const wchar_t arrowRight[] = L"\u2192"; const wchar_t arrowLeft [] = L"\uFF1C\u2013"; //fullwidth less-than + en dash const wchar_t arrowRight[] = L"\u2013\uFF1E"; //en dash + fullwidth greater-than const wchar_t angleRight[] = L"\uFF1E"; + => drawbacks: + - not drawn correctly before Vista + - used in sync log files where users expect ANSI: https://www.freefilesync.org/forum/viewtopic.php?t=4647 + - RTL: the full width less-than does not swap automatically +#endif - switch (var) + switch (var) { case DirectionConfig::TWO_WAY: return std::wstring(arrowLeft) + L" " + _("Two way") + L" " + arrowRight; @@ -543,12 +552,14 @@ MainConfiguration zen::merge(const std::vector<MainConfiguration>& mainCfgs) } //final assembly - zen::MainConfiguration cfgOut; + MainConfiguration cfgOut; cfgOut.cmpConfig = cmpCfgHead; cfgOut.syncCfg = syncCfgHead; cfgOut.globalFilter = globalFilter; cfgOut.firstPair = fpMerged[0]; cfgOut.additionalPairs.assign(fpMerged.begin() + 1, fpMerged.end()); - cfgOut.onCompletion = mainCfgs[0].onCompletion; + cfgOut.ignoreErrors = std::all_of(mainCfgs.begin(), mainCfgs.end(), [](const MainConfiguration& mainCfg) { return mainCfg.ignoreErrors; }); + //cfgOut.postSyncCommand = mainCfgs[0].postSyncCommand; -> better leave at default ... !? + //cfgOut.postSyncCondition = mainCfgs[0].postSyncCondition; -> return cfgOut; } diff --git a/FreeFileSync/Source/structures.h b/FreeFileSync/Source/structures.h index 91b7df1c..ee8f625c 100755 --- a/FreeFileSync/Source/structures.h +++ b/FreeFileSync/Source/structures.h @@ -361,6 +361,14 @@ bool operator==(const FolderPairEnh& lhs, const FolderPairEnh& rhs) } +enum class PostSyncCondition +{ + COMPLETION, + ERRORS, + SUCCESS +}; + + struct MainConfiguration { CompConfig cmpConfig; //global compare settings: may be overwritten by folder pair settings @@ -370,7 +378,10 @@ struct MainConfiguration FolderPairEnh firstPair; //there needs to be at least one pair! std::vector<FolderPairEnh> additionalPairs; - Zstring onCompletion; //user-defined command line + bool ignoreErrors = false; //true: errors will still be logged + + Zstring postSyncCommand; //user-defined command line + PostSyncCondition postSyncCondition = PostSyncCondition::COMPLETION; std::wstring getCompVariantName() const; std::wstring getSyncVariantName() const; @@ -380,12 +391,14 @@ struct MainConfiguration inline bool operator==(const MainConfiguration& lhs, const MainConfiguration& rhs) { - return lhs.cmpConfig == rhs.cmpConfig && - lhs.syncCfg == rhs.syncCfg && - lhs.globalFilter == rhs.globalFilter && - lhs.firstPair == rhs.firstPair && - lhs.additionalPairs == rhs.additionalPairs && - lhs.onCompletion == rhs.onCompletion; + return lhs.cmpConfig == rhs.cmpConfig && + lhs.syncCfg == rhs.syncCfg && + lhs.globalFilter == rhs.globalFilter && + lhs.firstPair == rhs.firstPair && + lhs.additionalPairs == rhs.additionalPairs && + lhs.ignoreErrors == rhs.ignoreErrors && + lhs.postSyncCommand == rhs.postSyncCommand && + lhs.postSyncCondition == rhs.postSyncCondition; } diff --git a/FreeFileSync/Source/synchronization.cpp b/FreeFileSync/Source/synchronization.cpp index b756459a..0aa4500e 100755 --- a/FreeFileSync/Source/synchronization.cpp +++ b/FreeFileSync/Source/synchronization.cpp @@ -1280,7 +1280,7 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& file, SyncOperation syn //file.removeObject<sideTrg>(); -> doesn't make sense for isFollowedSymlink(); "file, sideTrg" evaluated below! //if fail-safe file copy is active, then the next operation will be a simple "rename" - //=> don't risk reportStatus() throwing GuiAbortProcess() leaving the target deleted rather than updated! + //=> don't risk reportStatus() throwing AbortProcess() leaving the target deleted rather than updated! //=> if failSafeFileCopy_ : don't run callbacks that could throw }; @@ -1434,7 +1434,7 @@ void SynchronizeFolderPair::synchronizeLinkInt(SymlinkPair& symlink, SyncOperati //symlink.removeObject<sideTrg>(); -> "symlink, sideTrg" evaluated below! - //=> don't risk reportStatus() throwing GuiAbortProcess() leaving the target deleted rather than updated: + //=> don't risk reportStatus() throwing AbortProcess() leaving the target deleted rather than updated: //reportStatus(txtOverwritingLink, AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //restore status text AFS::copySymlink(symlink.getAbstractPath<sideSrc>(), @@ -2027,8 +2027,8 @@ void zen::synchronize(const std::chrono::system_clock::time_point& syncStartTime for (const auto& item : diskSpaceMissing) msg += L"\n\n" + AFS::getDisplayPath(item.first) + L"\n" + - _("Required:") + L" " + filesizeToShortString(item.second.first) + L"\n" + - _("Available:") + L" " + filesizeToShortString(item.second.second); + _("Required:") + L" " + formatFilesizeShort(item.second.first) + L"\n" + + _("Available:") + L" " + formatFilesizeShort(item.second.second); callback.reportWarning(msg, warnings.warnNotEnoughDiskSpace); } diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp index 0c1eef12..fdacb34a 100755 --- a/FreeFileSync/Source/ui/batch_config.cpp +++ b/FreeFileSync/Source/ui/batch_config.cpp @@ -9,9 +9,10 @@ #include <wx+/std_button_layout.h> #include <wx+/font_size.h> #include <wx+/image_resources.h> +#include <wx+/image_tools.h> +#include <wx+/choice_enum.h> #include "gui_generated.h" #include "folder_selector.h" -#include "../ui/on_completion_box.h" #include "../lib/help_provider.h" @@ -21,21 +22,26 @@ using namespace xmlAccess; namespace { +struct BatchDialogConfig +{ + BatchExclusiveConfig batchExCfg; + bool ignoreErrors = false; +}; + + class BatchDialog : public BatchDlgGenerated { public: - BatchDialog(wxWindow* parent, - XmlBatchConfig& batchCfg, //in/out - std::vector<Zstring>& onCompletionHistory, - size_t onCompletionHistoryMax); + BatchDialog(wxWindow* parent, BatchDialogConfig& dlgCfg); private: void OnClose (wxCloseEvent& event) override { EndModal(ReturnBatchConfig::BUTTON_CANCEL); } void OnCancel (wxCommandEvent& event) override { EndModal(ReturnBatchConfig::BUTTON_CANCEL); } void OnSaveBatchJob(wxCommandEvent& event) override; - void OnErrorPopup (wxCommandEvent& event) override { localBatchCfg.handleError = ON_ERROR_POPUP; updateGui(); } - void OnErrorIgnore (wxCommandEvent& event) override { localBatchCfg.handleError = ON_ERROR_IGNORE; updateGui(); } - void OnErrorStop (wxCommandEvent& event) override { localBatchCfg.handleError = ON_ERROR_STOP; updateGui(); } + + void OnToggleIgnoreErrors(wxCommandEvent& event) override { updateGui(); } + void OnToggleRunMinimized(wxCommandEvent& event) override { updateGui(); } + void OnHelpScheduleBatch(wxHyperlinkEvent& event) override { displayHelpEntry(L"schedule-a-batch-job", this); } void OnToggleGenerateLogfile(wxCommandEvent& event) override { updateGui(); } @@ -43,38 +49,38 @@ private: void updateGui(); //re-evaluate gui after config changes - void setConfig(const XmlBatchConfig& batchCfg); - XmlBatchConfig getConfig() const; + void setConfig(const BatchDialogConfig& batchCfg); + BatchDialogConfig getConfig() const; - XmlBatchConfig& batchCfgOutRef; //output only! - std::vector<Zstring>& onCompletionHistoryOut; // + //output-only parameters + BatchDialogConfig& dlgCfgOut_; - XmlBatchConfig localBatchCfg; //a mixture of settings some of which have OWNERSHIP WITHIN GUI CONTROLS! use getConfig() to resolve + std::unique_ptr<FolderSelector> logfileDir_; //always bound, solve circular compile-time dependency - std::unique_ptr<FolderSelector> logfileDir; //always bound, solve circular compile-time dependency + EnumDescrList<PostSyncAction> enumPostSyncAction_; }; //################################################################################################################################### -BatchDialog::BatchDialog(wxWindow* parent, - XmlBatchConfig& batchCfg, - std::vector<Zstring>& onCompletionHistory, - size_t onCompletionHistoryMax) : +BatchDialog::BatchDialog(wxWindow* parent, BatchDialogConfig& dlgCfg) : BatchDlgGenerated(parent), - batchCfgOutRef(batchCfg), - onCompletionHistoryOut(onCompletionHistory) + dlgCfgOut_(dlgCfg) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonSaveAs).setCancel(m_buttonCancel)); m_staticTextDescr->SetLabel(replaceCpy(m_staticTextDescr->GetLabel(), L"%x", L"FreeFileSync.exe <" + _("job name") + L">.ffs_batch")); - m_comboBoxOnCompletion->setHistory(onCompletionHistory, onCompletionHistoryMax); - m_bitmapBatchJob->SetBitmap(getResourceImage(L"batch")); - logfileDir = std::make_unique<FolderSelector>(*m_panelLogfile, *m_buttonSelectLogFolder, *m_bpButtonSelectAltLogFolder, *m_logFolderPath, nullptr /*staticText*/, nullptr /*wxWindow*/); + logfileDir_ = std::make_unique<FolderSelector>(*m_panelLogfile, *m_buttonSelectLogFolder, *m_bpButtonSelectAltLogFolder, *m_logFolderPath, nullptr /*staticText*/, nullptr /*wxWindow*/); - setConfig(batchCfg); + enumPostSyncAction_. + add(PostSyncAction::SUMMARY, _("Show summary")). + add(PostSyncAction::EXIT, replaceCpy(_("E&xit"), L"&", L"")). //reuse translation + add(PostSyncAction::SLEEP, _("Sleep")). + add(PostSyncAction::SHUTDOWN, _("Shut down")); + + setConfig(dlgCfg); GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! @@ -86,81 +92,96 @@ BatchDialog::BatchDialog(wxWindow* parent, void BatchDialog::updateGui() //re-evaluate gui after config changes { - XmlBatchConfig cfg = getConfig(); //resolve parameter ownership: some on GUI controls, others member variables + const BatchDialogConfig dlgCfg = getConfig(); //resolve parameter ownership: some on GUI controls, others member variables - m_panelLogfile ->Enable(m_checkBoxGenerateLogfile->GetValue()); //enabled status is *not* directly dependent from resolved config! (but transitively) - m_spinCtrlLogfileLimit->Enable(m_checkBoxGenerateLogfile->GetValue() && m_checkBoxLogfilesLimit->GetValue()); + m_bitmapIgnoreErrors->SetBitmap(getResourceImage(dlgCfg.ignoreErrors ? L"msg_error_medium_ignored" : L"msg_error_medium")); - m_radioBtnIgnoreErrors ->SetValue(false); - m_radioBtnPopupOnErrors ->SetValue(false); - m_radioBtnStopOnError ->SetValue(false); - switch (cfg.handleError) //*not* owned by GUI controls - { - case ON_ERROR_IGNORE: - m_radioBtnIgnoreErrors->SetValue(true); - break; - case ON_ERROR_POPUP: - m_radioBtnPopupOnErrors->SetValue(true); - break; - case ON_ERROR_STOP: - m_radioBtnStopOnError->SetValue(true); - break; - } + m_radioBtnErrorDialogShow ->Enable(!dlgCfg.ignoreErrors); + m_radioBtnErrorDialogCancel->Enable(!dlgCfg.ignoreErrors); + + + m_bitmapMinimizeToTray->SetBitmap(dlgCfg.batchExCfg.runMinimized ? getResourceImage(L"minimize_to_tray") : greyScale(getResourceImage(L"minimize_to_tray"))); + + + m_panelLogfile->Enable(m_checkBoxSaveLog->GetValue()); //enabled status is *not* directly dependent from resolved config! (but transitively) + m_bitmapLogFile->SetBitmap(m_checkBoxSaveLog->GetValue() ? getResourceImage(L"log_file") : greyScale(getResourceImage(L"log_file"))); + m_checkBoxLogfilesLimit->Enable(m_checkBoxSaveLog->GetValue()); + m_spinCtrlLogfileLimit ->Enable(m_checkBoxSaveLog->GetValue() && m_checkBoxLogfilesLimit->GetValue()); } -void BatchDialog::setConfig(const XmlBatchConfig& batchCfg) +void BatchDialog::setConfig(const BatchDialogConfig& dlgCfg) { - localBatchCfg = batchCfg; //contains some parameters not owned by GUI controls + m_checkBoxIgnoreErrors->SetValue(dlgCfg.ignoreErrors); //transfer parameter ownership to GUI - m_checkBoxRunMinimized->SetValue(batchCfg.runMinimized); - logfileDir->setPath(batchCfg.logFolderPathPhrase); - m_comboBoxOnCompletion->setValue(batchCfg.mainCfg.onCompletion); + m_radioBtnErrorDialogShow ->SetValue(false); + m_radioBtnErrorDialogCancel->SetValue(false); + + switch (dlgCfg.batchExCfg.batchErrorDialog) + { + case BatchErrorDialog::SHOW: + m_radioBtnErrorDialogShow->SetValue(true); + break; + case BatchErrorDialog::CANCEL: + m_radioBtnErrorDialogCancel->SetValue(true); + break; + } + + m_checkBoxRunMinimized->SetValue(dlgCfg.batchExCfg.runMinimized); + setEnumVal(enumPostSyncAction_, *m_choicePostSyncAction, dlgCfg.batchExCfg.postSyncAction); + logfileDir_->setPath(dlgCfg.batchExCfg.logFolderPathPhrase); //map single parameter "logfiles limit" to all three checkboxs and spin ctrl: - m_checkBoxGenerateLogfile->SetValue(batchCfg.logfilesCountLimit != 0); - m_checkBoxLogfilesLimit ->SetValue(batchCfg.logfilesCountLimit >= 0); - m_spinCtrlLogfileLimit ->SetValue(batchCfg.logfilesCountLimit >= 0 ? batchCfg.logfilesCountLimit : 100 /*XmlBatchConfig().logfilesCountLimit*/); + m_checkBoxSaveLog ->SetValue(dlgCfg.batchExCfg.logfilesCountLimit != 0); + m_checkBoxLogfilesLimit->SetValue(dlgCfg.batchExCfg.logfilesCountLimit > 0); + m_spinCtrlLogfileLimit ->SetValue(dlgCfg.batchExCfg.logfilesCountLimit > 0 ? dlgCfg.batchExCfg.logfilesCountLimit : 100 /*XmlBatchConfig().logfilesCountLimit*/); //attention: emits a "change value" event!! => updateGui() called implicitly! updateGui(); //re-evaluate gui after config changes } -XmlBatchConfig BatchDialog::getConfig() const +BatchDialogConfig BatchDialog::getConfig() const { - XmlBatchConfig batchCfg = localBatchCfg; + BatchDialogConfig dlgCfg = {}; - //load parameters with ownership within GIU controls... + dlgCfg.ignoreErrors = m_checkBoxIgnoreErrors->GetValue(); - //load structure with batch settings "batchCfg" - batchCfg.runMinimized = m_checkBoxRunMinimized->GetValue(); - batchCfg.logFolderPathPhrase = utfTo<Zstring>(logfileDir->getPath()); - batchCfg.mainCfg.onCompletion = m_comboBoxOnCompletion->getValue(); - //get single parameter "logfiles limit" from all three checkboxes and spin ctrl: - batchCfg.logfilesCountLimit = m_checkBoxGenerateLogfile->GetValue() ? (m_checkBoxLogfilesLimit->GetValue() ? m_spinCtrlLogfileLimit->GetValue() : -1) : 0; + dlgCfg.batchExCfg.batchErrorDialog = m_radioBtnErrorDialogCancel->GetValue() ? BatchErrorDialog::CANCEL : BatchErrorDialog::SHOW; + dlgCfg.batchExCfg.runMinimized = m_checkBoxRunMinimized->GetValue(); + dlgCfg.batchExCfg.postSyncAction = getEnumVal(enumPostSyncAction_, *m_choicePostSyncAction); + dlgCfg.batchExCfg.logFolderPathPhrase = utfTo<Zstring>(logfileDir_->getPath()); - return batchCfg; + dlgCfg.batchExCfg.logfilesCountLimit = m_checkBoxSaveLog->GetValue() ? (m_checkBoxLogfilesLimit->GetValue() ? m_spinCtrlLogfileLimit->GetValue() : -1) : 0; + //get single parameter "logfiles limit" from all three checkboxes and spin ctrl + + return dlgCfg; } void BatchDialog::OnSaveBatchJob(wxCommandEvent& event) { - batchCfgOutRef = getConfig(); - m_comboBoxOnCompletion->addItemHistory(); //a good place to commit current "on completion" history item - onCompletionHistoryOut = m_comboBoxOnCompletion->getHistory(); + dlgCfgOut_ = getConfig(); EndModal(ReturnBatchConfig::BUTTON_SAVE_AS); } } -ReturnBatchConfig::ButtonPressed zen::customizeBatchConfig(wxWindow* parent, - xmlAccess::XmlBatchConfig& batchCfg, //in/out - std::vector<Zstring>& onCompletionHistory, - size_t onCompletionHistoryMax) +ReturnBatchConfig::ButtonPressed zen::showBatchConfigDialog(wxWindow* parent, + BatchExclusiveConfig& batchExCfg, + bool& ignoreErrors) { - BatchDialog batchDlg(parent, batchCfg, onCompletionHistory, onCompletionHistoryMax); - return static_cast<ReturnBatchConfig::ButtonPressed>(batchDlg.ShowModal()); + BatchDialogConfig dlgCfg = { batchExCfg, ignoreErrors }; + + BatchDialog batchDlg(parent, dlgCfg); + + const auto rv = static_cast<ReturnBatchConfig::ButtonPressed>(batchDlg.ShowModal()); + if (rv != ReturnBatchConfig::BUTTON_CANCEL) + { + batchExCfg = dlgCfg.batchExCfg; + ignoreErrors = dlgCfg.ignoreErrors; + } + return rv; } diff --git a/FreeFileSync/Source/ui/batch_config.h b/FreeFileSync/Source/ui/batch_config.h index bb3c3bf4..f60d6e97 100755 --- a/FreeFileSync/Source/ui/batch_config.h +++ b/FreeFileSync/Source/ui/batch_config.h @@ -24,10 +24,10 @@ struct ReturnBatchConfig //show and let user customize batch settings (without saving) -ReturnBatchConfig::ButtonPressed customizeBatchConfig(wxWindow* parent, - xmlAccess::XmlBatchConfig& batchCfg, //in/out - std::vector<Zstring>& onCompletionHistory, - size_t onCompletionHistoryMax); +ReturnBatchConfig::ButtonPressed showBatchConfigDialog(wxWindow* parent, + + xmlAccess::BatchExclusiveConfig& batchExCfg, + bool& ignoreErrors); } #endif //BATCH_CONFIG_H_3921674832168945 diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index ba9ae644..f6d466a7 100755 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -7,9 +7,9 @@ #include "batch_status_handler.h" #include <zen/shell_execute.h> #include <zen/thread.h> +#include <zen/shutdown.h> #include <wx+/popup_dlg.h> #include <wx/app.h> -#include "on_completion_box.h" #include "../lib/ffs_paths.h" #include "../lib/resolve_path.h" #include "../lib/status_handler_impl.h" @@ -17,6 +17,7 @@ #include "../fs/concrete.h" using namespace zen; +using namespace xmlAccess; namespace @@ -28,7 +29,7 @@ namespace std::unique_ptr<AFS::OutputStream> prepareNewLogfile(const AbstractPath& logFolderPath, //throw FileError const std::wstring& jobName, const std::chrono::system_clock::time_point& batchStartTime, - const std::wstring& status, + const std::wstring& failStatus, ProcessCallback& pc) { assert(!jobName.empty()); @@ -46,8 +47,8 @@ std::unique_ptr<AFS::OutputStream> prepareNewLogfile(const AbstractPath& logFold Zstring logFileName = utfTo<Zstring>(jobName) + Zstr(" ") + formatTime<Zstring>(Zstr("%Y-%m-%d %H%M%S"), timeStamp) + Zstr(".") + printNumber<Zstring>(Zstr("%03d"), static_cast<int>(timeMs)); //[ms] should yield a fairly unique name - if (!status.empty()) - logFileName += utfTo<Zstring>(L" [" + status + L"]"); + if (!failStatus.empty()) + logFileName += utfTo<Zstring>(L" [" + failStatus + L"]"); logFileName += Zstr(".log"); const AbstractPath logFilePath = AFS::appendRelPath(logFolderPath, logFileName); @@ -135,23 +136,33 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, const Zstring& logFolderPathPhrase, //may be empty int logfilesCountLimit, size_t lastSyncsLogFileSizeMax, - const xmlAccess::OnError handleError, + bool ignoreErrors, + BatchErrorDialog batchErrorDialog, size_t automaticRetryCount, size_t automaticRetryDelay, FfsReturnCode& returnCode, - const Zstring& onCompletion, - std::vector<Zstring>& onCompletionHistory) : - showFinalResults_(showProgress), //=> exit immediately or wait when finished + const Zstring& postSyncCommand, + PostSyncCondition postSyncCondition, + PostSyncAction postSyncAction) : logfilesCountLimit_(logfilesCountLimit), lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax), - handleError_(handleError), + batchErrorDialog_(batchErrorDialog), returnCode_(returnCode), automaticRetryCount_(automaticRetryCount), automaticRetryDelay_(automaticRetryDelay), - progressDlg_(createProgressDialog(*this, [this] { this->onProgressDialogTerminate(); }, *this, nullptr, showProgress, jobName, soundFileSyncComplete, onCompletion, onCompletionHistory)), - jobName_(jobName), - batchStartTime_(batchStartTime), - logFolderPathPhrase_(logFolderPathPhrase) + progressDlg_(createProgressDialog(*this, [this] { this->onProgressDialogTerminate(); }, +*this, +nullptr, //parentWindow +showProgress, +jobName, +soundFileSyncComplete, +ignoreErrors, +postSyncAction)), + jobName_(jobName), + batchStartTime_(batchStartTime), + logFolderPathPhrase_(logFolderPathPhrase), + postSyncCommand_(postSyncCommand), + postSyncCondition_(postSyncCondition) { //ATTENTION: "progressDlg_" is an unmanaged resource!!! However, at this point we already consider construction complete! => //ZEN_ON_SCOPE_FAIL( cleanup(); ); //destructor call would lead to member double clean-up!!! @@ -165,63 +176,36 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, BatchStatusHandler::~BatchStatusHandler() { - //------------ "on completion" command conceptually is part of the sync, not cleanup -------------------------------------- - - //decide whether to stay on status screen or exit immediately... - if (progressDlg_) - { - if (switchToGuiRequested_) //-> avoid recursive yield() calls, thous switch not before ending batch mode - showFinalResults_ = false; - else if (progressDlg_->getWindowIfVisible()) - showFinalResults_ = true; - - //execute "on completion" command (even in case of ignored errors) - if (!abortIsRequested()) //if aborted (manually), we don't execute the command - { - const Zstring finalCommand = progressDlg_->getExecWhenFinishedCommand(); //final value (after possible user modification) - if (!finalCommand.empty()) - { - if (isCloseProgressDlgCommand(finalCommand)) - showFinalResults_ = false; //take precedence over current visibility status - else - try - { - //use EXEC_TYPE_ASYNC until there is reason not to: http://www.freefilesync.org/forum/viewtopic.php?t=31 - tryReportingError([&] { shellExecute(expandMacros(finalCommand), EXEC_TYPE_ASYNC); }, //throw FileError - *this); //throw X? - } - catch (...) {} - } - } - } - //------------ end of sync: begin of cleanup -------------------------------------- - const int totalErrors = errorLog_.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log const int totalWarnings = errorLog_.getItemCount(TYPE_WARNING); //finalize error log - std::wstring status; //additionally indicate errors in log file name + SyncProgressDialog::SyncResult finalStatus = SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS; std::wstring finalStatusMsg; - if (abortIsRequested()) + std::wstring failStatus; //additionally indicate errors in log file name + if (getAbortStatus()) { + finalStatus = SyncProgressDialog::RESULT_ABORTED; raiseReturnCode(returnCode_, FFS_RC_ABORTED); finalStatusMsg = _("Synchronization stopped"); errorLog_.logMsg(finalStatusMsg, TYPE_ERROR); - status = _("Stopped"); + failStatus = _("Stopped"); } else if (totalErrors > 0) { + finalStatus = SyncProgressDialog::RESULT_FINISHED_WITH_ERROR; raiseReturnCode(returnCode_, FFS_RC_FINISHED_WITH_ERRORS); finalStatusMsg = _("Synchronization completed with errors"); errorLog_.logMsg(finalStatusMsg, TYPE_ERROR); - status = _("Error"); + failStatus = _("Error"); } else if (totalWarnings > 0) { + finalStatus = SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS; raiseReturnCode(returnCode_, FFS_RC_FINISHED_WITH_WARNINGS); finalStatusMsg = _("Synchronization completed with warnings"); errorLog_.logMsg(finalStatusMsg, TYPE_WARNING); - status = _("Warning"); + failStatus = _("Warning"); } else { @@ -233,6 +217,33 @@ BatchStatusHandler::~BatchStatusHandler() errorLog_.logMsg(finalStatusMsg, TYPE_INFO); } + //post sync command + Zstring commandLine = [&] + { + if (!getAbortStatus() || *getAbortStatus() != AbortTrigger::USER) //user cancelled => don't run post sync command! + switch (postSyncCondition_) + { + case PostSyncCondition::COMPLETION: + return postSyncCommand_; + case PostSyncCondition::ERRORS: + if (finalStatus == SyncProgressDialog::RESULT_ABORTED || + finalStatus == SyncProgressDialog::RESULT_FINISHED_WITH_ERROR) + return postSyncCommand_; + break; + case PostSyncCondition::SUCCESS: + if (finalStatus == SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS || + finalStatus == SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS) + return postSyncCommand_; + break; + } + return Zstring(); + }(); + trim(commandLine); + + if (!commandLine.empty()) + errorLog_.logMsg(replaceCpy(_("Executing command %x"), L"%x", fmtPath(commandLine)), TYPE_INFO); + + //----------------- write results into user-specified logfile ------------------------ const SummaryInfo summary = { jobName_, @@ -242,7 +253,6 @@ BatchStatusHandler::~BatchStatusHandler() std::time(nullptr) - startTime_ }; - //----------------- write results into user-specified logfile ------------------------ //create not before destruction: 1. avoid issues with FFS trying to sync open log file 2. simplify transactional retry on failure 3. no need to rename log file to include status // 4. failure to write to particular stream must not be retried! if (logfilesCountLimit_ != 0) @@ -255,7 +265,7 @@ BatchStatusHandler::~BatchStatusHandler() { tryReportingError([&] //errors logged here do not impact final status calculation above! => not a problem! { - std::unique_ptr<AFS::OutputStream> logFileStream = prepareNewLogfile(logFolderPath, jobName_, batchStartTime_, status, *this); //throw FileError; return value always bound! + std::unique_ptr<AFS::OutputStream> logFileStream = prepareNewLogfile(logFolderPath, jobName_, batchStartTime_, failStatus, *this); //throw FileError; return value always bound! streamToLogFile(summary, errorLog_, *logFileStream); //throw FileError, (X) logFileStream->finalize(); //throw FileError, (X) @@ -278,32 +288,60 @@ BatchStatusHandler::~BatchStatusHandler() catch (...) {} } } - //----------------- write results into LastSyncs.log------------------------ + //write results into LastSyncs.log try { saveToLastSyncsLog(summary, errorLog_, lastSyncsLogFileSizeMax_, OnUpdateLogfileStatusNoThrow(*this, utfTo<std::wstring>(getLastSyncsLogfilePath()))); //throw FileError } catch (FileError&) { assert(false); } + //execute post sync command *after* writing log files, so that user can refer to the log via the command! + if (!commandLine.empty()) + try + { + //use EXEC_TYPE_ASYNC until there is reason not to: http://www.freefilesync.org/forum/viewtopic.php?t=31 + tryReportingError([&] { shellExecute(expandMacros(commandLine), EXEC_TYPE_ASYNC); /*throw FileError*/ }, *this); //throw X + } + catch (...) {} + if (progressDlg_) { - if (showFinalResults_) //warning: wxWindow::Show() is called within processHasFinished()! - { + //post sync action + bool showSummary = true; + if (!getAbortStatus() || *getAbortStatus() != AbortTrigger::USER) //user cancelled => don't run post sync action! + switch (progressDlg_->getOptionPostSyncAction()) + { + case PostSyncAction::SUMMARY: + break; + case PostSyncAction::EXIT: + showSummary = false; + break; + case PostSyncAction::SLEEP: + try + { + tryReportingError([&] { suspendSystem(); /*throw FileError*/ }, *this); //throw X + } + catch (...) {} + break; + case PostSyncAction::SHUTDOWN: + showSummary = false; + try + { + tryReportingError([&] { shutdownSystem(); /*throw FileError*/ }, *this); //throw X + } + catch (...) {} + break; + } + if (switchToGuiRequested_) //-> avoid recursive yield() calls, thous switch not before ending batch mode + showSummary = false; + + //close progress dialog + if (showSummary) //warning: wxWindow::Show() is called within processHasFinished()! //notify about (logical) application main window => program won't quit, but stay on this dialog //setMainWindow(progressDlg_->getAsWindow()); -> not required anymore since we block waiting until dialog is closed below - - //notify to progressDlg_ that current process has ended - if (abortIsRequested()) - progressDlg_->processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog_); //enable okay and close events - else if (totalErrors > 0) - progressDlg_->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_ERROR, errorLog_); - else if (totalWarnings > 0) - progressDlg_->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS, errorLog_); - else - progressDlg_->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS, errorLog_); - } + progressDlg_->processHasFinished(finalStatus, errorLog_); else - progressDlg_->closeWindowDirectly(); //progressDlg_ is main window => program will quit directly + progressDlg_->closeWindowDirectly(); //progressDlg_ is main window => program will quit shortly after //wait until progress dialog notified shutdown via onProgressDialogTerminate() //-> required since it has our "this" pointer captured in lambda "notifyWindowTerminate"! @@ -346,54 +384,55 @@ void BatchStatusHandler::reportInfo(const std::wstring& text) void BatchStatusHandler::reportWarning(const std::wstring& warningMessage, bool& warningActive) { + if (!progressDlg_) abortProcessNow(); + errorLog_.logMsg(warningMessage, TYPE_WARNING); if (!warningActive) return; - switch (handleError_) - { - case xmlAccess::ON_ERROR_POPUP: + if (!progressDlg_->getOptionIgnoreErrors()) + switch (batchErrorDialog_) { - if (!progressDlg_) abortProcessNow(); - PauseTimers dummy(*progressDlg_); - forceUiRefresh(); - - bool dontWarnAgain = false; - switch (showQuestionDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::WARNING, PopupDialogCfg(). - setDetailInstructions(warningMessage + L"\n\n" + _("You can switch to FreeFileSync's main window to resolve this issue.")). - setCheckBox(dontWarnAgain, _("&Don't show this warning again"), QuestionButton2::NO), - _("&Ignore"), _("&Switch"))) + case BatchErrorDialog::SHOW: { - case QuestionButton2::YES: //ignore - warningActive = !dontWarnAgain; - break; - - case QuestionButton2::NO: //switch - errorLog_.logMsg(_("Switching to FreeFileSync's main window"), TYPE_INFO); - switchToGuiRequested_ = true; //treat as a special kind of cancel - requestAbortion(); // - throw BatchRequestSwitchToMainDialog(); - - case QuestionButton2::CANCEL: - abortProcessNow(); - break; + PauseTimers dummy(*progressDlg_); + forceUiRefresh(); + + bool dontWarnAgain = false; + switch (showQuestionDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::WARNING, PopupDialogCfg(). + setDetailInstructions(warningMessage + L"\n\n" + _("You can switch to FreeFileSync's main window to resolve this issue.")). + setCheckBox(dontWarnAgain, _("&Don't show this warning again"), QuestionButton2::NO), + _("&Ignore"), _("&Switch"))) + { + case QuestionButton2::YES: //ignore + warningActive = !dontWarnAgain; + break; + + case QuestionButton2::NO: //switch + errorLog_.logMsg(_("Switching to FreeFileSync's main window"), TYPE_INFO); + switchToGuiRequested_ = true; //treat as a special kind of cancel + userRequestAbort(); // + throw BatchRequestSwitchToMainDialog(); + + case QuestionButton2::CANCEL: + userAbortProcessNow(); //throw AbortProcess + break; + } } - } - break; //keep it! last switch might not find match + break; //keep it! last switch might not find match - case xmlAccess::ON_ERROR_STOP: - abortProcessNow(); - break; - - case xmlAccess::ON_ERROR_IGNORE: - break; - } + case BatchErrorDialog::CANCEL: + abortProcessNow(); //not user-initiated! throw AbortProcess + break; + } } ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& errorMessage, size_t retryNumber) { + if (!progressDlg_) abortProcessNow(); + //auto-retry if (retryNumber < automaticRetryCount_) { @@ -414,44 +453,46 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er //always, except for "retry": auto guardWriteLog = zen::makeGuard<ScopeGuardRunMode::ON_EXIT>([&] { errorLog_.logMsg(errorMessage, TYPE_ERROR); }); - switch (handleError_) + + if (!progressDlg_->getOptionIgnoreErrors()) { - case xmlAccess::ON_ERROR_POPUP: + switch (batchErrorDialog_) { - if (!progressDlg_) abortProcessNow(); - PauseTimers dummy(*progressDlg_); - forceUiRefresh(); - - switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::ERROR2, PopupDialogCfg(). - setDetailInstructions(errorMessage), - _("&Ignore"), _("Ignore &all"), _("&Retry"))) + case BatchErrorDialog::SHOW: { - case ConfirmationButton3::ACCEPT: //ignore - return ProcessCallback::IGNORE_ERROR; - - case ConfirmationButton3::ACCEPT_ALL: //ignore all - handleError_ = xmlAccess::ON_ERROR_IGNORE; - return ProcessCallback::IGNORE_ERROR; + PauseTimers dummy(*progressDlg_); + forceUiRefresh(); - case ConfirmationButton3::DECLINE: //retry - guardWriteLog.dismiss(); - errorLog_.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); - return ProcessCallback::RETRY; - - case ConfirmationButton3::CANCEL: - abortProcessNow(); - break; + switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::ERROR2, PopupDialogCfg(). + setDetailInstructions(errorMessage), + _("&Ignore"), _("Ignore &all"), _("&Retry"))) + { + case ConfirmationButton3::ACCEPT: //ignore + return ProcessCallback::IGNORE_ERROR; + + case ConfirmationButton3::ACCEPT_ALL: //ignore all + progressDlg_->setOptionIgnoreErrors(true); + return ProcessCallback::IGNORE_ERROR; + + case ConfirmationButton3::DECLINE: //retry + guardWriteLog.dismiss(); + errorLog_.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); + return ProcessCallback::RETRY; + + case ConfirmationButton3::CANCEL: + userAbortProcessNow(); //throw AbortProcess + break; + } } - } - break; //used if last switch didn't find a match - - case xmlAccess::ON_ERROR_STOP: - abortProcessNow(); - break; + break; //used if last switch didn't find a match - case xmlAccess::ON_ERROR_IGNORE: - return ProcessCallback::IGNORE_ERROR; + case BatchErrorDialog::CANCEL: + abortProcessNow(); //not user-initiated! throw AbortProcess + break; + } } + else + return ProcessCallback::IGNORE_ERROR; assert(false); return ProcessCallback::IGNORE_ERROR; //dummy value @@ -460,42 +501,41 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er void BatchStatusHandler::reportFatalError(const std::wstring& errorMessage) { + if (!progressDlg_) abortProcessNow(); + errorLog_.logMsg(errorMessage, TYPE_FATAL_ERROR); - switch (handleError_) - { - case xmlAccess::ON_ERROR_POPUP: + if (!progressDlg_->getOptionIgnoreErrors()) + switch (batchErrorDialog_) { - if (!progressDlg_) abortProcessNow(); - PauseTimers dummy(*progressDlg_); - forceUiRefresh(); - - switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::ERROR2, - PopupDialogCfg().setTitle(_("Serious Error")). - setDetailInstructions(errorMessage), - _("&Ignore"), _("Ignore &all"))) + case BatchErrorDialog::SHOW: { - case ConfirmationButton2::ACCEPT: - break; + PauseTimers dummy(*progressDlg_); + forceUiRefresh(); - case ConfirmationButton2::ACCEPT_ALL: - handleError_ = xmlAccess::ON_ERROR_IGNORE; - break; + switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::ERROR2, + PopupDialogCfg().setTitle(_("Serious Error")). + setDetailInstructions(errorMessage), + _("&Ignore"), _("Ignore &all"))) + { + case ConfirmationButton2::ACCEPT: + break; - case ConfirmationButton2::CANCEL: - abortProcessNow(); - break; - } - } - break; + case ConfirmationButton2::ACCEPT_ALL: + progressDlg_->setOptionIgnoreErrors(true); + break; - case xmlAccess::ON_ERROR_STOP: - abortProcessNow(); + case ConfirmationButton2::CANCEL: + userAbortProcessNow(); //throw AbortProcess + break; + } + } break; - case xmlAccess::ON_ERROR_IGNORE: - break; - } + case BatchErrorDialog::CANCEL: + abortProcessNow(); //not user-initiated! throw AbortProcess + break; + } } @@ -506,15 +546,7 @@ void BatchStatusHandler::forceUiRefresh() } -void BatchStatusHandler::abortProcessNow() -{ - requestAbortion(); //just make sure... - throw BatchAbortProcess(); //abort can be triggered by progressDlg_ -} - - void BatchStatusHandler::onProgressDialogTerminate() { - //it's responsibility of "progressDlg_" to call requestAbortion() when closing dialog progressDlg_ = nullptr; } diff --git a/FreeFileSync/Source/ui/batch_status_handler.h b/FreeFileSync/Source/ui/batch_status_handler.h index 7a0499bc..b7c08a2b 100755 --- a/FreeFileSync/Source/ui/batch_status_handler.h +++ b/FreeFileSync/Source/ui/batch_status_handler.h @@ -16,12 +16,11 @@ #include "../lib/return_codes.h" -//Exception classes used to abort the "compare" and "sync" process -class BatchAbortProcess {}; class BatchRequestSwitchToMainDialog {}; + //BatchStatusHandler(SyncProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks! -class BatchStatusHandler : public zen::StatusHandler //throw BatchAbortProcess, BatchRequestSwitchToMainDialog +class BatchStatusHandler : public zen::StatusHandler //throw AbortProcess, BatchRequestSwitchToMainDialog { public: BatchStatusHandler(bool showProgress, //defines: -start minimized and -quit immediately when finished @@ -31,12 +30,14 @@ public: const Zstring& logFolderPathPhrase, int logfilesCountLimit, //0: logging inactive; < 0: no limit size_t lastSyncsLogFileSizeMax, - const xmlAccess::OnError handleError, + bool ignoreErrors, + xmlAccess::BatchErrorDialog batchErrorDialog, size_t automaticRetryCount, size_t automaticRetryDelay, zen::FfsReturnCode& returnCode, - const Zstring& onCompletion, - std::vector<Zstring>& onCompletionHistory); + const Zstring& postSyncCommand, + zen::PostSyncCondition postSyncCondition, + xmlAccess::PostSyncAction postSyncAction); ~BatchStatusHandler(); void initNewPhase (int itemsTotal, int64_t bytesTotal, Phase phaseID) override; @@ -48,16 +49,13 @@ public: Response reportError (const std::wstring& errorMessage, size_t retryNumber ) override; void reportFatalError(const std::wstring& errorMessage ) override; - void abortProcessNow() override final; //throw BatchAbortProcess - private: void onProgressDialogTerminate(); - bool showFinalResults_; bool switchToGuiRequested_ = false; const int logfilesCountLimit_; const size_t lastSyncsLogFileSizeMax_; - xmlAccess::OnError handleError_; + const xmlAccess::BatchErrorDialog batchErrorDialog_; zen::ErrorLog errorLog_; //list of non-resolved errors and warnings zen::FfsReturnCode& returnCode_; @@ -71,6 +69,8 @@ private: const time_t startTime_ = std::time(nullptr); //don't use wxStopWatch: may overflow after a few days due to ::QueryPerformanceCounter() const Zstring logFolderPathPhrase_; + const Zstring postSyncCommand_; + const zen::PostSyncCondition postSyncCondition_; }; #endif //BATCH_STATUS_HANDLER_H_857390451451234566 diff --git a/FreeFileSync/Source/ui/column_attr.h b/FreeFileSync/Source/ui/column_attr.h index 0a8413e3..5b607eec 100755 --- a/FreeFileSync/Source/ui/column_attr.h +++ b/FreeFileSync/Source/ui/column_attr.h @@ -9,6 +9,7 @@ #include <vector> + namespace zen { enum class ColumnTypeRim @@ -35,10 +36,10 @@ std::vector<ColumnAttributeRim> getDefaultColumnAttributesLeft() { return //harmonize with main_dlg.cpp::onGridLabelContextRim() => expects stretched ITEM_PATH and non-stretched other columns! { - { ColumnTypeRim::ITEM_PATH, -80, 1, true }, //stretch to full width and substract sum of fixed-size widths! - { ColumnTypeRim::EXTENSION, 60, 0, false }, - { ColumnTypeRim::DATE, 140, 0, false }, - { ColumnTypeRim::SIZE, 80, 0, true }, + { ColumnTypeRim::ITEM_PATH, -100, 1, true }, + { ColumnTypeRim::EXTENSION, 60, 0, false }, + { ColumnTypeRim::DATE, 140, 0, false }, + { ColumnTypeRim::SIZE, 100, 0, true }, }; } diff --git a/FreeFileSync/Source/ui/on_completion_box.cpp b/FreeFileSync/Source/ui/command_box.cpp index e5c91321..a881a6b7 100755 --- a/FreeFileSync/Source/ui/on_completion_box.cpp +++ b/FreeFileSync/Source/ui/command_box.cpp @@ -4,82 +4,66 @@ // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** -#include "on_completion_box.h" +#include "command_box.h" #include <deque> #include <zen/i18n.h> #include <algorithm> #include <zen/stl_tools.h> #include <zen/utf.h> -//#ifdef ZEN_WIN -// #include <zen/win_ver.h> -//#endif using namespace zen; namespace { -std::wstring getCmdTxtCloseProgressDlg() { return L"Close progress dialog"; } //special command //mark for extraction: _("Close progress dialog") - -std::wstring getSeparationLine() { return L"---------------------------------------------------------------------------------------------------------------"; } +inline +std::wstring getSeparationLine() { return std::wstring(50, EM_DASH); } //no space between dashes! std::vector<std::pair<std::wstring, Zstring>> getDefaultCommands() //(gui name/command) pairs { - std::vector<std::pair<std::wstring, Zstring>> output; - - auto addEntry = [&](const std::wstring& name, const Zstring& value) { output.emplace_back(name, value); }; - - addEntry(_("Log off" ), Zstr("gnome-session-quit --no-prompt")); //alternative requiring admin: sudo killall Xorg - //alternative without admin: dbus-send --session --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.Logout uint32:1 - addEntry(_("Standby" ), Zstr("systemctl suspend")); //alternative requiring admin: sudo pm-suspend - addEntry(_("Shut down"), Zstr("systemctl poweroff")); //alternative requiring admin: sudo shutdown -h 1 - - return output; -} - -const wxEventType wxEVT_VALIDATE_USER_SELECTION = wxNewEventType(); + return + { + //{_("Sleep"), Zstr("rundll32.exe powrprof.dll,SetSuspendState Sleep")}, + }; } -bool isCloseProgressDlgCommand(const Zstring& value) -{ - return trimCpy(utfTo<std::wstring>(value)) == getCmdTxtCloseProgressDlg(); +const wxEventType wxEVT_VALIDATE_USER_SELECTION = wxNewEventType(); } -OnCompletionBox::OnCompletionBox(wxWindow* parent, - wxWindowID id, - const wxString& value, - const wxPoint& pos, - const wxSize& size, - int n, - const wxString choices[], - long style, - const wxValidator& validator, - const wxString& name) : +CommandBox::CommandBox(wxWindow* parent, + wxWindowID id, + const wxString& value, + const wxPoint& pos, + const wxSize& size, + int n, + const wxString choices[], + long style, + const wxValidator& validator, + const wxString& name) : wxComboBox(parent, id, value, pos, size, n, choices, style, validator, name), defaultCommands_(getDefaultCommands()) { - //##################################### - /*##*/ SetMinSize(wxSize(150, -1)); //## workaround yet another wxWidgets bug: default minimum size is much too large for a wxComboBox - //##################################### + //#################################### + /*#*/ SetMinSize(wxSize(150, -1)); //# workaround yet another wxWidgets bug: default minimum size is much too large for a wxComboBox + //#################################### - Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (OnCompletionBox::OnKeyEvent ), nullptr, this); - Connect(wxEVT_LEFT_DOWN, wxEventHandler (OnCompletionBox::OnUpdateList), nullptr, this); - Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(OnCompletionBox::OnSelection ), nullptr, this); - Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (OnCompletionBox::OnMouseWheel), nullptr, this); + Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (CommandBox::OnKeyEvent ), nullptr, this); + Connect(wxEVT_LEFT_DOWN, wxEventHandler (CommandBox::OnUpdateList), nullptr, this); + Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(CommandBox::OnSelection ), nullptr, this); + Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (CommandBox::OnMouseWheel), nullptr, this); - Connect(wxEVT_VALIDATE_USER_SELECTION, wxCommandEventHandler(OnCompletionBox::OnValidateSelection), nullptr, this); + Connect(wxEVT_VALIDATE_USER_SELECTION, wxCommandEventHandler(CommandBox::OnValidateSelection), nullptr, this); } -void OnCompletionBox::addItemHistory() +void CommandBox::addItemHistory() { const Zstring command = trimCpy(getValue()); if (command == utfTo<Zstring>(getSeparationLine()) || //do not add sep. line - command == utfTo<Zstring>(getCmdTxtCloseProgressDlg()) || //do not add special command command.empty()) return; @@ -98,52 +82,38 @@ void OnCompletionBox::addItemHistory() } -Zstring OnCompletionBox::getValue() const +Zstring CommandBox::getValue() const { - auto value = trimCpy(copyStringTo<std::wstring>(GetValue())); - - if (value == zen::translate(getCmdTxtCloseProgressDlg())) //undo translation for config file storage - value = getCmdTxtCloseProgressDlg(); - - return utfTo<Zstring>(value); + return utfTo<Zstring>(trimCpy(GetValue())); } -void OnCompletionBox::setValue(const Zstring& value) +void CommandBox::setValue(const Zstring& value) { - auto tmp = trimCpy(utfTo<std::wstring>(value)); - - if (tmp == getCmdTxtCloseProgressDlg()) - tmp = zen::translate(getCmdTxtCloseProgressDlg()); //have this symbolic constant translated properly - - setValueAndUpdateList(tmp); + setValueAndUpdateList(trimCpy(utfTo<std::wstring>(value))); } //set value and update list are technically entangled: see potential bug description below -void OnCompletionBox::setValueAndUpdateList(const std::wstring& value) +void CommandBox::setValueAndUpdateList(const std::wstring& value) { //it may be a little lame to update the list on each mouse-button click, but it should be working and we dont't have to manipulate wxComboBox internals std::deque<std::wstring> items; - //1. special command - items.push_back(zen::translate(getCmdTxtCloseProgressDlg())); - - //2. built in commands + //1. built in commands for (const auto& item : defaultCommands_) items.push_back(item.first); - //3. history elements - if (!history_.empty()) - { - auto histSorted = history_; - std::sort(histSorted.begin(), histSorted.end(), LessNaturalSort() /*even on Linux*/); + //2. history elements + auto histSorted = history_; + std::sort(histSorted.begin(), histSorted.end(), LessNaturalSort() /*even on Linux*/); + if (!items.empty() && !histSorted.empty()) items.push_back(getSeparationLine()); - for (const Zstring& hist : histSorted) - items.push_back(utfTo<std::wstring>(hist)); - } + + for (const Zstring& hist : histSorted) + items.push_back(utfTo<std::wstring>(hist)); //attention: if the target value is not part of the dropdown list, SetValue() will look for a string that *starts with* this value: //e.g. if the dropdown list contains "222" SetValue("22") will erroneously set and select "222" instead, while "111" would be set correctly! @@ -165,7 +135,7 @@ void OnCompletionBox::setValueAndUpdateList(const std::wstring& value) } -void OnCompletionBox::OnSelection(wxCommandEvent& event) +void CommandBox::OnSelection(wxCommandEvent& event) { wxCommandEvent dummy2(wxEVT_VALIDATE_USER_SELECTION); //we cannot replace built-in commands at this position in call stack, so defer to a later time! if (auto handler = GetEventHandler()) @@ -175,7 +145,7 @@ void OnCompletionBox::OnSelection(wxCommandEvent& event) } -void OnCompletionBox::OnValidateSelection(wxCommandEvent& event) +void CommandBox::OnValidateSelection(wxCommandEvent& event) { const auto value = copyStringTo<std::wstring>(GetValue()); @@ -188,14 +158,14 @@ void OnCompletionBox::OnValidateSelection(wxCommandEvent& event) } -void OnCompletionBox::OnUpdateList(wxEvent& event) +void CommandBox::OnUpdateList(wxEvent& event) { setValue(getValue()); event.Skip(); } -void OnCompletionBox::OnKeyEvent(wxKeyEvent& event) +void CommandBox::OnKeyEvent(wxKeyEvent& event) { const int keyCode = event.GetKeyCode(); diff --git a/FreeFileSync/Source/ui/on_completion_box.h b/FreeFileSync/Source/ui/command_box.h index 1360514c..40503ebd 100755 --- a/FreeFileSync/Source/ui/on_completion_box.h +++ b/FreeFileSync/Source/ui/command_box.h @@ -4,8 +4,8 @@ // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** -#ifndef ON_COMPLETION_BOX_H_18947773210473214 -#define ON_COMPLETION_BOX_H_18947773210473214 +#ifndef COMMAND_BOX_H_18947773210473214 +#define COMMAND_BOX_H_18947773210473214 #include <vector> #include <string> @@ -16,23 +16,20 @@ //combobox with history function + functionality to delete items (DEL) -//special command -bool isCloseProgressDlgCommand(const Zstring& value); - -class OnCompletionBox : public wxComboBox +class CommandBox : public wxComboBox { public: - OnCompletionBox(wxWindow* parent, - wxWindowID id, - const wxString& value = {}, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - int n = 0, - const wxString choices[] = nullptr, - long style = 0, - const wxValidator& validator = wxDefaultValidator, - const wxString& name = wxComboBoxNameStr); + CommandBox(wxWindow* parent, + wxWindowID id, + const wxString& value = {}, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + int n = 0, + const wxString choices[] = nullptr, + long style = 0, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxComboBoxNameStr); void setHistory(const std::vector<Zstring>& history, size_t historyMax) { history_ = history; historyMax_ = historyMax; } std::vector<Zstring> getHistory() const { return history_; } @@ -59,4 +56,4 @@ private: }; -#endif //ON_COMPLETION_BOX_H_18947773210473214 +#endif //COMMAND_BOX_H_18947773210473214 diff --git a/FreeFileSync/Source/ui/custom_grid.cpp b/FreeFileSync/Source/ui/custom_grid.cpp index e68d1f6c..a3689c90 100755 --- a/FreeFileSync/Source/ui/custom_grid.cpp +++ b/FreeFileSync/Source/ui/custom_grid.cpp @@ -15,6 +15,7 @@ #include <zen/scope_guard.h> #include <wx+/tooltip.h> #include <wx+/rtl.h> +#include <wx+/dc.h> #include <wx+/image_tools.h> #include <wx+/image_resources.h> #include "../file_hierarchy.h" @@ -411,9 +412,9 @@ private: break; case ColumnTypeRim::SIZE: //return utfTo<std::wstring>(file.getFileId<side>()); // -> test file id - return toGuiString(file.getFileSize<side>()); + return formatNumber(file.getFileSize<side>()); case ColumnTypeRim::DATE: - return utcToLocalTimeString(file.getLastWriteTime<side>()); + return formatUtcToLocalTime(file.getLastWriteTime<side>()); case ColumnTypeRim::EXTENSION: return utfTo<std::wstring>(getFileExtension(file.getItemName<side>())); } @@ -445,7 +446,7 @@ private: case ColumnTypeRim::SIZE: return L"<" + _("Symlink") + L">"; case ColumnTypeRim::DATE: - return utcToLocalTimeString(symlink.getLastWriteTime<side>()); + return formatUtcToLocalTime(symlink.getLastWriteTime<side>()); case ColumnTypeRim::EXTENSION: return utfTo<std::wstring>(getFileExtension(symlink.getItemName<side>())); } @@ -748,14 +749,14 @@ private: [&](const FilePair& file) { toolTip += L"\n" + - _("Size:") + L" " + zen::filesizeToShortString(file.getFileSize<side>()) + L"\n" + - _("Date:") + L" " + zen::utcToLocalTimeString(file.getLastWriteTime<side>()); + _("Size:") + L" " + zen::formatFilesizeShort(file.getFileSize<side>()) + L"\n" + + _("Date:") + L" " + zen::formatUtcToLocalTime(file.getLastWriteTime<side>()); }, [&](const SymlinkPair& symlink) { toolTip += L"\n" + - _("Date:") + L" " + zen::utcToLocalTimeString(symlink.getLastWriteTime<side>()); + _("Date:") + L" " + zen::formatUtcToLocalTime(symlink.getLastWriteTime<side>()); }); } return toolTip; diff --git a/FreeFileSync/Source/ui/folder_history_box.h b/FreeFileSync/Source/ui/folder_history_box.h index 5dbc4cbb..4dfb94e1 100755 --- a/FreeFileSync/Source/ui/folder_history_box.h +++ b/FreeFileSync/Source/ui/folder_history_box.h @@ -31,7 +31,7 @@ public: const std::vector<Zstring>& getList() const { return folderPathPhrases_; } - static const wxString separationLine() { return L"---------------------------------------------------------------------------------------------------------------"; } + static const wxString separationLine() { return wxString(50, EM_DASH); } void addItem(const Zstring& folderPathPhrase) { diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index c8244569..72b7a4ad 100755 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -5,8 +5,8 @@ // PLEASE DO "NOT" EDIT THIS FILE! /////////////////////////////////////////////////////////////////////////// +#include "command_box.h" #include "folder_history_box.h" -#include "on_completion_box.h" #include "triple_splitter.h" #include "wx+/bitmap_button.h" #include "wx+/graph.h" @@ -1140,7 +1140,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer2381->Add( m_toggleBtnByContent, 0, wxEXPAND|wxBOTTOM, 5 ); m_toggleBtnBySize = new wxToggleButton( m_panelComparisonSettings, wxID_ANY, _("File size"), wxDefaultPosition, wxSize( -1, 30 ), 0 ); - m_toggleBtnBySize->SetValue( true ); m_toggleBtnBySize->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); bSizer2381->Add( m_toggleBtnBySize, 0, wxEXPAND, 5 ); @@ -1158,8 +1157,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer2371 = new wxBoxSizer( wxHORIZONTAL ); m_bitmapCompVariant = new wxStaticBitmap( m_panelComparisonSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); - m_bitmapCompVariant->SetToolTip( _("Detect synchronization directions with the help of database files") ); - bSizer2371->Add( m_bitmapCompVariant, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); m_staticTextCompVarDescription = new wxStaticText( m_panelComparisonSettings, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); @@ -1661,8 +1658,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer233->Add( bSizerKeepVerticalHeight, 0, 0, 5 ); m_bitmapDatabase = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); - m_bitmapDatabase->SetToolTip( _("Detect synchronization directions with the help of database files") ); - bSizer233->Add( m_bitmapDatabase, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxLEFT, 10 ); m_staticTextSyncVarDescription = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); @@ -1717,7 +1712,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer234 = new wxBoxSizer( wxVERTICAL ); m_toggleBtnRecycler = new wxToggleButton( m_panelSyncSettings, wxID_ANY, _("&Recycle bin"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); - m_toggleBtnRecycler->SetValue( true ); bSizer234->Add( m_toggleBtnRecycler, 0, wxEXPAND, 5 ); m_toggleBtnPermanent = new wxToggleButton( m_panelSyncSettings, wxID_ANY, _("&Permanent"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); @@ -1835,38 +1829,42 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizerMiscConfig = new wxBoxSizer( wxHORIZONTAL ); - m_staticText88 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Handle errors:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText88->Wrap( -1 ); - bSizerMiscConfig->Add( m_staticText88, 0, wxTOP|wxBOTTOM|wxLEFT, 10 ); - - wxBoxSizer* bSizer175; - bSizer175 = new wxBoxSizer( wxVERTICAL ); - - m_radioBtnPopupOnErrors = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Pop-up"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); - m_radioBtnPopupOnErrors->SetValue( true ); - m_radioBtnPopupOnErrors->SetToolTip( _("Show pop-up on errors or warnings") ); + wxBoxSizer* bSizer242; + bSizer242 = new wxBoxSizer( wxHORIZONTAL ); - bSizer175->Add( m_radioBtnPopupOnErrors, 0, wxEXPAND|wxALL, 5 ); + m_bitmapIgnoreErrors = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer242->Add( m_bitmapIgnoreErrors, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - m_radioBtnIgnoreErrors = new wxRadioButton( m_panelSyncSettings, wxID_ANY, _("&Ignore"), wxDefaultPosition, wxDefaultSize, 0 ); - m_radioBtnIgnoreErrors->SetToolTip( _("Hide all error and warning messages") ); + m_checkBoxIgnoreErrors = new wxCheckBox( m_panelSyncSettings, wxID_ANY, _("&Ignore errors"), wxDefaultPosition, wxDefaultSize, 0 ); + m_checkBoxIgnoreErrors->SetToolTip( _("Show pop-up on errors or warnings") ); - bSizer175->Add( m_radioBtnIgnoreErrors, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + bSizer242->Add( m_checkBoxIgnoreErrors, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - bSizerMiscConfig->Add( bSizer175, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + bSizerMiscConfig->Add( bSizer242, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); m_staticline57 = new wxStaticLine( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); bSizerMiscConfig->Add( m_staticline57, 0, wxEXPAND, 5 ); bSizerOnCompletion = new wxBoxSizer( wxVERTICAL ); - m_staticText89 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("On completion:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText89 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Run command after synchronization:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText89->Wrap( -1 ); bSizerOnCompletion->Add( m_staticText89, 0, wxBOTTOM, 5 ); - m_comboBoxOnCompletion = new OnCompletionBox( m_panelSyncSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); - bSizerOnCompletion->Add( m_comboBoxOnCompletion, 0, wxEXPAND, 5 ); + wxBoxSizer* bSizer251; + bSizer251 = new wxBoxSizer( wxHORIZONTAL ); + + wxArrayString m_choicePostSyncConditionChoices; + m_choicePostSyncCondition = new wxChoice( m_panelSyncSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choicePostSyncConditionChoices, 0 ); + m_choicePostSyncCondition->SetSelection( 0 ); + bSizer251->Add( m_choicePostSyncCondition, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + m_comboBoxPostSyncCommand = new CommandBox( m_panelSyncSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); + bSizer251->Add( m_comboBoxPostSyncCommand, 1, wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizerOnCompletion->Add( bSizer251, 0, wxEXPAND, 5 ); bSizerMiscConfig->Add( bSizerOnCompletion, 1, wxALL, 10 ); @@ -1956,8 +1954,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_toggleBtnVersioning->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnDeletionVersioning ), NULL, this ); m_hyperlinkVersioning->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpVersioning ), NULL, this ); m_choiceVersioningStyle->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeSyncOption ), NULL, this ); - m_radioBtnPopupOnErrors->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnErrorPopup ), NULL, this ); - m_radioBtnIgnoreErrors->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnErrorIgnore ), NULL, this ); + m_checkBoxIgnoreErrors->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleIgnoreErrors ), NULL, this ); m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnOkay ), NULL, this ); m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnCancel ), NULL, this ); } @@ -2119,61 +2116,25 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, wxBoxSizer* bSizer185; bSizer185 = new wxBoxSizer( wxVERTICAL ); - wxFlexGridSizer* fgSizer16; - fgSizer16 = new wxFlexGridSizer( 0, 2, 0, 0 ); - fgSizer16->AddGrowableCol( 1 ); - fgSizer16->SetFlexibleDirection( wxBOTH ); - fgSizer16->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + wxBoxSizer* bSizer245; + bSizer245 = new wxBoxSizer( wxHORIZONTAL ); m_staticText12311 = new wxStaticText( m_panel41, wxID_ANY, _("Server name or IP address:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText12311->Wrap( -1 ); - fgSizer16->Add( m_staticText12311, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxTOP|wxBOTTOM|wxLEFT, 5 ); - - wxBoxSizer* bSizer183; - bSizer183 = new wxBoxSizer( wxHORIZONTAL ); + bSizer245->Add( m_staticText12311, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); m_textCtrlServer = new wxTextCtrl( m_panel41, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 260, -1 ), 0 ); - bSizer183->Add( m_textCtrlServer, 1, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + bSizer245->Add( m_textCtrlServer, 1, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); m_staticText1233 = new wxStaticText( m_panel41, wxID_ANY, _("Port:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText1233->Wrap( -1 ); - bSizer183->Add( m_staticText1233, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); + bSizer245->Add( m_staticText1233, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); m_textCtrlPort = new wxTextCtrl( m_panel41, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 60, -1 ), 0 ); - bSizer183->Add( m_textCtrlPort, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - - - fgSizer16->Add( bSizer183, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); - + bSizer245->Add( m_textCtrlPort, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - fgSizer16->Add( 0, 0, 0, 0, 5 ); - wxBoxSizer* bSizer181; - bSizer181 = new wxBoxSizer( wxHORIZONTAL ); - - m_staticText1381 = new wxStaticText( m_panel41, wxID_ANY, _("Example:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText1381->Wrap( -1 ); - m_staticText1381->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - - bSizer181->Add( m_staticText1381, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - - m_staticText1382 = new wxStaticText( m_panel41, wxID_ANY, _("website.com"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText1382->Wrap( -1 ); - m_staticText1382->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - - bSizer181->Add( m_staticText1382, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - - m_staticText138 = new wxStaticText( m_panel41, wxID_ANY, _("66.198.240.22"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText138->Wrap( -1 ); - m_staticText138->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); - - bSizer181->Add( m_staticText138, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - - - fgSizer16->Add( bSizer181, 0, wxALIGN_CENTER_VERTICAL, 5 ); - - - bSizer185->Add( fgSizer16, 0, wxALL|wxEXPAND, 5 ); + bSizer185->Add( bSizer245, 0, wxALL|wxEXPAND, 5 ); m_staticline58 = new wxStaticLine( m_panel41, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer185->Add( m_staticline58, 0, wxEXPAND, 5 ); @@ -2899,35 +2860,45 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi wxBoxSizer* bSizer199; bSizer199 = new wxBoxSizer( wxHORIZONTAL ); - m_panelProgressLabel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); - m_panelProgressLabel->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + bSizerProgressFooter = new wxBoxSizer( wxHORIZONTAL ); - wxBoxSizer* bSizer201; - bSizer201 = new wxBoxSizer( wxVERTICAL ); + m_bitmapIgnoreErrors = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizerProgressFooter->Add( m_bitmapIgnoreErrors, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + m_checkBoxIgnoreErrors = new wxCheckBox( this, wxID_ANY, _("&Ignore errors"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizerProgressFooter->Add( m_checkBoxIgnoreErrors, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + + + bSizer199->Add( bSizerProgressFooter, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + bSizerProgressGraph = new wxBoxSizer( wxHORIZONTAL ); + + m_panelProgressGraph = new zen::Graph2D( this, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ), 0 ); + m_panelProgressGraph->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + + bSizerProgressGraph->Add( m_panelProgressGraph, 1, wxEXPAND, 5 ); + + wxBoxSizer* bSizer247; + bSizer247 = new wxBoxSizer( wxVERTICAL ); wxStaticText* m_staticText99; - m_staticText99 = new wxStaticText( m_panelProgressLabel, wxID_ANY, _("Bytes:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText99 = new wxStaticText( this, wxID_ANY, _("Bytes"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText99->Wrap( -1 ); - bSizer201->Add( m_staticText99, 0, wxALL, 5 ); + bSizer247->Add( m_staticText99, 0, wxALL, 5 ); wxStaticText* m_staticText100; - m_staticText100 = new wxStaticText( m_panelProgressLabel, wxID_ANY, _("Items:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText100 = new wxStaticText( this, wxID_ANY, _("Items"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText100->Wrap( -1 ); - bSizer201->Add( m_staticText100, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + bSizer247->Add( m_staticText100, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - m_panelProgressLabel->SetSizer( bSizer201 ); - m_panelProgressLabel->Layout(); - bSizer201->Fit( m_panelProgressLabel ); - bSizer199->Add( m_panelProgressLabel, 0, 0, 5 ); + bSizerProgressGraph->Add( bSizer247, 0, 0, 5 ); - m_panelGraphProgress = new zen::Graph2D( this, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ), 0 ); - m_panelGraphProgress->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); - bSizer199->Add( m_panelGraphProgress, 1, wxEXPAND, 5 ); + bSizer199->Add( bSizerProgressGraph, 1, 0, 5 ); - bSizer181->Add( bSizer199, 1, wxEXPAND|wxTOP, 5 ); + bSizer181->Add( bSizer199, 0, wxTOP|wxEXPAND, 5 ); bSizer40->Add( bSizer181, 1, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); @@ -2936,6 +2907,9 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi this->SetSizer( bSizer40 ); this->Layout(); bSizer40->Fit( this ); + + // Connect Events + m_checkBoxIgnoreErrors->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( CompareProgressDlgGenerated::OnToggleIgnoreErrors ), NULL, this ); } CompareProgressDlgGenerated::~CompareProgressDlgGenerated() @@ -3189,6 +3163,26 @@ SyncProgressPanelGenerated::SyncProgressPanelGenerated( wxWindow* parent, wxWind bSizer161->Add( 550, 0, 0, 0, 5 ); + bSizerProgressFooter = new wxBoxSizer( wxHORIZONTAL ); + + m_bitmapIgnoreErrors = new wxStaticBitmap( m_panelProgress, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizerProgressFooter->Add( m_bitmapIgnoreErrors, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + m_checkBoxIgnoreErrors = new wxCheckBox( m_panelProgress, wxID_ANY, _("&Ignore errors"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizerProgressFooter->Add( m_checkBoxIgnoreErrors, 1, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + + m_staticText137 = new wxStaticText( m_panelProgress, wxID_ANY, _("When finished:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText137->Wrap( -1 ); + bSizerProgressFooter->Add( m_staticText137, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + wxArrayString m_choicePostSyncActionChoices; + m_choicePostSyncAction = new wxChoice( m_panelProgress, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choicePostSyncActionChoices, 0 ); + m_choicePostSyncAction->SetSelection( 0 ); + bSizerProgressFooter->Add( m_choicePostSyncAction, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer161->Add( bSizerProgressFooter, 0, wxEXPAND|wxBOTTOM|wxRIGHT, 10 ); + bSizer173->Add( bSizer161, 1, wxEXPAND|wxLEFT, 10 ); @@ -3202,6 +3196,8 @@ SyncProgressPanelGenerated::SyncProgressPanelGenerated( wxWindow* parent, wxWind bSizerRoot->Add( m_panelProgress, 1, wxEXPAND, 5 ); m_notebookResult = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_FIXEDWIDTH ); + m_notebookResult->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + bSizerRoot->Add( m_notebookResult, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); @@ -3210,27 +3206,6 @@ SyncProgressPanelGenerated::SyncProgressPanelGenerated( wxWindow* parent, wxWind bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); - wxBoxSizer* bSizer160; - bSizer160 = new wxBoxSizer( wxHORIZONTAL ); - - bSizerOnCompletion = new wxBoxSizer( wxHORIZONTAL ); - - m_staticText87 = new wxStaticText( this, wxID_ANY, _("On completion:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText87->Wrap( -1 ); - bSizerOnCompletion->Add( m_staticText87, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - - m_comboBoxOnCompletion = new OnCompletionBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); - bSizerOnCompletion->Add( m_comboBoxOnCompletion, 1, wxALIGN_CENTER_VERTICAL, 5 ); - - - bSizer160->Add( bSizerOnCompletion, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - - bSizer160->Add( 0, 0, 0, 0, 5 ); - - - bSizerStdButtons->Add( bSizer160, 1, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); - m_buttonClose = new wxButton( this, wxID_OK, _("Close"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); m_buttonClose->SetDefault(); m_buttonClose->Enable( false ); @@ -3244,7 +3219,7 @@ SyncProgressPanelGenerated::SyncProgressPanelGenerated( wxWindow* parent, wxWind bSizerStdButtons->Add( m_buttonStop, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); - bSizerRoot->Add( bSizerStdButtons, 0, wxEXPAND, 5 ); + bSizerRoot->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 ); this->SetSizer( bSizerRoot ); @@ -3258,10 +3233,7 @@ SyncProgressPanelGenerated::~SyncProgressPanelGenerated() LogPanelGenerated::LogPanelGenerated( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style ) : wxPanel( parent, id, pos, size, style ) { - this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); - - wxBoxSizer* bSizer179; - bSizer179 = new wxBoxSizer( wxVERTICAL ); + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); wxBoxSizer* bSizer153; bSizer153 = new wxBoxSizer( wxHORIZONTAL ); @@ -3289,12 +3261,9 @@ LogPanelGenerated::LogPanelGenerated( wxWindow* parent, wxWindowID id, const wxP bSizer153->Add( m_gridMessages, 1, wxEXPAND, 5 ); - bSizer179->Add( bSizer153, 1, wxEXPAND, 5 ); - - - this->SetSizer( bSizer179 ); + this->SetSizer( bSizer153 ); this->Layout(); - bSizer179->Fit( this ); + bSizer153->Fit( this ); // Connect Events m_bpButtonErrors->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( LogPanelGenerated::OnErrors ), NULL, this ); @@ -3339,62 +3308,110 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer180; bSizer180 = new wxBoxSizer( wxHORIZONTAL ); - m_staticText82 = new wxStaticText( m_panel35, wxID_ANY, _("Handle errors:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText82->Wrap( -1 ); - bSizer180->Add( m_staticText82, 0, wxTOP|wxBOTTOM|wxLEFT, 10 ); + wxBoxSizer* bSizer236; + bSizer236 = new wxBoxSizer( wxHORIZONTAL ); - wxBoxSizer* bSizer169; - bSizer169 = new wxBoxSizer( wxVERTICAL ); + m_bitmapMinimizeToTray = new wxStaticBitmap( m_panel35, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer236->Add( m_bitmapMinimizeToTray, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - m_radioBtnPopupOnErrors = new wxRadioButton( m_panel35, wxID_ANY, _("&Pop-up"), wxDefaultPosition, wxDefaultSize, 0 ); - m_radioBtnPopupOnErrors->SetValue( true ); - m_radioBtnPopupOnErrors->SetToolTip( _("Show pop-up on errors or warnings") ); + m_checkBoxRunMinimized = new wxCheckBox( m_panel35, wxID_ANY, _("Run minimized"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizer236->Add( m_checkBoxRunMinimized, 1, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - bSizer169->Add( m_radioBtnPopupOnErrors, 0, wxEXPAND|wxALL, 5 ); - m_radioBtnIgnoreErrors = new wxRadioButton( m_panel35, wxID_ANY, _("&Ignore"), wxDefaultPosition, wxDefaultSize, 0 ); - m_radioBtnIgnoreErrors->SetToolTip( _("Hide all error and warning messages") ); + bSizer180->Add( bSizer236, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - bSizer169->Add( m_radioBtnIgnoreErrors, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + m_staticline26 = new wxStaticLine( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer180->Add( m_staticline26, 0, wxEXPAND, 5 ); - m_radioBtnStopOnError = new wxRadioButton( m_panel35, wxID_ANY, _("&Stop"), wxDefaultPosition, wxDefaultSize, 0 ); - m_radioBtnStopOnError->SetToolTip( _("Stop synchronization at first error") ); + wxBoxSizer* bSizer242; + bSizer242 = new wxBoxSizer( wxVERTICAL ); - bSizer169->Add( m_radioBtnStopOnError, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + wxBoxSizer* bSizer243; + bSizer243 = new wxBoxSizer( wxHORIZONTAL ); + m_bitmapIgnoreErrors = new wxStaticBitmap( m_panel35, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer243->Add( m_bitmapIgnoreErrors, 0, wxALIGN_CENTER_VERTICAL, 5 ); - bSizer180->Add( bSizer169, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + m_checkBoxIgnoreErrors = new wxCheckBox( m_panel35, wxID_ANY, _("&Ignore errors"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizer243->Add( m_checkBoxIgnoreErrors, 1, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); - m_staticline26 = new wxStaticLine( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); - bSizer180->Add( m_staticline26, 0, wxEXPAND, 5 ); - wxBoxSizer* bSizer170; - bSizer170 = new wxBoxSizer( wxVERTICAL ); + bSizer242->Add( bSizer243, 0, wxTOP|wxRIGHT|wxLEFT|wxALIGN_CENTER_HORIZONTAL, 5 ); - m_checkBoxRunMinimized = new wxCheckBox( m_panel35, wxID_ANY, _("Run minimized"), wxDefaultPosition, wxDefaultSize, 0 ); - bSizer170->Add( m_checkBoxRunMinimized, 0, wxEXPAND|wxALL, 5 ); + wxBoxSizer* bSizer246; + bSizer246 = new wxBoxSizer( wxVERTICAL ); + + m_radioBtnErrorDialogShow = new wxRadioButton( m_panel35, wxID_ANY, _("&Show error dialog"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); + m_radioBtnErrorDialogShow->SetValue( true ); + m_radioBtnErrorDialogShow->SetToolTip( _("Show pop-up on errors or warnings") ); + + bSizer246->Add( m_radioBtnErrorDialogShow, 0, wxALL|wxEXPAND, 5 ); + + m_radioBtnErrorDialogCancel = new wxRadioButton( m_panel35, wxID_ANY, _("&Cancel"), wxDefaultPosition, wxDefaultSize, 0 ); + m_radioBtnErrorDialogCancel->SetToolTip( _("Stop synchronization at first error") ); + + bSizer246->Add( m_radioBtnErrorDialogCancel, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 ); + + + bSizer242->Add( bSizer246, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); - m_staticText81 = new wxStaticText( m_panel35, wxID_ANY, _("On completion:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText81->Wrap( -1 ); - bSizer170->Add( m_staticText81, 0, wxALL, 5 ); - m_comboBoxOnCompletion = new OnCompletionBox( m_panel35, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); - bSizer170->Add( m_comboBoxOnCompletion, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + bSizer180->Add( bSizer242, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + m_staticline261 = new wxStaticLine( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer180->Add( m_staticline261, 0, wxEXPAND, 5 ); - bSizer180->Add( bSizer170, 1, wxALL, 5 ); + wxBoxSizer* bSizer247; + bSizer247 = new wxBoxSizer( wxVERTICAL ); + m_staticText137 = new wxStaticText( m_panel35, wxID_ANY, _("When finished:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText137->Wrap( -1 ); + bSizer247->Add( m_staticText137, 0, wxTOP|wxRIGHT|wxLEFT, 5 ); - bSizer172->Add( bSizer180, 0, wxEXPAND, 5 ); + wxArrayString m_choicePostSyncActionChoices; + m_choicePostSyncAction = new wxChoice( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choicePostSyncActionChoices, 0 ); + m_choicePostSyncAction->SetSelection( 0 ); + bSizer247->Add( m_choicePostSyncAction, 0, wxALL, 5 ); + + + bSizer180->Add( bSizer247, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_staticline262 = new wxStaticLine( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer180->Add( m_staticline262, 0, wxEXPAND, 5 ); + + + bSizer172->Add( bSizer180, 0, 0, 5 ); m_staticline25 = new wxStaticLine( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer172->Add( m_staticline25, 0, wxEXPAND, 5 ); + wxBoxSizer* bSizer237; + bSizer237 = new wxBoxSizer( wxHORIZONTAL ); + + m_bitmapLogFile = new wxStaticBitmap( m_panel35, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer237->Add( m_bitmapLogFile, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + wxBoxSizer* bSizer191; bSizer191 = new wxBoxSizer( wxVERTICAL ); - m_checkBoxGenerateLogfile = new wxCheckBox( m_panel35, wxID_ANY, _("Save log:"), wxDefaultPosition, wxDefaultSize, 0 ); - bSizer191->Add( m_checkBoxGenerateLogfile, 0, wxEXPAND|wxALL, 5 ); + wxBoxSizer* bSizer238; + bSizer238 = new wxBoxSizer( wxHORIZONTAL ); + + m_checkBoxSaveLog = new wxCheckBox( m_panel35, wxID_ANY, _("Save log:"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizer238->Add( m_checkBoxSaveLog, 1, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_checkBoxLogfilesLimit = new wxCheckBox( m_panel35, wxID_ANY, _("Limit:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_checkBoxLogfilesLimit->SetToolTip( _("Limit maximum number of log files") ); + + bSizer238->Add( m_checkBoxLogfilesLimit, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_spinCtrlLogfileLimit = new wxSpinCtrl( m_panel35, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 70, -1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 ); + m_spinCtrlLogfileLimit->SetToolTip( _("Limit maximum number of log files") ); + + bSizer238->Add( m_spinCtrlLogfileLimit, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bSizer191->Add( bSizer238, 0, wxEXPAND, 5 ); m_panelLogfile = new wxPanel( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelLogfile->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -3415,16 +3432,6 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer1721->Add( m_bpButtonSelectAltLogFolder, 0, wxEXPAND, 5 ); - m_checkBoxLogfilesLimit = new wxCheckBox( m_panelLogfile, wxID_ANY, _("Limit:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_checkBoxLogfilesLimit->SetToolTip( _("Limit maximum number of log files") ); - - bSizer1721->Add( m_checkBoxLogfilesLimit, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_spinCtrlLogfileLimit = new wxSpinCtrl( m_panelLogfile, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 70, -1 ), wxSP_ARROW_KEYS, 1, 2000000000, 1 ); - m_spinCtrlLogfileLimit->SetToolTip( _("Limit maximum number of log files") ); - - bSizer1721->Add( m_spinCtrlLogfileLimit, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_panelLogfile->SetSizer( bSizer1721 ); m_panelLogfile->Layout(); @@ -3432,7 +3439,10 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer191->Add( m_panelLogfile, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 ); - bSizer172->Add( bSizer191, 0, wxEXPAND|wxALL, 5 ); + bSizer237->Add( bSizer191, 1, 0, 5 ); + + + bSizer172->Add( bSizer237, 0, wxEXPAND|wxALL, 5 ); m_hyperlink17 = new wxHyperlinkCtrl( m_panel35, wxID_ANY, _("How can I schedule a batch job?"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); bSizer172->Add( m_hyperlink17, 0, wxALL, 10 ); @@ -3469,10 +3479,11 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS // Connect Events this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( BatchDlgGenerated::OnClose ) ); - m_radioBtnPopupOnErrors->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorPopup ), NULL, this ); - m_radioBtnIgnoreErrors->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorIgnore ), NULL, this ); - m_radioBtnStopOnError->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorStop ), NULL, this ); - m_checkBoxGenerateLogfile->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnToggleGenerateLogfile ), NULL, this ); + m_checkBoxRunMinimized->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnToggleRunMinimized ), NULL, this ); + m_checkBoxIgnoreErrors->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnToggleIgnoreErrors ), NULL, this ); + m_radioBtnErrorDialogShow->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorDialogShow ), NULL, this ); + m_radioBtnErrorDialogCancel->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorDialogCancel ), NULL, this ); + m_checkBoxSaveLog->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnToggleGenerateLogfile ), NULL, this ); m_checkBoxLogfilesLimit->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnToggleLogfilesLimit ), NULL, this ); m_hyperlink17->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( BatchDlgGenerated::OnHelpScheduleBatch ), NULL, this ); m_buttonSaveAs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnSaveBatchJob ), NULL, this ); @@ -3593,6 +3604,9 @@ CopyToDlgGenerated::CopyToDlgGenerated( wxWindow* parent, wxWindowID id, const w m_panel31 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel31->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxBoxSizer* bSizer242; + bSizer242 = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* bSizer185; bSizer185 = new wxBoxSizer( wxHORIZONTAL ); @@ -3606,29 +3620,32 @@ CopyToDlgGenerated::CopyToDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer185->Add( m_textCtrlFileList, 1, wxEXPAND, 5 ); - m_panel31->SetSizer( bSizer185 ); - m_panel31->Layout(); - bSizer185->Fit( m_panel31 ); - bSizer24->Add( m_panel31, 1, wxEXPAND, 5 ); + bSizer242->Add( bSizer185, 1, wxEXPAND, 5 ); wxBoxSizer* bSizer182; bSizer182 = new wxBoxSizer( wxHORIZONTAL ); - m_targetFolderPath = new FolderHistoryBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); + m_targetFolderPath = new FolderHistoryBox( m_panel31, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); bSizer182->Add( m_targetFolderPath, 1, wxALIGN_CENTER_VERTICAL, 5 ); - m_buttonSelectTargetFolder = new wxButton( this, wxID_ANY, _("Browse"), wxDefaultPosition, wxDefaultSize, 0 ); + m_buttonSelectTargetFolder = new wxButton( m_panel31, wxID_ANY, _("Browse"), wxDefaultPosition, wxDefaultSize, 0 ); m_buttonSelectTargetFolder->SetToolTip( _("Select a folder") ); bSizer182->Add( m_buttonSelectTargetFolder, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonSelectAltTargetFolder = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 30, -1 ), wxBU_AUTODRAW ); + m_bpButtonSelectAltTargetFolder = new wxBitmapButton( m_panel31, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 30, -1 ), wxBU_AUTODRAW ); m_bpButtonSelectAltTargetFolder->SetToolTip( _("Access online storage") ); bSizer182->Add( m_bpButtonSelectAltTargetFolder, 0, wxEXPAND, 5 ); - bSizer24->Add( bSizer182, 0, wxEXPAND|wxALL, 5 ); + bSizer242->Add( bSizer182, 0, wxALL|wxEXPAND, 10 ); + + + m_panel31->SetSizer( bSizer242 ); + m_panel31->Layout(); + bSizer242->Fit( m_panel31 ); + bSizer24->Add( m_panel31, 1, wxEXPAND, 5 ); m_staticline9 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer24->Add( m_staticline9, 0, wxEXPAND, 5 ); diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index b9f8c45d..6de17864 100755 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -11,8 +11,8 @@ #include <wx/artprov.h> #include <wx/xrc/xmlres.h> #include <wx/intl.h> +class CommandBox; class FolderHistoryBox; -class OnCompletionBox; class ToggleButton; namespace zen { class BitmapTextButton; } namespace zen { class Graph2D; } @@ -387,13 +387,12 @@ protected: wxStaticText* m_staticTextNamingCvtPart3; wxStaticLine* m_staticline582; wxBoxSizer* bSizerMiscConfig; - wxStaticText* m_staticText88; - wxRadioButton* m_radioBtnPopupOnErrors; - wxRadioButton* m_radioBtnIgnoreErrors; + wxStaticBitmap* m_bitmapIgnoreErrors; + wxCheckBox* m_checkBoxIgnoreErrors; wxStaticLine* m_staticline57; wxBoxSizer* bSizerOnCompletion; wxStaticText* m_staticText89; - OnCompletionBox* m_comboBoxOnCompletion; + CommandBox* m_comboBoxPostSyncCommand; wxBoxSizer* bSizerStdButtons; wxButton* m_buttonOkay; wxButton* m_buttonCancel; @@ -438,14 +437,14 @@ protected: virtual void OnDeletionVersioning( wxCommandEvent& event ) { event.Skip(); } virtual void OnHelpVersioning( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnChangeSyncOption( wxCommandEvent& event ) { event.Skip(); } - virtual void OnErrorPopup( wxCommandEvent& event ) { event.Skip(); } - virtual void OnErrorIgnore( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToggleIgnoreErrors( wxCommandEvent& event ) { event.Skip(); } virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } public: wxBitmapButton* m_bpButtonSelectAltFolder; + wxChoice* m_choicePostSyncCondition; ConfigDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1, -1 ), long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxRESIZE_BORDER ); ~ConfigDlgGenerated(); @@ -501,9 +500,6 @@ protected: wxTextCtrl* m_textCtrlServer; wxStaticText* m_staticText1233; wxTextCtrl* m_textCtrlPort; - wxStaticText* m_staticText1381; - wxStaticText* m_staticText1382; - wxStaticText* m_staticText138; wxStaticLine* m_staticline58; wxBoxSizer* bSizerAuth; wxBoxSizer* bSizerFtpEncrypt; @@ -686,10 +682,17 @@ protected: wxStaticText* m_staticTextTimeRemaining; wxStaticText* m_staticTextTimeElapsed; wxStaticText* m_staticTextStatus; - wxPanel* m_panelProgressLabel; + wxBoxSizer* bSizerProgressGraph; + zen::Graph2D* m_panelProgressGraph; + + // Virtual event handlers, overide them in your derived class + virtual void OnToggleIgnoreErrors( wxCommandEvent& event ) { event.Skip(); } + public: - zen::Graph2D* m_panelGraphProgress; + wxBoxSizer* bSizerProgressFooter; + wxStaticBitmap* m_bitmapIgnoreErrors; + wxCheckBox* m_checkBoxIgnoreErrors; CompareProgressDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1, -1 ), long style = wxRAISED_BORDER ); ~CompareProgressDlgGenerated(); @@ -705,7 +708,7 @@ private: protected: wxBoxSizer* bSizer42; - wxStaticText* m_staticText87; + wxStaticText* m_staticText137; public: wxBoxSizer* bSizerRoot; @@ -728,11 +731,13 @@ public: wxStaticBitmap* m_bitmapGraphKeyBytes; wxStaticBitmap* m_bitmapGraphKeyItems; zen::Graph2D* m_panelGraphItems; + wxBoxSizer* bSizerProgressFooter; + wxStaticBitmap* m_bitmapIgnoreErrors; + wxCheckBox* m_checkBoxIgnoreErrors; + wxChoice* m_choicePostSyncAction; wxNotebook* m_notebookResult; wxStaticLine* m_staticlineFooter; wxBoxSizer* bSizerStdButtons; - wxBoxSizer* bSizerOnCompletion; - OnCompletionBox* m_comboBoxOnCompletion; wxButton* m_buttonClose; wxButton* m_buttonPause; wxButton* m_buttonStop; @@ -781,20 +786,23 @@ protected: wxStaticText* m_staticTextDescr; wxStaticLine* m_staticline18; wxPanel* m_panel35; - wxStaticText* m_staticText82; - wxRadioButton* m_radioBtnPopupOnErrors; - wxRadioButton* m_radioBtnIgnoreErrors; - wxRadioButton* m_radioBtnStopOnError; - wxStaticLine* m_staticline26; + wxStaticBitmap* m_bitmapMinimizeToTray; wxCheckBox* m_checkBoxRunMinimized; - wxStaticText* m_staticText81; - OnCompletionBox* m_comboBoxOnCompletion; + wxStaticLine* m_staticline26; + wxStaticBitmap* m_bitmapIgnoreErrors; + wxCheckBox* m_checkBoxIgnoreErrors; + wxRadioButton* m_radioBtnErrorDialogShow; + wxRadioButton* m_radioBtnErrorDialogCancel; + wxStaticLine* m_staticline261; + wxStaticText* m_staticText137; + wxStaticLine* m_staticline262; wxStaticLine* m_staticline25; - wxCheckBox* m_checkBoxGenerateLogfile; - wxPanel* m_panelLogfile; - wxButton* m_buttonSelectLogFolder; + wxStaticBitmap* m_bitmapLogFile; + wxCheckBox* m_checkBoxSaveLog; wxCheckBox* m_checkBoxLogfilesLimit; wxSpinCtrl* m_spinCtrlLogfileLimit; + wxPanel* m_panelLogfile; + wxButton* m_buttonSelectLogFolder; wxHyperlinkCtrl* m_hyperlink17; wxStaticLine* m_staticline13; wxBoxSizer* bSizerStdButtons; @@ -803,9 +811,10 @@ protected: // Virtual event handlers, overide them in your derived class virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnErrorPopup( wxCommandEvent& event ) { event.Skip(); } - virtual void OnErrorIgnore( wxCommandEvent& event ) { event.Skip(); } - virtual void OnErrorStop( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToggleRunMinimized( wxCommandEvent& event ) { event.Skip(); } + virtual void OnToggleIgnoreErrors( wxCommandEvent& event ) { event.Skip(); } + virtual void OnErrorDialogShow( wxCommandEvent& event ) { event.Skip(); } + virtual void OnErrorDialogCancel( wxCommandEvent& event ) { event.Skip(); } virtual void OnToggleGenerateLogfile( wxCommandEvent& event ) { event.Skip(); } virtual void OnToggleLogfilesLimit( wxCommandEvent& event ) { event.Skip(); } virtual void OnHelpScheduleBatch( wxHyperlinkEvent& event ) { event.Skip(); } @@ -814,6 +823,7 @@ protected: public: + wxChoice* m_choicePostSyncAction; FolderHistoryBox* m_logFolderPath; wxBitmapButton* m_bpButtonSelectAltLogFolder; diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index 19a76e88..52a75366 100755 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -6,12 +6,12 @@ #include "gui_status_handler.h" #include <zen/shell_execute.h> +#include <zen/shutdown.h> #include <wx/app.h> #include <wx/wupdlock.h> #include <wx+/bitmap_button.h> #include <wx+/popup_dlg.h> #include "main_dlg.h" -#include "on_completion_box.h" #include "../lib/generate_logfile.h" #include "../lib/resolve_path.h" #include "../lib/status_handler_impl.h" @@ -23,7 +23,7 @@ using namespace xmlAccess; StatusHandlerTemporaryPanel::StatusHandlerTemporaryPanel(MainDialog& dlg) : mainDlg_(dlg) { { - mainDlg_.compareStatus_->init(*this); //clear old values before showing panel + mainDlg_.compareStatus_->init(*this, false /*ignoreErrors*/); //clear old values before showing panel //------------------------------------------------------------------ const wxAuiPaneInfo& topPanel = mainDlg_.auiMgr_.GetPane(mainDlg_.m_panelTopButtons); @@ -141,38 +141,33 @@ ProcessCallback::Response StatusHandlerTemporaryPanel::reportError(const std::ws //always, except for "retry": auto guardWriteLog = zen::makeGuard<ScopeGuardRunMode::ON_EXIT>([&] { errorLog_.logMsg(errorMessage, TYPE_ERROR); }); - switch (handleError_) + if (!mainDlg_.compareStatus_->getOptionIgnoreErrors()) { - case ON_GUIERROR_POPUP: - { - forceUiRefresh(); + forceUiRefresh(); - switch (showConfirmationDialog(&mainDlg_, DialogInfoType::ERROR2, PopupDialogCfg(). - setDetailInstructions(errorMessage), - _("&Ignore"), _("Ignore &all"), _("&Retry"))) - { - case ConfirmationButton3::ACCEPT: //ignore - return ProcessCallback::IGNORE_ERROR; + switch (showConfirmationDialog(&mainDlg_, DialogInfoType::ERROR2, PopupDialogCfg(). + setDetailInstructions(errorMessage), + _("&Ignore"), _("Ignore &all"), _("&Retry"))) + { + case ConfirmationButton3::ACCEPT: //ignore + return ProcessCallback::IGNORE_ERROR; - case ConfirmationButton3::ACCEPT_ALL: //ignore all - handleError_ = ON_GUIERROR_IGNORE; - return ProcessCallback::IGNORE_ERROR; + case ConfirmationButton3::ACCEPT_ALL: //ignore all + mainDlg_.compareStatus_->setOptionIgnoreErrors(true); + return ProcessCallback::IGNORE_ERROR; - case ConfirmationButton3::DECLINE: //retry - guardWriteLog.dismiss(); - errorLog_.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); //explain why there are duplicate "doing operation X" info messages in the log! - return ProcessCallback::RETRY; + case ConfirmationButton3::DECLINE: //retry + guardWriteLog.dismiss(); + errorLog_.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); //explain why there are duplicate "doing operation X" info messages in the log! + return ProcessCallback::RETRY; - case ConfirmationButton3::CANCEL: - abortProcessNow(); - break; - } + case ConfirmationButton3::CANCEL: + userAbortProcessNow(); //throw AbortProcess + break; } - break; - - case ON_GUIERROR_IGNORE: - return ProcessCallback::IGNORE_ERROR; } + else + return ProcessCallback::IGNORE_ERROR; assert(false); return ProcessCallback::IGNORE_ERROR; //dummy return value @@ -195,161 +190,192 @@ void StatusHandlerTemporaryPanel::reportWarning(const std::wstring& warningMessa if (!warningActive) //if errors are ignored, then warnings should also return; - switch (handleError_) + if (!mainDlg_.compareStatus_->getOptionIgnoreErrors()) { - case ON_GUIERROR_POPUP: - { - forceUiRefresh(); + forceUiRefresh(); - bool dontWarnAgain = false; - switch (showConfirmationDialog(&mainDlg_, DialogInfoType::WARNING, - PopupDialogCfg().setDetailInstructions(warningMessage). - setCheckBox(dontWarnAgain, _("&Don't show this warning again")), - _("&Ignore"))) - { - case ConfirmationButton::ACCEPT: - warningActive = !dontWarnAgain; - break; - case ConfirmationButton::CANCEL: - abortProcessNow(); - break; - } + bool dontWarnAgain = false; + switch (showConfirmationDialog(&mainDlg_, DialogInfoType::WARNING, + PopupDialogCfg().setDetailInstructions(warningMessage). + setCheckBox(dontWarnAgain, _("&Don't show this warning again")), + _("&Ignore"))) + { + case ConfirmationButton::ACCEPT: + warningActive = !dontWarnAgain; + break; + case ConfirmationButton::CANCEL: + userAbortProcessNow(); //throw AbortProcess + break; } - break; - - case ON_GUIERROR_IGNORE: - break; //if errors are ignored, then warnings should also } + //else: if errors are ignored, then warnings should also } void StatusHandlerTemporaryPanel::forceUiRefresh() { - mainDlg_.compareStatus_->updateStatusPanelNow(); -} - - -void StatusHandlerTemporaryPanel::abortProcessNow() -{ - requestAbortion(); //just make sure... - throw GuiAbortProcess(); + mainDlg_.compareStatus_->updateGui(); } void StatusHandlerTemporaryPanel::OnAbortCompare(wxCommandEvent& event) { - requestAbortion(); + userRequestAbort(); } //######################################################################################################## StatusHandlerFloatingDialog::StatusHandlerFloatingDialog(wxFrame* parentDlg, size_t lastSyncsLogFileSizeMax, - OnGuiError handleError, + bool ignoreErrors, size_t automaticRetryCount, size_t automaticRetryDelay, const std::wstring& jobName, const Zstring& soundFileSyncComplete, - const Zstring& onCompletion, - std::vector<Zstring>& onCompletionHistory) : - progressDlg_(createProgressDialog(*this, [this] { this->onProgressDialogTerminate(); }, *this, parentDlg, true, jobName, soundFileSyncComplete, onCompletion, onCompletionHistory)), - lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax), - handleError_(handleError), - automaticRetryCount_(automaticRetryCount), - automaticRetryDelay_(automaticRetryDelay), - jobName_(jobName), -startTime_(std::time(nullptr)) {} + const Zstring& postSyncCommand, + PostSyncCondition postSyncCondition, + bool& exitAfterSync) : + progressDlg_(createProgressDialog(*this, [this] { this->onProgressDialogTerminate(); }, +*this, +parentDlg, +true, /*showProgress*/ +jobName, +soundFileSyncComplete, +ignoreErrors, +PostSyncAction::SUMMARY)), + lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax), + automaticRetryCount_(automaticRetryCount), + automaticRetryDelay_(automaticRetryDelay), + jobName_(jobName), + startTime_(std::time(nullptr)), + postSyncCommand_(postSyncCommand), + postSyncCondition_(postSyncCondition), + exitAfterSync_(exitAfterSync) +{ + assert(!exitAfterSync); +} StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() { - //------------ "on completion" command conceptually is part of the sync, not cleanup -------------------------------------- - - //decide whether to stay on status screen or exit immediately... - bool showFinalResults = true; - - if (progressDlg_) - { - //execute "on completion" command (even in case of ignored errors) - if (!abortIsRequested()) //if aborted (manually), we don't execute the command - { - const Zstring finalCommand = progressDlg_->getExecWhenFinishedCommand(); //final value (after possible user modification) - if (!finalCommand.empty()) - { - if (isCloseProgressDlgCommand(finalCommand)) - showFinalResults = false; //take precedence over current visibility status - else - try - { - //use EXEC_TYPE_ASYNC until there is reason not to: https://sourceforge.net/p/freefilesync/discussion/help/thread/828dca52 - tryReportingError([&] { shellExecute(expandMacros(finalCommand), EXEC_TYPE_ASYNC); }, //throw FileError - *this); //throw X? - } - catch (...) {} - } - } - } - //------------ end of sync: begin of cleanup -------------------------------------- - const int totalErrors = errorLog_.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log const int totalWarnings = errorLog_.getItemCount(TYPE_WARNING); //finalize error log - std::wstring finalStatus; - if (abortIsRequested()) + SyncProgressDialog::SyncResult finalStatus = SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS; + std::wstring finalStatusMsg; + if (getAbortStatus()) { - finalStatus = _("Synchronization stopped"); - errorLog_.logMsg(finalStatus, TYPE_ERROR); + finalStatus = SyncProgressDialog::RESULT_ABORTED; + finalStatusMsg = _("Synchronization stopped"); + errorLog_.logMsg(finalStatusMsg, TYPE_ERROR); } else if (totalErrors > 0) { - finalStatus = _("Synchronization completed with errors"); - errorLog_.logMsg(finalStatus, TYPE_ERROR); + finalStatus = SyncProgressDialog::RESULT_FINISHED_WITH_ERROR; + finalStatusMsg = _("Synchronization completed with errors"); + errorLog_.logMsg(finalStatusMsg, TYPE_ERROR); } else if (totalWarnings > 0) { - finalStatus = _("Synchronization completed with warnings"); - errorLog_.logMsg(finalStatus, TYPE_WARNING); //give status code same warning priority as display category! + finalStatus = SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS; + finalStatusMsg = _("Synchronization completed with warnings"); + errorLog_.logMsg(finalStatusMsg, TYPE_WARNING); //give status code same warning priority as display category! } else { if (getItemsTotal(PHASE_SYNCHRONIZING) == 0 && //we're past "initNewPhase(PHASE_SYNCHRONIZING)" at this point! getBytesTotal(PHASE_SYNCHRONIZING) == 0) - finalStatus = _("Nothing to synchronize"); //even if "ignored conflicts" occurred! + finalStatusMsg = _("Nothing to synchronize"); //even if "ignored conflicts" occurred! else - finalStatus = _("Synchronization completed successfully"); - errorLog_.logMsg(finalStatus, TYPE_INFO); + finalStatusMsg = _("Synchronization completed successfully"); + errorLog_.logMsg(finalStatusMsg, TYPE_INFO); } + //post sync command + Zstring commandLine = [&] + { + if (!getAbortStatus() || *getAbortStatus() != AbortTrigger::USER) //user cancelled => don't run post sync command! + switch (postSyncCondition_) + { + case PostSyncCondition::COMPLETION: + return postSyncCommand_; + case PostSyncCondition::ERRORS: + if (finalStatus == SyncProgressDialog::RESULT_ABORTED || + finalStatus == SyncProgressDialog::RESULT_FINISHED_WITH_ERROR) + return postSyncCommand_; + break; + case PostSyncCondition::SUCCESS: + if (finalStatus == SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS || + finalStatus == SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS) + return postSyncCommand_; + break; + } + return Zstring(); + }(); + trim(commandLine); + + if (!commandLine.empty()) + errorLog_.logMsg(replaceCpy(_("Executing command %x"), L"%x", fmtPath(commandLine)), TYPE_INFO); + + //----------------- write results into LastSyncs.log------------------------ const SummaryInfo summary = { - jobName_, finalStatus, + jobName_, finalStatusMsg, getItemsCurrent(PHASE_SYNCHRONIZING), getBytesCurrent(PHASE_SYNCHRONIZING), getItemsTotal (PHASE_SYNCHRONIZING), getBytesTotal (PHASE_SYNCHRONIZING), std::time(nullptr) - startTime_ }; - //----------------- write results into LastSyncs.log------------------------ try { saveToLastSyncsLog(summary, errorLog_, lastSyncsLogFileSizeMax_, OnUpdateLogfileStatusNoThrow(*this, utfTo<std::wstring>(getLastSyncsLogfilePath()))); //throw FileError } catch (FileError&) { assert(false); } - if (progressDlg_) - { - //notify to progressDlg that current process has ended - if (showFinalResults) + //execute post sync command *after* writing log files, so that user can refer to the log via the command! + if (!commandLine.empty()) + try { - if (abortIsRequested()) - progressDlg_->processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog_); //enable okay and close events - else if (totalErrors > 0) - progressDlg_->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_ERROR, errorLog_); - else if (totalWarnings > 0) - progressDlg_->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS, errorLog_); - else - progressDlg_->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS, errorLog_); + //use EXEC_TYPE_ASYNC until there is reason not to: http://www.freefilesync.org/forum/viewtopic.php?t=31 + tryReportingError([&] { shellExecute(expandMacros(commandLine), EXEC_TYPE_ASYNC); /*throw FileError*/ }, *this); //throw X } + catch (...) {} + + if (progressDlg_) + { + //post sync action + bool showSummary = true; + if (!getAbortStatus() || *getAbortStatus() != AbortTrigger::USER) //user cancelled => don't run post sync action! + switch (progressDlg_->getOptionPostSyncAction()) + { + case PostSyncAction::SUMMARY: + break; + case PostSyncAction::EXIT: + showSummary = false; + exitAfterSync_ = true; //program shutdown must be handled by calling context! + break; + case PostSyncAction::SLEEP: + try + { + tryReportingError([&] { suspendSystem(); /*throw FileError*/ }, *this); //throw X + } + catch (...) {} + break; + case PostSyncAction::SHUTDOWN: + showSummary = false; + exitAfterSync_ = true; + try + { + tryReportingError([&] { shutdownSystem(); /*throw FileError*/ }, *this); //throw X + } + catch (...) {} + break; + } + + //close progress dialog + if (showSummary) + progressDlg_->processHasFinished(finalStatus, errorLog_); else progressDlg_->closeWindowDirectly(); @@ -394,6 +420,8 @@ void StatusHandlerFloatingDialog::reportInfo(const std::wstring& text) ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::wstring& errorMessage, size_t retryNumber) { + if (!progressDlg_) abortProcessNow(); + //auto-retry if (retryNumber < automaticRetryCount_) { @@ -413,40 +441,34 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws //always, except for "retry": auto guardWriteLog = zen::makeGuard<ScopeGuardRunMode::ON_EXIT>([&] { errorLog_.logMsg(errorMessage, TYPE_ERROR); }); - switch (handleError_) + if (!progressDlg_->getOptionIgnoreErrors()) { - case ON_GUIERROR_POPUP: - { - if (!progressDlg_) abortProcessNow(); - PauseTimers dummy(*progressDlg_); - forceUiRefresh(); + PauseTimers dummy(*progressDlg_); + forceUiRefresh(); - switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::ERROR2, PopupDialogCfg(). - setDetailInstructions(errorMessage), - _("&Ignore"), _("Ignore &all"), _("&Retry"))) - { - case ConfirmationButton3::ACCEPT: //ignore - return ProcessCallback::IGNORE_ERROR; + switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::ERROR2, PopupDialogCfg(). + setDetailInstructions(errorMessage), + _("&Ignore"), _("Ignore &all"), _("&Retry"))) + { + case ConfirmationButton3::ACCEPT: //ignore + return ProcessCallback::IGNORE_ERROR; - case ConfirmationButton3::ACCEPT_ALL: //ignore all - handleError_ = ON_GUIERROR_IGNORE; - return ProcessCallback::IGNORE_ERROR; + case ConfirmationButton3::ACCEPT_ALL: //ignore all + progressDlg_->setOptionIgnoreErrors(true); + return ProcessCallback::IGNORE_ERROR; - case ConfirmationButton3::DECLINE: //retry - guardWriteLog.dismiss(); - errorLog_.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); //explain why there are duplicate "doing operation X" info messages in the log! - return ProcessCallback::RETRY; + case ConfirmationButton3::DECLINE: //retry + guardWriteLog.dismiss(); + errorLog_.logMsg(errorMessage + L"\n-> " + _("Retrying operation..."), TYPE_INFO); //explain why there are duplicate "doing operation X" info messages in the log! + return ProcessCallback::RETRY; - case ConfirmationButton3::CANCEL: - abortProcessNow(); - break; - } + case ConfirmationButton3::CANCEL: + userAbortProcessNow(); //throw AbortProcess + break; } - break; - - case ON_GUIERROR_IGNORE: - return ProcessCallback::IGNORE_ERROR; } + else + return ProcessCallback::IGNORE_ERROR; assert(false); return ProcessCallback::IGNORE_ERROR; //dummy value @@ -455,75 +477,64 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws void StatusHandlerFloatingDialog::reportFatalError(const std::wstring& errorMessage) { + if (!progressDlg_) abortProcessNow(); + errorLog_.logMsg(errorMessage, TYPE_FATAL_ERROR); - switch (handleError_) + if (!progressDlg_->getOptionIgnoreErrors()) { - case ON_GUIERROR_POPUP: + PauseTimers dummy(*progressDlg_); + forceUiRefresh(); + + switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::ERROR2, + PopupDialogCfg().setTitle(_("Serious Error")). + setDetailInstructions(errorMessage), + _("&Ignore"), _("Ignore &all"))) { - if (!progressDlg_) abortProcessNow(); - PauseTimers dummy(*progressDlg_); - forceUiRefresh(); - - switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::ERROR2, - PopupDialogCfg().setTitle(_("Serious Error")). - setDetailInstructions(errorMessage), - _("&Ignore"), _("Ignore &all"))) - { - case ConfirmationButton2::ACCEPT: - break; + case ConfirmationButton2::ACCEPT: + break; - case ConfirmationButton2::ACCEPT_ALL: - handleError_ = ON_GUIERROR_IGNORE; - break; + case ConfirmationButton2::ACCEPT_ALL: + progressDlg_->setOptionIgnoreErrors(true); + break; - case ConfirmationButton2::CANCEL: - abortProcessNow(); - break; - } + case ConfirmationButton2::CANCEL: + userAbortProcessNow(); //throw AbortProcess + break; } - break; - - case ON_GUIERROR_IGNORE: - break; } } void StatusHandlerFloatingDialog::reportWarning(const std::wstring& warningMessage, bool& warningActive) { + if (!progressDlg_) abortProcessNow(); + errorLog_.logMsg(warningMessage, TYPE_WARNING); if (!warningActive) return; - switch (handleError_) + if (!progressDlg_->getOptionIgnoreErrors()) { - case ON_GUIERROR_POPUP: + PauseTimers dummy(*progressDlg_); + forceUiRefresh(); + + bool dontWarnAgain = false; + switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::WARNING, + PopupDialogCfg().setDetailInstructions(warningMessage). + setCheckBox(dontWarnAgain, _("&Don't show this warning again")), + _("&Ignore"))) { - if (!progressDlg_) abortProcessNow(); - PauseTimers dummy(*progressDlg_); - forceUiRefresh(); - - bool dontWarnAgain = false; - switch (showConfirmationDialog(progressDlg_->getWindowIfVisible(), DialogInfoType::WARNING, - PopupDialogCfg().setDetailInstructions(warningMessage). - setCheckBox(dontWarnAgain, _("&Don't show this warning again")), - _("&Ignore"))) - { - case ConfirmationButton::ACCEPT: - warningActive = !dontWarnAgain; - break; - case ConfirmationButton::CANCEL: - abortProcessNow(); - break; - } + case ConfirmationButton::ACCEPT: + warningActive = !dontWarnAgain; + break; + case ConfirmationButton::CANCEL: + userAbortProcessNow(); //throw AbortProcess + break; } - break; - - case ON_GUIERROR_IGNORE: - break; //if errors are ignored, then warnings should be, too } + //else: if errors are ignored, then warnings should be, too } @@ -534,15 +545,7 @@ void StatusHandlerFloatingDialog::forceUiRefresh() } -void StatusHandlerFloatingDialog::abortProcessNow() -{ - requestAbortion(); //just make sure... - throw GuiAbortProcess(); //abort can be triggered by progressDlg -} - - void StatusHandlerFloatingDialog::onProgressDialogTerminate() { - //it's responsibility of "progressDlg" to call requestAbortion() when closing dialog progressDlg_ = nullptr; } diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h index 7ea2a8c3..0efb7e20 100755 --- a/FreeFileSync/Source/ui/gui_status_handler.h +++ b/FreeFileSync/Source/ui/gui_status_handler.h @@ -12,16 +12,13 @@ #include "progress_indicator.h" #include "main_dlg.h" #include "../lib/status_handler.h" -#include "../lib/process_xml.h" +//#include "../lib/process_xml.h" -//Exception class used to abort the "compare" and "sync" process -class GuiAbortProcess {}; - //classes handling sync and compare errors as well as status feedback //StatusHandlerTemporaryPanel(CompareProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks! -class StatusHandlerTemporaryPanel : private wxEvtHandler, public zen::StatusHandler //throw GuiAbortProcess +class StatusHandlerTemporaryPanel : private wxEvtHandler, public zen::StatusHandler //throw AbortProcess { public: StatusHandlerTemporaryPanel(MainDialog& dlg); @@ -35,7 +32,6 @@ public: void reportWarning (const std::wstring& warningMessage, bool& warningActive) override; void forceUiRefresh() override; - void abortProcessNow() override final; //throw GuiAbortProcess zen::ErrorLog getErrorLog() const { return errorLog_; } @@ -44,24 +40,24 @@ private: void OnAbortCompare(wxCommandEvent& event); //handle abort button click MainDialog& mainDlg_; - xmlAccess::OnGuiError handleError_ = xmlAccess::ON_GUIERROR_POPUP; zen::ErrorLog errorLog_; }; //StatusHandlerFloatingDialog(SyncProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks! -class StatusHandlerFloatingDialog : public zen::StatusHandler +class StatusHandlerFloatingDialog : public zen::StatusHandler //throw AbortProcess { public: StatusHandlerFloatingDialog(wxFrame* parentDlg, size_t lastSyncsLogFileSizeMax, - xmlAccess::OnGuiError handleError, + bool ignoreErrors, size_t automaticRetryCount, size_t automaticRetryDelay, const std::wstring& jobName, const Zstring& soundFileSyncComplete, - const Zstring& onCompletion, - std::vector<Zstring>& onCompletionHistory); + const Zstring& postSyncCommand, + zen::PostSyncCondition postSyncCondition, + bool& exitAfterSync); ~StatusHandlerFloatingDialog(); void initNewPhase (int itemsTotal, int64_t bytesTotal, Phase phaseID) override; @@ -73,19 +69,20 @@ public: void reportWarning (const std::wstring& warningMessage, bool& warningActive) override; void forceUiRefresh() override; - void abortProcessNow() override final; //throw GuiAbortProcess private: void onProgressDialogTerminate(); SyncProgressDialog* progressDlg_; //managed to have shorter lifetime than this handler! const size_t lastSyncsLogFileSizeMax_; - xmlAccess::OnGuiError handleError_; zen::ErrorLog errorLog_; const size_t automaticRetryCount_; const size_t automaticRetryDelay_; const std::wstring jobName_; const time_t startTime_; //don't use wxStopWatch: may overflow after a few days due to ::QueryPerformanceCounter() + const Zstring postSyncCommand_; + const zen::PostSyncCondition postSyncCondition_; + bool& exitAfterSync_; }; diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index 3a61dcfd..b90415f0 100755 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -42,6 +42,7 @@ #include "../lib/help_provider.h" #include "../lib/lock_holder.h" #include "../lib/localization.h" +#include "../version/version.h" using namespace zen; @@ -687,7 +688,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, wxMenuItem* newItem = new wxMenuItem(menu, wxID_ANY, _("&Show details")); this->Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnMenuUpdateAvailable)); menu->Append(newItem); //pass ownership - m_menubar1->Append(menu, L"\u21D2 " + _("A new version of FreeFileSync is available:") + L" \u2605 " + globalSettings.gui.lastOnlineVersion + L" \u2605"); + m_menubar1->Append(menu, L"\u2605 " + replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo<std::wstring>(globalSettings.gui.lastOnlineVersion)) + L" \u2605"); //"BLACK STAR" } //notify about (logical) application main window => program won't quit, but stay on this dialog @@ -1233,14 +1234,14 @@ void MainDialog::copyToAlternateFolder(const std::vector<zen::FileSystemObject*> statusHandler); //"clearSelection" not needed/desired } - catch (GuiAbortProcess&) {} + catch (AbortProcess&) {} //updateGui(); -> not needed } void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selectionLeft, - const std::vector<FileSystemObject*>& selectionRight) + const std::vector<FileSystemObject*>& selectionRight, bool moveToRecycler) { std::vector<FileSystemObject*> rowsLeftTmp = selectionLeft; std::vector<FileSystemObject*> rowsRightTmp = selectionRight; @@ -1253,7 +1254,7 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec //sigh: do senseless vector<FileSystemObject*> -> vector<const FileSystemObject*> conversion: if (zen::showDeleteDialog(this, { rowsLeftTmp.begin(), rowsLeftTmp.end() }, { rowsRightTmp.begin(), rowsRightTmp.end() }, - globalCfg_.gui.mainDlg.manualDeletionUseRecycler) != ReturnSmallDlg::BUTTON_OKAY) + moveToRecycler) != ReturnSmallDlg::BUTTON_OKAY) return; disableAllElements(true); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! @@ -1268,7 +1269,7 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec zen::deleteFromGridAndHD(rowsLeftTmp, rowsRightTmp, folderCmp_, extractDirectionCfg(getConfig().mainCfg), - globalCfg_.gui.mainDlg.manualDeletionUseRecycler, + moveToRecycler, globalCfg_.optDialogs.warnRecyclerMissing, statusHandler); @@ -1278,7 +1279,7 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec m_gridOverview->clearSelection(ALLOW_GRID_EVENT); } - catch (GuiAbortProcess&) {} //do not clear grids, if aborted! + catch (AbortProcess&) {} //do not clear grids, if aborted! //remove rows that are empty: just a beautification, invalid rows shouldn't cause issues gridDataView_->removeInvalidRows(); @@ -1473,12 +1474,12 @@ void MainDialog::openExternalApplication(const Zstring& commandLinePhrase, bool try { - StatusHandlerTemporaryPanel statusHandler(*this); //throw GuiAbortProcess + StatusHandlerTemporaryPanel statusHandler(*this); //throw AbortProcess tempFileBuf_.createTempFiles(nonNativeFiles, statusHandler); //"clearSelection" not needed/desired } - catch (GuiAbortProcess&) { return; } + catch (AbortProcess&) { return; } //updateGui(); -> not needed } @@ -1513,20 +1514,20 @@ void MainDialog::setStatusBarFileStatistics(size_t filesOnLeftView, setText(*m_staticTextStatusLeftDirs, _P("1 directory", "%x directories", foldersOnLeftView)); setText(*m_staticTextStatusLeftFiles, _P("1 file", "%x files", filesOnLeftView)); - setText(*m_staticTextStatusLeftBytes, L"(" + filesizeToShortString(filesizeLeftView) + L")"); + setText(*m_staticTextStatusLeftBytes, L"(" + formatFilesizeShort(filesizeLeftView) + L")"); //------------------------------------------------------------------------------ bSizerStatusRightDirectories->Show(foldersOnRightView > 0); bSizerStatusRightFiles ->Show(filesOnRightView > 0); setText(*m_staticTextStatusRightDirs, _P("1 directory", "%x directories", foldersOnRightView)); setText(*m_staticTextStatusRightFiles, _P("1 file", "%x files", filesOnRightView)); - setText(*m_staticTextStatusRightBytes, L"(" + filesizeToShortString(filesizeRightView) + L")"); + setText(*m_staticTextStatusRightBytes, L"(" + formatFilesizeShort(filesizeRightView) + L")"); //------------------------------------------------------------------------------ wxString statusCenterNew; if (gridDataView_->rowsTotal() > 0) { statusCenterNew = _P("Showing %y of 1 row", "Showing %y of %x rows", gridDataView_->rowsTotal()); - replace(statusCenterNew, L"%y", toGuiString(gridDataView_->rowsOnView())); //%x is already used as plural form placeholder! + replace(statusCenterNew, L"%y", formatNumber(gridDataView_->rowsOnView())); //%x is already used as plural form placeholder! } //fill middle text (considering flashStatusInformation()) @@ -1803,7 +1804,7 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event) case WXK_DELETE: case WXK_NUMPAD_DELETE: - deleteSelectedFiles(getTreeSelection(), getTreeSelection()); + deleteSelectedFiles(getTreeSelection(), getTreeSelection(), !event.ShiftDown() /*moveToRecycler*/); return; } @@ -1884,7 +1885,7 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) { case WXK_DELETE: case WXK_NUMPAD_DELETE: - deleteSelectedFiles(selectionLeft, selectionRight); + deleteSelectedFiles(selectionLeft, selectionRight, !event.ShiftDown() /*moveToRecycler*/); return; case WXK_SPACE: @@ -2146,7 +2147,7 @@ void MainDialog::onNaviGridContext(GridClickEvent& event) menu.addSeparator(); - menu.addItem(_("&Delete") + L"\tDel", [&] { deleteSelectedFiles(selection, selection); }, nullptr, haveNonEmptyItems); + menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selection, selection, true /*moveToRecycler*/); }, nullptr, haveNonEmptyItems); menu.popup(*this); } @@ -2314,8 +2315,7 @@ void MainDialog::onMainGridContextRim(bool leftSide) //---------------------------------------------------------------------------------------------------- menu.addSeparator(); - - menu.addItem(_("&Delete") + L"\tDel", [&] { deleteSelectedFiles(nonEmptySelectionLeft, nonEmptySelectionRight); }, nullptr, + menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(nonEmptySelectionLeft, nonEmptySelectionRight, true /*moveToRecycler*/); }, nullptr, !nonEmptySelectionLeft.empty() || !nonEmptySelectionRight.empty()); menu.popup(*this); @@ -2340,17 +2340,23 @@ void MainDialog::addFilterPhrase(const Zstring& phrase, bool include, bool requi if (requireNewLine) { - trim(filterString, false, true, [](Zchar c) { return c == FILTER_ITEM_SEPARATOR || c == Zstr('\n'); }); + trim(filterString, false, true, [](Zchar c) { return c == FILTER_ITEM_SEPARATOR || c == Zstr('\n') || c == Zstr(' '); }); if (!filterString.empty()) filterString += Zstr("\n"); filterString += phrase; } else { - trim(filterString, false, true, [](Zchar c) { return c == Zstr('\n'); }); - if (!filterString.empty() && !endsWith(filterString, FILTER_ITEM_SEPARATOR)) + trim(filterString, false, true, [](Zchar c) { return c == Zstr('\n') || c == Zstr(' '); }); + + if (filterString.empty()) + ; + else if (endsWith(filterString, FILTER_ITEM_SEPARATOR)) + filterString += Zstr(" "); + else filterString += Zstr("\n"); - filterString += phrase + FILTER_ITEM_SEPARATOR; //append FILTER_ITEM_SEPARATOR to 'mark' that next extension exclude should write to same line + + filterString += phrase + Zstr(' ') + FILTER_ITEM_SEPARATOR; //append FILTER_ITEM_SEPARATOR to 'mark' that next extension exclude should write to same line } updateGlobalFilterButton(); @@ -2811,7 +2817,7 @@ void MainDialog::removeCfgHistoryItems(const std::vector<Zstring>& filePaths) void MainDialog::updateUnsavedCfgStatus() { - const Zstring activeCfgFilename = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); + const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); const bool haveUnsavedCfg = lastConfigurationSaved_ != getConfig(); @@ -2835,16 +2841,15 @@ void MainDialog::updateUnsavedCfgStatus() if (haveUnsavedCfg) title += L'*'; - if (!activeCfgFilename.empty()) - title += utfTo<wxString>(activeCfgFilename); + if (!activeCfgFilePath.empty()) + title += utfTo<wxString>(activeCfgFilePath); else if (activeConfigFiles_.size() > 1) { - const wchar_t* EN_DASH = L" \u2013 "; title += xmlAccess::extractJobName(activeConfigFiles_[0]); - std::for_each(activeConfigFiles_.begin() + 1, activeConfigFiles_.end(), [&](const Zstring& filepath) { title += EN_DASH + xmlAccess::extractJobName(filepath); }); + std::for_each(activeConfigFiles_.begin() + 1, activeConfigFiles_.end(), [&](const Zstring& filepath) { title += SPACED_DASH + xmlAccess::extractJobName(filepath); }); } else - title += L"FreeFileSync - " + _("Folder Comparison and Synchronization"); + title += wxString(L"FreeFileSync ") + zen::ffsVersion + SPACED_DASH + _("Folder Comparison and Synchronization"); SetTitle(title); } @@ -2852,29 +2857,29 @@ void MainDialog::updateUnsavedCfgStatus() void MainDialog::OnConfigSave(wxCommandEvent& event) { - const Zstring activeCfgFilename = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); + const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); using namespace xmlAccess; //if we work on a single named configuration document: save directly if changed //else: always show file dialog - if (activeCfgFilename.empty()) + if (activeCfgFilePath.empty()) trySaveConfig(nullptr); else try { - switch (getXmlType(activeCfgFilename)) //throw FileError + switch (getXmlType(activeCfgFilePath)) //throw FileError { case XML_TYPE_GUI: - trySaveConfig(&activeCfgFilename); + trySaveConfig(&activeCfgFilePath); break; case XML_TYPE_BATCH: - trySaveBatchConfig(&activeCfgFilename); + trySaveBatchConfig(&activeCfgFilePath); break; case XML_TYPE_GLOBAL: case XML_TYPE_OTHER: showNotificationDialog(this, DialogInfoType::ERROR2, - PopupDialogCfg().setDetailInstructions(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(activeCfgFilename)))); + PopupDialogCfg().setDetailInstructions(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(activeCfgFilePath)))); break; } } @@ -2929,7 +2934,7 @@ bool MainDialog::trySaveConfig(const Zstring* guiFilename) //return true if save try { - xmlAccess::writeConfig(guiCfg, targetFilename); //throw FileError + writeConfig(guiCfg, targetFilename); //throw FileError setLastUsedConfig(targetFilename, guiCfg); flashStatusInformation(_("Configuration saved")); @@ -2947,33 +2952,29 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchFileToUpdate) { using namespace xmlAccess; - //essentially behave like trySaveConfig(): the collateral damage of not saving GUI-only settings "m_bpButtonViewTypeSyncAction" is negliable + //essentially behave like trySaveConfig(): the collateral damage of not saving GUI-only settings "m_bpButtonViewTypeSyncAction" is negligible - const Zstring activeCfgFilename = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); - const XmlGuiConfig guiCfg = getConfig(); + const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); //prepare batch config: reuse existing batch-specific settings from file if available - XmlBatchConfig batchCfg; + BatchExclusiveConfig batchExCfg; try { Zstring referenceBatchFile; if (batchFileToUpdate) referenceBatchFile = *batchFileToUpdate; - else if (!activeCfgFilename.empty()) - if (getXmlType(activeCfgFilename) == XML_TYPE_BATCH) //throw FileError - referenceBatchFile = activeCfgFilename; + else if (!activeCfgFilePath.empty()) + if (getXmlType(activeCfgFilePath) == XML_TYPE_BATCH) //throw FileError + referenceBatchFile = activeCfgFilePath; - if (referenceBatchFile.empty()) - batchCfg = convertGuiToBatch(guiCfg, nullptr); - else + if (!referenceBatchFile.empty()) { XmlBatchConfig referenceBatchCfg; std::wstring warningMsg; readConfig(referenceBatchFile, referenceBatchCfg, warningMsg); //throw FileError //=> ignore warnings altogether: user has seen them already when loading the config file! - - batchCfg = convertGuiToBatch(guiCfg, &referenceBatchCfg); + batchExCfg = referenceBatchCfg.batchExCfg; } } catch (const FileError& e) @@ -2991,13 +2992,13 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchFileToUpdate) else { //let user update batch config: this should change batch-exclusive settings only, else the "setLastUsedConfig" below would be somewhat of a lie - if (customizeBatchConfig(this, - batchCfg, //in/out - globalCfg_.gui.onCompletionHistory, - globalCfg_.gui.onCompletionHistoryMax) != ReturnBatchConfig::BUTTON_SAVE_AS) + if (showBatchConfigDialog(this, + batchExCfg, + currentCfg_.mainCfg.ignoreErrors) != ReturnBatchConfig::BUTTON_SAVE_AS) return false; + updateUnsavedCfgStatus(); //nothing else to update on GUI! - Zstring defaultFileName = !activeCfgFilename.empty() ? activeCfgFilename : Zstr("BatchRun.ffs_batch"); + Zstring defaultFileName = !activeCfgFilePath.empty() ? activeCfgFilePath : Zstr("BatchRun.ffs_batch"); //attention: activeConfigFiles may be a *.ffs_gui file! We don't want to overwrite it with a BATCH config! if (endsWith(defaultFileName, Zstr(".ffs_gui"))) defaultFileName = beforeLast(defaultFileName, Zstr("."), IF_MISSING_RETURN_NONE) + Zstr(".ffs_batch"); @@ -3014,11 +3015,14 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchFileToUpdate) targetFilename = utfTo<Zstring>(filePicker.GetPath()); } + const XmlGuiConfig guiCfg = getConfig(); + const XmlBatchConfig batchCfg = convertGuiToBatch(guiCfg, batchExCfg); + try { writeConfig(batchCfg, targetFilename); //throw FileError - setLastUsedConfig(targetFilename, guiCfg); //[!] behave as if we had saved guiCfg + flashStatusInformation(_("Configuration saved")); return true; } @@ -3034,18 +3038,18 @@ bool MainDialog::saveOldConfig() //return false on user abort { if (lastConfigurationSaved_ != getConfig()) { - const Zstring activeCfgFilename = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); + const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); //notify user about changed settings if (globalCfg_.optDialogs.popupOnConfigChange) - if (!activeCfgFilename.empty()) + if (!activeCfgFilePath.empty()) //only if check is active and non-default config file loaded { bool neverSaveChanges = false; switch (showQuestionDialog(this, DialogInfoType::INFO, PopupDialogCfg(). - setTitle(utfTo<wxString>(activeCfgFilename)). + setTitle(utfTo<wxString>(activeCfgFilePath)). setMainInstructions(replaceCpy(_("Do you want to save changes to %x?"), L"%x", - fmtPath(afterLast(activeCfgFilename, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)))). + fmtPath(afterLast(activeCfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)))). setCheckBox(neverSaveChanges, _("Never save &changes"), QuestionButton2::YES), _("&Save"), _("Do&n't save"))) { @@ -3054,16 +3058,16 @@ bool MainDialog::saveOldConfig() //return false on user abort try { - switch (getXmlType(activeCfgFilename)) //throw FileError + switch (getXmlType(activeCfgFilePath)) //throw FileError { case XML_TYPE_GUI: - return trySaveConfig(&activeCfgFilename); + return trySaveConfig(&activeCfgFilePath); case XML_TYPE_BATCH: - return trySaveBatchConfig(&activeCfgFilename); + return trySaveBatchConfig(&activeCfgFilePath); case XML_TYPE_GLOBAL: case XML_TYPE_OTHER: showNotificationDialog(this, DialogInfoType::ERROR2, - PopupDialogCfg().setDetailInstructions(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(activeCfgFilename)))); + PopupDialogCfg().setDetailInstructions(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(activeCfgFilePath)))); return false; } } @@ -3094,11 +3098,11 @@ bool MainDialog::saveOldConfig() //return false on user abort void MainDialog::OnConfigLoad(wxCommandEvent& event) { - const Zstring activeCfgFilename = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); + const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); wxFileDialog filePicker(this, wxString(), - utfTo<wxString>(beforeLast(activeCfgFilename, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)), //default dir + utfTo<wxString>(beforeLast(activeCfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)), //default dir wxString(), //default file wxString(L"FreeFileSync (*.ffs_gui; *.ffs_batch)|*.ffs_gui;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", wxFD_OPEN | wxFD_MULTIPLE); @@ -3262,7 +3266,7 @@ void MainDialog::OnCfgHistoryKeyEvent(wxKeyEvent& event) void MainDialog::OnClose(wxCloseEvent& event) { - //attention: system shutdown: is handled in onQueryEndSession()! + //attention: system shutdown is handled in onQueryEndSession()! //regular destruction handling if (event.CanVeto()) @@ -3428,9 +3432,10 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde const SyncConfig syncCfgOld = currentCfg_.mainCfg.syncCfg; const FilterConfig filterCfgOld = currentCfg_.mainCfg.globalFilter; - const xmlAccess::OnGuiError handleErrorOld = currentCfg_.handleError; - const Zstring onCompletionCommandOld = currentCfg_.mainCfg.onCompletion; - const std::vector<Zstring> onCompletionHistoryOld = globalCfg_.gui.onCompletionHistory; + const bool ignoreErrorsOld = currentCfg_.mainCfg.ignoreErrors; + const Zstring postSyncCommandOld = currentCfg_.mainCfg.postSyncCommand; + const PostSyncCondition postSyncConditionOld = currentCfg_.mainCfg.postSyncCondition; + const std::vector<Zstring> commandHistoryOld = globalCfg_.gui.commandHistory; if (showSyncConfigDlg(this, panelToShow, @@ -3441,10 +3446,11 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde currentCfg_.mainCfg.syncCfg, currentCfg_.mainCfg.globalFilter, - currentCfg_.handleError, - currentCfg_.mainCfg.onCompletion, - globalCfg_.gui.onCompletionHistory, - globalCfg_.gui.onCompletionHistoryMax) == ReturnSyncConfig::BUTTON_OKAY) + currentCfg_.mainCfg.ignoreErrors, + currentCfg_.mainCfg.postSyncCommand, + currentCfg_.mainCfg.postSyncCondition, + globalCfg_.gui.commandHistory, + globalCfg_.gui.commandHistoryMax) == ReturnSyncConfig::BUTTON_OKAY) { assert(folderPairConfig.size() == folderPairConfigOld.size()); @@ -3504,9 +3510,10 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde return false; }(); - const bool miscConfigChanged = currentCfg_.handleError != handleErrorOld || - currentCfg_.mainCfg.onCompletion != onCompletionCommandOld; - //globalCfg.gui.onCompletionHistory != onCompletionHistoryOld; + const bool miscConfigChanged = currentCfg_.mainCfg.ignoreErrors != ignoreErrorsOld || + currentCfg_.mainCfg.postSyncCommand != postSyncCommandOld || + currentCfg_.mainCfg.postSyncCondition != postSyncConditionOld; + //globalCfg.gui.commandHistory != commandHistoryOld; //------------------------------------------------ @@ -3748,9 +3755,9 @@ void MainDialog::OnCompare(wxCommandEvent& event) globalCfg_.createLockFile, dirLocks, cmpConfig, - statusHandler); //throw GuiAbortProcess + statusHandler); //throw AbortProcess } - catch (GuiAbortProcess&) + catch (AbortProcess&) { updateGui(); //refresh grid in ANY case! (also on abort) return; @@ -3849,13 +3856,13 @@ void MainDialog::updateStatistics() auto setIntValue = [&setValue](wxStaticText& txtControl, int value, wxStaticBitmap& bmpControl, const wchar_t* bmpName) { - setValue(txtControl, value == 0, toGuiString(value), bmpControl, bmpName); + setValue(txtControl, value == 0, formatNumber(value), bmpControl, bmpName); }; //update preview of item count and bytes to be transferred: const SyncStatistics st(folderCmp_); - setValue(*m_staticTextData, st.getBytesToProcess() == 0, filesizeToShortString(st.getBytesToProcess()), *m_bitmapData, L"data"); + setValue(*m_staticTextData, st.getBytesToProcess() == 0, formatFilesizeShort(st.getBytesToProcess()), *m_bitmapData, L"data"); setIntValue(*m_staticTextCreateLeft, st.createCount< LEFT_SIDE>(), *m_bitmapCreateLeft, L"so_create_left_small"); setIntValue(*m_staticTextUpdateLeft, st.updateCount< LEFT_SIDE>(), *m_bitmapUpdateLeft, L"so_update_left_small"); setIntValue(*m_staticTextDeleteLeft, st.deleteCount< LEFT_SIDE>(), *m_bitmapDeleteLeft, L"so_delete_left_small"); @@ -3915,12 +3922,13 @@ void MainDialog::OnStartSync(wxCommandEvent& event) globalCfg_.optDialogs.confirmSyncStart = !dontShowAgain; } + bool exitAfterSync = false; try { const std::chrono::system_clock::time_point syncStartTime = std::chrono::system_clock::now(); //PERF_START; - const Zstring activeCfgFilename = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); + const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); const auto& guiCfg = getConfig(); @@ -3928,15 +3936,16 @@ void MainDialog::OnStartSync(wxCommandEvent& event) ZEN_ON_SCOPE_EXIT(enableAllElements()); //class handling status updates and error messages - StatusHandlerFloatingDialog statusHandler(this, //throw GuiAbortProcess + StatusHandlerFloatingDialog statusHandler(this, //throw AbortProcess globalCfg_.lastSyncsLogFileSizeMax, - currentCfg_.handleError, + currentCfg_.mainCfg.ignoreErrors, globalCfg_.automaticRetryCount, globalCfg_.automaticRetryDelay, - xmlAccess::extractJobName(activeCfgFilename), + xmlAccess::extractJobName(activeCfgFilePath), globalCfg_.soundFileSyncFinished, - guiCfg.mainCfg.onCompletion, - globalCfg_.gui.onCompletionHistory); + guiCfg.mainCfg.postSyncCommand, + guiCfg.mainCfg.postSyncCondition, + exitAfterSync); //inform about (important) non-default global settings logNonDefaultSettings(globalCfg_, statusHandler); //let's report here rather than before comparison (user might have changed global settings in the meantime!) @@ -3979,15 +3988,15 @@ void MainDialog::OnStartSync(wxCommandEvent& event) globalCfg_.optDialogs, statusHandler); } - catch (GuiAbortProcess&) - { - //do NOT disable the sync button: user might want to try to sync the REMAINING rows - } //enableSynchronization(false); + catch (AbortProcess&) {} //remove empty rows: just a beautification, invalid rows shouldn't cause issues gridDataView_->removeInvalidRows(); updateGui(); + + if (exitAfterSync) + Destroy(); //don't use Close(): we don't want to show the prompt to save current config in OnClose() } @@ -4732,14 +4741,14 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) const char CSV_SEP = haveCommaAsDecimalSep ? ';' : ','; - auto fmtValue = [&](const wxString& val) -> std::string + auto fmtValue = [&](const std::wstring& val) -> std::string { std::string&& tmp = utfTo<std::string>(val); if (contains(tmp, CSV_SEP)) return '\"' + tmp + '\"'; else - return tmp; + return std::move(tmp); }; std::string header; //perf: wxString doesn't model exponential growth and so is out, std::string doesn't give performance guarantee! @@ -4749,8 +4758,8 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) header += fmtValue(_("Folder Pairs")) + LINE_BREAK; std::for_each(begin(folderCmp_), end(folderCmp_), [&](BaseFolderPair& baseFolder) { - header += utfTo<std::string>(AFS::getDisplayPath(baseFolder.getAbstractPath< LEFT_SIDE>())) + CSV_SEP; - header += utfTo<std::string>(AFS::getDisplayPath(baseFolder.getAbstractPath<RIGHT_SIDE>())) + LINE_BREAK; + header += fmtValue(AFS::getDisplayPath(baseFolder.getAbstractPath< LEFT_SIDE>())) + CSV_SEP; + header += fmtValue(AFS::getDisplayPath(baseFolder.getAbstractPath<RIGHT_SIDE>())) + LINE_BREAK; }); header += LINE_BREAK; diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index fc0e4cd8..7e686431 100755 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -119,7 +119,7 @@ private: const std::vector<zen::FileSystemObject*>& selectionRight); void deleteSelectedFiles(const std::vector<zen::FileSystemObject*>& selectionLeft, - const std::vector<zen::FileSystemObject*>& selectionRight); + const std::vector<zen::FileSystemObject*>& selectionRight, bool moveToRecycler); void openExternalApplication(const Zstring& commandLinePhrase, bool leftSide, const std::vector<zen::FileSystemObject*>& selectionLeft, diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index 48848df2..c926f25d 100755 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -28,16 +28,17 @@ #include <zen/file_access.h> #include <zen/thread.h> #include <wx+/rtl.h> +#include <wx+/choice_enum.h> #include "gui_generated.h" #include "../lib/ffs_paths.h" #include "../lib/perf_check.h" #include "tray_icon.h" #include "taskbar.h" -#include "on_completion_box.h" #include "app_icon.h" using namespace zen; +using namespace xmlAccess; namespace @@ -51,14 +52,14 @@ inline wxColor getColorGridLine() { return { 192, 192, 192 }; } //light grey inline wxColor getColorBytes() { return { 111, 255, 99 }; } //light green inline wxColor getColorItems() { return { 127, 147, 255 }; } //light blue -inline wxColor getColorBytesRim() { return { 20, 200, 0 }; } //medium green -inline wxColor getColorItemsRim() { return { 90, 120, 255 }; } //medium blue +inline wxColor getColorBytesRim() { return { 20, 200, 0 }; } //medium green +inline wxColor getColorItemsRim() { return { 90, 120, 255 }; } //medium blue inline wxColor getColorBytesBackground() { return { 205, 255, 202 }; } //faint green inline wxColor getColorItemsBackground() { return { 198, 206, 255 }; } //faint blue -inline wxColor getColorBytesBackgroundRim() { return { 12, 128, 0 }; } //dark green -inline wxColor getColorItemsBackgroundRim() { return { 53, 25, 255 }; } //dark blue +inline wxColor getColorBytesBackgroundRim() { return { 12, 128, 0 }; } //dark green +inline wxColor getColorItemsBackgroundRim() { return { 53, 25, 255 }; } //dark blue //don't use wxStopWatch for long-running measurements: internally it uses ::QueryPerformanceCounter() which can overflow after only a few days: @@ -150,7 +151,7 @@ public: void setFraction(double fraction) { fraction_ = fraction; } //value between [0, 1] private: - std::pair<double, double> getRangeX() const override { return std::make_pair(0, 1); } + std::pair<double, double> getRangeX() const override { return { 0, 1 }; } std::vector<CurvePoint> getPoints(double minX, double maxX, const wxSize& areaSizePx) const override { @@ -172,7 +173,7 @@ private: class CurveDataProgressSeparatorLine : public CurveData { - std::pair<double, double> getRangeX() const override { return std::make_pair(0, 1); } + std::pair<double, double> getRangeX() const override { return { 0, 1 }; } std::vector<CurvePoint> getPoints(double minX, double maxX, const wxSize& areaSizePx) const override { @@ -191,29 +192,36 @@ class CompareProgressDialog::Impl : public CompareProgressDlgGenerated public: Impl(wxFrame& parentWindow); - void init(const Statistics& syncStat); //constructor/destructor semantics, but underlying Window is reused - void teardown(); // + void init(const Statistics& syncStat, bool ignoreErrors); //constructor/destructor semantics, but underlying Window is reused + void teardown(); // void initNewPhase(); - void updateStatusPanelNow(); + void updateProgressGui(); + + bool getOptionIgnoreErrors() const { return m_checkBoxIgnoreErrors->GetValue(); } + void setOptionIgnoreErrors(bool ignoreErrors) { m_checkBoxIgnoreErrors->SetValue(ignoreErrors); updateStaticGui(); } private: + void OnToggleIgnoreErrors(wxCommandEvent& event) override { updateStaticGui(); } + + void updateStaticGui(); + wxFrame& parentWindow_; - wxString titleTextBackup; + wxString parentTitleBackup_; - StopWatch timeElapsed; - int64_t binCompStartMs = 0; //begin of binary comparison phase in [ms] + StopWatch timeElapsed_; + int64_t binCompStartMs_ = 0; //begin of binary comparison phase in [ms] const Statistics* syncStat_ = nullptr; //only bound while sync is running std::unique_ptr<Taskbar> taskbar_; - std::unique_ptr<PerfCheck> perf; //estimate remaining time + std::unique_ptr<PerfCheck> perf_; //estimate remaining time - int64_t timeLastSpeedEstimateMs = -1000000; //used for calculating intervals between showing and collecting perf samples + int64_t timeLastSpeedEstimateMs_ = -1000000; //used for calculating intervals between showing and collecting perf samples //initial value: just some big number - std::shared_ptr<CurveDataProgressBar> curveDataBytes{ std::make_shared<CurveDataProgressBar>(true /*drawTop*/) }; - std::shared_ptr<CurveDataProgressBar> curveDataItems{ std::make_shared<CurveDataProgressBar>(false /*drawTop*/) }; + std::shared_ptr<CurveDataProgressBar> curveDataBytes_{ std::make_shared<CurveDataProgressBar>(true /*drawTop*/) }; + std::shared_ptr<CurveDataProgressBar> curveDataItems_{ std::make_shared<CurveDataProgressBar>(false /*drawTop*/) }; }; @@ -226,16 +234,16 @@ CompareProgressDialog::Impl::Impl(wxFrame& parentWindow) : m_staticTextItemsFound ->Hide(); //init graph - m_panelGraphProgress->setAttributes(Graph2D::MainAttributes().setMinY(0).setMaxY(2). + m_panelProgressGraph->setAttributes(Graph2D::MainAttributes().setMinY(0).setMaxY(2). setLabelX(Graph2D::LABEL_X_NONE). setLabelY(Graph2D::LABEL_Y_NONE). setBackgroundColor(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)). setSelectionMode(Graph2D::SELECT_NONE)); - m_panelGraphProgress->addCurve(curveDataBytes, Graph2D::CurveAttributes().setLineWidth(1).fillPolygonArea(getColorBytes()).setColor(Graph2D::getBorderColor())); - m_panelGraphProgress->addCurve(curveDataItems, Graph2D::CurveAttributes().setLineWidth(1).fillPolygonArea(getColorItems()).setColor(Graph2D::getBorderColor())); + m_panelProgressGraph->addCurve(curveDataBytes_, Graph2D::CurveAttributes().setLineWidth(1).fillPolygonArea(getColorBytes()).setColor(Graph2D::getBorderColor())); + m_panelProgressGraph->addCurve(curveDataItems_, Graph2D::CurveAttributes().setLineWidth(1).fillPolygonArea(getColorItems()).setColor(Graph2D::getBorderColor())); - m_panelGraphProgress->addCurve(std::make_shared<CurveDataProgressSeparatorLine>(), Graph2D::CurveAttributes().setLineWidth(1).setColor(Graph2D::getBorderColor())); + m_panelProgressGraph->addCurve(std::make_shared<CurveDataProgressSeparatorLine>(), Graph2D::CurveAttributes().setLineWidth(1).setColor(Graph2D::getBorderColor())); m_panelStatistics->Layout(); Layout(); @@ -245,10 +253,10 @@ CompareProgressDialog::Impl::Impl(wxFrame& parentWindow) : } -void CompareProgressDialog::Impl::init(const Statistics& syncStat) +void CompareProgressDialog::Impl::init(const Statistics& syncStat, bool ignoreErrors) { syncStat_ = &syncStat; - titleTextBackup = parentWindow_.GetTitle(); + parentTitleBackup_ = parentWindow_.GetTitle(); try //try to get access to Windows 7/Ubuntu taskbar { @@ -257,11 +265,10 @@ void CompareProgressDialog::Impl::init(const Statistics& syncStat) catch (const TaskbarNotAvailable&) {} //initialize progress indicator - m_panelProgressLabel->Hide(); - m_panelGraphProgress->Hide(); + bSizerProgressGraph->Show(false); - perf.reset(); - timeElapsed.restart(); //measure total time + perf_.reset(); + timeElapsed_.restart(); //measure total time //initially hide status that's relevant for comparing bytewise only m_staticTextItemsFoundLabel->Show(); @@ -273,7 +280,11 @@ void CompareProgressDialog::Impl::init(const Statistics& syncStat) m_staticTextTimeRemainingLabel->Hide(); m_staticTextTimeRemaining ->Hide(); - updateStatusPanelNow(); + //allow changing a few options dynamically during sync + m_checkBoxIgnoreErrors->SetValue(ignoreErrors); + + updateStaticGui(); + updateProgressGui(); m_panelStatistics->Layout(); Layout(); @@ -283,7 +294,7 @@ void CompareProgressDialog::Impl::init(const Statistics& syncStat) void CompareProgressDialog::Impl::teardown() { syncStat_ = nullptr; - parentWindow_.SetTitle(titleTextBackup); + parentWindow_.SetTitle(parentTitleBackup_); taskbar_.reset(); } @@ -300,13 +311,12 @@ void CompareProgressDialog::Impl::initNewPhase() case ProcessCallback::PHASE_COMPARING_CONTENT: case ProcessCallback::PHASE_SYNCHRONIZING: //start to measure perf - perf = std::make_unique<PerfCheck>(WINDOW_REMAINING_TIME_MS, WINDOW_BYTES_PER_SEC); - timeLastSpeedEstimateMs = -1000000; //some big number + perf_ = std::make_unique<PerfCheck>(WINDOW_REMAINING_TIME_MS, WINDOW_BYTES_PER_SEC); + timeLastSpeedEstimateMs_ = -1000000; //some big number - binCompStartMs = timeElapsed.timeMs(); + binCompStartMs_ = timeElapsed_.timeMs(); - m_panelProgressLabel->Show(); - m_panelGraphProgress->Show(); + bSizerProgressGraph->Show(true); //show status for comparing bytewise m_staticTextItemsFoundLabel->Hide(); @@ -323,11 +333,17 @@ void CompareProgressDialog::Impl::initNewPhase() break; } - updateStatusPanelNow(); + updateProgressGui(); } -void CompareProgressDialog::Impl::updateStatusPanelNow() +void CompareProgressDialog::Impl::updateStaticGui() +{ + m_bitmapIgnoreErrors->SetBitmap(getResourceImage(m_checkBoxIgnoreErrors->GetValue() ? L"msg_error_medium_ignored" : L"msg_error_medium")); +} + + +void CompareProgressDialog::Impl::updateProgressGui() { if (!syncStat_) //no comparison running!! return; @@ -339,7 +355,7 @@ void CompareProgressDialog::Impl::updateStatusPanelNow() }; bool layoutChanged = false; //avoid screen flicker by calling layout() only if necessary - const int64_t timeNowMs = timeElapsed.timeMs(); + const int64_t timeNowMs = timeElapsed_.timeMs(); //status texts setText(*m_staticTextStatus, replaceCpy(syncStat_->currentStatusText(), L'\n', L' ')); //no layout update for status texts! @@ -350,10 +366,10 @@ void CompareProgressDialog::Impl::updateStatusPanelNow() case ProcessCallback::PHASE_NONE: case ProcessCallback::PHASE_SCANNING: { - const wxString& scannedObjects = toGuiString(syncStat_->getItemsCurrent(ProcessCallback::PHASE_SCANNING)); + const wxString& scannedObjects = formatNumber(syncStat_->getItemsCurrent(ProcessCallback::PHASE_SCANNING)); //dialog caption, taskbar - setTitle(scannedObjects + L" - " + getDialogPhaseText(syncStat_, false /*paused*/, SyncProgressDialog::RESULT_ABORTED)); + setTitle(scannedObjects + SPACED_DASH + getDialogPhaseText(syncStat_, false /*paused*/, SyncProgressDialog::RESULT_ABORTED)); if (taskbar_.get()) //support Windows 7 taskbar taskbar_->setStatus(Taskbar::STATUS_INDETERMINATE); @@ -376,7 +392,7 @@ void CompareProgressDialog::Impl::updateStatusPanelNow() const double fractionItems = itemsTotal == 0 ? 0 : 1.0 * itemsCurrent / itemsTotal; //dialog caption, taskbar - setTitle(fractionToString(fractionTotal) + wxT(" - ") + getDialogPhaseText(syncStat_, false /*paused*/, SyncProgressDialog::RESULT_ABORTED)); + setTitle(formatFraction(fractionTotal) + SPACED_DASH + getDialogPhaseText(syncStat_, false /*paused*/, SyncProgressDialog::RESULT_ABORTED)); if (taskbar_.get()) { taskbar_->setProgress(fractionTotal); @@ -384,36 +400,36 @@ void CompareProgressDialog::Impl::updateStatusPanelNow() } //progress indicator, shown for binary comparison only - curveDataBytes->setFraction(fractionBytes); - curveDataItems->setFraction(fractionItems); + curveDataBytes_->setFraction(fractionBytes); + curveDataItems_->setFraction(fractionItems); //remaining item and byte count - setText(*m_staticTextItemsRemaining, toGuiString(itemsTotal - itemsCurrent), &layoutChanged); - setText(*m_staticTextBytesRemaining, L"(" + filesizeToShortString(bytesTotal - bytesCurrent) + L")", &layoutChanged); + setText(*m_staticTextItemsRemaining, formatNumber(itemsTotal - itemsCurrent), &layoutChanged); + setText(*m_staticTextBytesRemaining, L"(" + formatFilesizeShort(bytesTotal - bytesCurrent) + L")", &layoutChanged); //remaining time and speed: only visible during binary comparison - assert(perf); - if (perf) - if (numeric::dist(timeLastSpeedEstimateMs, timeNowMs) >= 500) + assert(perf_); + if (perf_) + if (numeric::dist(timeLastSpeedEstimateMs_, timeNowMs) >= 500) { - timeLastSpeedEstimateMs = timeNowMs; + timeLastSpeedEstimateMs_ = timeNowMs; - if (numeric::dist(binCompStartMs, timeNowMs) >= 1000) //discard stats for first second: probably messy - perf->addSample(itemsCurrent, bytesCurrent, timeNowMs); + if (numeric::dist(binCompStartMs_, timeNowMs) >= 1000) //discard stats for first second: probably messy + perf_->addSample(itemsCurrent, bytesCurrent, timeNowMs); //current speed -> Win 7 copy uses 1 sec update interval instead - Opt<std::wstring> bps = perf->getBytesPerSecond(); - Opt<std::wstring> ips = perf->getItemsPerSecond(); - m_panelGraphProgress->setAttributes(m_panelGraphProgress->getAttributes().setCornerText(bps ? *bps : L"", Graph2D::CORNER_TOP_RIGHT)); - m_panelGraphProgress->setAttributes(m_panelGraphProgress->getAttributes().setCornerText(ips ? *ips : L"", Graph2D::CORNER_BOTTOM_RIGHT)); + Opt<std::wstring> bps = perf_->getBytesPerSecond(); + Opt<std::wstring> ips = perf_->getItemsPerSecond(); + m_panelProgressGraph->setAttributes(m_panelProgressGraph->getAttributes().setCornerText(bps ? *bps : L"", Graph2D::CORNER_TOP_LEFT)); + m_panelProgressGraph->setAttributes(m_panelProgressGraph->getAttributes().setCornerText(ips ? *ips : L"", Graph2D::CORNER_BOTTOM_LEFT)); //remaining time: display with relative error of 10% - based on samples taken every 0.5 sec only //-> call more often than once per second to correctly show last few seconds countdown, but don't call too often to avoid occasional jitter - Opt<double> remTimeSec = perf->getRemainingTimeSec(bytesTotal - bytesCurrent); - setText(*m_staticTextTimeRemaining, remTimeSec ? remainingTimeToString(*remTimeSec) : L"-", &layoutChanged); + Opt<double> remTimeSec = perf_->getRemainingTimeSec(bytesTotal - bytesCurrent); + setText(*m_staticTextTimeRemaining, remTimeSec ? formatRemainingTime(*remTimeSec) : L"-", &layoutChanged); } - m_panelGraphProgress->Refresh(); + m_panelProgressGraph->Refresh(); } break; } @@ -438,33 +454,15 @@ void CompareProgressDialog::Impl::updateStatusPanelNow() //######################################################################################## //redirect to implementation -CompareProgressDialog::CompareProgressDialog(wxFrame& parentWindow) : - pimpl(new Impl(parentWindow)) {} //owned by parentWindow +CompareProgressDialog::CompareProgressDialog(wxFrame& parentWindow) : pimpl_(new Impl(parentWindow)) {} //owned by parentWindow +wxWindow* CompareProgressDialog::getAsWindow() { return pimpl_; } +void CompareProgressDialog::init(const Statistics& syncStat, bool ignoreErrors) { pimpl_->init(syncStat, ignoreErrors); } +void CompareProgressDialog::teardown() { pimpl_->teardown(); } +void CompareProgressDialog::initNewPhase() { pimpl_->initNewPhase(); } +void CompareProgressDialog::updateGui() { pimpl_->updateProgressGui(); } +bool CompareProgressDialog::getOptionIgnoreErrors() const { return pimpl_->getOptionIgnoreErrors(); } +void CompareProgressDialog::setOptionIgnoreErrors(bool ignoreErrors) { pimpl_->setOptionIgnoreErrors(ignoreErrors); } -wxWindow* CompareProgressDialog::getAsWindow() -{ - return pimpl; -} - -void CompareProgressDialog::init(const Statistics& syncStat) -{ - pimpl->init(syncStat); -} - -void CompareProgressDialog::teardown() -{ - pimpl->teardown(); -} - -void CompareProgressDialog::initNewPhase() -{ - pimpl->initNewPhase(); -} - -void CompareProgressDialog::updateStatusPanelNow() -{ - pimpl->updateStatusPanelNow(); -} //######################################################################################## namespace @@ -761,17 +759,21 @@ private: class LogPanel : public LogPanelGenerated { public: - LogPanel(wxWindow* parent, const ErrorLog& log) : LogPanelGenerated(parent), msgView(std::make_shared<MessageView>(log)) + LogPanel(wxWindow* parent, const ErrorLog& log) : LogPanelGenerated(parent), msgView_(std::make_shared<MessageView>(log)) { const int errorCount = log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); const int warningCount = log.getItemCount(TYPE_WARNING); const int infoCount = log.getItemCount(TYPE_INFO); - auto initButton = [](ToggleButton& btn, const wchar_t* imgName, const wxString& tooltip) { btn.init(getImageButtonPressed(imgName), getImageButtonReleased(imgName)); btn.SetToolTip(tooltip); }; + auto initButton = [](ToggleButton& btn, const wchar_t* imgName, const wxString& tooltip) + { + btn.init(getImageButtonPressed(imgName), getImageButtonReleased(imgName)); + btn.SetToolTip(tooltip); + }; - initButton(*m_bpButtonErrors, L"msg_error", _("Error" ) + printNumber<std::wstring>(L" (%d)", errorCount )); - initButton(*m_bpButtonWarnings, L"msg_warning", _("Warning") + printNumber<std::wstring>(L" (%d)", warningCount)); - initButton(*m_bpButtonInfo, L"msg_info", _("Info" ) + printNumber<std::wstring>(L" (%d)", infoCount )); + initButton(*m_bpButtonErrors, L"msg_error", _("Error" ) + L" (" + formatNumber(errorCount) + L")"); + initButton(*m_bpButtonWarnings, L"msg_warning", _("Warning") + L" (" + formatNumber(warningCount) + L")"); + initButton(*m_bpButtonInfo, L"msg_info", _("Info" ) + L" (" + formatNumber(infoCount) + L")"); m_bpButtonErrors ->setActive(true); m_bpButtonWarnings->setActive(true); @@ -786,7 +788,7 @@ public: const int colMsgTimeWidth = GridDataMessages::getColumnTimeDefaultWidth(*m_gridMessages); const int colMsgCategoryWidth = GridDataMessages::getColumnCategoryDefaultWidth(); - m_gridMessages->setDataProvider(std::make_shared<GridDataMessages>(msgView)); + m_gridMessages->setDataProvider(std::make_shared<GridDataMessages>(msgView_)); m_gridMessages->setColumnLabelHeight(0); m_gridMessages->showRowLabel(false); m_gridMessages->setRowHeight(rowHeight); @@ -839,7 +841,7 @@ private: if (m_bpButtonInfo->isActive()) includedTypes |= TYPE_INFO; - msgView->updateView(includedTypes); //update MVC "model" + msgView_->updateView(includedTypes); //update MVC "model" m_gridMessages->Refresh(); //update MVC "view" } @@ -890,13 +892,13 @@ private: void onLocalKeyEvent(wxKeyEvent& event) //process key events without explicit menu entry :) { - if (processingKeyEventHandler) //avoid recursion + if (processingKeyEventHandler_) //avoid recursion { event.Skip(); return; } - processingKeyEventHandler = true; - ZEN_ON_SCOPE_EXIT(processingKeyEventHandler = false); + processingKeyEventHandler_ = true; + ZEN_ON_SCOPE_EXIT(processingKeyEventHandler_ = false); const int keyCode = event.GetKeyCode(); @@ -988,8 +990,8 @@ private: } } - std::shared_ptr<MessageView> msgView; //bound! - bool processingKeyEventHandler = false; + std::shared_ptr<MessageView> msgView_; //bound! + bool processingKeyEventHandler_ = false; }; //######################################################################################## @@ -1035,7 +1037,7 @@ private: if (samples_.empty()) return std::make_pair(0.0, 0.0); - double upperEndMs = std::max(samples_.rbegin()->first, lastSample_.first); + const double upperEndMs = std::max(samples_.rbegin()->first, lastSample_.first); /* //report some additional width by 5% elapsed time to make graph recalibrate before hitting the right border @@ -1044,8 +1046,8 @@ private: upperEndMs += 0.05 *(upperEndMs - samples.begin()->first); */ - return std::make_pair(samples_.begin()->first / 1000.0, //need not start with 0, e.g. "binary comparison, graph reset, followed by sync" - upperEndMs / 1000.0); + return { samples_.begin()->first / 1000.0, //need not start with 0, e.g. "binary comparison, graph reset, followed by sync" + upperEndMs / 1000.0}; } Opt<CurvePoint> getLessEq(double x) const override //x: seconds since begin @@ -1088,7 +1090,7 @@ private: }; -class CurveDataRectangleArea : public CurveData +class CurveDataTotalBlock : public CurveData { public: void setValue (double x, double y) { x_ = x; y_ = y; } @@ -1096,7 +1098,7 @@ public: double getValueX() const { return x_; } private: - std::pair<double, double> getRangeX() const override { return std::make_pair(x_, x_); } //conceptually just a vertical line! + std::pair<double, double> getRangeX() const override { return { x_, x_ }; } //conceptually just a vertical line! std::vector<CurvePoint> getPoints(double minX, double maxX, const wxSize& areaSizePx) const override { @@ -1113,6 +1115,32 @@ private: }; +class CurveDataProcessedBlock : public CurveData +{ +public: + void setValue(double x1, double x2, double y) { x1_ = x1; x2_ = x2; y_ = y; } + +private: + std::pair<double, double> getRangeX() const override { return { x1_, x2_ }; } + + std::vector<CurvePoint> getPoints(double minX, double maxX, const wxSize& areaSizePx) const override + { + return + { + { 0, y_ }, + { x1_, y_ }, + { x1_, 0 }, + { x1_, y_ }, + { x2_, y_ }, + }; + } + + double x1_ = 0; //time elapsed in seconds + double x2_ = 0; //total time (estimated) + double y_ = 0; //items/bytes processed +}; + + const double stretchDefaultBlockSize = 1.4; //enlarge block default size @@ -1136,7 +1164,7 @@ struct LabelFormatterBytes : public LabelFormatter return e * numeric::nearMatch(a, std::begin(steps), std::end(steps)); } - wxString formatText(double value, double optimalBlockSize) const override { return filesizeToShortString(static_cast<int64_t>(value)); } + wxString formatText(double value, double optimalBlockSize) const override { return formatFilesizeShort(static_cast<int64_t>(value)); } }; @@ -1154,7 +1182,7 @@ struct LabelFormatterItemCount : public LabelFormatter wxString formatText(double value, double optimalBlockSize) const override { - return toGuiString(numeric::round(value)); //not enough room for a "%x items" representation + return formatNumber(numeric::round(value)); //not enough room for a "%x items" representation } }; @@ -1215,8 +1243,8 @@ public: bool showProgress, const wxString& jobName, const Zstring& soundFileSyncComplete, - const Zstring& onCompletion, - std::vector<Zstring>& onCompletionHistory); + bool ignoreErrors, + PostSyncAction postSyncAction); ~SyncProgressDialogImpl() override; //call this in StatusUpdater derived class destructor at the LATEST(!) to prevent access to currentStatusUpdater @@ -1228,9 +1256,11 @@ public: void initNewPhase () override; void notifyProgressChange() override; - void updateGui () override { updateGuiInt(true /*allowYield*/); } + void updateGui () override { updateProgressGui(true /*allowYield*/); } - Zstring getExecWhenFinishedCommand() const override { return pnl_.m_comboBoxOnCompletion->getValue(); } + bool getOptionIgnoreErrors() const override { return pnl_.m_checkBoxIgnoreErrors->GetValue(); } + void setOptionIgnoreErrors(bool ignoreErrors) override { pnl_.m_checkBoxIgnoreErrors->SetValue(ignoreErrors); updateStaticGui(); } + PostSyncAction getOptionPostSyncAction() const override { return getEnumVal(enumPostSyncAction_, *pnl_.m_choicePostSyncAction); } void stopTimer() override //halt all internal counters! { @@ -1244,8 +1274,6 @@ public: } private: - void updateGuiInt(bool allowYield); - void OnKeyPressed(wxKeyEvent& event); void OnOkay (wxCommandEvent& event); void OnPause (wxCommandEvent& event); @@ -1253,11 +1281,14 @@ private: void OnClose (wxCloseEvent& event); void OnIconize(wxIconizeEvent& event); void OnMinimizeToTray(wxCommandEvent& event) { minimizeToTray(); } + void OnToggleIgnoreErrors(wxCommandEvent& event) { updateStaticGui(); } void minimizeToTray(); void resumeFromSystray(); - void updateDialogStatus(); + void updateStaticGui(); + void updateProgressGui(bool allowYield); + void setExternalStatus(const wxString& status, const wxString& progress); //progress may be empty! SyncProgressPanelGenerated& pnl_; //wxPanel containing the GUI controls of *this @@ -1285,16 +1316,18 @@ private: //help calculate total speed int64_t phaseStartMs_ = 0; //begin of current phase in [ms] - std::shared_ptr<CurveDataStatistics > curveDataBytes { std::make_shared<CurveDataStatistics>() }; - std::shared_ptr<CurveDataStatistics > curveDataItems { std::make_shared<CurveDataStatistics>() }; - std::shared_ptr<CurveDataRectangleArea> curveDataBytesCurrent{ std::make_shared<CurveDataRectangleArea>() }; - std::shared_ptr<CurveDataRectangleArea> curveDataItemsCurrent{ std::make_shared<CurveDataRectangleArea>() }; - std::shared_ptr<CurveDataRectangleArea> curveDataBytesTotal { std::make_shared<CurveDataRectangleArea>() }; - std::shared_ptr<CurveDataRectangleArea> curveDataItemsTotal { std::make_shared<CurveDataRectangleArea>() }; + std::shared_ptr<CurveDataStatistics > curveDataBytes_ { std::make_shared<CurveDataStatistics>() }; + std::shared_ptr<CurveDataStatistics > curveDataItems_ { std::make_shared<CurveDataStatistics>() }; + std::shared_ptr<CurveDataProcessedBlock> curveDataBytesCurrent_{ std::make_shared<CurveDataProcessedBlock>() }; + std::shared_ptr<CurveDataProcessedBlock> curveDataItemsCurrent_{ std::make_shared<CurveDataProcessedBlock>() }; + std::shared_ptr<CurveDataTotalBlock > curveDataBytesTotal_ { std::make_shared<CurveDataTotalBlock>() }; + std::shared_ptr<CurveDataTotalBlock > curveDataItemsTotal_ { std::make_shared<CurveDataTotalBlock>() }; - wxString parentFrameTitleBackup_; + wxString parentTitleBackup_; std::unique_ptr<FfsTrayIcon> trayIcon_; //optional: if filled all other windows should be hidden and conversely std::unique_ptr<Taskbar> taskbar_; + + EnumDescrList<PostSyncAction> enumPostSyncAction_; }; @@ -1308,8 +1341,8 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF bool showProgress, const wxString& jobName, const Zstring& soundFileSyncComplete, - const Zstring& onCompletion, - std::vector<Zstring>& onCompletionHistory) : + bool ignoreErrors, + PostSyncAction postSyncAction) : TopLevelDialog(parentFrame, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, style), //title is overwritten anyway in setExternalStatus() pnl_(*new SyncProgressPanelGenerated(this)), //ownership passed to "this" jobName_ (jobName), @@ -1337,6 +1370,7 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF pnl_.m_buttonPause->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnPause ), NULL, this); pnl_.m_buttonStop ->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnCancel), NULL, this); pnl_.m_bpButtonMinimizeToTray->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnMinimizeToTray), NULL, this); + pnl_.m_checkBoxIgnoreErrors->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnToggleIgnoreErrors), NULL, this); assert(pnl_.m_buttonClose->GetId() == wxID_OK); //we cannot use wxID_CLOSE else Esc key won't work: yet another wxWidgets bug?? @@ -1344,7 +1378,7 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF setRelativeFontSize(*pnl_.m_staticTextPhase, 1.5); if (parentFrame_) - parentFrameTitleBackup_ = parentFrame_->GetTitle(); //save old title (will be used as progress indicator) + parentTitleBackup_ = parentFrame_->GetTitle(); //save old title (will be used as progress indicator) //pnl.m_animCtrlSyncing->SetAnimation(getResourceAnimation(L"working")); //pnl.m_animCtrlSyncing->Play(); @@ -1384,14 +1418,14 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF setBackgroundColor(wxColor(208, 208, 208)). //light grey setSelectionMode(Graph2D::SELECT_NONE)); - pnl_.m_panelGraphBytes->setCurve(curveDataBytesTotal, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(*wxWHITE).setColor(wxColor(192, 192, 192))); //medium grey - pnl_.m_panelGraphItems->setCurve(curveDataItemsTotal, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(*wxWHITE).setColor(wxColor(192, 192, 192))); //medium grey + pnl_.m_panelGraphBytes->setCurve(curveDataBytesTotal_, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(*wxWHITE).setColor(wxColor(192, 192, 192))); //medium grey + pnl_.m_panelGraphItems->setCurve(curveDataItemsTotal_, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(*wxWHITE).setColor(wxColor(192, 192, 192))); //medium grey - pnl_.m_panelGraphBytes->addCurve(curveDataBytesCurrent, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(getColorBytesBackground()).setColor(getColorBytesBackgroundRim())); - pnl_.m_panelGraphItems->addCurve(curveDataItemsCurrent, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(getColorItemsBackground()).setColor(getColorItemsBackgroundRim())); + pnl_.m_panelGraphBytes->addCurve(curveDataBytesCurrent_, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(getColorBytesBackground()).setColor(getColorBytesBackgroundRim())); + pnl_.m_panelGraphItems->addCurve(curveDataItemsCurrent_, Graph2D::CurveAttributes().setLineWidth(1).fillCurveArea(getColorItemsBackground()).setColor(getColorItemsBackgroundRim())); - pnl_.m_panelGraphBytes->addCurve(curveDataBytes, Graph2D::CurveAttributes().setLineWidth(2).fillCurveArea(getColorBytes()).setColor(getColorBytesRim())); - pnl_.m_panelGraphItems->addCurve(curveDataItems, Graph2D::CurveAttributes().setLineWidth(2).fillCurveArea(getColorItems()).setColor(getColorItemsRim())); + pnl_.m_panelGraphBytes->addCurve(curveDataBytes_, Graph2D::CurveAttributes().setLineWidth(2).fillCurveArea(getColorBytes()).setColor(getColorBytesRim())); + pnl_.m_panelGraphItems->addCurve(curveDataItems_, Graph2D::CurveAttributes().setLineWidth(2).fillCurveArea(getColorItems()).setColor(getColorItemsRim())); //graph legend: auto generateSquareBitmap = [&](const wxColor& fillCol, const wxColor& borderCol) @@ -1408,11 +1442,18 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF pnl_.m_bitmapGraphKeyBytes->SetBitmap(generateSquareBitmap(getColorBytes(), getColorBytesRim())); pnl_.m_bitmapGraphKeyItems->SetBitmap(generateSquareBitmap(getColorItems(), getColorItemsRim())); - //allow changing the "on completion" command - pnl_.m_comboBoxOnCompletion->setHistory(onCompletionHistory, onCompletionHistory.size()); //-> we won't use addItemHistory() later - pnl_.m_comboBoxOnCompletion->setValue(onCompletion); + //allow changing a few options dynamically during sync + pnl_.m_checkBoxIgnoreErrors->SetValue(ignoreErrors); + + enumPostSyncAction_. + add(PostSyncAction::SUMMARY, _("Show summary")). + add(PostSyncAction::EXIT, replaceCpy(_("E&xit"), L"&", L"")). //reuse translation + add(PostSyncAction::SLEEP, _("Sleep")). + add(PostSyncAction::SHUTDOWN, _("Shut down")); + + setEnumVal(enumPostSyncAction_, *pnl_.m_choicePostSyncAction, postSyncAction); - updateDialogStatus(); //null-status will be shown while waiting for dir locks + updateStaticGui(); //null-status will be shown while waiting for dir locks this->GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() pnl_.Layout(); @@ -1424,7 +1465,7 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF pnl_.m_buttonStop->SetFocus(); //don't steal focus when starting in sys-tray! //clear gui flicker, remove dummy texts: window must be visible to make this work! - updateGuiInt(true /*allowYield*/); //at least on OS X a real Yield() is required to flush pending GUI updates; Update() is not enough + updateProgressGui(true /*allowYield*/); //at least on OS X a real Yield() is required to flush pending GUI updates; Update() is not enough } else minimizeToTray(); @@ -1436,7 +1477,7 @@ SyncProgressDialogImpl<TopLevelDialog>::~SyncProgressDialogImpl() { if (parentFrame_) { - parentFrame_->SetTitle(parentFrameTitleBackup_); //restore title text + parentFrame_->SetTitle(parentTitleBackup_); //restore title text //make sure main dialog is shown again if still "minimized to systray"! see SyncProgressDialog::closeWindowDirectly() parentFrame_->Show(); @@ -1478,15 +1519,15 @@ void SyncProgressDialogImpl<TopLevelDialog>::OnKeyPressed(wxKeyEvent& event) template <class TopLevelDialog> void SyncProgressDialogImpl<TopLevelDialog>::initNewPhase() { - updateDialogStatus(); //evaluates "syncStat_->currentPhase()" + updateStaticGui(); //evaluates "syncStat_->currentPhase()" //reset graphs (e.g. after binary comparison) - curveDataBytesCurrent->setValue(0, 0); - curveDataItemsCurrent->setValue(0, 0); - curveDataBytesTotal ->setValue(0, 0); - curveDataItemsTotal ->setValue(0, 0); - curveDataBytes ->clear(); - curveDataItems ->clear(); + curveDataBytesCurrent_->setValue(0, 0, 0); + curveDataItemsCurrent_->setValue(0, 0, 0); + curveDataBytesTotal_ ->setValue(0, 0); + curveDataItemsTotal_ ->setValue(0, 0); + curveDataBytes_ ->clear(); + curveDataItems_ ->clear(); notifyProgressChange(); //make sure graphs get initial values @@ -1496,7 +1537,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::initNewPhase() phaseStartMs_ = timeElapsed_.timeMs(); - updateGuiInt(false /*allowYield*/); + updateProgressGui(false /*allowYield*/); } @@ -1516,8 +1557,8 @@ void SyncProgressDialogImpl<TopLevelDialog>::notifyProgressChange() //noexcept! const int64_t bytesCurrent = syncStat_->getBytesCurrent(syncStat_->currentPhase()); const int itemsCurrent = syncStat_->getItemsCurrent(syncStat_->currentPhase()); - curveDataBytes->addRecord(timeElapsed_.timeMs(), bytesCurrent); - curveDataItems->addRecord(timeElapsed_.timeMs(), itemsCurrent); + curveDataBytes_->addRecord(timeElapsed_.timeMs(), bytesCurrent); + curveDataItems_->addRecord(timeElapsed_.timeMs(), itemsCurrent); } break; } @@ -1538,9 +1579,9 @@ void SyncProgressDialogImpl<TopLevelDialog>::setExternalStatus(const wxString& s systrayTooltip += L" " + progress; //window caption/taskbar; inverse order: progress, status, jobname - wxString title = progress.empty() ? status : progress + L" - " + status; + wxString title = progress.empty() ? status : progress + SPACED_DASH + status; if (!jobName_.empty()) - title += L" - \"" + jobName_ + L"\""; + title += wxString(SPACED_DASH) + L"\"" + jobName_ + L"\""; //systray tooltip, if window is minimized if (trayIcon_.get()) @@ -1558,7 +1599,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::setExternalStatus(const wxString& s template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield) +void SyncProgressDialogImpl<TopLevelDialog>::updateProgressGui(bool allowYield) { if (!syncStat_) //sync not running return; @@ -1574,7 +1615,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield) case ProcessCallback::PHASE_NONE: case ProcessCallback::PHASE_SCANNING: //dialog caption, taskbar, systray tooltip - setExternalStatus(getDialogPhaseText(syncStat_, paused_, finalResult_), toGuiString(syncStat_->getItemsCurrent(ProcessCallback::PHASE_SCANNING))); //status text may be "paused"! + setExternalStatus(getDialogPhaseText(syncStat_, paused_, finalResult_), formatNumber(syncStat_->getItemsCurrent(ProcessCallback::PHASE_SCANNING))); //status text may be "paused"! //progress indicators if (trayIcon_.get()) trayIcon_->setProgress(1); //100% = regular FFS logo @@ -1604,29 +1645,30 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield) //---------------------------------------------------------------------------------------------------- //dialog caption, taskbar, systray tooltip - setExternalStatus(getDialogPhaseText(syncStat_, paused_, finalResult_), fractionToString(fractionTotal)); //status text may be "paused"! + setExternalStatus(getDialogPhaseText(syncStat_, paused_, finalResult_), formatFraction(fractionTotal)); //status text may be "paused"! //progress indicators if (trayIcon_.get()) trayIcon_->setProgress(fractionTotal); if (taskbar_.get()) taskbar_->setProgress(fractionTotal); + const double timeTotalSecTentative = bytesTotal == bytesCurrent ? timeNowMs / 1000.0 : std::max(curveDataBytesTotal_->getValueX(), timeNowMs / 1000.0); + //constant line graph - curveDataBytesCurrent->setValue(timeNowMs / 1000.0, bytesCurrent); - curveDataItemsCurrent->setValue(timeNowMs / 1000.0, itemsCurrent); + curveDataBytesCurrent_->setValue(timeNowMs / 1000.0, timeTotalSecTentative, bytesCurrent); + curveDataItemsCurrent_->setValue(timeNowMs / 1000.0, timeTotalSecTentative, itemsCurrent); //tentatively update total time, may be improved on below: - const double timeTotalSecTentative = bytesTotal == bytesCurrent ? timeNowMs / 1000.0 : std::max(curveDataBytesTotal->getValueX(), timeNowMs / 1000.0); - curveDataBytesTotal->setValue(timeTotalSecTentative, bytesTotal); - curveDataItemsTotal->setValue(timeTotalSecTentative, itemsTotal); + curveDataBytesTotal_->setValue(timeTotalSecTentative, bytesTotal); + curveDataItemsTotal_->setValue(timeTotalSecTentative, itemsTotal); //even though notifyProgressChange() already set the latest data, let's add another sample to have all curves consider "timeNowMs" //no problem with adding too many records: CurveDataStatistics will remove duplicate entries! - curveDataBytes->addRecord(timeNowMs, bytesCurrent); - curveDataItems->addRecord(timeNowMs, itemsCurrent); + curveDataBytes_->addRecord(timeNowMs, bytesCurrent); + curveDataItems_->addRecord(timeNowMs, itemsCurrent); //remaining item and byte count - setText(*pnl_.m_staticTextItemsRemaining, toGuiString(itemsTotal - itemsCurrent), &layoutChanged); - setText(*pnl_.m_staticTextBytesRemaining, L"(" + filesizeToShortString(bytesTotal - bytesCurrent) + L")", &layoutChanged); + setText(*pnl_.m_staticTextItemsRemaining, formatNumber(itemsTotal - itemsCurrent), &layoutChanged); + setText(*pnl_.m_staticTextBytesRemaining, L"(" + formatFilesizeShort(bytesTotal - bytesCurrent) + L")", &layoutChanged); //it's possible data remaining becomes shortly negative if last file synced has ADS data and the bytesTotal was not yet corrected! //remaining time and speed @@ -1648,15 +1690,18 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield) //remaining time: display with relative error of 10% - based on samples taken every 0.5 sec only //-> call more often than once per second to correctly show last few seconds countdown, but don't call too often to avoid occasional jitter Opt<double> remTimeSec = perf_->getRemainingTimeSec(bytesTotal - bytesCurrent); - setText(*pnl_.m_staticTextTimeRemaining, remTimeSec ? remainingTimeToString(*remTimeSec) : L"-", &layoutChanged); + setText(*pnl_.m_staticTextTimeRemaining, remTimeSec ? formatRemainingTime(*remTimeSec) : L"-", &layoutChanged); //update estimated total time marker with precision of "10% remaining time" only to avoid needless jumping around: const double timeRemainingSec = remTimeSec ? *remTimeSec : 0; const double timeTotalSec = timeNowMs / 1000.0 + timeRemainingSec; - if (numeric::dist(curveDataBytesTotal->getValueX(), timeTotalSec) > 0.1 * timeRemainingSec) + if (numeric::dist(curveDataBytesTotal_->getValueX(), timeTotalSec) > 0.1 * timeRemainingSec) { - curveDataBytesTotal->setValueX(timeTotalSec); - curveDataItemsTotal->setValueX(timeTotalSec); + curveDataBytesTotal_->setValueX(timeTotalSec); + curveDataItemsTotal_->setValueX(timeTotalSec); + //don't forget to update these, too: + curveDataBytesCurrent_->setValue(timeNowMs / 1000.0, timeTotalSec, bytesCurrent); + curveDataItemsCurrent_->setValue(timeNowMs / 1000.0, timeTotalSec, itemsCurrent); } } @@ -1728,7 +1773,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield) template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::updateDialogStatus() //depends on "syncStat_, paused_, finalResult" +void SyncProgressDialogImpl<TopLevelDialog>::updateStaticGui() //depends on "syncStat_, paused_, finalResult" { auto setStatusBitmap = [&](const wchar_t* bmpName, const wxString& tooltip) { @@ -1832,6 +1877,9 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateDialogStatus() //depends on " if (syncStat_) //sync running pnl_.m_buttonPause->SetLabel(paused_ ? _("&Continue") : _("&Pause")); + + pnl_.m_bitmapIgnoreErrors->SetBitmap(getResourceImage(pnl_.m_checkBoxIgnoreErrors->GetValue() ? L"msg_error_medium_ignored" : L"msg_error_medium")); + pnl_.Layout(); this->Refresh(); //a few pixels below the status text need refreshing } @@ -1842,7 +1890,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::closeWindowDirectly() //this should { paused_ = false; //you never know? //ATTENTION: dialog may live a little longer, so watch callbacks! - //e.g. wxGTK calls OnIconize after wxWindow::Close() (better not ask why) and before physical destruction! => indirectly calls updateDialogStatus(), which reads syncStat_!!! + //e.g. wxGTK calls OnIconize after wxWindow::Close() (better not ask why) and before physical destruction! => indirectly calls updateStaticGui(), which reads syncStat_!!! syncStat_ = nullptr; abortCb_ = nullptr; //resumeFromSystray(); -> NO, instead ~SyncProgressDialogImpl() makes sure that main dialog is shown again! @@ -1863,7 +1911,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::processHasFinished(SyncResult resul //update numbers one last time (as if sync were still running) notifyProgressChange(); //make one last graph entry at the *current* time - updateGuiInt(false /*allowYield*/); + updateProgressGui(false /*allowYield*/); switch (syncStat_->currentPhase()) //no matter if paused or not { @@ -1885,7 +1933,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::processHasFinished(SyncResult resul //set overall speed (instead of current speed) const int64_t timeDelta = timeElapsed_.timeMs() - phaseStartMs_; //we need to consider "time within current phase" not total "timeElapsed"! - const wxString overallBytesPerSecond = timeDelta == 0 ? std::wstring() : filesizeToShortString(bytesCurrent * 1000 / timeDelta) + _("/sec"); + const wxString overallBytesPerSecond = timeDelta == 0 ? std::wstring() : formatFilesizeShort(bytesCurrent * 1000 / timeDelta) + _("/sec"); const wxString overallItemsPerSecond = timeDelta == 0 ? std::wstring() : replaceCpy(_("%x items/sec"), L"%x", formatThreeDigitPrecision(itemsCurrent * 1000.0 / timeDelta)); pnl_.m_panelGraphBytes->setAttributes(pnl_.m_panelGraphBytes->getAttributes().setCornerText(overallBytesPerSecond, Graph2D::CORNER_TOP_LEFT)); @@ -1893,8 +1941,8 @@ void SyncProgressDialogImpl<TopLevelDialog>::processHasFinished(SyncResult resul //show new element "items processed" pnl_.m_panelItemsProcessed->Show(); - pnl_.m_staticTextItemsProcessed->SetLabel(toGuiString(itemsCurrent)); - pnl_.m_staticTextBytesProcessed->SetLabel(L"(" + filesizeToShortString(bytesCurrent) + L")"); + pnl_.m_staticTextItemsProcessed->SetLabel(formatNumber(itemsCurrent)); + pnl_.m_staticTextBytesProcessed->SetLabel(L"(" + formatFilesizeShort(bytesCurrent) + L")"); //hide remaining elements... if (itemsCurrent == itemsTotal && //...if everything was processed successfully @@ -1911,7 +1959,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::processHasFinished(SyncResult resul abortCb_ = nullptr; //---------------------------------- - updateDialogStatus(); + updateStaticGui(); setExternalStatus(getDialogPhaseText(syncStat_, paused_, finalResult_), wxString()); resumeFromSystray(); //if in tray mode... @@ -1928,7 +1976,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::processHasFinished(SyncResult resul pnl_.m_buttonClose->SetFocus(); - pnl_.bSizerOnCompletion->Show(false); + pnl_.bSizerProgressFooter->Show(false); //set std order after button visibility was set setStandardButtonLayout(*pnl_.bSizerStdButtons, StdButtons().setAffirmative(pnl_.m_buttonClose)); @@ -1949,13 +1997,13 @@ void SyncProgressDialogImpl<TopLevelDialog>::processHasFinished(SyncResult resul assert(wasDetached); (void)wasDetached; pnl_.m_panelProgress->Reparent(pnl_.m_notebookResult); - pnl_.m_notebookResult->AddPage(pnl_.m_panelProgress, _("Progress"), true); + pnl_.m_notebookResult->AddPage(pnl_.m_panelProgress, _("Progress"), true /*bSelect*/); //2. log file const size_t posLog = 1; assert(pnl_.m_notebookResult->GetPageCount() == 1); LogPanel* logPanel = new LogPanel(pnl_.m_notebookResult, log); //owned by m_notebookResult - pnl_.m_notebookResult->AddPage(logPanel, _("Log"), false); + pnl_.m_notebookResult->AddPage(logPanel, _("Log"), false /*bSelect*/); //bSizerHoldStretch->Insert(0, logPanel, 1, wxEXPAND); //show log instead of graph if errors occurred! (not required for ignored warnings) @@ -2007,10 +2055,10 @@ template <class TopLevelDialog> void SyncProgressDialogImpl<TopLevelDialog>::OnCancel(wxCommandEvent& event) { paused_ = false; - updateDialogStatus(); //update status + pause button + updateStaticGui(); //update status + pause button if (abortCb_) - abortCb_->requestAbortion(); + abortCb_->userRequestAbort(); //no Layout() or UI-update here to avoid cascaded Yield()-call! } @@ -2019,7 +2067,7 @@ template <class TopLevelDialog> void SyncProgressDialogImpl<TopLevelDialog>::OnPause(wxCommandEvent& event) { paused_ = !paused_; - updateDialogStatus(); //update status + pause button + updateStaticGui(); //update status + pause button } @@ -2029,7 +2077,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::OnClose(wxCloseEvent& event) //this event handler may be called *during* sync, e.g. due to a system shutdown (Windows), anytime (OS X) //try to stop sync gracefully and cross fingers: if (abortCb_) - abortCb_->requestAbortion(); + abortCb_->userRequestAbort(); //Note: we must NOT veto dialog destruction, else we will cancel system shutdown if this dialog is application main window (like in batch mode) notifyWindowTerminate_(); //don't wait until delayed "Destroy()" finally calls destructor -> avoid calls to processHasFinished()/closeWindowDirectly() @@ -2085,7 +2133,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::minimizeToTray() trayIcon_ = std::make_unique<FfsTrayIcon>([this] { this->resumeFromSystray(); }); //FfsTrayIcon lifetime is a subset of "this"'s lifetime! //we may destroy FfsTrayIcon even while in the FfsTrayIcon callback!!!! - updateGuiInt(false /*allowYield*/); //set tray tooltip + progress: e.g. no updates while paused + updateProgressGui(false /*allowYield*/); //set tray tooltip + progress: e.g. no updates while paused this->Hide(); if (parentFrame_) @@ -2115,8 +2163,8 @@ void SyncProgressDialogImpl<TopLevelDialog>::resumeFromSystray() this->Raise(); this->SetFocus(); - updateDialogStatus(); //restore Windows 7 task bar status (e.g. required in pause mode) - updateGuiInt(false) /*allowYield*/; //restore Windows 7 task bar progress (e.g. required in pause mode) + updateStaticGui(); //restore Windows 7 task bar status (e.g. required in pause mode) + updateProgressGui(false /*allowYield*/); //restore Windows 7 task bar progress (e.g. required in pause mode) } } @@ -2130,8 +2178,8 @@ SyncProgressDialog* createProgressDialog(zen::AbortCallback& abortCb, bool showProgress, const wxString& jobName, const Zstring& soundFileSyncComplete, - const Zstring& onCompletion, - std::vector<Zstring>& onCompletionHistory) + bool ignoreErrors, + PostSyncAction postSyncAction) { if (parentWindow) //sync from GUI { @@ -2139,13 +2187,13 @@ SyncProgressDialog* createProgressDialog(zen::AbortCallback& abortCb, //https://groups.google.com/forum/#!topic/wx-users/J5SjjLaBOQE return new SyncProgressDialogImpl<wxDialog>(wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxMINIMIZE_BOX | wxRESIZE_BORDER, [&](wxDialog& progDlg) { return parentWindow; }, - abortCb, notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, soundFileSyncComplete, onCompletion, onCompletionHistory); + abortCb, notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, soundFileSyncComplete, ignoreErrors, postSyncAction); } else //FFS batch job { auto dlg = new SyncProgressDialogImpl<wxFrame>(wxDEFAULT_FRAME_STYLE, [](wxFrame& progDlg) { return &progDlg; }, - abortCb, notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, soundFileSyncComplete, onCompletion, onCompletionHistory); + abortCb, notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, soundFileSyncComplete, ignoreErrors, postSyncAction); //only top level windows should have an icon: dlg->SetIcon(getFfsIcon()); diff --git a/FreeFileSync/Source/ui/progress_indicator.h b/FreeFileSync/Source/ui/progress_indicator.h index 8fe84abf..f17116ba 100755 --- a/FreeFileSync/Source/ui/progress_indicator.h +++ b/FreeFileSync/Source/ui/progress_indicator.h @@ -12,6 +12,7 @@ #include <zen/zstring.h> #include <wx/frame.h> #include "../lib/status_handler.h" +#include "../lib/process_xml.h" class CompareProgressDialog @@ -21,16 +22,20 @@ public: wxWindow* getAsWindow(); //convenience! don't abuse! - void init(const zen::Statistics& syncStat); //begin of sync: make visible, set pointer to "syncStat", initialize all status values + void init(const zen::Statistics& syncStat, bool ignoreErrors); //begin of sync: make visible, set pointer to "syncStat", initialize all status values void teardown(); //end of sync: hide again, clear pointer to "syncStat" void initNewPhase(); //call after "StatusHandler::initNewPhase" - void updateStatusPanelNow(); + void updateGui(); + + //allow changing a few options dynamically during sync + bool getOptionIgnoreErrors() const; + void setOptionIgnoreErrors(bool ignoreError); private: class Impl; - Impl* const pimpl; + Impl* const pimpl_; }; @@ -58,7 +63,10 @@ struct SyncProgressDialog virtual void notifyProgressChange() = 0; //noexcept, required by graph! virtual void updateGui () = 0; //update GUI and process Window messages - virtual Zstring getExecWhenFinishedCommand() const = 0; //final value (after possible user modification) + //allow changing a few options dynamically during sync + virtual bool getOptionIgnoreErrors() const = 0; + virtual void setOptionIgnoreErrors(bool ignoreError) = 0; + virtual xmlAccess::PostSyncAction getOptionPostSyncAction() const = 0; virtual void stopTimer () = 0; //halt all internal timers! virtual void resumeTimer() = 0; // @@ -75,8 +83,8 @@ SyncProgressDialog* createProgressDialog(zen::AbortCallback& abortCb, bool showProgress, const wxString& jobName, const Zstring& soundFileSyncComplete, - const Zstring& onCompletion, - std::vector<Zstring>& onCompletionHistory); //changing parameter! + bool ignoreErrors, + xmlAccess::PostSyncAction postSyncAction); //DON'T delete the pointer! it will be deleted by the user clicking "OK/Cancel"/wxWindow::Destroy() after processHasFinished() or closeWindowDirectly() diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index 62ba774d..e63ff95c 100755 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -89,8 +89,7 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) //build information - wxString build = formatTime<std::wstring>(FORMAT_DATE, getCompileTime()); - build += L" - Unicode"; + wxString build = formatTime<std::wstring>(FORMAT_DATE, getCompileTime()) + SPACED_DASH + L"Unicode"; #ifndef wxUSE_UNICODE #error what is going on? #endif @@ -177,9 +176,9 @@ private: std::shared_ptr<FolderHistory> folderHistory_; //output-only parameters: - Zstring& lastUsedPathOut; - bool& keepRelPathsOut; - bool& overwriteIfExistsOut; + Zstring& lastUsedPathOut_; + bool& keepRelPathsOut_; + bool& overwriteIfExistsOut_; }; @@ -192,9 +191,9 @@ CopyToDialog::CopyToDialog(wxWindow* parent, bool& overwriteIfExists) : CopyToDlgGenerated(parent), folderHistory_(folderHistory), - lastUsedPathOut(lastUsedPath), - keepRelPathsOut(keepRelPaths), - overwriteIfExistsOut(overwriteIfExists) + lastUsedPathOut_(lastUsedPath), + keepRelPathsOut_(keepRelPaths), + overwriteIfExistsOut_(overwriteIfExists) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOK).setCancel(m_buttonCancel)); @@ -250,11 +249,11 @@ void CopyToDialog::OnOK(wxCommandEvent& event) } //------------------------------------------------------------- - lastUsedPathOut = targetFolder->getPath(); - keepRelPathsOut = m_checkBoxKeepRelPath->GetValue(); - overwriteIfExistsOut = m_checkBoxOverwriteIfExists->GetValue(); + lastUsedPathOut_ = targetFolder->getPath(); + keepRelPathsOut_ = m_checkBoxKeepRelPath->GetValue(); + overwriteIfExistsOut_ = m_checkBoxOverwriteIfExists->GetValue(); - folderHistory_->addItem(lastUsedPathOut); + folderHistory_->addItem(lastUsedPathOut_); EndModal(ReturnSmallDlg::BUTTON_OKAY); } @@ -412,7 +411,7 @@ private: void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } //output-only parameters: - bool& dontShowAgainOut; + bool& dontShowAgainOut_; }; @@ -421,7 +420,7 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent, const SyncStatistics& st, bool& dontShowAgain) : SyncConfirmationDlgGenerated(parent), - dontShowAgainOut(dontShowAgain) + dontShowAgainOut_(dontShowAgain) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonStartSync).setCancel(m_buttonCancel)); @@ -448,10 +447,10 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent, auto setIntValue = [&setValue](wxStaticText& txtControl, int value, wxStaticBitmap& bmpControl, const wchar_t* bmpName) { - setValue(txtControl, value == 0, toGuiString(value), bmpControl, bmpName); + setValue(txtControl, value == 0, formatNumber(value), bmpControl, bmpName); }; - setValue(*m_staticTextData, st.getBytesToProcess() == 0, filesizeToShortString(st.getBytesToProcess()), *m_bitmapData, L"data"); + setValue(*m_staticTextData, st.getBytesToProcess() == 0, formatFilesizeShort(st.getBytesToProcess()), *m_bitmapData, L"data"); setIntValue(*m_staticTextCreateLeft, st.createCount< LEFT_SIDE>(), *m_bitmapCreateLeft, L"so_create_left_small"); setIntValue(*m_staticTextUpdateLeft, st.updateCount< LEFT_SIDE>(), *m_bitmapUpdateLeft, L"so_update_left_small"); setIntValue(*m_staticTextDeleteLeft, st.deleteCount< LEFT_SIDE>(), *m_bitmapDeleteLeft, L"so_delete_left_small"); @@ -471,7 +470,7 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent, void SyncConfirmationDlg::OnStartSync(wxCommandEvent& event) { - dontShowAgainOut = m_checkBoxDontShowAgain->GetValue(); + dontShowAgainOut_ = m_checkBoxDontShowAgain->GetValue(); EndModal(ReturnSmallDlg::BUTTON_OKAY); } @@ -512,16 +511,16 @@ private: void setExtApp(const xmlAccess::ExternalApps& extApp); xmlAccess::ExternalApps getExtApp() const; - std::map<std::wstring, std::wstring> descriptionTransToEng; //"translated description" -> "english" mapping for external application config + std::map<std::wstring, std::wstring> descriptionTransToEng_; //"translated description" -> "english" mapping for external application config //output-only parameters: - xmlAccess::XmlGlobalSettings& globalSettingsOut; + xmlAccess::XmlGlobalSettings& globalSettingsOut_; }; OptionsDlg::OptionsDlg(wxWindow* parent, xmlAccess::XmlGlobalSettings& globalSettings) : OptionsDlgGenerated(parent), - globalSettingsOut(globalSettings) + globalSettingsOut_(globalSettings) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOkay).setCancel(m_buttonCancel)); @@ -602,14 +601,14 @@ void OptionsDlg::updateGui() void OptionsDlg::OnOkay(wxCommandEvent& event) { //write settings only when okay-button is pressed (except hidden dialog reset)! - globalSettingsOut.failSafeFileCopy = m_checkBoxFailSafe->GetValue(); - globalSettingsOut.copyLockedFiles = m_checkBoxCopyLocked->GetValue(); - globalSettingsOut.copyFilePermissions = m_checkBoxCopyPermissions->GetValue(); + globalSettingsOut_.failSafeFileCopy = m_checkBoxFailSafe->GetValue(); + globalSettingsOut_.copyLockedFiles = m_checkBoxCopyLocked->GetValue(); + globalSettingsOut_.copyFilePermissions = m_checkBoxCopyPermissions->GetValue(); - globalSettingsOut.automaticRetryCount = m_spinCtrlAutoRetryCount->GetValue(); - globalSettingsOut.automaticRetryDelay = m_spinCtrlAutoRetryDelay->GetValue(); + globalSettingsOut_.automaticRetryCount = m_spinCtrlAutoRetryCount->GetValue(); + globalSettingsOut_.automaticRetryDelay = m_spinCtrlAutoRetryDelay->GetValue(); - globalSettingsOut.gui.externelApplications = getExtApp(); + globalSettingsOut_.gui.externelApplications = getExtApp(); EndModal(ReturnSmallDlg::BUTTON_OKAY); } @@ -622,7 +621,7 @@ void OptionsDlg::OnResetDialogs(wxCommandEvent& event) _("&Show"))) { case ConfirmationButton::ACCEPT: - globalSettingsOut.optDialogs = xmlAccess::OptionalDialogs(); + globalSettingsOut_.optDialogs = xmlAccess::OptionalDialogs(); break; case ConfirmationButton::CANCEL: break; @@ -665,7 +664,7 @@ void OptionsDlg::setExtApp(const xmlAccess::ExternalApps& extApp) const std::wstring description = zen::translate(it->first); if (description != it->first) //remember english description to save in GlobalSettings.xml later rather than hard-code translation - descriptionTransToEng[description] = it->first; + descriptionTransToEng_[description] = it->first; m_gridCustomCommand->SetCellValue(row, 0, description); m_gridCustomCommand->SetCellValue(row, 1, utfTo<wxString>(it->second)); //commandline @@ -682,8 +681,8 @@ xmlAccess::ExternalApps OptionsDlg::getExtApp() const auto commandline = utfTo<Zstring> (m_gridCustomCommand->GetCellValue(i, 1)); //try to undo translation of description for GlobalSettings.xml - auto it = descriptionTransToEng.find(description); - if (it != descriptionTransToEng.end()) + auto it = descriptionTransToEng_.find(description); + if (it != descriptionTransToEng_.end()) description = it->second; if (!description.empty() || !commandline.empty()) @@ -748,15 +747,15 @@ private: } //output-only parameters: - time_t& timeFromOut; - time_t& timeToOut; + time_t& timeFromOut_; + time_t& timeToOut_; }; SelectTimespanDlg::SelectTimespanDlg(wxWindow* parent, time_t& timeFrom, time_t& timeTo) : SelectTimespanDlgGenerated(parent), - timeFromOut(timeFrom), - timeToOut(timeTo) + timeFromOut_(timeFrom), + timeToOut_(timeTo) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOkay).setCancel(m_buttonCancel)); @@ -799,8 +798,8 @@ void SelectTimespanDlg::OnOkay(wxCommandEvent& event) to += wxTimeSpan::Day(); to -= wxTimeSpan::Second(); //go back to end of previous day - timeFromOut = from.GetTicks(); - timeToOut = to .GetTicks(); + timeFromOut_ = from.GetTicks(); + timeToOut_ = to .GetTicks(); /* { @@ -932,7 +931,7 @@ private: { const double fraction = bytesTotal_ == 0 ? 0 : 1.0 * bytesCurrent_ / bytesTotal_; m_staticTextHeader->SetLabel(_("Downloading update...") + L" " + - numberTo<std::wstring>(numeric::round(fraction * 100)) + L"% (" + filesizeToShortString(bytesCurrent_) + L")"); + numberTo<std::wstring>(numeric::round(fraction * 100)) + L"% (" + formatFilesizeShort(bytesCurrent_) + L")"); m_gaugeProgress->SetValue(numeric::round(fraction * GAUGE_FULL_RANGE)); m_staticTextDetails->SetLabel(utfTo<std::wstring>(filePath_)); diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index 249f614d..0863232a 100755 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -16,7 +16,7 @@ #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> #include "gui_generated.h" -#include "on_completion_box.h" +#include "command_box.h" #include "folder_selector.h" #include "../file_hierarchy.h" #include "../lib/help_provider.h" @@ -29,6 +29,24 @@ using namespace xmlAccess; namespace { +struct MiscSyncConfig +{ + bool ignoreErrors = false; + Zstring postSyncCommand; + PostSyncCondition postSyncCondition = PostSyncCondition::COMPLETION; + std::vector<Zstring> commandHistory; +}; + + +struct GlobalSyncConfig +{ + CompConfig cmpConfig; + SyncConfig syncCfg; + FilterConfig filter; + MiscSyncConfig miscCfg; +}; + + class ConfigDialog : public ConfigDlgGenerated { public: @@ -37,7 +55,7 @@ public: int localPairIndexToShow, std::vector<LocalPairConfig>& folderPairConfig, GlobalSyncConfig& globalCfg, - size_t onCompletionHistoryMax); + size_t commandHistoryMax); private: void OnOkay (wxCommandEvent& event) override; @@ -128,10 +146,11 @@ private: void updateSyncGui(); + EnumDescrList<PostSyncCondition> enumPostSyncCondition_; + //----------------------------------------------------- - void OnErrorPopup (wxCommandEvent& event) override { onGuiError_ = ON_GUIERROR_POPUP; updateMiscGui(); } //parameter NOT owned by radio button - void OnErrorIgnore(wxCommandEvent& event) override { onGuiError_ = ON_GUIERROR_IGNORE; updateMiscGui(); } // + void OnToggleIgnoreErrors(wxCommandEvent& event) override { updateMiscGui(); } MiscSyncConfig getMiscSyncOptions() const; void setMiscSyncOptions(const MiscSyncConfig& miscCfg); @@ -141,7 +160,6 @@ private: //parameters with ownership NOT within GUI controls! DirectionConfig directionCfg_; DeletionPolicy handleDeletion_ = DeletionPolicy::RECYCLER; //use Recycler, delete permanently or move to user-defined location - OnGuiError onGuiError_ = ON_GUIERROR_POPUP; EnumDescrList<VersioningStyle> enumVersioningStyle_; FolderSelector versioningFolder_; @@ -162,7 +180,7 @@ private: int selectedPairIndexToShow_ = EMPTY_PAIR_INDEX_SELECTED; static const int EMPTY_PAIR_INDEX_SELECTED = -2; - const size_t onCompletionHistoryMax_; + const size_t commandHistoryMax_; }; //################################################################################################################# @@ -206,14 +224,14 @@ ConfigDialog::ConfigDialog(wxWindow* parent, int localPairIndexToShow, std::vector<LocalPairConfig>& folderPairConfig, GlobalSyncConfig& globalCfg, - size_t onCompletionHistoryMax) : + size_t commandHistoryMax) : ConfigDlgGenerated(parent), versioningFolder_(*m_panelVersioning, *m_buttonSelectVersioningFolder, *m_bpButtonSelectAltFolder, *m_versioningFolderPath, nullptr /*staticText*/, nullptr /*dropWindow2*/), globalCfgOut_(globalCfg), folderPairConfigOut_(folderPairConfig), globalCfg_(globalCfg), folderPairConfig_(folderPairConfig), - onCompletionHistoryMax_(onCompletionHistoryMax) + commandHistoryMax_(commandHistoryMax) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOkay).setCancel(m_buttonCancel)); @@ -305,6 +323,14 @@ ConfigDialog::ConfigDialog(wxWindow* parent, add(VersioningStyle::REPLACE, _("Replace"), _("Move files and replace if existing")). add(VersioningStyle::ADD_TIMESTAMP, _("Time stamp"), _("Append a time stamp to each file name")); + enumPostSyncCondition_. + add(PostSyncCondition::COMPLETION, _("On completion:")). + add(PostSyncCondition::ERRORS, _("On errors:")). + add(PostSyncCondition::SUCCESS, _("On success:")); + + m_comboBoxPostSyncCommand->SetHint(_("Example:") + L" systemctl poweroff"); + + //use spacer to keep dialog height stable, no matter if versioning options are visible //bSizerDelHandling->Add(0, m_panelVersioning->GetSize().GetHeight()); @@ -523,10 +549,9 @@ void ConfigDialog::updateCompGui() m_notebook->SetPageImage(static_cast<size_t>(SyncConfigPanel::COMPARISON), static_cast<int>(m_checkBoxUseLocalCmpOptions->GetValue() ? ConfigTypeImage::COMPARISON : ConfigTypeImage::COMPARISON_GREY)); - auto setBitmap = [&](wxStaticBitmap& bmpCtrl, bool active, const wxBitmap& bmp) + auto setBitmap = [&](wxStaticBitmap& bmpCtrl, const wxBitmap& bmp) { - if (active && - m_checkBoxUseLocalCmpOptions->GetValue()) //help wxWidgets a little to render inactive config state (need on Windows, NOT on Linux!) + if (m_checkBoxUseLocalCmpOptions->GetValue()) //help wxWidgets a little to render inactive config state (needed on Windows, NOT on Linux!) bmpCtrl.SetBitmap(bmp); else bmpCtrl.SetBitmap(greyScale(bmp)); @@ -537,23 +562,33 @@ void ConfigDialog::updateCompGui() m_toggleBtnBySize ->SetValue(false); m_toggleBtnByContent ->SetValue(false); - if (m_checkBoxUseLocalCmpOptions->GetValue()) //help wxWidgets a little to render inactive config state (need on Windows, NOT on Linux!) + if (m_checkBoxUseLocalCmpOptions->GetValue()) //help wxWidgets a little to render inactive config state (needed on Windows, NOT on Linux!) switch (localCmpVar_) { case CompareVariant::TIME_SIZE: m_toggleBtnByTimeSize->SetValue(true); - setBitmap(*m_bitmapCompVariant, true, getResourceImage(L"file-time")); break; case CompareVariant::CONTENT: m_toggleBtnByContent->SetValue(true); - setBitmap(*m_bitmapCompVariant, true, getResourceImage(L"file-content")); break; case CompareVariant::SIZE: m_toggleBtnBySize->SetValue(true); - setBitmap(*m_bitmapCompVariant, true, getResourceImage(L"file-size")); break; } + switch (localCmpVar_) //unconditionally update image, including "local options off" + { + case CompareVariant::TIME_SIZE: + setBitmap(*m_bitmapCompVariant, getResourceImage(L"file-time")); + break; + case CompareVariant::CONTENT: + setBitmap(*m_bitmapCompVariant, getResourceImage(L"file-content")); + break; + case CompareVariant::SIZE: + setBitmap(*m_bitmapCompVariant, getResourceImage(L"file-size")); + break; + } + //active variant description: setText(*m_staticTextCompVarDescription, getCompVariantDescription(localCmpVar_)); m_staticTextCompVarDescription->Wrap(400); //needs to be reapplied after SetLabel() @@ -885,7 +920,7 @@ void ConfigDialog::updateSyncGui() auto setBitmap = [&](wxStaticBitmap& bmpCtrl, const wxBitmap& bmp) { - if (m_checkBoxUseLocalSyncOptions->GetValue()) //help wxWidgets a little to render inactive config state (need on Windows, NOT on Linux!) + if (m_checkBoxUseLocalSyncOptions->GetValue()) //help wxWidgets a little to render inactive config state (needed on Windows, NOT on Linux!) bmpCtrl.SetBitmap(bmp); else bmpCtrl.SetBitmap(greyScale(bmp)); @@ -920,7 +955,7 @@ void ConfigDialog::updateSyncGui() m_toggleBtnUpdate->SetValue(false); m_toggleBtnCustom->SetValue(false); - if (m_checkBoxUseLocalSyncOptions->GetValue()) //help wxWidgets a little to render inactive config state (need on Windows, NOT on Linux!) + if (m_checkBoxUseLocalSyncOptions->GetValue()) //help wxWidgets a little to render inactive config state (needed on Windows, NOT on Linux!) switch (directionCfg_.var) { case DirectionConfig::TWO_WAY: @@ -941,27 +976,37 @@ void ConfigDialog::updateSyncGui() m_toggleBtnPermanent ->SetValue(false); m_toggleBtnVersioning->SetValue(false); - switch (handleDeletion_) + if (m_checkBoxUseLocalSyncOptions->GetValue()) //help wxWidgets a little to render inactive config state (needed on Windows, NOT on Linux!) + switch (handleDeletion_) //unconditionally update image, including "local options off" + { + case DeletionPolicy::RECYCLER: + m_toggleBtnRecycler->SetValue(true); + break; + case DeletionPolicy::PERMANENT: + m_toggleBtnPermanent->SetValue(true); + break; + case DeletionPolicy::VERSIONING: + m_toggleBtnVersioning->SetValue(true); + break; + } + + switch (handleDeletion_) //unconditionally update image, including "local options off" { case DeletionPolicy::RECYCLER: - m_toggleBtnRecycler->SetValue(true); setBitmap(*m_bitmapDeletionType, getResourceImage(L"delete_recycler")); setText(*m_staticTextDeletionTypeDescription, _("Back up deleted and overwritten files in the recycle bin")); break; case DeletionPolicy::PERMANENT: - m_toggleBtnPermanent->SetValue(true); setBitmap(*m_bitmapDeletionType, getResourceImage(L"delete_permanently")); setText(*m_staticTextDeletionTypeDescription, _("Delete or overwrite files permanently")); break; case DeletionPolicy::VERSIONING: - m_toggleBtnVersioning->SetValue(true); setBitmap(*m_bitmapDeletionType, getResourceImage(L"delete_versioning_small")); setText(*m_staticTextDeletionTypeDescription, _("Move files to a user-defined folder")); break; } //m_staticTextDeletionTypeDescription->Wrap(200); //needs to be reapplied after SetLabel() - const bool versioningSelected = handleDeletion_ == DeletionPolicy::VERSIONING; m_panelVersioning ->Show(versioningSelected); m_hyperlinkVersioning->Show(versioningSelected); @@ -998,18 +1043,20 @@ MiscSyncConfig ConfigDialog::getMiscSyncOptions() const assert(selectedPairIndexToShow_ == -1); MiscSyncConfig miscCfg; - miscCfg.handleError = onGuiError_; - miscCfg.onCompletionCommand = m_comboBoxOnCompletion->getValue(); - miscCfg.onCompletionHistory = m_comboBoxOnCompletion->getHistory(); + miscCfg.ignoreErrors = m_checkBoxIgnoreErrors->GetValue(); + miscCfg.postSyncCommand = m_comboBoxPostSyncCommand->getValue(); + miscCfg.postSyncCondition = getEnumVal(enumPostSyncCondition_, *m_choicePostSyncCondition), + miscCfg.commandHistory = m_comboBoxPostSyncCommand->getHistory(); return miscCfg; } void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg) { - onGuiError_ = miscCfg.handleError; - m_comboBoxOnCompletion->setValue(miscCfg.onCompletionCommand); - m_comboBoxOnCompletion->setHistory(miscCfg.onCompletionHistory, onCompletionHistoryMax_); + m_checkBoxIgnoreErrors->SetValue(miscCfg.ignoreErrors); + m_comboBoxPostSyncCommand->setValue(miscCfg.postSyncCommand); + setEnumVal(enumPostSyncCondition_, *m_choicePostSyncCondition, miscCfg.postSyncCondition), + m_comboBoxPostSyncCommand->setHistory(miscCfg.commandHistory, commandHistoryMax_); updateMiscGui(); } @@ -1017,15 +1064,9 @@ void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg) void ConfigDialog::updateMiscGui() { - switch (onGuiError_) - { - case ON_GUIERROR_IGNORE: - m_radioBtnIgnoreErrors->SetValue(true); - break; - case ON_GUIERROR_POPUP: - m_radioBtnPopupOnErrors->SetValue(true); - break; - } + const MiscSyncConfig activeCfg = getMiscSyncOptions(); + + m_bitmapIgnoreErrors->SetBitmap(getResourceImage(activeCfg.ignoreErrors ? L"msg_error_medium_ignored" : L"msg_error_medium")); } @@ -1102,7 +1143,7 @@ bool ConfigDialog::unselectFolderPairConfig() //------------------------------------------------------------- - m_comboBoxOnCompletion->addItemHistory(); //commit current "on completion" history item + m_comboBoxPostSyncCommand->addItemHistory(); //commit current "on completion" history item if (selectedPairIndexToShow_ < 0) { @@ -1148,28 +1189,30 @@ ReturnSyncConfig::ButtonPressed zen::showSyncConfigDlg(wxWindow* parent, SyncConfig& globalSyncCfg, FilterConfig& globalFilter, - xmlAccess::OnGuiError& handleError, - Zstring& onCompletionCommand, - std::vector<Zstring>& onCompletionHistory, + bool& ignoreErrors, + Zstring& postSyncCommand, + PostSyncCondition& postSyncCondition, + std::vector<Zstring>& commandHistory, - size_t onCompletionHistoryMax) + size_t commandHistoryMax) { GlobalSyncConfig globalCfg; globalCfg.cmpConfig = globalCmpConfig; globalCfg.syncCfg = globalSyncCfg; globalCfg.filter = globalFilter; - globalCfg.miscCfg.handleError = handleError; - globalCfg.miscCfg.onCompletionCommand = onCompletionCommand; - globalCfg.miscCfg.onCompletionHistory = onCompletionHistory; + globalCfg.miscCfg.ignoreErrors = ignoreErrors; + globalCfg.miscCfg.postSyncCommand = postSyncCommand; + globalCfg.miscCfg.postSyncCondition = postSyncCondition; + globalCfg.miscCfg.commandHistory = commandHistory; ConfigDialog syncDlg(parent, panelToShow, localPairIndexToShow, folderPairConfig, globalCfg, - onCompletionHistoryMax); - auto rv = static_cast<ReturnSyncConfig::ButtonPressed>(syncDlg.ShowModal()); + commandHistoryMax); + const auto rv = static_cast<ReturnSyncConfig::ButtonPressed>(syncDlg.ShowModal()); if (rv != ReturnSyncConfig::BUTTON_CANCEL) { @@ -1177,9 +1220,10 @@ ReturnSyncConfig::ButtonPressed zen::showSyncConfigDlg(wxWindow* parent, globalSyncCfg = globalCfg.syncCfg; globalFilter = globalCfg.filter; - handleError = globalCfg.miscCfg.handleError; - onCompletionCommand = globalCfg.miscCfg.onCompletionCommand; - onCompletionHistory = globalCfg.miscCfg.onCompletionHistory; + ignoreErrors = globalCfg.miscCfg.ignoreErrors; + postSyncCommand = globalCfg.miscCfg.postSyncCommand ; + postSyncCondition = globalCfg.miscCfg.postSyncCondition; + commandHistory = globalCfg.miscCfg.commandHistory; } return rv; diff --git a/FreeFileSync/Source/ui/sync_cfg.h b/FreeFileSync/Source/ui/sync_cfg.h index e4b9def8..fc7b07e5 100755 --- a/FreeFileSync/Source/ui/sync_cfg.h +++ b/FreeFileSync/Source/ui/sync_cfg.h @@ -38,23 +38,6 @@ struct LocalPairConfig }; -struct MiscSyncConfig -{ - xmlAccess::OnGuiError handleError = xmlAccess::ON_GUIERROR_POPUP; - Zstring onCompletionCommand; - std::vector<Zstring> onCompletionHistory; -}; - - -struct GlobalSyncConfig -{ - CompConfig cmpConfig; - SyncConfig syncCfg; - FilterConfig filter; - MiscSyncConfig miscCfg; -}; - - ReturnSyncConfig::ButtonPressed showSyncConfigDlg(wxWindow* parent, SyncConfigPanel panelToShow, int localPairIndexToShow, //< 0 to show global config @@ -65,11 +48,12 @@ ReturnSyncConfig::ButtonPressed showSyncConfigDlg(wxWindow* parent, SyncConfig& globalSyncCfg, FilterConfig& globalFilter, - xmlAccess::OnGuiError& handleError, - Zstring& onCompletionCommand, - std::vector<Zstring>& onCompletionHistory, + bool& ignoreErrors, + Zstring& postSyncCommand, + PostSyncCondition& postSyncCondition, + std::vector<Zstring>& commandHistory, - size_t onCompletionHistoryMax); + size_t commandHistoryMax); } #endif //SYNC_CFG_H_31289470134253425 diff --git a/FreeFileSync/Source/ui/tree_view.cpp b/FreeFileSync/Source/ui/tree_view.cpp index 6ccd58f0..e7c5a699 100755 --- a/FreeFileSync/Source/ui/tree_view.cpp +++ b/FreeFileSync/Source/ui/tree_view.cpp @@ -13,6 +13,7 @@ #include <zen/stl_tools.h> #include <zen/format_unit.h> #include <wx+/rtl.h> +#include <wx+/dc.h> #include <wx+/context_menu.h> #include <wx+/image_resources.h> #include "../lib/icon_buffer.h" @@ -23,7 +24,6 @@ using namespace zen; namespace { const int WIDTH_PERCENTAGE_BAR = 60; -const wchar_t* EN_DASH = L"\u2013"; } @@ -193,7 +193,7 @@ Zstring zen::getShortDisplayNameForFolderPair(const AbstractPath& itemPathL, con else if (AFS::isNullPath(itemPathR)) return getLastComponent(itemPathL); else - return getLastComponent(itemPathL) + Zstr(" ") + utfTo<Zstring>(EN_DASH) + Zstr(" ") + + return getLastComponent(itemPathL) + utfTo<Zstring>(SPACED_DASH) + getLastComponent(itemPathR); } @@ -795,7 +795,7 @@ private: return dirRight; else if (dirRight.empty()) return dirLeft; - return dirLeft + L" " + EN_DASH + L"\n" + dirRight; + return dirLeft + L" \u2013"/*en dash*/ + L"\n" + dirRight; } break; @@ -823,10 +823,10 @@ private: break; case ColumnTypeNavi::ITEM_COUNT: - return toGuiString(node->itemCount_); + return formatNumber(node->itemCount_); case ColumnTypeNavi::BYTES: - return filesizeToShortString(node->bytes_); + return formatFilesizeShort(node->bytes_); } } return std::wstring(); diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp index 598873e4..d1288951 100755 --- a/FreeFileSync/Source/ui/version_check.cpp +++ b/FreeFileSync/Source/ui/version_check.cpp @@ -119,7 +119,7 @@ void showUpdateAvailableDialog(wxWindow* parent, const std::string& onlineVersio switch (showConfirmationDialog(parent, DialogInfoType::INFO, PopupDialogCfg(). setIcon(getResourceImage(L"update_available")). setTitle(_("Check for Program Updates")). - setMainInstructions(_("A new version of FreeFileSync is available:") + L" " + utfTo<std::wstring>(onlineVersion) + L"\n" + _("Download now?")). + setMainInstructions(replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo<std::wstring>(onlineVersion)) + L"\n" + _("Download now?")). setDetailInstructions(updateDetailsMsg), _("&Download"))) { diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index fc23b8d3..75692b6d 100755 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace zen { -const char ffsVersion[] = "9.4"; //internal linkage! +const char ffsVersion[] = "9.5"; //internal linkage! const char FFS_VERSION_SEPARATOR = '.'; } diff --git a/wx+/choice_enum.h b/wx+/choice_enum.h index d564fef8..03048c43 100755 --- a/wx+/choice_enum.h +++ b/wx+/choice_enum.h @@ -19,9 +19,9 @@ Member variable: Constructor code: enumDescrMap. - add(ON_ERROR_POPUP , "Show pop-up" , "Show pop-up on errors or warnings"). <- add localization - add(ON_ERROR_IGNORE, "Ignore errors" , "Hide all error and warning messages"). - add(ON_ERROR_EXIT , "Exit instantly", "Abort synchronization immediately"); + add(ON_ERROR_POPUP, "Show pop-up", "Show pop-up on errors or warnings"). <- add localization + add(ON_ERROR_IGNORE, "Ignore errors", "Hide all error and warning messages"). + add(ON_ERROR_EXIT, "Exit instantly", "Abort synchronization immediately"); Set enum value: setEnumVal(enumDescrMap, *m_choiceHandleError, value); @@ -31,10 +31,13 @@ class BufferedPaintDC */ - - - - +inline +void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) +{ + wxDCPenChanger dummy (dc, col); + wxDCBrushChanger dummy2(dc, col); + dc.DrawRectangle(rect); +} @@ -49,10 +52,10 @@ public: auto it = refDcToAreaMap().find(&dc); if (it != refDcToAreaMap().end()) { - oldRect = it->second; + oldRect_ = it->second; wxRect tmp = r; - tmp.Intersect(*oldRect); //better safe than sorry + tmp.Intersect(*oldRect_); //better safe than sorry dc_.SetClippingRegion(tmp); // it->second = tmp; } @@ -66,10 +69,10 @@ public: ~RecursiveDcClipper() { dc_.DestroyClippingRegion(); - if (oldRect) + if (oldRect_) { - dc_.SetClippingRegion(*oldRect); - refDcToAreaMap()[&dc_] = *oldRect; + dc_.SetClippingRegion(*oldRect_); + refDcToAreaMap()[&dc_] = *oldRect_; } else refDcToAreaMap().erase(&dc_); @@ -79,7 +82,7 @@ private: //associate "active" clipping area with each DC static std::unordered_map<wxDC*, wxRect>& refDcToAreaMap() { static std::unordered_map<wxDC*, wxRect> clippingAreas; return clippingAreas; } - Opt<wxRect> oldRect; + Opt<wxRect> oldRect_; wxDC& dc_; }; @@ -95,7 +98,7 @@ struct BufferedPaintDC : public wxPaintDC { BufferedPaintDC(wxWindow& wnd, Opt<w class BufferedPaintDC : public wxMemoryDC { public: - BufferedPaintDC(wxWindow& wnd, Opt<wxBitmap>& buffer) : buffer_(buffer), paintDc(&wnd) + BufferedPaintDC(wxWindow& wnd, Opt<wxBitmap>& buffer) : buffer_(buffer), paintDc_(&wnd) { const wxSize clientSize = wnd.GetClientSize(); if (clientSize.GetWidth() > 0 && clientSize.GetHeight() > 0) //wxBitmap asserts this!! width may be 0; test case "Grid::CornerWin": compare both sides, then change config @@ -105,7 +108,7 @@ public: SelectObject(*buffer); - if (paintDc.IsOk() && paintDc.GetLayoutDirection() == wxLayout_RightToLeft) + if (paintDc_.IsOk() && paintDc_.GetLayoutDirection() == wxLayout_RightToLeft) SetLayoutDirection(wxLayout_RightToLeft); } else @@ -118,18 +121,18 @@ public: { if (GetLayoutDirection() == wxLayout_RightToLeft) { - paintDc.SetLayoutDirection(wxLayout_LeftToRight); //workaround bug in wxDC::Blit() + paintDc_.SetLayoutDirection(wxLayout_LeftToRight); //workaround bug in wxDC::Blit() SetLayoutDirection(wxLayout_LeftToRight); // } const wxPoint origin = GetDeviceOrigin(); - paintDc.Blit(0, 0, buffer_->GetWidth(), buffer_->GetHeight(), this, -origin.x, -origin.y); + paintDc_.Blit(0, 0, buffer_->GetWidth(), buffer_->GetHeight(), this, -origin.x, -origin.y); } } private: Opt<wxBitmap>& buffer_; - wxPaintDC paintDc; + wxPaintDC paintDc_; }; #endif } diff --git a/wx+/graph.cpp b/wx+/graph.cpp index 78399c3f..132361b3 100755 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -200,13 +200,12 @@ void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const Conver } -void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Graph2D::PosCorner pos) +void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Graph2D::PosCorner pos, const wxColor& backgroundColor) { if (txt.empty()) return; const int borderX = 5; const int borderY = 2; //it looks like wxDC::GetMultiLineTextExtent() precisely returns width, but too large a height: maybe they consider "text row height"? - wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels wxSize txtExtent = dc.GetMultiLineTextExtent(txt); txtExtent.x += 2 * borderX; txtExtent.y += 2 * borderY; @@ -227,6 +226,13 @@ void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Grap drawPos.y += graphArea.height - txtExtent.GetHeight(); break; } + + { + //add text shadow to improve readability: + wxDCTextColourChanger dummy(dc, backgroundColor); + dc.DrawText(txt, drawPos + wxPoint(borderX + 1, borderY + 1)); + } + wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); dc.DrawText(txt, drawPos + wxPoint(borderX, borderY)); } @@ -463,26 +469,26 @@ Graph2D::Graph2D(wxWindow* parent, void Graph2D::onPaintEvent(wxPaintEvent& event) { //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)! - BufferedPaintDC dc(*this, doubleBuffer); + BufferedPaintDC dc(*this, doubleBuffer_); render(dc); } void Graph2D::OnMouseLeftDown(wxMouseEvent& event) { - activeSel = std::make_unique<MouseSelection>(*this, event.GetPosition()); + activeSel_ = std::make_unique<MouseSelection>(*this, event.GetPosition()); if (!event.ControlDown()) - oldSel.clear(); + oldSel_.clear(); Refresh(); } void Graph2D::OnMouseMovement(wxMouseEvent& event) { - if (activeSel.get()) + if (activeSel_.get()) { - activeSel->refCurrentPos() = event.GetPosition(); //corresponding activeSel->refSelection() is updated in Graph2D::render() + activeSel_->refCurrentPos() = event.GetPosition(); //corresponding activeSel->refSelection() is updated in Graph2D::render() Refresh(); } } @@ -490,18 +496,18 @@ void Graph2D::OnMouseMovement(wxMouseEvent& event) void Graph2D::OnMouseLeftUp(wxMouseEvent& event) { - if (activeSel.get()) + if (activeSel_.get()) { - if (activeSel->getStartPos() != activeSel->refCurrentPos()) //if it's just a single mouse click: discard selection + if (activeSel_->getStartPos() != activeSel_->refCurrentPos()) //if it's just a single mouse click: discard selection { - GraphSelectEvent selEvent(activeSel->refSelection()); //fire off GraphSelectEvent + GraphSelectEvent selEvent(activeSel_->refSelection()); //fire off GraphSelectEvent if (wxEvtHandler* handler = GetEventHandler()) handler->AddPendingEvent(selEvent); - oldSel.push_back(activeSel->refSelection()); //commit selection + oldSel_.push_back(activeSel_->refSelection()); //commit selection } - activeSel.reset(); + activeSel_.reset(); Refresh(); } } @@ -509,7 +515,7 @@ void Graph2D::OnMouseLeftUp(wxMouseEvent& event) void Graph2D::OnMouseCaptureLost(wxMouseCaptureLostEvent& event) { - activeSel.reset(); + activeSel_.reset(); Refresh(); } @@ -536,18 +542,13 @@ void Graph2D::render(wxDC& dc) const using namespace numeric; //set label font right at the start so that it is considered by wxDC::GetTextExtent() below! - dc.SetFont(labelFont); + dc.SetFont(labelFont_); const wxRect clientRect = GetClientRect(); //DON'T use wxDC::GetSize()! DC may be larger than visible area! - { - //clear complete client area; set label background color - const wxColor backCol = GetBackgroundColour(); //user-configurable! + + clearArea(dc, clientRect, GetBackgroundColour() /*user-configurable!*/); //wxPanel::GetClassDefaultAttributes().colBg : //wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); - wxDCPenChanger dummy (dc, backCol); - wxDCBrushChanger dummy2(dc, backCol); - dc.DrawRectangle(clientRect); - } /* ----------------------- @@ -560,28 +561,28 @@ void Graph2D::render(wxDC& dc) const int xLabelPosY = clientRect.y; int yLabelPosX = clientRect.x; - switch (attr.labelposX) + switch (attr_.labelposX) { case LABEL_X_TOP: - graphArea.y += attr.xLabelHeight; - graphArea.height -= attr.xLabelHeight; + graphArea.y += attr_.xLabelHeight; + graphArea.height -= attr_.xLabelHeight; break; case LABEL_X_BOTTOM: - xLabelPosY += clientRect.height - attr.xLabelHeight; - graphArea.height -= attr.xLabelHeight; + xLabelPosY += clientRect.height - attr_.xLabelHeight; + graphArea.height -= attr_.xLabelHeight; break; case LABEL_X_NONE: break; } - switch (attr.labelposY) + switch (attr_.labelposY) { case LABEL_Y_LEFT: - graphArea.x += attr.yLabelWidth; - graphArea.width -= attr.yLabelWidth; + graphArea.x += attr_.yLabelWidth; + graphArea.width -= attr_.yLabelWidth; break; case LABEL_Y_RIGHT: - yLabelPosX += clientRect.width - attr.yLabelWidth; - graphArea.width -= attr.yLabelWidth; + yLabelPosX += clientRect.width - attr_.yLabelWidth; + graphArea.width -= attr_.yLabelWidth; break; case LABEL_Y_NONE: break; @@ -590,7 +591,7 @@ void Graph2D::render(wxDC& dc) const { //paint graph background (excluding label area) wxDCPenChanger dummy (dc, getBorderColor()); - wxDCBrushChanger dummy2(dc, attr.backgroundColor); + wxDCBrushChanger dummy2(dc, attr_.backgroundColor); //accessibility: consider system text and background colors; small drawback: color of graphs is NOT connected to the background! => responsibility of client to use correct colors dc.DrawRectangle(graphArea); @@ -598,13 +599,13 @@ void Graph2D::render(wxDC& dc) const } //set label areas respecting graph area border! - const wxRect xLabelArea(graphArea.x, xLabelPosY, graphArea.width, attr.xLabelHeight); - const wxRect yLabelArea(yLabelPosX, graphArea.y, attr.yLabelWidth, graphArea.height); + const wxRect xLabelArea(graphArea.x, xLabelPosY, graphArea.width, attr_.xLabelHeight); + const wxRect yLabelArea(yLabelPosX, graphArea.y, attr_.yLabelWidth, graphArea.height); const wxPoint graphAreaOrigin = graphArea.GetTopLeft(); //detect x value range - double minX = attr.minXauto ? std::numeric_limits<double>::infinity() : attr.minX; //automatic: ensure values are initialized by first curve - double maxX = attr.maxXauto ? -std::numeric_limits<double>::infinity() : attr.maxX; // + double minX = attr_.minXauto ? std::numeric_limits<double>::infinity() : attr_.minX; //automatic: ensure values are initialized by first curve + double maxX = attr_.maxXauto ? -std::numeric_limits<double>::infinity() : attr_.maxX; // for (auto it = curves_.begin(); it != curves_.end(); ++it) if (const CurveData* curve = it->first.get()) { @@ -612,9 +613,9 @@ void Graph2D::render(wxDC& dc) const assert(rangeX.first <= rangeX.second + 1.0e-9); //GCC fucks up badly when comparing two *binary identical* doubles and finds "begin > end" with diff of 1e-18 - if (attr.minXauto) + if (attr_.minXauto) minX = std::min(minX, rangeX.first); - if (attr.maxXauto) + if (attr_.maxXauto) maxX = std::max(maxX, rangeX.second); } @@ -624,15 +625,15 @@ void Graph2D::render(wxDC& dc) const { int blockCountX = 0; //enlarge minX, maxX to a multiple of a "useful" block size - if (attr.labelposX != LABEL_X_NONE && attr.labelFmtX.get()) + if (attr_.labelposX != LABEL_X_NONE && attr_.labelFmtX.get()) blockCountX = widenRange(minX, maxX, //in/out graphArea.width, minimalBlockSizePx.GetWidth() * 7, - *attr.labelFmtX); + *attr_.labelFmtX); //get raw values + detect y value range - double minY = attr.minYauto ? std::numeric_limits<double>::infinity() : attr.minY; //automatic: ensure values are initialized by first curve - double maxY = attr.maxYauto ? -std::numeric_limits<double>::infinity() : attr.maxY; // + double minY = attr_.minYauto ? std::numeric_limits<double>::infinity() : attr_.minY; //automatic: ensure values are initialized by first curve + double maxY = attr_.maxYauto ? -std::numeric_limits<double>::infinity() : attr_.maxY; // std::vector<std::vector<CurvePoint>> curvePoints(curves_.size()); std::vector<std::vector<char>> oobMarker (curves_.size()); //effectively a std::vector<bool> marking points that start an out-of-bounds line @@ -651,12 +652,12 @@ void Graph2D::render(wxDC& dc) const const bool doPolygonCut = curves_[index].second.fillMode == CurveAttributes::FILL_POLYGON; //impacts auto minY/maxY!! cutPointsOutsideX(points, marker, minX, maxX, doPolygonCut); - if (attr.minYauto || attr.maxYauto) + if (attr_.minYauto || attr_.maxYauto) { auto itPair = std::minmax_element(points.begin(), points.end(), [](const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.y < rhs.y; }); - if (attr.minYauto) + if (attr_.minYauto) minY = std::min(minY, itPair.first->y); - if (attr.maxYauto) + if (attr_.maxYauto) maxY = std::max(maxY, itPair.second->y); } } @@ -666,11 +667,11 @@ void Graph2D::render(wxDC& dc) const { int blockCountY = 0; //enlarge minY, maxY to a multiple of a "useful" block size - if (attr.labelposY != LABEL_Y_NONE && attr.labelFmtY.get()) + if (attr_.labelposY != LABEL_Y_NONE && attr_.labelFmtY.get()) blockCountY = widenRange(minY, maxY, //in/out graphArea.height, minimalBlockSizePx.GetHeight() * 3, - *attr.labelFmtY); + *attr_.labelFmtY); if (graphArea.width <= 1 || graphArea.height <= 1) return; const ConvertCoord cvrtX(minX, maxX, graphArea.width - 1); //map [minX, maxX] to [0, pixelWidth - 1] @@ -707,7 +708,7 @@ void Graph2D::render(wxDC& dc) const } //update active mouse selection - if (activeSel.get() && graphArea.width > 0 && graphArea.height > 0) + if (activeSel_.get() && graphArea.width > 0 && graphArea.height > 0) { auto widen = [](double* low, double* high) { @@ -717,8 +718,8 @@ void Graph2D::render(wxDC& dc) const *high += 0.5; }; - const wxPoint screenStart = activeSel->getStartPos() - graphAreaOrigin; //make relative to graphArea - const wxPoint screenCurrent = activeSel->refCurrentPos() - graphAreaOrigin; + const wxPoint screenStart = activeSel_->getStartPos() - graphAreaOrigin; //make relative to graphArea + const wxPoint screenCurrent = activeSel_->refCurrentPos() - graphAreaOrigin; //normalize positions: a mouse selection is symmetric and *not* an half-open range! double screenFromX = clampCpy(screenStart .x, 0, graphArea.width - 1); @@ -729,10 +730,10 @@ void Graph2D::render(wxDC& dc) const widen(&screenFromY, &screenToY); //save current selection as "double" coordinates - activeSel->refSelection().from = CurvePoint(cvrtX.screenToReal(screenFromX), + activeSel_->refSelection().from = CurvePoint(cvrtX.screenToReal(screenFromX), cvrtY.screenToReal(screenFromY)); - activeSel->refSelection().to = CurvePoint(cvrtX.screenToReal(screenToX), + activeSel_->refSelection().to = CurvePoint(cvrtX.screenToReal(screenToX), cvrtY.screenToReal(screenToY)); } @@ -751,9 +752,9 @@ void Graph2D::render(wxDC& dc) const } //2. draw all currently set mouse selections (including active selection) - std::vector<SelectionBlock> allSelections = oldSel; - if (activeSel) - allSelections.push_back(activeSel->refSelection()); + std::vector<SelectionBlock> allSelections = oldSel_; + if (activeSel_) + allSelections.push_back(activeSel_->refSelection()); { //alpha channel not supported on wxMSW, so draw selection before curves wxDCBrushChanger dummy(dc, wxColor(168, 202, 236)); //light blue @@ -788,7 +789,7 @@ void Graph2D::render(wxDC& dc) const numeric::round(screenFromY)) + graphAreaOrigin; const wxPoint pixelTo = wxPoint(numeric::round(screenToX), numeric::round(screenToY)) + graphAreaOrigin; - switch (attr.mouseSelMode) + switch (attr_.mouseSelMode) { case SELECT_NONE: break; @@ -806,8 +807,8 @@ void Graph2D::render(wxDC& dc) const } //3. draw labels and background grid - drawXLabel(dc, minX, maxX, blockCountX, cvrtX, graphArea, xLabelArea, *attr.labelFmtX); - drawYLabel(dc, minY, maxY, blockCountY, cvrtY, graphArea, yLabelArea, *attr.labelFmtY); + drawXLabel(dc, minX, maxX, blockCountX, cvrtX, graphArea, xLabelArea, *attr_.labelFmtX); + drawYLabel(dc, minY, maxY, blockCountY, cvrtY, graphArea, yLabelArea, *attr_.labelFmtY); //4. finally draw curves { @@ -843,8 +844,8 @@ void Graph2D::render(wxDC& dc) const } //5. draw corner texts - for (const auto& ct : attr.cornerTexts) - drawCornerText(dc, graphArea, ct.second, ct.first); + for (const auto& ct : attr_.cornerTexts) + drawCornerText(dc, graphArea, ct.second, ct.first, attr_.backgroundColor); } } } diff --git a/wx+/graph.h b/wx+/graph.h index 89475611..6007ca30 100755 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -27,8 +27,8 @@ Example: setLabelX(Graph2D::LABEL_X_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()). setLabelY(Graph2D::LABEL_Y_RIGHT, 60, std::make_shared<LabelFormatterBytes>())); //set graph data - std::shared_ptr<CurveData> curveDataBytes = ... - m_panelGraph->setCurve(curveDataBytes, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0))); + std::shared_ptr<CurveData> curveDataBytes_ = ... + m_panelGraph->setCurve(curveDataBytes_, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0))); */ struct CurvePoint @@ -78,7 +78,7 @@ struct ArrayCurveData : public SparseCurveData virtual size_t getSize () const = 0; private: - std::pair<double, double> getRangeX() const override { const size_t sz = getSize(); return std::make_pair(0.0, sz == 0 ? 0.0 : sz - 1.0); } + std::pair<double, double> getRangeX() const override { const size_t sz = getSize(); return { 0.0, sz == 0 ? 0.0 : sz - 1.0}; } Opt<CurvePoint> getLessEq(double x) const override { @@ -295,17 +295,17 @@ public: wxColor backgroundColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SelMode mouseSelMode = SELECT_RECTANGLE; }; - void setAttributes(const MainAttributes& newAttr) { attr = newAttr; Refresh(); } - MainAttributes getAttributes() const { return attr; } + void setAttributes(const MainAttributes& newAttr) { attr_ = newAttr; Refresh(); } + MainAttributes getAttributes() const { return attr_; } - std::vector<SelectionBlock> getSelections() const { return oldSel; } + std::vector<SelectionBlock> getSelections() const { return oldSel_; } void setSelections(const std::vector<SelectionBlock>& sel) { - oldSel = sel; - activeSel.reset(); + oldSel_ = sel; + activeSel_.reset(); Refresh(); } - void clearSelection() { oldSel.clear(); Refresh(); } + void clearSelection() { oldSel_.clear(); Refresh(); } private: void OnMouseLeftDown(wxMouseEvent& event); @@ -336,18 +336,18 @@ private: wxPoint posDragCurrent; SelectionBlock selBlock; }; - std::vector<SelectionBlock> oldSel; //applied selections - std::shared_ptr<MouseSelection> activeSel; //set during mouse selection + std::vector<SelectionBlock> oldSel_; //applied selections + std::shared_ptr<MouseSelection> activeSel_; //set during mouse selection - MainAttributes attr; //global attributes + MainAttributes attr_; //global attributes - Opt<wxBitmap> doubleBuffer; + Opt<wxBitmap> doubleBuffer_; using CurveList = std::vector<std::pair<std::shared_ptr<CurveData>, CurveAttributes>>; CurveList curves_; //perf!!! generating the font is *very* expensive! don't do this repeatedly in Graph2D::render()! - const wxFont labelFont { wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial" }; + const wxFont labelFont_ { wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial" }; }; } diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 1d67d8ad..6652ec56 100755 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -13,10 +13,10 @@ #include <wx/tooltip.h> #include <wx/timer.h> #include <wx/utils.h> -//#include <zen/tick_count.h> #include <zen/string_tools.h> #include <zen/scope_guard.h> #include <zen/utf.h> +#include <zen/zstring.h> #include <zen/format_unit.h> #include "dc.h" @@ -31,14 +31,6 @@ wxColor Grid::getColorSelectionGradientTo () { return { 225, 234, 255 }; } // const int GridData::COLUMN_GAP_LEFT = 4; -void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) -{ - wxDCPenChanger dummy (dc, col); - wxDCBrushChanger dummy2(dc, col); - dc.DrawRectangle(rect); -} - - namespace { //let's NOT create wxWidgets objects statically: @@ -136,7 +128,6 @@ wxSize GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& //truncate large texts and add ellipsis assert(!contains(text, L"\n")); - const wchar_t ELLIPSIS = L'\u2026'; //"..." std::wstring textTrunc = text; wxSize extentTrunc = dc.GetTextExtent(textTrunc); @@ -476,7 +467,7 @@ public: } private: - static std::wstring formatRow(size_t row) { return toGuiString(row + 1); } //convert number to std::wstring including thousands separator + static std::wstring formatRow(size_t row) { return formatNumber(row + 1); } //convert number to std::wstring including thousands separator bool AcceptsFocus() const override { return false; } @@ -91,7 +91,6 @@ using GridColumnResizeEventFunction = void (wxEvtHandler::*)(GridColumnResizeEve //------------------------------------------------------------------------------------------------------------ class Grid; -void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col); class GridData { diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index bb5cd6c3..1e63346a 100755 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -6,6 +6,7 @@ #include "image_tools.h" #include <zen/string_tools.h> +#include <zen/zstring.h> #include <wx/app.h> @@ -150,9 +151,8 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const //for some reason wxDC::DrawText messes up "weak" bidi characters even when wxLayout_RightToLeft is set! (--> arrows in hebrew/arabic) //=> use mark characters instead: - const wchar_t rtlMark = L'\u200F'; //UTF-8: E2 80 8F if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) - textFmt = rtlMark + textFmt + rtlMark; + textFmt = RTL_MARK + textFmt + RTL_MARK; const std::vector<std::pair<wxString, wxSize>> lineInfo = getTextExtentInfo(textFmt, font); diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp index 19c721ad..1d18be5e 100755 --- a/wx+/popup_dlg.cpp +++ b/wx+/popup_dlg.cpp @@ -114,7 +114,7 @@ public: if (parent && parent->IsShownOnScreen()) SetTitle(titleTmp); else - SetTitle(wxTheApp->GetAppDisplayName() + L" - " + titleTmp); + SetTitle(wxTheApp->GetAppDisplayName() + SPACED_DASH + titleTmp); } int maxWidth = 500; diff --git a/zen/error_log.h b/zen/error_log.h index 90016666..0d62dd1f 100755 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -49,12 +49,12 @@ public: //subset of std::vector<> interface: using const_iterator = std::vector<LogEntry>::const_iterator; - const_iterator begin() const { return entries.begin(); } - const_iterator end () const { return entries.end (); } - bool empty() const { return entries.empty(); } + const_iterator begin() const { return entries_.begin(); } + const_iterator end () const { return entries_.end (); } + bool empty() const { return entries_.empty(); } private: - std::vector<LogEntry> entries; //list of non-resolved errors and warnings + std::vector<LogEntry> entries_; //list of non-resolved errors and warnings }; @@ -70,14 +70,14 @@ template <class String> inline void ErrorLog::logMsg(const String& text, zen::MessageType type) { const LogEntry newEntry = { std::time(nullptr), type, copyStringTo<MsgString>(text) }; - entries.push_back(newEntry); + entries_.push_back(newEntry); } inline int ErrorLog::getItemCount(int typeFilter) const { - return static_cast<int>(std::count_if(entries.begin(), entries.end(), [&](const LogEntry& e) { return e.type & typeFilter; })); + return static_cast<int>(std::count_if(entries_.begin(), entries_.end(), [&](const LogEntry& e) { return e.type & typeFilter; })); } @@ -103,7 +103,7 @@ String formatMessageImpl(const LogEntry& entry) //internal linkage return std::wstring(); }; - String formattedText = L"[" + formatTime<String>(FORMAT_TIME, getLocalTime(entry.time)) + L"] " + copyStringTo<String>(getTypeName()) + L": "; + String formattedText = L"[" + formatTime<String>(FORMAT_TIME, getLocalTime(entry.time)) + L"] " + copyStringTo<String>(getTypeName()) + L": "; const size_t prefixLen = formattedText.size(); for (auto it = entry.message.begin(); it != entry.message.end(); ) diff --git a/zen/file_error.h b/zen/file_error.h index b318e708..18e790de 100755 --- a/zen/file_error.h +++ b/zen/file_error.h @@ -14,8 +14,7 @@ namespace zen { -//A high-level exception class giving detailed context information for end users -class FileError +class FileError //A high-level exception class giving detailed context information for end users { public: explicit FileError(const std::wstring& msg) : msg_(msg) {} @@ -28,7 +27,7 @@ private: std::wstring msg_; }; -#define DEFINE_NEW_FILE_ERROR(X) struct X : public FileError { X(const std::wstring& msg) : FileError(msg) {} X(const std::wstring& msg, const std::wstring& descr) : FileError(msg, descr) {} }; +#define DEFINE_NEW_FILE_ERROR(X) struct X : public zen::FileError { X(const std::wstring& msg) : FileError(msg) {} X(const std::wstring& msg, const std::wstring& descr) : FileError(msg, descr) {} }; DEFINE_NEW_FILE_ERROR(ErrorTargetExisting); //DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing); diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index c0667fb1..e62c9286 100755 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -39,7 +39,7 @@ std::wstring zen::formatThreeDigitPrecision(double value) } -std::wstring zen::filesizeToShortString(int64_t size) +std::wstring zen::formatFilesizeShort(int64_t size) { //if (size < 0) return _("Error"); -> really? @@ -122,7 +122,7 @@ std::wstring roundToBlock(double timeInHigh, } -std::wstring zen::remainingTimeToString(double timeInSec) +std::wstring zen::formatRemainingTime(double timeInSec) { const int steps10[] = { 1, 2, 5, 10 }; const int steps24[] = { 1, 2, 3, 4, 6, 8, 12, 24 }; @@ -154,7 +154,7 @@ std::wstring zen::remainingTimeToString(double timeInSec) //} -std::wstring zen::fractionToString(double fraction) +std::wstring zen::formatFraction(double fraction) { return printNumber<std::wstring>(L"%.2f", fraction * 100.0) + L'%'; //no need to internationalize fraction!? } @@ -188,7 +188,7 @@ std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number) } -std::wstring zen::utcToLocalTimeString(int64_t utcTime) +std::wstring zen::formatUtcToLocalTime(int64_t utcTime) { auto errorMsg = [&] { return _("Error") + L" (time_t: " + numberTo<std::wstring>(utcTime) + L")"; }; diff --git a/zen/format_unit.h b/zen/format_unit.h index 336df74c..3dcc6858 100755 --- a/zen/format_unit.h +++ b/zen/format_unit.h @@ -15,16 +15,16 @@ namespace zen { -std::wstring filesizeToShortString(int64_t filesize); -std::wstring remainingTimeToString(double timeInSec); -std::wstring fractionToString(double fraction); //within [0, 1] -std::wstring utcToLocalTimeString(int64_t utcTime); //like Windows Explorer would... +std::wstring formatFilesizeShort(int64_t filesize); +std::wstring formatRemainingTime(double timeInSec); +std::wstring formatFraction(double fraction); //within [0, 1] +std::wstring formatUtcToLocalTime(int64_t utcTime); //like Windows Explorer would... std::wstring formatTwoDigitPrecision (double value); //format with fixed number of digits std::wstring formatThreeDigitPrecision(double value); //(unless value is too large) template <class NumberType> -std::wstring toGuiString(NumberType number); //format integer number including thousands separator +std::wstring formatNumber(NumberType number); //format integer number including thousands separator @@ -43,7 +43,7 @@ std::wstring includeNumberSeparator(const std::wstring& number); } template <class NumberType> inline -std::wstring toGuiString(NumberType number) +std::wstring formatNumber(NumberType number) { static_assert(IsInteger<NumberType>::value, ""); return ffs_Impl::includeNumberSeparator(zen::numberTo<std::wstring>(number)); @@ -106,7 +106,7 @@ std::wstring translate(const std::wstring& singular, const std::wstring& plural, return translation; } //fallback: - return replaceCpy(std::abs(n64) == 1 ? singular : plural, L"%x", toGuiString(n)); + return replaceCpy(std::abs(n64) == 1 ? singular : plural, L"%x", formatNumber(n)); } } diff --git a/zen/process_priority.h b/zen/process_priority.h index 07679b0c..ac96a8ae 100755 --- a/zen/process_priority.h +++ b/zen/process_priority.h @@ -3,13 +3,13 @@ // * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** + #ifndef PROCESS_PRIORITY_H_83421759082143245 #define PROCESS_PRIORITY_H_83421759082143245 #include <memory> #include "file_error.h" - namespace zen { //signal a "busy" state to the operating system @@ -20,7 +20,7 @@ public: ~PreventStandby(); private: struct Impl; - const std::unique_ptr<Impl> pimpl; + const std::unique_ptr<Impl> pimpl_; }; //lower CPU and file I/O priorities @@ -31,7 +31,7 @@ public: ~ScheduleForBackgroundProcessing(); private: struct Impl; - const std::unique_ptr<Impl> pimpl; + const std::unique_ptr<Impl> pimpl_; }; } diff --git a/zen/shell_execute.h b/zen/shell_execute.h index 7dcd6653..077f18e7 100755 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -39,12 +39,23 @@ void shellExecute(const Zstring& command, ExecutionType type) //throw FileError if (type == EXEC_TYPE_SYNC) { //Posix::system - execute a shell command - int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect... + const int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect... if (rv == -1 || WEXITSTATUS(rv) == 127) //http://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)" throw FileError(_("Incorrect command line:") + L"\n" + utfTo<std::wstring>(command)); } else - runAsync([=] { int rv = ::system(command.c_str()); (void)rv; }); + { + runAsync([=] { int rv = ::system(command.c_str()); (void)rv; }); + + warn_static("finish:") + + + + + + + + } } } } diff --git a/zen/shutdown.cpp b/zen/shutdown.cpp new file mode 100755 index 00000000..b21ab8db --- /dev/null +++ b/zen/shutdown.cpp @@ -0,0 +1,62 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "shutdown.h" + #include <zen/shell_execute.h> + + +using namespace zen; + + + + +void zen::shutdownSystem() //throw FileError +{ + //https://linux.die.net/man/2/reboot => needs admin rights! + + //should work without admin rights: + shellExecute("sleep 1; systemctl poweroff", EXEC_TYPE_ASYNC); //throw FileError + //sleep 1: give FFS some time to properly shut down! + //Linux: main thread will wait on detached threads! + warn_static("get rid of shellExecute's thread implementation!") + +} + + +void zen::suspendSystem() //throw FileError +{ + //should work without admin rights: + shellExecute("systemctl suspend", EXEC_TYPE_ASYNC); //throw FileError + +} + +/* +Command line alternatives: + +#ifdef ZEN_WIN +#ifdef ZEN_WIN_VISTA_AND_LATER + Shut down: shutdown /s /t 60 + Sleep: rundll32.exe powrprof.dll,SetSuspendState Sleep + Log off: shutdown /l +#else //XP + Shut down: shutdown -s -t 60 + Standby: rundll32.exe powrprof.dll,SetSuspendState //this triggers standby OR hibernate, depending on whether hibernate setting is active! no suspend on XP? + Log off: shutdown -l +#endif + +#elif defined ZEN_LINUX + Shut down: systemctl poweroff //alternative requiring admin: sudo shutdown -h 1 + Sleep: systemctl suspend //alternative requiring admin: sudo pm-suspend + Log off: gnome-session-quit --no-prompt + //alternative requiring admin: sudo killall Xorg + //alternative without admin: dbus-send --session --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.Logout uint32:1 + +#elif defined ZEN_MAC + Shut down: osascript -e 'tell application "System Events" to shut down' + Sleep: osascript -e 'tell application "System Events" to sleep' + Log off: osascript -e 'tell application "System Events" to log out' +#endif +*/ diff --git a/zen/shutdown.h b/zen/shutdown.h new file mode 100755 index 00000000..e64dee41 --- /dev/null +++ b/zen/shutdown.h @@ -0,0 +1,18 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef SHUTDOWN_H_3423847870238407783265 +#define SHUTDOWN_H_3423847870238407783265 + +#include "file_error.h" + +namespace zen +{ +void shutdownSystem(); //throw FileError +void suspendSystem(); // +} + +#endif //SHUTDOWN_H_3423847870238407783265 diff --git a/zen/sys_error.cpp b/zen/sys_error.cpp new file mode 100755 index 00000000..fd4c8f1e --- /dev/null +++ b/zen/sys_error.cpp @@ -0,0 +1,6 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + diff --git a/zen/sys_error.h b/zen/sys_error.h index 026a3be5..f4361e69 100755 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -26,6 +26,8 @@ ErrorCode getLastError(); std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec); std::wstring formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg); + + //A low-level exception class giving (non-translated) detail information only - same conceptional level like "GetLastError()"! class SysError { @@ -37,7 +39,7 @@ private: std::wstring msg_; }; -#define DEFINE_NEW_SYS_ERROR(X) struct X : public SysError { X(const std::wstring& msg) : SysError(msg) {} }; +#define DEFINE_NEW_SYS_ERROR(X) struct X : public zen::SysError { X(const std::wstring& msg) : SysError(msg) {} }; @@ -191,11 +191,11 @@ bool isValid(const std::tm& t) auto inRange = [](int value, int minVal, int maxVal) { return minVal <= value && value <= maxVal; }; //http://www.cplusplus.com/reference/clibrary/ctime/tm/ - return inRange(t.tm_sec , 0, 61) && - inRange(t.tm_min , 0, 59) && + return inRange(t.tm_sec, 0, 61) && + inRange(t.tm_min, 0, 59) && inRange(t.tm_hour, 0, 23) && inRange(t.tm_mday, 1, 31) && - inRange(t.tm_mon , 0, 11) && + inRange(t.tm_mon, 0, 11) && //tm_year inRange(t.tm_wday, 0, 6) && inRange(t.tm_yday, 0, 365); diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 6b41af13..fb62424a 100755 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -74,9 +74,12 @@ int cmpStringNaturalLinux(const char* lhs, size_t lhsLen, const char* rhs, size_ - compare strings after conceptually creating blocks of whitespace/numbers/text - implement strict weak ordering! - don't follow broken "strnatcasecmp": https://github.com/php/php-src/blob/master/ext/standard/strnatcmp.c - 1. incorrect non-ASCII CI-comparison 2. incorrect bounds checks - 3. incorrect trimming of *all* whitespace 4. arbitrary handling of leading 0 only at string begin - 5. incorrect handling of whitespace following a number 6. code is a mess + 1. incorrect non-ASCII CI-comparison + 2. incorrect bounds checks + 3. incorrect trimming of *all* whitespace + 4. arbitrary handling of leading 0 only at string begin + 5. incorrect handling of whitespace following a number + 6. code is a mess */ for (;;) { diff --git a/zen/zstring.h b/zen/zstring.h index 273efd2f..33f48990 100755 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -72,6 +72,17 @@ S ciReplaceCpy(const S& str, const T& oldTerm, const U& newTerm); +//common unicode sequences +const wchar_t EM_DASH = L'\u2014'; +const wchar_t* const SPACED_DASH = L" \u2013 "; //using 'EN DASH' +const wchar_t LTR_MARK = L'\u200E'; //UTF-8: E2 80 8E +const wchar_t RTL_MARK = L'\u200F'; //UTF-8: E2 80 8F +const wchar_t ELLIPSIS = L'\u2026'; //"..." + + + + + //################################# inline implementation ######################################## inline |