diff options
87 files changed, 1838 insertions, 1605 deletions
diff --git a/FreeFileSync/Build/Changelog.txt b/FreeFileSync/Build/Changelog.txt index cb3a1188..592e8e9b 100644 --- a/FreeFileSync/Build/Changelog.txt +++ b/FreeFileSync/Build/Changelog.txt @@ -1,3 +1,21 @@ +FreeFileSync 7.7 [2015-12-01] +----------------------------- +Support variable drive letters for config history when using FreeFileSync portable +Skip non-storage functional objects at MTP device level +Log and show error messages without hanging when running as a service +Navigate between sync settings panels with arrow keys +Fixed volume shadow copy file path generation +Handle integer overflows when comparing file times +Ignore more than one file time shift +Reworked grid to support mouse highlight areas +Allow minute precision for file time shifts +Warn about unsupported MTP and SFTP paths in RealtimeSync +Strip superfluous mode parameters when creating a directory (Linux, OS X) +Correctly detect system language for English UK +Store program language by name to handle changing ids +Fixed crash during application exit after using SFTP + + FreeFileSync 7.6 [2015-11-01] ----------------------------- Create missing synchronization base folders only on demand diff --git a/FreeFileSync/Build/Help/html/RealtimeSync.html b/FreeFileSync/Build/Help/html/RealtimeSync.html index 0b983f60..5e13b569 100644 --- a/FreeFileSync/Build/Help/html/RealtimeSync.html +++ b/FreeFileSync/Build/Help/html/RealtimeSync.html @@ -108,8 +108,14 @@ <h2>Limitations:</h2> <ul> - <li>If multiple changes happen at the same time, only the name of the first file is written to variable <b><span class="command-line">%changed_file%</span></b>. - <li>While RealtimeSync is executing the command line, monitoring is inactive and changes occurring during this time are not detected. + <li>If multiple changes happen at the same time, only the path of the first file is written to variable <b><span class="command-line">%changed_file%</span></b>. + <li>While RealtimeSync is executing the command line, monitoring for changed files is deliberately inactive. </ul> + <p> + The command line usually starts a synchronization task using FreeFileSync which naturally leads to additional file change notifications. + Therefore the RealtimeSync change detection has to be deactivated to not go into an endless loop. + On the other hand it is not likely that changes happen in first place since RealtimeSync runs the command line only after a user-specified idle time has passed. + 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 diff --git a/FreeFileSync/Build/Help/html/daylight-saving-time.html b/FreeFileSync/Build/Help/html/daylight-saving-time.html index 0b8f00d4..a46cf2fa 100644 --- a/FreeFileSync/Build/Help/html/daylight-saving-time.html +++ b/FreeFileSync/Build/Help/html/daylight-saving-time.html @@ -44,12 +44,19 @@ <ol> <li><p> - In FreeFileSync's comparison settings you can enter a full-hour time shift to ignore during comparison: - If you need to handle differences due to daylight saving time enter a one hour shift. If the differences are caused by changing the time zone - enter a larger shift as needed. + In FreeFileSync's comparison settings you can enter one or more time shifts to ignore during comparison: + If you need to handle differences due to daylight saving time enter a single one hour shift. If the differences are caused by changing the time zone + enter an appropriate shift as needed. + <br> </p> <img src="../images/ignore-time-shift.png" alt="Ignore daylight saving time shift"><br> <br> + <div class="box-outer"><div class="bluebox"><div class="box-inner"> + <b>Note</b><br> + 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></div></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. diff --git a/FreeFileSync/Build/Help/html/run-as-service.html b/FreeFileSync/Build/Help/html/run-as-service.html index 19ea255e..2a5d1e8f 100644 --- a/FreeFileSync/Build/Help/html/run-as-service.html +++ b/FreeFileSync/Build/Help/html/run-as-service.html @@ -31,7 +31,9 @@ <ol> <li><p> RealtimeSync should be monitoring while a user is logged in:<br> - Create a new shortcut, enter the command line from above as target and place it into the user's autostart folder. + 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>%AppData%\Microsoft\Windows\Start Menu\Programs\Startup</b></span> in the Windows Explorer address bar to find the folder quickly.) + </p> <img src="../images/create-shortcut.png" alt="Create shortcut"><br><br> <img src="../images/shortcut-properties.png" alt="Shortcut properties"> diff --git a/FreeFileSync/Build/Help/html/schedule-a-batch-job.html b/FreeFileSync/Build/Help/html/schedule-a-batch-job.html index 47c42a02..a2ee464f 100644 --- a/FreeFileSync/Build/Help/html/schedule-a-batch-job.html +++ b/FreeFileSync/Build/Help/html/schedule-a-batch-job.html @@ -11,20 +11,21 @@ <ol> <li>Create a new batch job via FreeFileSync's main dialog: <b>Menu → File → Save as batch job...</b><br> + <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 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> - Alternatively if you want to see the progress, but not pause at the results, it's sufficient to only select the <i>On completion</i> action <b>Close progress dialog</b>. + Alternatively if you want to see the progress, but not wait at the results dialog, it's sufficient to only select the <i>On completion</i> action <b>Close progress dialog</b>. <br><br> - <img src="../images/setup-batch-job.png" alt="Setup a FreeFileSync batch job"> - <br><br> - <div class="box-outer"><div class="bluebox"><div class="box-inner"> <b>Note</b><br> - Even if the progress dialog is not shown at the beginning, you can make it visible at any time <b>during</b> - synchronization by double-clicking the FreeFileSync notification area icon. + 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></div></div> <br> @@ -39,9 +40,9 @@ <br> <hr/> - <h2>A. Windows 7 Task Scheduler:</h2> + <h2>A. Windows Task Scheduler:</h2> <ul> - <li>Click on Start and run <span class="command-line"><b>taskschd.msc</b></span>. + <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. @@ -49,14 +50,15 @@ <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/win7-scheduler.png" alt="Windows 7 Task Scheduler"> + <img src="../images/windows-scheduler.png" alt="Windows Task Scheduler"> </ul> <div class="box-outer"><div class="bluebox"><div class="box-inner"> <b>Note</b><br> - Beginning with Windows Vista the <i>Program/script</i> always needs to point to an executable file like FreeFileSync.exe even - if ffs_batch file associations are set up. If a ffs_batch file were entered instead the task would return with - error code 0xC1, "%1 is not a valid Win32 application". + In Windows 7 <i>Program/script</i> always needs to point to an executable file like FreeFileSync.exe even + when the ffs_batch file association is registered. If a ffs_batch file were 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>. </div></div></div> <br> diff --git a/FreeFileSync/Build/Help/html/synchronize-with-sftp.html b/FreeFileSync/Build/Help/html/synchronize-with-sftp.html index 4d2f1016..87e758e5 100644 --- a/FreeFileSync/Build/Help/html/synchronize-with-sftp.html +++ b/FreeFileSync/Build/Help/html/synchronize-with-sftp.html @@ -24,7 +24,7 @@ <h1>Synchronize with SFTP <span style="font-weight: normal">(Linux)</span></h1> - <p>An SFTP share can be easily mapped onto a local folder for use with FreeFileSync:</p> + <p>An SFTP share can be mapped to a local folder for use with FreeFileSync:</p> <div class="box-outer"><div class="greybox"><div class="box-inner"> <ul style="margin: 0"> diff --git a/FreeFileSync/Build/Help/html/tips-and-tricks.html b/FreeFileSync/Build/Help/html/tips-and-tricks.html index 473b716b..bb8b6ca2 100644 --- a/FreeFileSync/Build/Help/html/tips-and-tricks.html +++ b/FreeFileSync/Build/Help/html/tips-and-tricks.html @@ -31,7 +31,7 @@ <div class="tip">You can select multiple configurations at a time.</div> <img style="float:left; margin-right:10px" src="../images/config-multiple-selection.png" alt="Select multiple configurations"> - Select a few items via mouse and refine the selection by holding the Control button while clicking.<br> + Select a few items via mouse and refine the selection by holding the Control key while clicking.<br> <div style="clear:both"></div> <div class="separation_line"></div> @@ -79,7 +79,7 @@ <img src="../images/remove-local-settings.png" alt="Remove local settings"> <div class="separation_line"></div> - <div class="tip">You can remove paths from the folder drop-down list by pressing the Delete button.</div> + <div class="tip">You can remove obsolete paths from the folder drop-down list by pressing the Delete key.</div> <img src="../images/remove-drop-down-path.png" alt="Remove drop-down path"> <div class="separation_line"></div> diff --git a/FreeFileSync/Build/Help/images/comparison-settings.png b/FreeFileSync/Build/Help/images/comparison-settings.png Binary files differindex 0a99fa78..756cde04 100644 --- a/FreeFileSync/Build/Help/images/comparison-settings.png +++ b/FreeFileSync/Build/Help/images/comparison-settings.png diff --git a/FreeFileSync/Build/Help/images/create-shortcut.png b/FreeFileSync/Build/Help/images/create-shortcut.png Binary files differindex 487a1950..4aee94d9 100644 --- a/FreeFileSync/Build/Help/images/create-shortcut.png +++ b/FreeFileSync/Build/Help/images/create-shortcut.png diff --git a/FreeFileSync/Build/Help/images/ignore-time-shift.png b/FreeFileSync/Build/Help/images/ignore-time-shift.png Binary files differindex ef727265..8ea24423 100644 --- 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/schedule-realtimesync.png b/FreeFileSync/Build/Help/images/schedule-realtimesync.png Binary files differindex 32896be6..38c88429 100644 --- a/FreeFileSync/Build/Help/images/schedule-realtimesync.png +++ b/FreeFileSync/Build/Help/images/schedule-realtimesync.png diff --git a/FreeFileSync/Build/Help/images/shortcut-properties.png b/FreeFileSync/Build/Help/images/shortcut-properties.png Binary files differindex 99121856..15db849e 100644 --- a/FreeFileSync/Build/Help/images/shortcut-properties.png +++ b/FreeFileSync/Build/Help/images/shortcut-properties.png diff --git a/FreeFileSync/Build/Help/images/win7-scheduler.png b/FreeFileSync/Build/Help/images/win7-scheduler.png Binary files differdeleted file mode 100644 index b91b5a5a..00000000 --- a/FreeFileSync/Build/Help/images/win7-scheduler.png +++ /dev/null diff --git a/FreeFileSync/Build/Help/images/windows-scheduler.png b/FreeFileSync/Build/Help/images/windows-scheduler.png Binary files differnew file mode 100644 index 00000000..354cc9fa --- /dev/null +++ b/FreeFileSync/Build/Help/images/windows-scheduler.png diff --git a/FreeFileSync/Build/Languages/english_uk.lng b/FreeFileSync/Build/Languages/english_uk.lng index f7446956..fc1cec43 100644 --- a/FreeFileSync/Build/Languages/english_uk.lng +++ b/FreeFileSync/Build/Languages/english_uk.lng @@ -1,12 +1,27 @@ <header> <language>English (UK)</language> <translator>Robert Readman</translator> - <locale>en_GB</locale> + <locale>English (U.K.)</locale> <image>flag_england.png</image> <plural_count>2</plural_count> <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> +<source>This FreeFileSync installer for donors has reached its installation limit. Download the regular version from the FreeFileSync homepage now?</source> +<target></target> + +<source>Thanks for your donation and support!</source> +<target></target> + +<source>%x is not a regular directory name.</source> +<target></target> + +<source>empty</source> +<target></target> + +<source>Main config</source> +<target></target> + <source>Please enter a target folder.</source> <target></target> @@ -16,13 +31,13 @@ </source> <target></target> -<source>Copy to...</source> +<source>Loading...</source> <target></target> -<source>Copy items</source> +<source>Copy to...</source> <target></target> -<source>&Copy</source> +<source>Copy items</source> <target></target> <source>&Overwrite existing files</source> @@ -31,9 +46,45 @@ <source>&Keep relative paths</source> <target></target> +<source>Select Folder</source> +<target></target> + +<source>Select a directory on the server:</source> +<target></target> + <source>Port:</source> <target></target> +<source>Example:</source> +<target></target> + +<source>List of file time offsets to consider equal</source> +<target></target> + +<source>&Ignore time shift [hh:mm]</source> +<target></target> + +<source>Folder pair:</source> +<target></target> + +<source>Select alternative folder type</source> +<target></target> + +<source>SFTP folder</source> +<target></target> + +<source>Please select a folder on a local file system, network or an MTP device.</source> +<target></target> + +<source>The selected folder %x cannot be used with FreeFileSync.</source> +<target></target> + +<source>Start</source> +<target></target> + +<source>If you ignore this error the folders are considered empty. Missing folders are created automatically when needed.</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>Both sides have changed since last synchronisation.</target> @@ -136,9 +187,6 @@ <source>Cannot find the following folders:</source> <target>Cannot find the following folders:</target> -<source>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.</source> -<target>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronisation.</target> - <source>A folder input field is empty.</source> <target>A folder input field is empty.</target> @@ -282,9 +330,6 @@ Actual: %y bytes <source>Cannot find device %x.</source> <target>Cannot find device %x.</target> -<source>Cannot determine free disk space for %x.</source> -<target>Cannot determine free disk space for %x.</target> - <source>Cannot create directory %x.</source> <target>Cannot create directory %x.</target> @@ -303,12 +348,15 @@ Actual: %y bytes <source>Cannot resolve symbolic link %x.</source> <target>Cannot resolve symbolic link %x.</target> -<source>Unable to move %x to the recycle bin.</source> -<target>Unable to move %x to the recycle bin.</target> - <source>Cannot open directory %x.</source> <target>Cannot open directory %x.</target> +<source>Cannot determine free disk space for %x.</source> +<target>Cannot determine free disk space for %x.</target> + +<source>Unable to move %x to the recycle bin.</source> +<target>Unable to move %x to the recycle bin.</target> + <source>Incorrect command line:</source> <target>Incorrect command line:</target> @@ -399,9 +447,6 @@ Actual: %y bytes <source>Cannot set directory lock for %x.</source> <target>Cannot set directory lock for %x.</target> -<source>Scanning:</source> -<target>Scanning:</target> - <source> <pluralform>1 thread</pluralform> <pluralform>%x threads</pluralform> @@ -411,6 +456,9 @@ Actual: %y bytes <pluralform>%x threads</pluralform> </target> +<source>Scanning:</source> +<target>Scanning:</target> + <source>/sec</source> <target>/sec</target> @@ -521,9 +569,6 @@ The command is triggered if: - new folders arrive (e.g. USB stick insert) </target> -<source>&Start</source> -<target>&Start</target> - <source>About</source> <target>About</target> @@ -707,30 +752,6 @@ The command is triggered if: <source>Serious Error</source> <target>Serious Error</target> -<source>Check for Program Updates</source> -<target>Check for Program Updates</target> - -<source>A new version of FreeFileSync is available:</source> -<target>A new version of FreeFileSync is available:</target> - -<source>Download now?</source> -<target>Download now?</target> - -<source>&Download</source> -<target>&Download</target> - -<source>FreeFileSync is up to date.</source> -<target>FreeFileSync is up to date.</target> - -<source>Unable to connect to www.freefilesync.org.</source> -<target>Unable to connect to www.freefilesync.org.</target> - -<source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source> -<target>Cannot find current FreeFileSync version number online. Do you want to check manually?</target> - -<source>&Check</source> -<target>&Check</target> - <source>Symlink</source> <target>Symlink</target> @@ -791,11 +812,8 @@ The command is triggered if: <source>Paste</source> <target>Paste</target> -<source>Local Synchronization Settings</source> -<target>Local Synchronisation Settings</target> - -<source>The selected folder %x cannot be used with FreeFileSync. Please select a folder on a local file system, network or an MTP device.</source> -<target>The selected folder %x cannot be used with FreeFileSync. Please select a folder on a local file system, network or an MTP device.</target> +<source>Select SFTP folder</source> +<target>Select SFTP folder</target> <source>&New</source> <target>&New</target> @@ -866,9 +884,6 @@ The command is triggered if: <source>Remove folder pair</source> <target>Remove folder pair</target> -<source>Select SFTP folder</source> -<target>Select SFTP folder</target> - <source>Swap sides</source> <target>Swap sides</target> @@ -926,15 +941,6 @@ The command is triggered if: <source>Identify equal files by comparing the file content.</source> <target>Identify equal files by comparing the file content.</target> -<source>&Ignore time shift (in hours)</source> -<target>&Ignore time shift (in hours)</target> - -<source>Consider file times with specified offset as equal</source> -<target>Consider file times with specified offset as equal</target> - -<source>Handle daylight saving time</source> -<target>Handle daylight saving time</target> - <source>Include &symbolic links:</source> <target>Include &symbolic links:</target> @@ -947,6 +953,9 @@ The command is triggered if: <source>More information</source> <target>More information</target> +<source>Handle daylight saving time</source> +<target>Handle daylight saving time</target> + <source>Local settings:</source> <target>Local settings:</target> @@ -1036,15 +1045,15 @@ The command is triggered if: <source>OK</source> <target>OK</target> +<source>Arrange folder pair</source> +<target>Arrange folder pair</target> + <source>Enter your SFTP login details:</source> <target>Enter your SFTP login details:</target> <source>Server name or IP address:</source> <target>Server name or IP address:</target> -<source>Examples:</source> -<target>Examples:</target> - <source>User name:</source> <target>User name:</target> @@ -1066,9 +1075,6 @@ The command is triggered if: <source>&Don't show this dialog again</source> <target>&Don't show this dialogue again</target> -<source>Arrange folder pair</source> -<target>Arrange folder pair</target> - <source>Items found:</source> <target>Elements found:</target> @@ -1237,6 +1243,12 @@ This guarantees a consistent state even in case of a serious error. <source>Overview</source> <target>Overview</target> +<source>&Download</source> +<target>&Download</target> + +<source>A new version of FreeFileSync is available:</source> +<target>A new version of FreeFileSync is available:</target> + <source>Confirm</source> <target>Confirm</target> @@ -1348,9 +1360,6 @@ This guarantees a consistent state even in case of a serious error. <source>Remove entry from list</source> <target>Remove entry from list</target> -<source>Synchronization Settings</source> -<target>Synchronisation Settings</target> - <source>Clear filter</source> <target>Clear filter</target> @@ -1438,27 +1447,27 @@ This guarantees a consistent state even in case of a serious error. <source>Shut down</source> <target>Shut down</target> +<source>Paused</source> +<target>Paused</target> + +<source>Initializing...</source> +<target>Initialising...</target> + <source>Scanning...</source> <target>Scanning...</target> <source>Comparing content...</source> <target>Comparing content...</target> +<source>Completed</source> +<target>Completed</target> + <source>Info</source> <target>Info</target> <source>Select all</source> <target>Select all</target> -<source>Paused</source> -<target>Paused</target> - -<source>Initializing...</source> -<target>Initialising...</target> - -<source>Completed</source> -<target>Completed</target> - <source>&Continue</source> <target>&Continue</target> @@ -1525,6 +1534,15 @@ This guarantees a consistent state even in case of a serious error. <source>Configure your own synchronization rules.</source> <target>Configure your own synchronisation rules.</target> +<source>Synchronization Settings</source> +<target>Synchronisation Settings</target> + +<source>Comparison</source> +<target>Comparison</target> + +<source>Synchronization</source> +<target>Synchronisation</target> + <source>Today</source> <target>Today</target> @@ -1561,12 +1579,6 @@ This guarantees a consistent state even in case of a serious error. <source>Append a time stamp to each file name</source> <target>Append a time stamp to each file name</target> -<source>Comparison</source> -<target>Comparison</target> - -<source>Synchronization</source> -<target>Synchronisation</target> - <source>Leave as unresolved conflict</source> <target>Leave as unresolved conflict</target> @@ -1585,6 +1597,24 @@ This guarantees a consistent state even in case of a serious error. <source>Percentage</source> <target>Percentage</target> +<source>Check for Program Updates</source> +<target>Check for Program Updates</target> + +<source>Download now?</source> +<target>Download now?</target> + +<source>FreeFileSync is up to date.</source> +<target>FreeFileSync is up to date.</target> + +<source>Unable to connect to www.freefilesync.org.</source> +<target>Unable to connect to www.freefilesync.org.</target> + +<source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source> +<target>Cannot find current FreeFileSync version number online. Do you want to check manually?</target> + +<source>&Check</source> +<target>&Check</target> + <source>Unable to register to receive system messages.</source> <target>Unable to register to receive system messages.</target> diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index e109c58b..7480c49c 100644 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -7,17 +7,8 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> -<source>This FreeFileSync installer for donors has reached its installation limit. Download the regular version from the FreeFileSync homepage now?</source> -<target>Das FreeFileSync Installationsprogramm für Spender hat sein Installationslimit erreicht. Jetzt die normale Version von der FreeFileSync Homepage herunterladen?</target> - -<source>Thanks for your donation and support!</source> -<target>Danke für Ihre Spende und Unterstützung!</target> - -<source>%x is not a regular directory name.</source> -<target>%x ist kein regulärer Verzeichnisname.</target> - -<source>If you ignore this error the folders are considered empty. Missing folders are created automatically when needed.</source> -<target>Wird dieser Fehler ignoriert, werden die Ordner als leer angesehen. Fehlende Ordner werden bei Bedarf automatisch erstellt.</target> +<source>List of file time offsets to consider equal</source> +<target></target> <source>Both sides have changed since last synchronization.</source> <target>Beide Seiten wurden seit der letzten Synchronisation verändert.</target> @@ -121,6 +112,9 @@ <source>Cannot find the following folders:</source> <target>Die folgenden Ordner wurden nicht gefunden:</target> +<source>If you ignore this error the folders are considered empty. Missing folders are created automatically when needed.</source> +<target>Wird dieser Fehler ignoriert, werden die Ordner als leer angesehen. Fehlende Ordner werden bei Bedarf automatisch erstellt.</target> + <source>A folder input field is empty.</source> <target>Ein Ordnereingabefeld ist leer.</target> @@ -893,15 +887,6 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Identify equal files by comparing the file content.</source> <target>Erkenne gleiche Dateien durch Vergleich des Dateiinhaltes.</target> -<source>&Ignore time shift (in hours)</source> -<target>&Zeitversatz ignorieren (in Stunden)</target> - -<source>Consider file times with specified offset as equal</source> -<target>Dateizeiten mit angegebenem Versatz als gleich ansehen</target> - -<source>Handle daylight saving time</source> -<target>Sommerzeit berücksichtigen</target> - <source>Include &symbolic links:</source> <target>Symbolische &Verknüpfungen einschließen:</target> @@ -914,6 +899,15 @@ Die Befehlszeile wird ausgelöst, wenn: <source>More information</source> <target>Mehr Information</target> +<source>&Ignore time shift [hh:mm]</source> +<target>&Zeitversatz ignorieren [hh:mm]</target> + +<source>Example:</source> +<target>Beispiel:</target> + +<source>Handle daylight saving time</source> +<target>Sommerzeit berücksichtigen</target> + <source>Local settings:</source> <target>Lokale Einstellungen:</target> @@ -1015,9 +1009,6 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Port:</source> <target>Port:</target> -<source>Examples:</source> -<target>Beispiele:</target> - <source>User name:</source> <target>Benutzername:</target> @@ -1645,6 +1636,9 @@ Dadurch wird ein konsistenter Datenstand auch im schweren Fehlerfall garantiert. <source>Cannot copy permissions from %x to %y.</source> <target>Die Berechtigungen können nicht von %x nach %y kopiert werden.</target> +<source>%x is not a regular directory name.</source> +<target>%x ist kein regulärer Verzeichnisname.</target> + <source>Cannot find system function %x.</source> <target>Die Systemfunktion %x wurde nicht gefunden.</target> @@ -1777,3 +1771,9 @@ Dadurch wird ein konsistenter Datenstand auch im schweren Fehlerfall garantiert. <source>Edit with FreeFileSync</source> <target>Mit FreeFileSync editieren</target> +<source>Thanks for your donation and support!</source> +<target>Danke für Ihre Spende und Unterstützung!</target> + +<source>This FreeFileSync installer for donors has reached its installation limit. Download the regular version from the FreeFileSync homepage now?</source> +<target>Das FreeFileSync Installationsprogramm für Spender hat sein Installationslimit erreicht. Jetzt die normale Version von der FreeFileSync Homepage herunterladen?</target> + diff --git a/FreeFileSync/Build/Resources.zip b/FreeFileSync/Build/Resources.zip Binary files differindex b3be68ad..052e29d9 100644 --- a/FreeFileSync/Build/Resources.zip +++ b/FreeFileSync/Build/Resources.zip diff --git a/FreeFileSync/Source/RealtimeSync/application.cpp b/FreeFileSync/Source/RealtimeSync/application.cpp index a4ab815c..ebcc050c 100644 --- a/FreeFileSync/Source/RealtimeSync/application.cpp +++ b/FreeFileSync/Source/RealtimeSync/application.cpp @@ -36,6 +36,7 @@ using namespace zen; IMPLEMENT_APP(Application); + namespace { #ifdef _MSC_VER @@ -90,6 +91,7 @@ int Application::OnExit() { uninitializeHelp(); releaseWxLocale(); + cleanupResourceImages(); return wxApp::OnExit(); } @@ -100,8 +102,8 @@ void Application::onEnterEventLoop(wxEvent& event) try { - int lid = xmlAccess::getProgramLanguage(); - setLanguage(lid); //throw FileError + wxLanguage lngId = xmlAccess::getProgramLanguage(); + setLanguage(lngId); //throw FileError } catch (const FileError& e) { @@ -140,30 +142,17 @@ void Application::onEnterEventLoop(wxEvent& event) int Application::OnRun() { - - auto processException = [](const std::wstring& msg) - { - //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! - logError(utfCvrtTo<std::string>(msg)); - wxSafeShowMessage(_("An exception occurred"), msg); - }; - try { wxApp::OnRun(); } - catch (const std::exception& e) //catch all STL exceptions - { - processException(utfCvrtTo<std::wstring>(e.what())); - return FFS_RC_EXCEPTION; - } - /* -> let it crash and create mini dump!!! - catch (...) + catch (const std::bad_alloc& e) //the only kind of exception we don't want crash dumps for { - processException(L"Unknown error."); + 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()); return FFS_RC_EXCEPTION; } - */ + //catch (...) -> let it crash and create mini dump!!! return FFS_RC_SUCCESS; //program's return code } diff --git a/FreeFileSync/Source/RealtimeSync/monitor.cpp b/FreeFileSync/Source/RealtimeSync/monitor.cpp index bfdfb93b..85851c1a 100644 --- a/FreeFileSync/Source/RealtimeSync/monitor.cpp +++ b/FreeFileSync/Source/RealtimeSync/monitor.cpp @@ -29,10 +29,17 @@ std::vector<Zstring> getFormattedDirs(const std::vector<Zstring>& folderPathPhra { std::set<Zstring, LessFilePath> folderPaths; //make unique for (const Zstring& phrase : std::set<Zstring, LessFilePath>(folderPathPhrases.begin(), folderPathPhrases.end())) + { + if (pathStartsWith(trimCpy(phrase), Zstr("ftp:")) || + pathStartsWith(trimCpy(phrase), Zstr("sftp:")) || + pathStartsWith(trimCpy(phrase), Zstr("mtp:"))) + throw FileError(_("The following path does not support directory monitoring:") + L"\n\n" + fmtPath(phrase)); + //make unique: no need to resolve duplicate phrases more than once! (consider "[volume name]" syntax) -> shouldn't this be already buffered by OS? folderPaths.insert(getResolvedFilePath(phrase)); + } - return std::vector<Zstring>(folderPaths.begin(), folderPaths.end()); + return { folderPaths.begin(), folderPaths.end() }; } @@ -59,7 +66,7 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw { const std::vector<Zstring> folderPathsFmt = getFormattedDirs(folderPathPhrases); //throw FileError if (folderPathsFmt.empty()) //pathological case, but we have to check else this function will wait endlessly - throw zen::FileError(_("A folder input field is empty.")); //should have been checked by caller! + throw FileError(_("A folder input field is empty.")); //should have been checked by caller! //detect when volumes are removed/are not available anymore std::vector<std::pair<Zstring, std::shared_ptr<DirWatcher>>> watches; @@ -122,7 +129,7 @@ WaitResult waitForChanges(const std::vector<Zstring>& folderPathPhrases, //throw #ifdef ZEN_MAC pathEndsWith(e.filepath_, Zstr("/.DS_Store")) || #endif - pathEndsWith(e.filepath_, Zstr(".ffs_tmp")) || + //pathEndsWith(e.filepath_, Zstr(".ffs_tmp")) || pathEndsWith(e.filepath_, Zstr(".ffs_lock")) || //sync.ffs_lock, sync.Del.ffs_lock pathEndsWith(e.filepath_, Zstr(".ffs_db")); //sync.ffs_db, .sync.tmp.ffs_db //no need to ignore temporal recycle bin directory: this must be caused by a file deletion anyway @@ -155,7 +162,7 @@ void waitForMissingDirs(const std::vector<Zstring>& folderPathPhrases, //throw F //support specifying volume by name => call getResolvedFilePath() repeatedly for (const Zstring& folderPathFmt : getFormattedDirs(folderPathPhrases)) //throw FileError { - auto ftDirExisting = runAsync([=]() -> bool + auto ftDirExisting = runAsync([=] { #ifdef ZEN_WIN //1. login to network share, if necessary -> we probably do NOT want multiple concurrent runs: GUI!? @@ -269,7 +276,7 @@ void rts::monitorDirectories(const std::vector<Zstring>& folderPathPhrases, unsi { execMonitoring(); //throw FileError } - catch (const zen::FileError& e) + catch (const FileError& e) { callback.reportError(e.toString()); } diff --git a/FreeFileSync/Source/RealtimeSync/tray_menu.cpp b/FreeFileSync/Source/RealtimeSync/tray_menu.cpp index 54444644..bbbc26db 100644 --- a/FreeFileSync/Source/RealtimeSync/tray_menu.cpp +++ b/FreeFileSync/Source/RealtimeSync/tray_menu.cpp @@ -25,6 +25,8 @@ using namespace zen; namespace { +const int RETRY_AFTER_ERROR_INTERVAL = 15; //unit: [s] + const std::int64_t TICKS_UPDATE_INTERVAL = rts::UI_UPDATE_INTERVAL* ticksPerSec() / 1000; TickVal lastExec = getTicks(); @@ -246,7 +248,7 @@ private: rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& config, const wxString& jobname) { std::vector<Zstring> dirNamesNonFmt = config.directories; - erase_if(dirNamesNonFmt, [](const Zstring& str) -> bool { return trimCpy(str).empty(); }); //remove empty entries WITHOUT formatting paths yet! + erase_if(dirNamesNonFmt, [](const Zstring& str) { return trimCpy(str).empty(); }); //remove empty entries WITHOUT formatting paths yet! if (dirNamesNonFmt.empty()) { @@ -305,8 +307,8 @@ rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& conf trayIcon.clearShowErrorRequested(); //wait for some time, then return to retry - static_assert(15 * 1000 % UI_UPDATE_INTERVAL == 0, ""); - for (int i = 0; i < 15 * 1000 / UI_UPDATE_INTERVAL; ++i) + static_assert(RETRY_AFTER_ERROR_INTERVAL * 1000 % UI_UPDATE_INTERVAL == 0, ""); + for (int i = 0; i < RETRY_AFTER_ERROR_INTERVAL * 1000 / UI_UPDATE_INTERVAL; ++i) { trayIcon.doUiRefreshNow(); //throw AbortMonitoring diff --git a/FreeFileSync/Source/RealtimeSync/xml_proc.cpp b/FreeFileSync/Source/RealtimeSync/xml_proc.cpp index b095daa7..59b0336c 100644 --- a/FreeFileSync/Source/RealtimeSync/xml_proc.cpp +++ b/FreeFileSync/Source/RealtimeSync/xml_proc.cpp @@ -99,7 +99,7 @@ xmlAccess::XmlRealConfig convertBatchToReal(const xmlAccess::XmlBatchConfig& bat uniqueFolders.insert(fp.folderPathPhraseRight_); } - uniqueFolders.erase(Zstring()); + erase_if(uniqueFolders, [](const Zstring& str) { return trimCpy(str).empty(); }); xmlAccess::XmlRealConfig output; output.directories.assign(uniqueFolders.begin(), uniqueFolders.end()); @@ -125,7 +125,7 @@ void xmlAccess::readRealOrBatchConfig(const Zstring& filepath, xmlAccess::XmlRea } -int xmlAccess::getProgramLanguage() +wxLanguage xmlAccess::getProgramLanguage() { xmlAccess::XmlGlobalSettings settings; std::wstring warningMsg; @@ -133,7 +133,7 @@ int xmlAccess::getProgramLanguage() { xmlAccess::readConfig(getGlobalConfigFile(), settings, warningMsg); //throw FileError } - catch (const FileError&) {} //user default language if error occurred + catch (const FileError&) {} //use default language if error occurred return settings.programLanguage; } diff --git a/FreeFileSync/Source/RealtimeSync/xml_proc.h b/FreeFileSync/Source/RealtimeSync/xml_proc.h index 9521b029..176be6c3 100644 --- a/FreeFileSync/Source/RealtimeSync/xml_proc.h +++ b/FreeFileSync/Source/RealtimeSync/xml_proc.h @@ -10,16 +10,15 @@ #include <vector> #include <zen/xml_io.h> #include <zen/zstring.h> - +#include <wx/language.h> namespace xmlAccess { struct XmlRealConfig { - XmlRealConfig() : delay(10) {} std::vector<Zstring> directories; Zstring commandline; - unsigned int delay; + unsigned int delay = 10; }; void readConfig(const Zstring& filepath, XmlRealConfig& config, std::wstring& warningMsg); //throw FileError @@ -29,7 +28,7 @@ void writeConfig(const XmlRealConfig& config, const Zstring& filepath); //throw //reuse (some of) FreeFileSync's xml files void readRealOrBatchConfig(const Zstring& filepath, xmlAccess::XmlRealConfig& config, std::wstring& warningMsg); //throw FileError -int getProgramLanguage(); +wxLanguage getProgramLanguage(); } #endif //XML_PROC_H_0813748158321813490 diff --git a/FreeFileSync/Source/algorithm.cpp b/FreeFileSync/Source/algorithm.cpp index 9a5379db..e49d6ead 100644 --- a/FreeFileSync/Source/algorithm.cpp +++ b/FreeFileSync/Source/algorithm.cpp @@ -194,7 +194,7 @@ const InSyncDescrFile& getDescriptor<RIGHT_SIDE>(const InSyncFile& dbFile) { ret template <SelectedSide side> inline -bool matchesDbEntry(const FilePair& file, const InSyncFolder::FileList::value_type* dbFile, unsigned int optTimeShiftHours) +bool matchesDbEntry(const FilePair& file, const InSyncFolder::FileList::value_type* dbFile, const std::vector<unsigned int>& ignoreTimeShiftMinutes) { if (file.isEmpty<side>()) return !dbFile; @@ -207,7 +207,7 @@ bool matchesDbEntry(const FilePair& file, const InSyncFolder::FileList::value_ty return file.getItemName<side>() == shortNameDb && //detect changes in case (windows) //respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes it's modification date by up to 2 seconds //we're not interested in "fileTimeTolerance" here! - sameFileTime(file.getLastWriteTime<side>(), descrDb.lastWriteTimeRaw, 2, optTimeShiftHours) && + sameFileTime(file.getLastWriteTime<side>(), descrDb.lastWriteTimeRaw, 2, ignoreTimeShiftMinutes) && file.getFileSize<side>() == dbFile->second.fileSize; //note: we do *not* consider FileId here, but are only interested in *visual* changes. Consider user moving data to some other medium, this is not a change! } @@ -215,7 +215,7 @@ bool matchesDbEntry(const FilePair& file, const InSyncFolder::FileList::value_ty //check whether database entry is in sync considering *current* comparison settings inline -bool stillInSync(const InSyncFile& dbFile, CompareVariant compareVar, int fileTimeTolerance, unsigned int optTimeShiftHours) +bool stillInSync(const InSyncFile& dbFile, CompareVariant compareVar, int fileTimeTolerance, const std::vector<unsigned int>& ignoreTimeShiftMinutes) { switch (compareVar) { @@ -223,7 +223,7 @@ bool stillInSync(const InSyncFile& dbFile, CompareVariant compareVar, int fileTi if (dbFile.cmpVar == CMP_BY_CONTENT) return true; //special rule: this is certainly "good enough" for CMP_BY_TIME_SIZE! return //case-sensitive short name match is a database invariant! - sameFileTime(dbFile.left.lastWriteTimeRaw, dbFile.right.lastWriteTimeRaw, fileTimeTolerance, optTimeShiftHours); + sameFileTime(dbFile.left.lastWriteTimeRaw, dbFile.right.lastWriteTimeRaw, fileTimeTolerance, ignoreTimeShiftMinutes); case CMP_BY_CONTENT: //case-sensitive short name match is a database invariant! @@ -245,7 +245,7 @@ const InSyncDescrLink& getDescriptor<RIGHT_SIDE>(const InSyncSymlink& dbLink) { //check whether database entry and current item match: *irrespective* of current comparison settings template <SelectedSide side> inline -bool matchesDbEntry(const SymlinkPair& symlink, const InSyncFolder::SymlinkList::value_type* dbSymlink, unsigned int optTimeShiftHours) +bool matchesDbEntry(const SymlinkPair& symlink, const InSyncFolder::SymlinkList::value_type* dbSymlink, const std::vector<unsigned int>& ignoreTimeShiftMinutes) { if (symlink.isEmpty<side>()) return !dbSymlink; @@ -257,13 +257,13 @@ bool matchesDbEntry(const SymlinkPair& symlink, const InSyncFolder::SymlinkList: return symlink.getItemName<side>() == shortNameDb && //respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes its modification date by up to 2 seconds - sameFileTime(symlink.getLastWriteTime<side>(), descrDb.lastWriteTimeRaw, 2, optTimeShiftHours); + sameFileTime(symlink.getLastWriteTime<side>(), descrDb.lastWriteTimeRaw, 2, ignoreTimeShiftMinutes); } //check whether database entry is in sync considering *current* comparison settings inline -bool stillInSync(const InSyncSymlink& dbLink, CompareVariant compareVar, int fileTimeTolerance, unsigned int optTimeShiftHours) +bool stillInSync(const InSyncSymlink& dbLink, CompareVariant compareVar, int fileTimeTolerance, const std::vector<unsigned int>& ignoreTimeShiftMinutes) { switch (compareVar) { @@ -271,7 +271,7 @@ bool stillInSync(const InSyncSymlink& dbLink, CompareVariant compareVar, int fil if (dbLink.cmpVar == CMP_BY_CONTENT) return true; //special rule: this is already "good enough" for CMP_BY_TIME_SIZE! return //case-sensitive short name match is a database invariant! - sameFileTime(dbLink.left.lastWriteTimeRaw, dbLink.right.lastWriteTimeRaw, fileTimeTolerance, optTimeShiftHours); + sameFileTime(dbLink.left.lastWriteTimeRaw, dbLink.right.lastWriteTimeRaw, fileTimeTolerance, ignoreTimeShiftMinutes); case CMP_BY_CONTENT: //case-sensitive short name match is a database invariant! @@ -318,7 +318,7 @@ private: DetectMovedFiles(BaseFolderPair& baseFolder, const InSyncFolder& dbFolder) : cmpVar (baseFolder.getCompVariant()), fileTimeTolerance(baseFolder.getFileTimeTolerance()), - optTimeShiftHours(baseFolder.getTimeShift()) + ignoreTimeShiftMinutes(baseFolder.getIgnoredTimeShift()) { recurse(baseFolder, &dbFolder); @@ -395,9 +395,9 @@ private: static bool sameSizeAndDate(const FilePair& file, const InSyncFile& dbFile) { return file.getFileSize<side>() == dbFile.fileSize && - sameFileTime(file.getLastWriteTime<side>(), getDescriptor<side>(dbFile).lastWriteTimeRaw, 2, 0); + sameFileTime(file.getLastWriteTime<side>(), getDescriptor<side>(dbFile).lastWriteTimeRaw, 2, {}); //- respect 2 second FAT/FAT32 precision! - //- a "optTimeShiftHours" != 0 may lead to false positive move detections => let's be conservative and not allow it + //- "ignoreTimeShiftMinutes" may lead to false positive move detections => let's be conservative and not allow it // (time shift is only ever required during FAT DST switches) //PS: *never* allow 2 sec tolerance as container predicate!! @@ -430,7 +430,7 @@ private: void findAndSetMovePair(const InSyncFile& dbFile) const { - if (stillInSync(dbFile, cmpVar, fileTimeTolerance, optTimeShiftHours)) + if (stillInSync(dbFile, cmpVar, fileTimeTolerance, ignoreTimeShiftMinutes)) if (FilePair* fileLeftOnly = getAssocFilePair<LEFT_SIDE>(dbFile, exLeftOnlyById, exLeftOnlyByPath)) if (sameSizeAndDate<LEFT_SIDE>(*fileLeftOnly, dbFile)) if (FilePair* fileRightOnly = getAssocFilePair<RIGHT_SIDE>(dbFile, exRightOnlyById, exRightOnlyByPath)) @@ -445,7 +445,7 @@ private: const CompareVariant cmpVar; const int fileTimeTolerance; - const unsigned int optTimeShiftHours; + const std::vector<unsigned int> ignoreTimeShiftMinutes; std::unordered_map<AFS::FileId, FilePair*, StringHash> exLeftOnlyById; //FilePair* == nullptr for duplicate ids! => consider aliasing through symlinks! std::unordered_map<AFS::FileId, FilePair*, StringHash> exRightOnlyById; //=> avoid ambiguity for mixtures of files/symlinks on one side and allow 1-1 mapping only! @@ -492,9 +492,9 @@ private: txtBothSidesChanged(_("Both sides have changed since last synchronization.")), txtNoSideChanged(_("Cannot determine sync-direction:") + L" \n" + _("No change since last synchronization.")), txtDbNotInSync(_("Cannot determine sync-direction:") + L" \n" + _("The database entry is not in sync considering current settings.")), - cmpVar (baseFolder.getCompVariant()), - fileTimeTolerance(baseFolder.getFileTimeTolerance()), - optTimeShiftHours(baseFolder.getTimeShift()) + cmpVar (baseFolder.getCompVariant()), + fileTimeTolerance (baseFolder.getFileTimeTolerance()), + ignoreTimeShiftMinutes(baseFolder.getIgnoredTimeShift()) { //-> considering filter not relevant: //if narrowing filter: all ok; if widening filter (if file ex on both sides -> conflict, fine; if file ex. on one side: copy to other side: fine) @@ -535,13 +535,13 @@ private: } //evaluation - const bool changeOnLeft = !matchesDbEntry<LEFT_SIDE >(file, dbEntry, optTimeShiftHours); - const bool changeOnRight = !matchesDbEntry<RIGHT_SIDE>(file, dbEntry, optTimeShiftHours); + const bool changeOnLeft = !matchesDbEntry<LEFT_SIDE >(file, dbEntry, ignoreTimeShiftMinutes); + const bool changeOnRight = !matchesDbEntry<RIGHT_SIDE>(file, dbEntry, ignoreTimeShiftMinutes); if (changeOnLeft != changeOnRight) { //if database entry not in sync according to current settings! -> do not set direction based on async status! - if (dbEntry && !stillInSync(dbEntry->second, cmpVar, fileTimeTolerance, optTimeShiftHours)) + if (dbEntry && !stillInSync(dbEntry->second, cmpVar, fileTimeTolerance, ignoreTimeShiftMinutes)) file.setSyncDirConflict(txtDbNotInSync); else file.setSyncDir(changeOnLeft ? SyncDirection::RIGHT : SyncDirection::LEFT); @@ -571,13 +571,13 @@ private: } //evaluation - const bool changeOnLeft = !matchesDbEntry<LEFT_SIDE >(symlink, dbEntry, optTimeShiftHours); - const bool changeOnRight = !matchesDbEntry<RIGHT_SIDE>(symlink, dbEntry, optTimeShiftHours); + const bool changeOnLeft = !matchesDbEntry<LEFT_SIDE >(symlink, dbEntry, ignoreTimeShiftMinutes); + const bool changeOnRight = !matchesDbEntry<RIGHT_SIDE>(symlink, dbEntry, ignoreTimeShiftMinutes); if (changeOnLeft != changeOnRight) { //if database entry not in sync according to current settings! -> do not set direction based on async status! - if (dbEntry && !stillInSync(dbEntry->second, cmpVar, fileTimeTolerance, optTimeShiftHours)) + if (dbEntry && !stillInSync(dbEntry->second, cmpVar, fileTimeTolerance, ignoreTimeShiftMinutes)) symlink.setSyncDirConflict(txtDbNotInSync); else symlink.setSyncDir(changeOnLeft ? SyncDirection::RIGHT : SyncDirection::LEFT); @@ -643,7 +643,7 @@ private: const CompareVariant cmpVar; const int fileTimeTolerance; - const unsigned int optTimeShiftHours; + const std::vector<unsigned int> ignoreTimeShiftMinutes; }; } diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index f3ceaa60..b3e2690b 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -28,6 +28,7 @@ #include <zen/win_ver.h> #include <zen/dll.h> #include "lib/app_user_mode_id.h" + #include <zen/service_notification.h> #elif defined ZEN_LINUX #include <gtk/gtk.h> @@ -39,6 +40,7 @@ using namespace xmlAccess; IMPLEMENT_APP(Application) + #ifdef _MSC_VER //catch CRT floating point errors: http://msdn.microsoft.com/en-us/library/k3backsw.aspx int _matherr(_Inout_ struct _exception* except) @@ -175,6 +177,7 @@ int Application::OnExit() { uninitializeHelp(); releaseWxLocale(); + cleanupResourceImages(); return wxApp::OnExit(); } @@ -219,29 +222,23 @@ void Application::onEnterEventLoop(wxEvent& event) int Application::OnRun() { - auto processException = [](const std::wstring& msg) - { - //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! - logError(utfCvrtTo<std::string>(msg)); - wxSafeShowMessage(L"FreeFileSync - " + _("An exception occurred"), msg); - }; - try { wxApp::OnRun(); } - catch (const std::exception& e) //catch all STL exceptions + catch (const std::bad_alloc& e) //the only kind of exception we don't want crash dumps for { - processException(utfCvrtTo<std::wstring>(e.what())); - return FFS_RC_EXCEPTION; - } - /* -> let it crash and create mini dump!!! - catch (...) - { - processException(L"Unknown error."); + logFatalError(e.what()); //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! + +#ifdef ZEN_WIN + showServiceMessage(utfCvrtTo<std::wstring>(e.what()), L"FreeFileSync - " + _("An exception occurred")); +#else //following will hang on Windows when FFS is run as a service! + wxSafeShowMessage(L"FreeFileSync - " + _("An exception occurred"), e.what()); +#endif return FFS_RC_EXCEPTION; } - */ + //catch (...) -> let it crash and create mini dump!!! + return returnCode; } @@ -275,9 +272,20 @@ void Application::launch(const std::vector<Zstring>& commandArgs) } catch (const FileError&) { assert(false); } - auto notifyError = [&](const std::wstring& msg, const std::wstring& title) + auto notifyFatalError = [&](const std::wstring& msg, const std::wstring& title) { - showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setTitle(title).setDetailInstructions(msg)); + 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(utfCvrtTo<std::string>(msg)); + +#ifdef ZEN_WIN + showServiceMessage(msg, titleTmp); +#else //following will hang on Windows when FFS is run as a service! + wxSafeShowMessage(title, msg); +#endif + raiseReturnCode(returnCode, FFS_RC_ABORTED); }; @@ -312,7 +320,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) { if (++it == commandArgs.end()) { - notifyError(replaceCpy(_("A directory path is expected after %x."), L"%x", utfCvrtTo<std::wstring>(optionLeftDir)), _("Syntax error")); + notifyFatalError(replaceCpy(_("A directory path is expected after %x."), L"%x", utfCvrtTo<std::wstring>(optionLeftDir)), _("Syntax error")); return; } dirPathPhrasesLeft.push_back(*it); @@ -321,7 +329,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) { if (++it == commandArgs.end()) { - notifyError(replaceCpy(_("A directory path is expected after %x."), L"%x", utfCvrtTo<std::wstring>(optionRightDir)), _("Syntax error")); + notifyFatalError(replaceCpy(_("A directory path is expected after %x."), L"%x", utfCvrtTo<std::wstring>(optionRightDir)), _("Syntax error")); return; } dirPathPhrasesRight.push_back(*it); @@ -340,7 +348,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) filePath += Zstr(".xml"); else { - notifyError(replaceCpy(_("Cannot find file %x."), L"%x", fmtPath(filePath)), std::wstring()); + notifyFatalError(replaceCpy(_("Cannot find file %x."), L"%x", fmtPath(filePath)), _("Error")); return; } } @@ -359,13 +367,13 @@ void Application::launch(const std::vector<Zstring>& commandArgs) globalConfigFile = filePath; break; case XML_TYPE_OTHER: - notifyError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath)), std::wstring()); + notifyFatalError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath)), _("Error")); return; } } catch (const FileError& e) { - notifyError(e.toString(), std::wstring()); + notifyFatalError(e.toString(), _("Error")); return; } } @@ -373,7 +381,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) if (dirPathPhrasesLeft.size() != dirPathPhrasesRight.size()) { - notifyError(_("Unequal number of left and right directories specified."), _("Syntax error")); + notifyFatalError(_("Unequal number of left and right directories specified."), _("Syntax error")); return; } @@ -391,7 +399,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) //check if config at folder-pair level is present: this probably doesn't make sense when replacing/adding the user-specified directories if (hasNonDefaultConfig(mainCfg.firstPair) || std::any_of(mainCfg.additionalPairs.begin(), mainCfg.additionalPairs.end(), hasNonDefaultConfig)) { - notifyError(_("The config file must not contain settings at directory pair level when directories are set via command line."), _("Syntax error")); + notifyFatalError(_("The config file must not contain settings at directory pair level when directories are set via command line."), _("Syntax error")); return false; } @@ -447,7 +455,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) } catch (const FileError& e) { - notifyError(e.toString(), std::wstring()); + notifyFatalError(e.toString(), _("Error")); return; } if (!replaceDirectories(batchCfg.mainCfg)) @@ -469,7 +477,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) } catch (const FileError& e) { - notifyError(e.toString(), std::wstring()); + notifyFatalError(e.toString(), _("Error")); return; } if (!replaceDirectories(guiCfg.mainCfg)) @@ -485,7 +493,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) { if (!dirPathPhrasesLeft.empty()) { - notifyError(_("Directories cannot be set for more than one configuration file."), _("Syntax error")); + notifyFatalError(_("Directories cannot be set for more than one configuration file."), _("Syntax error")); return; } @@ -505,7 +513,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) } catch (const FileError& e) { - notifyError(e.toString(), std::wstring()); + notifyFatalError(e.toString(), _("Error")); return; } runGuiMode(globalConfigFilePath, guiCfg, filepaths, !openForEdit); @@ -561,7 +569,7 @@ void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCf if (batchCfg.handleError == ON_ERROR_POPUP) showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(msg)); else //"exit" or "ignore" - logError(utfCvrtTo<std::string>(msg)); + logFatalError(utfCvrtTo<std::string>(msg)); raiseReturnCode(returnCode, rc); }; diff --git a/FreeFileSync/Source/application.h b/FreeFileSync/Source/application.h index 69250c32..a3c356c2 100644 --- a/FreeFileSync/Source/application.h +++ b/FreeFileSync/Source/application.h @@ -15,20 +15,17 @@ class Application : public wxApp { -public: - Application() : returnCode(zen::FFS_RC_SUCCESS) {} - private: bool OnInit() override; - int OnExit() override; int OnRun() override; + int OnExit() override; bool OnExceptionInMainLoop() override { throw; } //just re-throw and avoid display of additional messagebox: it will be caught in OnRun() void onEnterEventLoop(wxEvent& event); void onQueryEndSession(wxEvent& event); void launch(const std::vector<Zstring>& commandArgs); - zen::FfsReturnCode returnCode; + zen::FfsReturnCode returnCode = zen::FFS_RC_SUCCESS; }; #endif //APPLICATION_H_081568741942010985702395 diff --git a/FreeFileSync/Source/comparison.cpp b/FreeFileSync/Source/comparison.cpp index 1a3cb6d5..266604b3 100644 --- a/FreeFileSync/Source/comparison.cpp +++ b/FreeFileSync/Source/comparison.cpp @@ -32,7 +32,7 @@ std::vector<FolderPairCfg> zen::extractCompareCfg(const MainConfiguration& mainC enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->compareVar : mainCfg.cmpConfig.compareVar, enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->handleSymlinks : mainCfg.cmpConfig.handleSymlinks, fileTimeTolerance, - enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->optTimeShiftHours : mainCfg.cmpConfig.optTimeShiftHours, + enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->ignoreTimeShiftMinutes : mainCfg.cmpConfig.ignoreTimeShiftMinutes, normalizeFilters(mainCfg.globalFilter, enhPair.localFilter), @@ -275,11 +275,11 @@ std::wstring getDescrDiffMetaDate(const FileOrLinkPair& file) //----------------------------------------------------------------------------- -void categorizeSymlinkByTime(SymlinkPair& symlink, int fileTimeTolerance, unsigned int optTimeShiftHours) +void categorizeSymlinkByTime(SymlinkPair& symlink) { //categorize symlinks that exist on both sides switch (compareFileTime(symlink.getLastWriteTime<LEFT_SIDE>(), - symlink.getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance, optTimeShiftHours)) + symlink.getLastWriteTime<RIGHT_SIDE>(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift())) { case TimeResult::EQUAL: //Caveat: @@ -320,13 +320,13 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareByTimeSize(const Resolv //finish symlink categorization for (SymlinkPair* symlink : uncategorizedLinks) - categorizeSymlinkByTime(*symlink, fpConfig.fileTimeTolerance, fpConfig.optTimeShiftHours); + categorizeSymlinkByTime(*symlink); //categorize files that exist on both sides for (FilePair* file : uncategorizedFiles) { switch (compareFileTime(file->getLastWriteTime<LEFT_SIDE>(), - file->getLastWriteTime<RIGHT_SIDE>(), fpConfig.fileTimeTolerance, fpConfig.optTimeShiftHours)) + file->getLastWriteTime<RIGHT_SIDE>(), fpConfig.fileTimeTolerance, fpConfig.ignoreTimeShiftMinutes)) { case TimeResult::EQUAL: //Caveat: @@ -365,7 +365,7 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareByTimeSize(const Resolv } -void categorizeSymlinkByContent(SymlinkPair& symlink, int fileTimeTolerance, unsigned int optTimeShiftHours, ProcessCallback& callback) +void categorizeSymlinkByContent(SymlinkPair& symlink, ProcessCallback& callback) { //categorize symlinks that exist on both sides Zstring targetPathRawL; @@ -400,7 +400,7 @@ void categorizeSymlinkByContent(SymlinkPair& symlink, int fileTimeTolerance, uns if (symlink.getItemName<LEFT_SIDE>() != symlink.getItemName<RIGHT_SIDE>()) symlink.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(symlink)); else if (!sameFileTime(symlink.getLastWriteTime<LEFT_SIDE>(), - symlink.getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance, optTimeShiftHours)) + symlink.getLastWriteTime<RIGHT_SIDE>(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift())) symlink.setCategoryDiffMetadata(getDescrDiffMetaDate(symlink)); else symlink.setCategory<FILE_EQUAL>(); @@ -447,7 +447,7 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co //finish symlink categorization for (SymlinkPair* symlink : uncategorizedLinks) - categorizeSymlinkByContent(*symlink, w.second.fileTimeTolerance, w.second.optTimeShiftHours, callback_); + categorizeSymlinkByContent(*symlink, callback_); } //finish categorization... @@ -499,7 +499,7 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co if (file->getItemName<LEFT_SIDE>() != file->getItemName<RIGHT_SIDE>()) file->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*file)); else if (!sameFileTime(file->getLastWriteTime<LEFT_SIDE>(), - file->getLastWriteTime<RIGHT_SIDE>(), file->base().getFileTimeTolerance(), file->base().getTimeShift())) + file->getLastWriteTime<RIGHT_SIDE>(), file->base().getFileTimeTolerance(), file->base().getIgnoredTimeShift())) file->setCategoryDiffMetadata(getDescrDiffMetaDate(*file)); else file->setCategory<FILE_EQUAL>(); @@ -593,8 +593,6 @@ void MergeSides::fillOneSide(const FolderContainer& folderCont, const std::wstri template <class MapType, class ProcessLeftOnly, class ProcessRightOnly, class ProcessBoth> inline void linearMerge(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOnly lo, ProcessRightOnly ro, ProcessBoth bo) { - const auto lessKey = typename MapType::key_compare(); - auto itL = mapLeft .begin(); auto itR = mapRight.begin(); @@ -604,6 +602,8 @@ void linearMerge(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOnl if (itL == mapLeft .end()) return finishRight(); if (itR == mapRight.end()) return finishLeft (); + const auto lessKey = typename MapType::key_compare(); + for (;;) if (lessKey(itL->first, itR->first)) { @@ -630,7 +630,7 @@ void linearMerge(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOnl void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer& rhs, const std::wstring* errorMsg, HierarchyObject& output) { - typedef const FolderContainer::FileList::value_type FileData; + using FileData = const FolderContainer::FileList::value_type; linearMerge(lhs.files, rhs.files, [&](const FileData& fileLeft ) { FilePair& newItem = output.addSubFile<LEFT_SIDE >(fileLeft .first, fileLeft .second); checkFailedRead(newItem, errorMsg); }, //left only @@ -649,7 +649,7 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer }); //----------------------------------------------------------------------------------------------- - typedef const FolderContainer::SymlinkList::value_type SymlinkData; + using SymlinkData = const FolderContainer::SymlinkList::value_type; linearMerge(lhs.symlinks, rhs.symlinks, [&](const SymlinkData& symlinkLeft ) { SymlinkPair& newItem = output.addSubLink<LEFT_SIDE >(symlinkLeft .first, symlinkLeft .second); checkFailedRead(newItem, errorMsg); }, //left only @@ -667,7 +667,7 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer }); //----------------------------------------------------------------------------------------------- - typedef const FolderContainer::FolderList::value_type FolderData; + using FolderData = const FolderContainer::FolderList::value_type; linearMerge(lhs.folders, rhs.folders, [&](const FolderData& dirLeft) //left only @@ -768,7 +768,7 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv fpCfg.filter.nameFilter->copyFilterAddingExclusion(excludefilterFailedRead), fpCfg.compareVar, fpCfg.fileTimeTolerance, - fpCfg.optTimeShiftHours); + fpCfg.ignoreTimeShiftMinutes); //PERF_START; FolderContainer emptyFolderCont; //WTF!!! => using a temporary in the ternary conditional would implicitly call the FolderContainer copy-constructor!!!!!! diff --git a/FreeFileSync/Source/comparison.h b/FreeFileSync/Source/comparison.h index 0614378b..537c64a4 100644 --- a/FreeFileSync/Source/comparison.h +++ b/FreeFileSync/Source/comparison.h @@ -23,7 +23,7 @@ struct FolderPairCfg CompareVariant cmpVar, SymLinkHandling handleSymlinksIn, int fileTimeToleranceIn, - unsigned int optTimeShiftHoursIn, + const std::vector<unsigned int>& ignoreTimeShiftMinutesIn, const NormalizedFilter& filterIn, const DirectionConfig& directCfg) : folderPathPhraseLeft_ (folderPathPhraseLeft), @@ -31,7 +31,7 @@ struct FolderPairCfg compareVar(cmpVar), handleSymlinks(handleSymlinksIn), fileTimeTolerance(fileTimeToleranceIn), - optTimeShiftHours(optTimeShiftHoursIn), + ignoreTimeShiftMinutes(ignoreTimeShiftMinutesIn), filter(filterIn), directionCfg(directCfg) {} @@ -41,7 +41,7 @@ struct FolderPairCfg CompareVariant compareVar; SymLinkHandling handleSymlinks; int fileTimeTolerance; - unsigned int optTimeShiftHours; + std::vector<unsigned int> ignoreTimeShiftMinutes; NormalizedFilter filter; diff --git a/FreeFileSync/Source/file_hierarchy.h b/FreeFileSync/Source/file_hierarchy.h index 9737652f..1e7741ad 100644 --- a/FreeFileSync/Source/file_hierarchy.h +++ b/FreeFileSync/Source/file_hierarchy.h @@ -237,9 +237,9 @@ public: const HardFilter::FilterRef& filter, CompareVariant cmpVar, int fileTimeTolerance, - unsigned int optTimeShiftHours) : + const std::vector<unsigned int>& ignoreTimeShiftMinutes) : HierarchyObject(Zstring(), *this), - filter_(filter), cmpVar_(cmpVar), fileTimeTolerance_(fileTimeTolerance), optTimeShiftHours_(optTimeShiftHours), + filter_(filter), cmpVar_(cmpVar), fileTimeTolerance_(fileTimeTolerance), ignoreTimeShiftMinutes_(ignoreTimeShiftMinutes), dirExistsLeft_ (dirExistsLeft), dirExistsRight_(dirExistsRight), folderPathLeft_(folderPathLeft), @@ -256,7 +256,7 @@ public: const HardFilter& getFilter() const { return *filter_; } CompareVariant getCompVariant() const { return cmpVar_; } int getFileTimeTolerance() const { return fileTimeTolerance_; } - unsigned int getTimeShift() const { return optTimeShiftHours_; } + const std::vector<unsigned int>& getIgnoredTimeShift() const { return ignoreTimeShiftMinutes_; } void flip() override; @@ -264,7 +264,7 @@ private: const HardFilter::FilterRef filter_; //filter used while scanning directory: represents sub-view of actual files! const CompareVariant cmpVar_; const int fileTimeTolerance_; - const unsigned int optTimeShiftHours_; + const std::vector<unsigned int> ignoreTimeShiftMinutes_; bool dirExistsLeft_; bool dirExistsRight_; diff --git a/FreeFileSync/Source/fs/native.cpp b/FreeFileSync/Source/fs/native.cpp index 75c3de9c..a592f069 100644 --- a/FreeFileSync/Source/fs/native.cpp +++ b/FreeFileSync/Source/fs/native.cpp @@ -691,7 +691,13 @@ bool zen::acceptsItemPathPhraseNative(const Zstring& itemPathPhrase) //noexcept AbstractPath zen::createItemPathNative(const Zstring& itemPathPhrase) //noexcept { + warn_static("get volume by name hangs for idle HDD! => run createItemPathNative during getFolderStatusNonBlocking() but getResolvedFilePath currently not thread-safe!") const Zstring itemPathImpl = getResolvedFilePath(itemPathPhrase); return AbstractPath(std::make_shared<NativeFileSystem>(), itemPathImpl); - warn_static("get volume by name hangs for idle HDD! => run createItemPathNative during getFolderStatusNonBlocking() but getResolvedFilePath currently not thread-safe!") +} + + +AbstractPath zen::createItemPathNativeNoFormatting(const Zstring& nativePath) //noexcept +{ + return AbstractPath(std::make_shared<NativeFileSystem>(), nativePath); } diff --git a/FreeFileSync/Source/fs/native.h b/FreeFileSync/Source/fs/native.h index 2765eace..444cd93f 100644 --- a/FreeFileSync/Source/fs/native.h +++ b/FreeFileSync/Source/fs/native.h @@ -13,6 +13,8 @@ namespace zen { bool acceptsItemPathPhraseNative (const Zstring& itemPathPhrase); //noexcept AbstractPath createItemPathNative(const Zstring& itemPathPhrase); //noexcept + +AbstractPath createItemPathNativeNoFormatting(const Zstring& nativePath); //noexcept } #endif //FS_NATIVE_183247018532434563465 diff --git a/FreeFileSync/Source/lib/cmp_filetime.h b/FreeFileSync/Source/lib/cmp_filetime.h index fe1be035..d21005b8 100644 --- a/FreeFileSync/Source/lib/cmp_filetime.h +++ b/FreeFileSync/Source/lib/cmp_filetime.h @@ -10,32 +10,45 @@ #include <ctime> #include <algorithm> + namespace zen { -//--------------------------------------------------------------------------------------------------------------- inline -bool sameFileTime(std::int64_t lhs, std::int64_t rhs, int tolerance, unsigned int optTimeShiftHours) +bool sameFileTime(std::int64_t lhs, std::int64_t rhs, int tolerance, const std::vector<unsigned int>& ignoreTimeShiftMinutes) { - if (tolerance < 0) //= unlimited tolerance by convention! + if (tolerance < 0) //:= unlimited tolerance by convention! return true; if (lhs < rhs) std::swap(lhs, rhs); - if (lhs - rhs <= tolerance) + if (rhs > std::numeric_limits<std::int64_t>::max() - tolerance) //protect against overflow! return true; - if (optTimeShiftHours > 0) + if (lhs <= rhs + tolerance) + return true; + + for (unsigned int minutes : ignoreTimeShiftMinutes) { - const int shiftSec = static_cast<int>(optTimeShiftHours) * 3600; - if (rhs <= std::numeric_limits<std::int64_t>::max() - shiftSec) //protect against integer overflow! - { - const std::int64_t low = std::min(rhs + shiftSec, lhs); - const std::int64_t high = std::max(rhs + shiftSec, lhs); - - if (high - low <= tolerance) - return true; - } + assert(minutes > 0); + const int shiftSec = static_cast<int>(minutes) * 60; + + std::int64_t low = rhs; + std::int64_t high = lhs; + + if (low <= std::numeric_limits<std::int64_t>::max() - shiftSec) //protect against overflow! + low += shiftSec; + else + high -= shiftSec; + + if (high < low) + std::swap(high, low); + + if (low > std::numeric_limits<std::int64_t>::max() - tolerance) //protect against overflow! + return true; + + if (high <= low + tolerance) + return true; } return false; @@ -54,7 +67,7 @@ enum class TimeResult inline -TimeResult compareFileTime(std::int64_t lhs, std::int64_t rhs, int tolerance, unsigned int optTimeShiftHours) +TimeResult compareFileTime(std::int64_t lhs, std::int64_t rhs, int tolerance, const std::vector<unsigned int>& ignoreTimeShiftMinutes) { #if defined _MSC_VER && _MSC_VER < 1900 #error function scope static initialization is not yet thread-safe! @@ -63,7 +76,7 @@ TimeResult compareFileTime(std::int64_t lhs, std::int64_t rhs, int tolerance, un //number of seconds since Jan 1st 1970 + 1 year (needn't be too precise) static const std::int64_t oneYearFromNow = std::time(nullptr) + 365 * 24 * 3600; - if (sameFileTime(lhs, rhs, tolerance, optTimeShiftHours)) //last write time may differ by up to 2 seconds (NTFS vs FAT32) + if (sameFileTime(lhs, rhs, tolerance, ignoreTimeShiftMinutes)) //last write time may differ by up to 2 seconds (NTFS vs FAT32) return TimeResult::EQUAL; //check for erroneous dates diff --git a/FreeFileSync/Source/lib/dir_lock.cpp b/FreeFileSync/Source/lib/dir_lock.cpp index 5e837e15..abd8d633 100644 --- a/FreeFileSync/Source/lib/dir_lock.cpp +++ b/FreeFileSync/Source/lib/dir_lock.cpp @@ -572,7 +572,7 @@ bool tryLock(const Zstring& lockFilePath) //throw FileError //O_EXCL contains a race condition on NFS file systems: http://linux.die.net/man/2/open const int fileHandle = ::open(lockFilePath.c_str(), O_CREAT | O_EXCL | O_WRONLY, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666 if (fileHandle == -1) { if (errno == EEXIST) diff --git a/FreeFileSync/Source/lib/error_log.h b/FreeFileSync/Source/lib/error_log.h index 904f5946..0102bebd 100644 --- a/FreeFileSync/Source/lib/error_log.h +++ b/FreeFileSync/Source/lib/error_log.h @@ -16,7 +16,7 @@ namespace zen { //write error message to a file (even with corrupted stack)- call in desperate situations when no other means of error handling is available -void logError(const std::string& msg); //throw() +void logFatalError(const std::string& msg); //throw() @@ -28,7 +28,7 @@ void logError(const std::string& msg); //throw() //##################### implementation ############################ inline -void logError(const std::string& msg) //throw() +void logFatalError(const std::string& msg) //throw() { assert(false); //this is stuff we like to debug const std::string logEntry = "[" + formatTime<std::string>(FORMAT_DATE) + " "+ formatTime<std::string>(FORMAT_TIME) + "] " + msg; diff --git a/FreeFileSync/Source/lib/hard_filter.cpp b/FreeFileSync/Source/lib/hard_filter.cpp index 19bde7fd..a863a5cd 100644 --- a/FreeFileSync/Source/lib/hard_filter.cpp +++ b/FreeFileSync/Source/lib/hard_filter.cpp @@ -26,7 +26,7 @@ bool zen::operator<(const HardFilter& lhs, const HardFilter& rhs) namespace { -//constructing them in addFilterEntry becomes perf issue for large filter lists +//constructing these in addFilterEntry becomes perf issue for large filter lists: const Zstring asterisk = Zstr("*"); const Zstring sepAsterisk = FILE_NAME_SEPARATOR + asterisk; const Zstring asteriskSep = asterisk + FILE_NAME_SEPARATOR; @@ -341,8 +341,8 @@ bool NameFilter::isNull(const Zstring& includePhrase, const Zstring& excludePhra bool NameFilter::isNull() const { - static NameFilter output(Zstr("*"), Zstring()); - return *this == output; + static NameFilter nullInstance(Zstr("*"), Zstring()); + return *this == nullInstance; } diff --git a/FreeFileSync/Source/lib/help_provider.h b/FreeFileSync/Source/lib/help_provider.h index e84e8f34..629501ce 100644 --- a/FreeFileSync/Source/lib/help_provider.h +++ b/FreeFileSync/Source/lib/help_provider.h @@ -54,7 +54,7 @@ public: chmHlp->DisplaySection(replaceCpy(section, L'/', utfCvrtTo<wxString>(FILE_NAME_SEPARATOR))); } - void uninitialize() + void uninitialize() //avoid static init/teardown order fiasco { if (chmHlp) { diff --git a/FreeFileSync/Source/lib/icon_buffer.cpp b/FreeFileSync/Source/lib/icon_buffer.cpp index 617c6489..25bc158e 100644 --- a/FreeFileSync/Source/lib/icon_buffer.cpp +++ b/FreeFileSync/Source/lib/icon_buffer.cpp @@ -53,6 +53,7 @@ wxBitmap extractWxBitmap(ImageHolder&& ih) #ifdef ZEN_WIN +#pragma warning(suppress: 4592) //"symbol will be dynamically initialized (implementation limitation)" const std::set<Zstring, LessFilePath> linkExt { L"lnk", L"pif", L"url", L"website" }; @@ -335,10 +336,10 @@ private: }; -class RunOnStartup +class InitFileIconCacheOnStartup { public: - RunOnStartup() + InitFileIconCacheOnStartup() { #ifdef ZEN_WIN //icon_loader.h/file_icon_win.h prerequisites: 1. initialize COM, 2. initialize system image list diff --git a/FreeFileSync/Source/lib/localization.cpp b/FreeFileSync/Source/lib/localization.cpp index 12e50757..2bfc7715 100644 --- a/FreeFileSync/Source/lib/localization.cpp +++ b/FreeFileSync/Source/lib/localization.cpp @@ -36,7 +36,7 @@ namespace class FFSTranslation : public TranslationHandler { public: - FFSTranslation(const Zstring& filepath, wxLanguage languageId); //throw lngfile::ParsingError, parse_plural::ParsingError + FFSTranslation(const Zstring& lngFilePath, wxLanguage langId); //throw lngfile::ParsingError, parse_plural::ParsingError wxLanguage langId() const { return langId_; } @@ -68,21 +68,21 @@ private: Translation transMapping; //map original text |-> translation TranslationPlural transMappingPl; std::unique_ptr<parse_plural::PluralForm> pluralParser; //bound! - wxLanguage langId_; + const wxLanguage langId_; }; -FFSTranslation::FFSTranslation(const Zstring& filepath, wxLanguage languageId) : langId_(languageId) //throw lngfile::ParsingError, parse_plural::ParsingError +FFSTranslation::FFSTranslation(const Zstring& lngFilePath, wxLanguage langId) : langId_(langId) //throw lngfile::ParsingError, parse_plural::ParsingError { std::string inputStream; try { - inputStream = loadBinStream<std::string>(filepath, nullptr); //throw FileError + inputStream = loadBinStream<std::string>(lngFilePath, nullptr); //throw FileError } catch (const FileError& e) { throw lngfile::ParsingError(e.toString(), 0, 0); - //passing FileError is too high a level for Parsing error, OTOH user is unlikely to see this since file I/O issues are sorted out by ExistingTranslations()! + //passing FileError is too high a level for Parsing error, OTOH user is unlikely to see this since file I/O issues are sorted out by getExistingTranslations()! } lngfile::TransHeader header; @@ -115,7 +115,7 @@ FFSTranslation::FFSTranslation(const Zstring& filepath, wxLanguage languageId) : struct LessTranslation { - bool operator()(const ExistingTranslations::Entry& lhs, const ExistingTranslations::Entry& rhs) const + bool operator()(const TranslationInfo& lhs, const TranslationInfo& rhs) const { //use a more "natural" sort: ignore case and diacritics #ifdef ZEN_WIN @@ -145,14 +145,14 @@ struct LessTranslation #endif } }; -} -ExistingTranslations::ExistingTranslations() +std::vector<TranslationInfo> loadTranslations() { + std::vector<TranslationInfo> locMapping; { //default entry: - ExistingTranslations::Entry newEntry; + TranslationInfo newEntry; newEntry.languageID = wxLANGUAGE_ENGLISH_US; newEntry.languageName = L"English (US)"; newEntry.languageFile = L""; @@ -184,13 +184,17 @@ ExistingTranslations::ExistingTranslations() assert(!lngHeader.localeName .empty()); assert(!lngHeader.flagFile .empty()); /* - There is some buggy behavior in wxWidgets which maps "zh_TW" to simplified chinese. - Fortunately locales can be also entered as description. => use "Chinese (Traditional)" which works fine. + Some ISO codes are used by multiple wxLanguage ids which can lead to incorrect mapping!!! + => Identify by description, e.g. "Chinese (Traditional)". The following ids are affected: + wxLANGUAGE_CHINESE_TRADITIONAL + wxLANGUAGE_ENGLISH_UK + wxLANGUAGE_SPANISH //non-unique, but still mapped correctly (or is it incidentally???) + wxLANGUAGE_SERBIAN // */ if (const wxLanguageInfo* locInfo = wxLocale::FindLanguageInfo(utfCvrtTo<wxString>(lngHeader.localeName))) { - ExistingTranslations::Entry newEntry; - newEntry.languageID = locInfo->Language; + TranslationInfo newEntry; + newEntry.languageID = static_cast<wxLanguage>(locInfo->Language); newEntry.languageName = utfCvrtTo<std::wstring>(lngHeader.languageName); newEntry.languageFile = utfCvrtTo<std::wstring>(filepath); newEntry.translatorName = utfCvrtTo<std::wstring>(lngHeader.translatorName); @@ -204,18 +208,10 @@ ExistingTranslations::ExistingTranslations() } std::sort(locMapping.begin(), locMapping.end(), LessTranslation()); + return locMapping; } -const std::vector<ExistingTranslations::Entry>& ExistingTranslations::get() -{ - static ExistingTranslations instance; - return instance.locMapping; -} - - -namespace -{ wxLanguage mapLanguageDialect(wxLanguage language) { switch (static_cast<int>(language)) //avoid enumeration value wxLANGUAGE_*' not handled in switch [-Wswitch-enum] @@ -412,22 +408,29 @@ private: } +const std::vector<TranslationInfo>& zen::getExistingTranslations() +{ + static const std::vector<TranslationInfo> translations = loadTranslations(); + return translations; +} + + void zen::releaseWxLocale() { wxWidgetsLocale::getInstance().release(); } -void zen::setLanguage(int language) //throw FileError +void zen::setLanguage(wxLanguage lng) //throw FileError { - if (language == getLanguage() && wxWidgetsLocale::getInstance().getLanguage() == language) + if (getLanguage() == lng && wxWidgetsLocale::getInstance().getLanguage() == lng) return; //support polling //(try to) retrieve language file std::wstring languageFile; - for (const ExistingTranslations::Entry& e : ExistingTranslations::get()) - if (e.languageID == language) + for (const TranslationInfo& e : getExistingTranslations()) + if (e.languageID == lng) { languageFile = e.languageFile; break; @@ -439,7 +442,7 @@ void zen::setLanguage(int language) //throw FileError else try { - zen::setTranslator(std::make_unique<FFSTranslation>(utfCvrtTo<Zstring>(languageFile), static_cast<wxLanguage>(language))); //throw lngfile::ParsingError, parse_plural::ParsingError + zen::setTranslator(std::make_unique<FFSTranslation>(utfCvrtTo<Zstring>(languageFile), lng)); //throw lngfile::ParsingError, parse_plural::ParsingError } catch (lngfile::ParsingError& e) { @@ -455,18 +458,18 @@ void zen::setLanguage(int language) //throw FileError } //handle RTL swapping: we need wxWidgets to do this - wxWidgetsLocale::getInstance().init(languageFile.empty() ? wxLANGUAGE_ENGLISH : static_cast<wxLanguage>(language)); + wxWidgetsLocale::getInstance().init(languageFile.empty() ? wxLANGUAGE_ENGLISH : lng); } -int zen::getLanguage() +wxLanguage zen::getLanguage() { const FFSTranslation* loc = dynamic_cast<const FFSTranslation*>(zen::getTranslator()); return loc ? loc->langId() : wxLANGUAGE_ENGLISH_US; } -int zen::retrieveSystemLanguage() +wxLanguage zen::getSystemLanguage() { return mapLanguageDialect(static_cast<wxLanguage>(wxLocale::GetSystemLanguage())); } diff --git a/FreeFileSync/Source/lib/localization.h b/FreeFileSync/Source/lib/localization.h index 3663ac5b..efc0fa1a 100644 --- a/FreeFileSync/Source/lib/localization.h +++ b/FreeFileSync/Source/lib/localization.h @@ -9,33 +9,24 @@ #include <vector> #include <zen/file_error.h> +#include <wx/language.h> namespace zen { -class ExistingTranslations +struct TranslationInfo { -public: - struct Entry - { - int languageID; - std::wstring languageName; - std::wstring languageFile; - std::wstring translatorName; - std::wstring languageFlag; - }; - static const std::vector<Entry>& get(); - -private: - ExistingTranslations(); - ExistingTranslations (const ExistingTranslations&) = delete; - ExistingTranslations& operator=(const ExistingTranslations&) = delete; - std::vector<Entry> locMapping; + wxLanguage languageID = wxLANGUAGE_UNKNOWN; + std::wstring languageName; + std::wstring languageFile; + std::wstring translatorName; + std::wstring languageFlag; }; +const std::vector<TranslationInfo>& getExistingTranslations(); -void setLanguage(int language); //throw FileError -int getLanguage(); -int retrieveSystemLanguage(); +void setLanguage(wxLanguage lng); //throw FileError +wxLanguage getLanguage(); +wxLanguage getSystemLanguage(); void releaseWxLocale(); //wxLocale crashes miserably on wxGTK when destructor runs during global cleanup => call in wxApp::OnExit //"You should delete all wxWidgets object that you created by the time OnExit finishes. In particular, do not destroy them from application class' destructor!" diff --git a/FreeFileSync/Source/lib/parallel_scan.cpp b/FreeFileSync/Source/lib/parallel_scan.cpp index 2af42074..05ba6671 100644 --- a/FreeFileSync/Source/lib/parallel_scan.cpp +++ b/FreeFileSync/Source/lib/parallel_scan.cpp @@ -525,7 +525,8 @@ void zen::fillBuffer(const std::set<DirectoryKey>& keysToRead, //in FixedList<InterruptibleThread> worker; - ZEN_ON_SCOPE_FAIL( + ZEN_ON_SCOPE_FAIL + ( for (InterruptibleThread& wt : worker) wt.interrupt(); //interrupt all at once first, then join for (InterruptibleThread& wt : worker) diff --git a/FreeFileSync/Source/lib/parse_lng.h b/FreeFileSync/Source/lib/parse_lng.h index cb7866ef..78c45263 100644 --- a/FreeFileSync/Source/lib/parse_lng.h +++ b/FreeFileSync/Source/lib/parse_lng.h @@ -198,8 +198,8 @@ public: static const TokenMap& getList() { - static KnownTokens inst; - return inst.tokens; + static const TokenMap tokens = getTokens(); + return tokens; } static std::string text(Token::Type t) @@ -209,8 +209,9 @@ public: } private: - KnownTokens() + static TokenMap getTokens() { + TokenMap tokens; //header information tokens.emplace(Token::TK_HEADER_BEGIN, "<header>"); tokens.emplace(Token::TK_HEADER_END, "</header>"); @@ -234,8 +235,8 @@ private: tokens.emplace(Token::TK_TRG_END, "</target>"); tokens.emplace(Token::TK_PLURAL_BEGIN, "<pluralform>"); tokens.emplace(Token::TK_PLURAL_END, "</pluralform>"); + return tokens; } - TokenMap tokens; }; diff --git a/FreeFileSync/Source/lib/process_xml.cpp b/FreeFileSync/Source/lib/process_xml.cpp index 9201133a..c0af04c2 100644 --- a/FreeFileSync/Source/lib/process_xml.cpp +++ b/FreeFileSync/Source/lib/process_xml.cpp @@ -10,6 +10,8 @@ #include <zen/file_access.h> #include <zen/file_io.h> #include <zen/xml_io.h> +#include <zen/optional.h> +#include <wx/intl.h> #include "ffs_paths.h" using namespace zen; @@ -19,9 +21,9 @@ using namespace std::rel_ops; namespace { //------------------------------------------------------------------------------------------------------------------------------- -const int XML_FORMAT_VER_GLOBAL = 1; -const int XML_FORMAT_VER_FFS_GUI = 4; //for FFS 6.8 -const int XML_FORMAT_VER_FFS_BATCH = 4; // +const int XML_FORMAT_VER_GLOBAL = 2; // +const int XML_FORMAT_VER_FFS_GUI = 5; //for FFS 7.7 +const int XML_FORMAT_VER_FFS_BATCH = 5; // //------------------------------------------------------------------------------------------------------------------------------- } @@ -140,18 +142,44 @@ Zstring mergeFilterLines(const std::vector<Zstring>& filterLines) } } - namespace zen { template <> inline +void writeText(const wxLanguage& value, std::string& output) +{ + //use description as unique wxLanguage identifier, see localization.cpp + //=> handle changes to wxLanguage enum between wxWidgets versions + if (const wxLanguageInfo* lngInfo = wxLocale::GetLanguageInfo(value)) + output = utfCvrtTo<std::string>(lngInfo->Description); + else + { + assert(false); + output = "English (U.S.)"; + } + return; +} + +template <> inline +bool readText(const std::string& input, wxLanguage& value) +{ + if (const wxLanguageInfo* lngInfo = wxLocale::FindLanguageInfo(utfCvrtTo<wxString>(input))) + { + value = static_cast<wxLanguage>(lngInfo->Language); + return true; + } + return false; +} + + +template <> inline void writeText(const CompareVariant& value, std::string& output) { switch (value) { - case zen::CMP_BY_TIME_SIZE: + case CMP_BY_TIME_SIZE: output = "TimeAndSize"; break; - case zen::CMP_BY_CONTENT: + case CMP_BY_CONTENT: output = "Content"; break; } @@ -162,9 +190,9 @@ bool readText(const std::string& input, CompareVariant& value) { const std::string tmp = trimCpy(input); if (tmp == "TimeAndSize") - value = zen::CMP_BY_TIME_SIZE; + value = CMP_BY_TIME_SIZE; else if (tmp == "Content") - value = zen::CMP_BY_CONTENT; + value = CMP_BY_CONTENT; else return false; return true; @@ -370,25 +398,25 @@ void writeText(const ColumnTypeRim& value, std::string& output) { switch (value) { - case COL_TYPE_BASE_DIRECTORY: + case ColumnTypeRim::BASE_DIRECTORY: output = "Base"; break; - case COL_TYPE_FULL_PATH: + case ColumnTypeRim::FULL_PATH: output = "Full"; break; - case COL_TYPE_REL_FOLDER: + case ColumnTypeRim::REL_FOLDER: output = "Rel"; break; - case COL_TYPE_FILENAME: + case ColumnTypeRim::FILENAME: output = "Name"; break; - case COL_TYPE_SIZE: + case ColumnTypeRim::SIZE: output = "Size"; break; - case COL_TYPE_DATE: + case ColumnTypeRim::DATE: output = "Date"; break; - case COL_TYPE_EXTENSION: + case ColumnTypeRim::EXTENSION: output = "Ext"; break; } @@ -399,19 +427,19 @@ bool readText(const std::string& input, ColumnTypeRim& value) { const std::string tmp = trimCpy(input); if (tmp == "Base") - value = COL_TYPE_BASE_DIRECTORY; + value = ColumnTypeRim::BASE_DIRECTORY; else if (tmp == "Full") - value = COL_TYPE_FULL_PATH; + value = ColumnTypeRim::FULL_PATH; else if (tmp == "Rel") - value = COL_TYPE_REL_FOLDER; + value = ColumnTypeRim::REL_FOLDER; else if (tmp == "Name") - value = COL_TYPE_FILENAME; + value = ColumnTypeRim::FILENAME; else if (tmp == "Size") - value = COL_TYPE_SIZE; + value = ColumnTypeRim::SIZE; else if (tmp == "Date") - value = COL_TYPE_DATE; + value = ColumnTypeRim::DATE; else if (tmp == "Ext") - value = COL_TYPE_EXTENSION; + value = ColumnTypeRim::EXTENSION; else return false; return true; @@ -423,13 +451,13 @@ void writeText(const ColumnTypeNavi& value, std::string& output) { switch (value) { - case COL_TYPE_NAVI_BYTES: + case ColumnTypeNavi::BYTES: output = "Bytes"; break; - case COL_TYPE_NAVI_DIRECTORY: + case ColumnTypeNavi::DIRECTORY: output = "Tree"; break; - case COL_TYPE_NAVI_ITEM_COUNT: + case ColumnTypeNavi::ITEM_COUNT: output = "Count"; break; } @@ -440,11 +468,11 @@ bool readText(const std::string& input, ColumnTypeNavi& value) { const std::string tmp = trimCpy(input); if (tmp == "Bytes") - value = COL_TYPE_NAVI_BYTES; + value = ColumnTypeNavi::BYTES; else if (tmp == "Tree") - value = COL_TYPE_NAVI_DIRECTORY; + value = ColumnTypeNavi::DIRECTORY; else if (tmp == "Count") - value = COL_TYPE_NAVI_ITEM_COUNT; + value = ColumnTypeNavi::ITEM_COUNT; else return false; return true; @@ -702,24 +730,64 @@ void writeStruc(const ViewFilterDefault& value, XmlElement& output) actView.attribute("DeleteRight", value.deleteRight); actView.attribute("DoNothing" , value.doNothing); } +} + + +namespace +{ +#ifdef ZEN_WIN +Opt<wchar_t> getDriveLetter(const Zstring& path) +{ + assert(path.size() >= 3); + if (path.size() >= 3 && isAlpha(path[0]) && path[1] == L':' && path[2] == L'\\') + return path[0]; + return NoValue(); +} + +const wchar_t* ffsDriveLetterMacro = L"ffs_drive:"; //static construction/destruction fiasco => keep POD +#endif + +Zstring substituteFreeFileSyncDriveLetter(const Zstring& cfgFilePath) +{ +#ifdef ZEN_WIN + if (Opt<wchar_t> cfgDrive = getDriveLetter(cfgFilePath)) + if (Opt<wchar_t> ffsDrive = getDriveLetter(getFreeFileSyncLauncherPath())) + if (equalFilePath(*ffsDrive, *cfgDrive)) + return ffsDriveLetterMacro + Zstring(L"\\") + afterFirst(cfgFilePath, L'\\', IF_MISSING_RETURN_NONE); +#endif + return cfgFilePath; +} + + +Zstring resolveFreeFileSyncDriveMacro(const Zstring& cfgFilePhrase) +{ +#ifdef ZEN_WIN + if (startsWith(trimCpy(cfgFilePhrase, true, false), ffsDriveLetterMacro)) + if (Opt<wchar_t> ffsDrive = getDriveLetter(getFreeFileSyncLauncherPath())) + return *ffsDrive + Zstring(L":") + afterFirst(cfgFilePhrase, ffsDriveLetterMacro, IF_MISSING_RETURN_NONE); +#endif + return cfgFilePhrase; +} +} + + +namespace zen +{ +//FFS portable: use special syntax for config file paths: e.g. "ffs_drive:\SyncJob.ffs_gui" template <> inline -bool readStruc(const XmlElement& input, ConfigHistoryItem& value) +bool readText(const std::string& input, ConfigFileItem& value) { - XmlIn in(input); - bool rv1 = in(value.configFile); - //bool rv2 = in.attribute("LastUsed", value.lastUseTime); - return rv1 /*&& rv2*/; + value.filePath_ = resolveFreeFileSyncDriveMacro(utfCvrtTo<Zstring>(input)); + return true; } template <> inline -void writeStruc(const ConfigHistoryItem& value, XmlElement& output) +void writeText(const ConfigFileItem& value, std::string& output) { - XmlOut out(output); - out(value.configFile); - //out.attribute("LastUsed", value.lastUseTime); + output = utfCvrtTo<std::string>(substituteFreeFileSyncDriveLetter(value.filePath_)); } } @@ -728,9 +796,22 @@ namespace { void readConfig(const XmlIn& in, CompConfig& cmpConfig) { - in["Variant" ](cmpConfig.compareVar); - in["TimeShift"](cmpConfig.optTimeShiftHours); - in["Symlinks" ](cmpConfig.handleSymlinks); + in["Variant" ](cmpConfig.compareVar); + in["Symlinks"](cmpConfig.handleSymlinks); + + warn_static("remove old parameter after migration! 2015-11-05") + if (in["TimeShift"]) + { + std::wstring timeShiftPhrase; + if (in["TimeShift"](timeShiftPhrase)) + cmpConfig.ignoreTimeShiftMinutes = fromTimeShiftPhrase(timeShiftPhrase); + } + else + { + std::wstring timeShiftPhrase; + if (in["IgnoreTimeShift"](timeShiftPhrase)) + cmpConfig.ignoreTimeShiftMinutes = fromTimeShiftPhrase(timeShiftPhrase); + } } @@ -884,7 +965,12 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) { XmlIn inShared = in["Shared"]; - inShared["Language"].attribute("Id", config.programLanguage); + warn_static("remove old parameter after migration! 2015-11-07") + int langId = 0; + if (inShared["Language"] && inShared["Language"].get()->getAttribute("Id", langId)) + config.programLanguage = static_cast<wxLanguage>(langId); + else + inShared["Language"].attribute("Name", config.programLanguage); inShared["FailSafeFileCopy" ].attribute("Enabled", config.failsafeFileCopy); inShared["CopyLockedFiles" ].attribute("Enabled", config.copyLockedFiles); @@ -1124,9 +1210,9 @@ namespace { void writeConfig(const CompConfig& cmpConfig, XmlOut& out) { - out["Variant" ](cmpConfig.compareVar); - out["TimeShift"](cmpConfig.optTimeShiftHours); - out["Symlinks" ](cmpConfig.handleSymlinks); + out["Variant" ](cmpConfig.compareVar); + out["Symlinks"](cmpConfig.handleSymlinks); + out["IgnoreTimeShift"](toTimeShiftPhrase(cmpConfig.ignoreTimeShiftMinutes)); } @@ -1269,7 +1355,7 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) { XmlOut outShared = out["Shared"]; - outShared["Language"].attribute("Id", config.programLanguage); + outShared["Language"].attribute("Name", config.programLanguage); outShared["FailSafeFileCopy" ].attribute("Enabled", config.failsafeFileCopy); outShared["CopyLockedFiles" ].attribute("Enabled", config.copyLockedFiles); diff --git a/FreeFileSync/Source/lib/process_xml.h b/FreeFileSync/Source/lib/process_xml.h index 9d765130..dea35026 100644 --- a/FreeFileSync/Source/lib/process_xml.h +++ b/FreeFileSync/Source/lib/process_xml.h @@ -12,7 +12,6 @@ #include "localization.h" #include "../structures.h" #include "../ui/column_attr.h" -#include "../ui/folder_history_types.h" namespace xmlAccess @@ -122,6 +121,16 @@ struct ViewFilterDefault bool doNothing = true; }; + +struct ConfigFileItem +{ + ConfigFileItem() {} + explicit ConfigFileItem(const Zstring& filePath) : filePath_(filePath) {} + Zstring filePath_; + //add support? -> time_t lastSyncTime +}; + + Zstring getGlobalConfigFile(); struct XmlGlobalSettings @@ -130,7 +139,7 @@ struct XmlGlobalSettings //--------------------------------------------------------------------- //Shared (GUI/BATCH) settings - int programLanguage = zen::retrieveSystemLanguage(); + wxLanguage programLanguage = zen::getSystemLanguage(); bool failsafeFileCopy = true; bool copyLockedFiles = false; //safer default: avoid copies of partially written files bool copyFilePermissions = false; @@ -185,10 +194,10 @@ struct XmlGlobalSettings ExternalApps externelApplications; - std::vector<zen::ConfigHistoryItem> cfgFileHistory; + std::vector<ConfigFileItem> cfgFileHistory; size_t cfgFileHistMax = 30; - std::vector<Zstring> lastUsedConfigFiles; + std::vector<ConfigFileItem> lastUsedConfigFiles; std::vector<Zstring> folderHistoryLeft; std::vector<Zstring> folderHistoryRight; diff --git a/FreeFileSync/Source/lib/resolve_path.cpp b/FreeFileSync/Source/lib/resolve_path.cpp index 7cad395a..671edf55 100644 --- a/FreeFileSync/Source/lib/resolve_path.cpp +++ b/FreeFileSync/Source/lib/resolve_path.cpp @@ -141,14 +141,13 @@ Zstring resolveRelativePath(const Zstring& relativePath) class CsidlConstants { public: - typedef std::map<Zstring, Zstring, LessFilePath> CsidlToDirMap; //case-insensitive comparison + typedef std::map<Zstring, Zstring, LessFilePath> CsidlToDirMap; //case-insensitive! static const CsidlToDirMap& get() { #if defined _MSC_VER && _MSC_VER < 1900 #error function scope static initialization is not yet thread-safe! #endif - //function scope static initialization: avoid static initialization order problem in global namespace! static const CsidlToDirMap inst = createCsidlMapping(); return inst; } diff --git a/FreeFileSync/Source/lib/status_handler.h b/FreeFileSync/Source/lib/status_handler.h index 7aa77d04..0b3031d9 100644 --- a/FreeFileSync/Source/lib/status_handler.h +++ b/FreeFileSync/Source/lib/status_handler.h @@ -53,10 +53,8 @@ struct Statistics class StatusHandler : public ProcessCallback, public AbortCallback, public Statistics { public: - StatusHandler() : currentPhase_(PHASE_NONE), - numbersCurrent_(4), //init with phase count - numbersTotal_ (4), // - abortRequested(false) {} + StatusHandler() : numbersCurrent_(4), //init with phase count + numbersTotal_ (4) {} // protected: //implement parts of ProcessCallback @@ -80,8 +78,8 @@ protected: forceUiRefresh(); } - void reportStatus(const std::wstring& text) override { if (!abortRequested) statusText_ = text; requestUiRefresh(); /*throw X */ } - void reportInfo (const std::wstring& text) override { if (!abortRequested) statusText_ = text; requestUiRefresh(); /*throw X */ } //log text in derived class + void reportStatus(const std::wstring& text) override { assert(!text.empty()); if (!abortRequested) statusText_ = text; requestUiRefresh(); /*throw X */ } + void reportInfo (const std::wstring& text) override { assert(!text.empty()); if (!abortRequested) statusText_ = text; requestUiRefresh(); /*throw X */ } //log text in derived class //implement AbortCallback void requestAbortion() override @@ -132,12 +130,12 @@ private: static std::pair<int, std::int64_t>& refNumbers(StatNumbers& num, Phase phaseId) { return const_cast<std::pair<int, std::int64_t>&>(refNumbers(static_cast<const StatNumbers&>(num), phaseId)); } - Phase currentPhase_; + Phase currentPhase_ = PHASE_NONE; StatNumbers numbersCurrent_; StatNumbers numbersTotal_; std::wstring statusText_; - bool abortRequested; + bool abortRequested = false; }; } diff --git a/FreeFileSync/Source/process_callback.h b/FreeFileSync/Source/process_callback.h index 93dc0554..c9e9a997 100644 --- a/FreeFileSync/Source/process_callback.h +++ b/FreeFileSync/Source/process_callback.h @@ -46,8 +46,8 @@ struct ProcessCallback 6. directory deletion: may contain more items than scanned by FFS (excluded by filter) or less (contains followed symlinks) 7. delete directory to recycler: no matter how many child-elements exist, this is only 1 item to process! 8. user-defined deletion directory on different volume: full file copy required (instead of move) - 9. Binary file comparison: short-circuit behavior if files differ - 10. Error during file copy, retry: bytes were copied => increases total workload! + 9. Binary file comparison: short-circuit behavior after first difference is found + 10. Error during file copy, retry: bytes were copied => increases total workload! */ //opportunity to abort must be implemented in a frequently executed method like requestUiRefresh() diff --git a/FreeFileSync/Source/structures.cpp b/FreeFileSync/Source/structures.cpp index 0fc3e584..52e35bc5 100644 --- a/FreeFileSync/Source/structures.cpp +++ b/FreeFileSync/Source/structures.cpp @@ -15,6 +15,42 @@ using namespace zen; +std::vector<unsigned int> zen::fromTimeShiftPhrase(const std::wstring& timeShiftPhrase) +{ + std::wstring tmp = replaceCpy(timeShiftPhrase, L';', L','); //harmonize , and ; + replace(tmp, L'-', L""); //there is no negative shift => treat as positive! + + std::set<unsigned int> minutes; + for (const std::wstring& part : split(tmp, L',')) + { + if (contains(part, L':')) + minutes.insert(stringTo<unsigned int>(beforeFirst(part, L':', IF_MISSING_RETURN_NONE)) * 60 + + stringTo<unsigned int>(afterFirst (part, L':', IF_MISSING_RETURN_NONE))); + else + minutes.insert(stringTo<unsigned int>(part) * 60); + } + minutes.erase(0); + + return { minutes.begin(), minutes.end() }; +} + + +std::wstring zen::toTimeShiftPhrase(const std::vector<unsigned int>& ignoreTimeShiftMinutes) +{ + std::wstring phrase; + for (auto it = ignoreTimeShiftMinutes.begin(); it != ignoreTimeShiftMinutes.end(); ++it) + { + if (it != ignoreTimeShiftMinutes.begin()) + phrase += L", "; + + phrase += numberTo<std::wstring>(*it / 60); + if (*it % 60 != 0) + phrase += L':' + printNumber<std::wstring>(L"%02d", static_cast<int>(*it % 60)); + } + return phrase; +} + + std::wstring zen::getVariantName(CompareVariant var) { switch (var) diff --git a/FreeFileSync/Source/structures.h b/FreeFileSync/Source/structures.h index 822beed0..a089a853 100644 --- a/FreeFileSync/Source/structures.h +++ b/FreeFileSync/Source/structures.h @@ -167,20 +167,24 @@ struct CompConfig { CompareVariant compareVar = CMP_BY_TIME_SIZE; SymLinkHandling handleSymlinks = SYMLINK_EXCLUDE; - unsigned int optTimeShiftHours = 0; //if != 0: treat modification times with this offset as equal + std::vector<unsigned int> ignoreTimeShiftMinutes; //treat modification times with these offsets as equal }; inline bool operator==(const CompConfig& lhs, const CompConfig& rhs) { - return lhs.compareVar == rhs.compareVar && - lhs.handleSymlinks == rhs.handleSymlinks && - lhs.optTimeShiftHours == rhs.optTimeShiftHours; + return lhs.compareVar == rhs.compareVar && + lhs.handleSymlinks == rhs.handleSymlinks && + lhs.ignoreTimeShiftMinutes == rhs.ignoreTimeShiftMinutes; } inline bool effectivelyEqual(const CompConfig& lhs, const CompConfig& rhs) { return lhs == rhs; } //no change in behavior +//convert "ignoreTimeShiftMinutes" into compact format: +std::vector<unsigned int> fromTimeShiftPhrase(const std::wstring& timeShiftPhrase); +std::wstring toTimeShiftPhrase (const std::vector<unsigned int>& ignoreTimeShiftMinutes); + enum DeletionPolicy { diff --git a/FreeFileSync/Source/synchronization.cpp b/FreeFileSync/Source/synchronization.cpp index 3aec81ba..130689bf 100644 --- a/FreeFileSync/Source/synchronization.cpp +++ b/FreeFileSync/Source/synchronization.cpp @@ -1679,7 +1679,7 @@ AFS::FileAttribAfterCopy SynchronizeFolderPair::copyFileWithCallback(const Abstr #ifdef ZEN_WIN try { - return copyOperation(sourcePath); + return copyOperation(sourcePath); //throw FileError, ErrorFileLocked } catch (ErrorFileLocked& e1) { @@ -1701,7 +1701,8 @@ AFS::FileAttribAfterCopy SynchronizeFolderPair::copyFileWithCallback(const Abstr } //now try again - return copyOperation(createItemPathNative(nativeShadowPath)); + return copyOperation(createItemPathNativeNoFormatting(nativeShadowPath)); //throw FileError, ErrorFileLocked + //avoid getResolvedFilePath()! => destroys "\\?\GLOBALROOT\" } throw; } @@ -2015,7 +2016,7 @@ void zen::synchronize(const TimeComp& timeStamp, freeSpace < minSpaceNeeded) diskSpaceMissing.emplace_back(baseFolderPath, std::make_pair(minSpaceNeeded, freeSpace)); } - catch (FileError&) { assert(false); } //for warning only => no need for tryReportingError() + catch (FileError&) {} //for warning only => no need for tryReportingError() }; const std::pair<std::int64_t, std::int64_t> spaceNeeded = MinimumDiskSpaceNeeded::calculate(*j); checkSpace(j->getAbstractPath<LEFT_SIDE >(), spaceNeeded.first); @@ -2160,13 +2161,15 @@ void zen::synchronize(const TimeComp& timeStamp, //execute synchronization recursively //update synchronization database in case of errors: - ZEN_ON_SCOPE_FAIL(try + ZEN_ON_SCOPE_FAIL + ( + try { if (folderPairCfg.saveSyncDB_) zen::saveLastSynchronousState(*j, nullptr); } //throw FileError catch (FileError&) {} - ); + ); if (jobType[folderIndex] == FolderPairJobType::PROCESS) { diff --git a/FreeFileSync/Source/ui/column_attr.h b/FreeFileSync/Source/ui/column_attr.h index 797c6136..6504f843 100644 --- a/FreeFileSync/Source/ui/column_attr.h +++ b/FreeFileSync/Source/ui/column_attr.h @@ -11,39 +11,39 @@ namespace zen { -enum ColumnTypeRim +enum class ColumnTypeRim { - COL_TYPE_FULL_PATH, - COL_TYPE_BASE_DIRECTORY, - COL_TYPE_REL_FOLDER, - COL_TYPE_FILENAME, - COL_TYPE_SIZE, - COL_TYPE_DATE, - COL_TYPE_EXTENSION + FULL_PATH, + BASE_DIRECTORY, + REL_FOLDER, + FILENAME, + SIZE, + DATE, + EXTENSION }; struct ColumnAttributeRim { - ColumnAttributeRim() : type_(COL_TYPE_BASE_DIRECTORY), offset_(0), stretch_(0), visible_(false) {} + ColumnAttributeRim() {} ColumnAttributeRim(ColumnTypeRim type, int offset, int stretch, bool visible) : type_(type), offset_(offset), stretch_(stretch), visible_(visible) {} - ColumnTypeRim type_; - int offset_; - int stretch_; - bool visible_; + ColumnTypeRim type_ = ColumnTypeRim::FULL_PATH; + int offset_ = 0; + int stretch_ = 0;; + bool visible_ = false; }; inline std::vector<ColumnAttributeRim> getDefaultColumnAttributesLeft() { std::vector<ColumnAttributeRim> attr; - attr.emplace_back(COL_TYPE_FULL_PATH, 250, 0, false); - attr.emplace_back(COL_TYPE_BASE_DIRECTORY, 200, 0, false); - attr.emplace_back(COL_TYPE_REL_FOLDER, -280, 1, true); //stretch to full width and substract sum of fixed size widths! - attr.emplace_back(COL_TYPE_FILENAME, 200, 0, true); - attr.emplace_back(COL_TYPE_DATE, 112, 0, false); - attr.emplace_back(COL_TYPE_SIZE, 80, 0, true); - attr.emplace_back(COL_TYPE_EXTENSION, 60, 0, false); + attr.emplace_back(ColumnTypeRim::FULL_PATH, 250, 0, false); + attr.emplace_back(ColumnTypeRim::BASE_DIRECTORY, 200, 0, false); + attr.emplace_back(ColumnTypeRim::REL_FOLDER, -280, 1, true); //stretch to full width and substract sum of fixed size widths! + attr.emplace_back(ColumnTypeRim::FILENAME, 200, 0, true); + attr.emplace_back(ColumnTypeRim::DATE, 112, 0, false); + attr.emplace_back(ColumnTypeRim::SIZE, 80, 0, true); + attr.emplace_back(ColumnTypeRim::EXTENSION, 60, 0, false); return attr; } @@ -51,58 +51,58 @@ inline std::vector<ColumnAttributeRim> getDefaultColumnAttributesRight() { std::vector<ColumnAttributeRim> attr; - attr.emplace_back(COL_TYPE_FULL_PATH, 250, 0, false); - attr.emplace_back(COL_TYPE_BASE_DIRECTORY, 200, 0, false); - attr.emplace_back(COL_TYPE_REL_FOLDER , -280, 1, false); //already shown on left side - attr.emplace_back(COL_TYPE_FILENAME, 200, 0, true); - attr.emplace_back(COL_TYPE_DATE, 112, 0, false); - attr.emplace_back(COL_TYPE_SIZE, 80, 0, true); - attr.emplace_back(COL_TYPE_EXTENSION, 60, 0, false); + attr.emplace_back(ColumnTypeRim::FULL_PATH, 250, 0, false); + attr.emplace_back(ColumnTypeRim::BASE_DIRECTORY, 200, 0, false); + attr.emplace_back(ColumnTypeRim::REL_FOLDER , -280, 1, false); //already shown on left side + attr.emplace_back(ColumnTypeRim::FILENAME, 200, 0, true); + attr.emplace_back(ColumnTypeRim::DATE, 112, 0, false); + attr.emplace_back(ColumnTypeRim::SIZE, 80, 0, true); + attr.emplace_back(ColumnTypeRim::EXTENSION, 60, 0, false); return attr; } //------------------------------------------------------------------ -enum ColumnTypeMiddle +enum class ColumnTypeCenter { - COL_TYPE_CHECKBOX, - COL_TYPE_CMP_CATEGORY, - COL_TYPE_SYNC_ACTION, + CHECKBOX, + CMP_CATEGORY, + SYNC_ACTION, }; //------------------------------------------------------------------ -enum ColumnTypeNavi +enum class ColumnTypeNavi { - COL_TYPE_NAVI_BYTES, - COL_TYPE_NAVI_DIRECTORY, - COL_TYPE_NAVI_ITEM_COUNT + BYTES, + DIRECTORY, + ITEM_COUNT }; struct ColumnAttributeNavi { - ColumnAttributeNavi() : type_(COL_TYPE_NAVI_DIRECTORY), offset_(0), stretch_(0), visible_(false) {} + ColumnAttributeNavi() {} ColumnAttributeNavi(ColumnTypeNavi type, int offset, int stretch, bool visible) : type_(type), offset_(offset), stretch_(stretch), visible_(visible) {} - ColumnTypeNavi type_; - int offset_; - int stretch_; - bool visible_; + ColumnTypeNavi type_ = ColumnTypeNavi::DIRECTORY; + int offset_ = 0; + int stretch_ = 0;; + bool visible_ = false; }; const bool defaultValueShowPercentage = true; -const ColumnTypeNavi defaultValueLastSortColumn = COL_TYPE_NAVI_BYTES; //remember sort on navigation panel +const ColumnTypeNavi defaultValueLastSortColumn = ColumnTypeNavi::BYTES; //remember sort on navigation panel const bool defaultValueLastSortAscending = false; // inline std::vector<ColumnAttributeNavi> getDefaultColumnAttributesNavi() { std::vector<ColumnAttributeNavi> attr; - attr.emplace_back(COL_TYPE_NAVI_DIRECTORY, -120, 1, true); //stretch to full width and substract sum of fixed size widths - attr.emplace_back(COL_TYPE_NAVI_ITEM_COUNT, 60, 0, true); - attr.emplace_back(COL_TYPE_NAVI_BYTES, 60, 0, true); //GTK needs a few pixels width more + attr.emplace_back(ColumnTypeNavi::DIRECTORY, -120, 1, true); //stretch to full width and substract sum of fixed size widths + attr.emplace_back(ColumnTypeNavi::ITEM_COUNT, 60, 0, true); + attr.emplace_back(ColumnTypeNavi::BYTES, 60, 0, true); //GTK needs a few pixels width more return attr; } } diff --git a/FreeFileSync/Source/ui/custom_grid.cpp b/FreeFileSync/Source/ui/custom_grid.cpp index 3eec8375..ff8c8b6d 100644 --- a/FreeFileSync/Source/ui/custom_grid.cpp +++ b/FreeFileSync/Source/ui/custom_grid.cpp @@ -29,14 +29,16 @@ const wxEventType zen::EVENT_GRID_SYNC_DIRECTION = wxNewEventType(); namespace { -const wxColour COLOR_ORANGE (238, 201, 0); -const wxColour COLOR_GREY (212, 208, 200); -const wxColour COLOR_YELLOW (247, 252, 62); -const wxColour COLOR_YELLOW_LIGHT(253, 252, 169); -const wxColour COLOR_CMP_RED (255, 185, 187); -const wxColour COLOR_SYNC_BLUE (185, 188, 255); -const wxColour COLOR_SYNC_GREEN (196, 255, 185); -const wxColour COLOR_NOT_ACTIVE (228, 228, 228); //light grey +//let's NOT create wxWidgets objects statically: +inline wxColor getColorOrange () { return { 238, 201, 0 }; } +inline wxColor getColorGrey () { return { 212, 208, 200 }; } +inline wxColor getColorYellow () { return { 247, 252, 62 }; } +//inline wxColor getColorYellowLight() { return { 253, 252, 169 }; } +inline wxColor getColorCmpRed () { return { 255, 185, 187 }; } +inline wxColor getColorSyncBlue () { return { 185, 188, 255 }; } +inline wxColor getColorSyncGreen() { return { 196, 255, 185 }; } +inline wxColor getColorNotActive() { return { 228, 228, 228 }; } //light grey +inline wxColor getColorGridLine () { return { 192, 192, 192 }; } //light grey const size_t ROW_COUNT_IF_NO_DATA = 0; @@ -50,41 +52,24 @@ class hierarchy: /|\ | __________|__________ | | | | - GridDataLeft GridDataRight GridDataMiddle + GridDataLeft GridDataRight GridDataCenter */ - - -void refreshCell(Grid& grid, size_t row, ColumnType colType) -{ - wxRect cellArea = grid.getCellArea(row, colType); //returns empty rect if column not found; absolute coordinates! - if (cellArea.height > 0) - { - cellArea.SetTopLeft(grid.CalcScrolledPosition(cellArea.GetTopLeft())); - grid.getMainWin().RefreshRect(cellArea, false); - } -} - - std::pair<ptrdiff_t, ptrdiff_t> getVisibleRows(const Grid& grid) //returns range [from, to) { const wxSize clientSize = grid.getMainWin().GetClientSize(); if (clientSize.GetHeight() > 0) { - wxPoint topLeft = grid.CalcUnscrolledPosition(wxPoint(0, 0)); - wxPoint bottom = grid.CalcUnscrolledPosition(wxPoint(0, clientSize.GetHeight() - 1)); + const wxPoint topLeft = grid.CalcUnscrolledPosition(wxPoint(0, 0)); + const wxPoint bottom = grid.CalcUnscrolledPosition(wxPoint(0, clientSize.GetHeight() - 1)); const ptrdiff_t rowCount = grid.getRowCount(); const ptrdiff_t rowFrom = grid.getRowAtPos(topLeft.y); //return -1 for invalid position, rowCount if out of range - if (rowFrom >= 0) - { - const ptrdiff_t rowTo = grid.getRowAtPos(bottom.y); - if (0 <= rowTo && rowTo < rowCount) - return std::make_pair(rowFrom, rowTo + 1); - else - return std::make_pair(rowFrom, rowCount); - } + const ptrdiff_t rowTo = grid.getRowAtPos(bottom.y); + if (rowFrom >= 0 && rowTo >= 0) + return std::make_pair(rowFrom, std::min(rowTo + 1, rowCount)); } + assert(false); return std::make_pair(0, 0); } @@ -143,7 +128,6 @@ struct IconManager { IconManager(GridDataLeft& provLeft, GridDataRight& provRight, IconBuffer::IconSize sz) : iconBuffer(sz), - //fileIcon (IconBuffer::genericFileIcon(sz)), dirIcon (IconBuffer::genericDirIcon (sz)), linkOverlayIcon(IconBuffer::linkOverlayIcon(sz)), iconUpdater(std::make_unique<IconUpdater>(provLeft, provRight, iconBuffer)) {} @@ -151,13 +135,11 @@ struct IconManager void startIconUpdater(); IconBuffer& refIconBuffer() { return iconBuffer; } - //wxBitmap getGenericFileIcon() const { return fileIcon; } - wxBitmap getGenericDirIcon () const { return dirIcon; } - wxBitmap getLinkOverlayIcon() const { return linkOverlayIcon; } + const wxBitmap& getGenericDirIcon () const { return dirIcon; } + const wxBitmap& getLinkOverlayIcon() const { return linkOverlayIcon; } private: IconBuffer iconBuffer; - //const wxBitmap fileIcon; const wxBitmap dirIcon; const wxBitmap linkOverlayIcon; @@ -190,15 +172,10 @@ protected: private: size_t getRowCount() const override { - if (gridDataView_) - { - if (gridDataView_->rowsTotal() == 0) - return ROW_COUNT_IF_NO_DATA; - return gridDataView_->rowsOnView(); - } - else + if (!gridDataView_ || gridDataView_->rowsTotal() == 0) return ROW_COUNT_IF_NO_DATA; + return gridDataView_->rowsOnView(); //return std::max(MIN_ROW_COUNT, gridDataView_ ? gridDataView_->rowsOnView() : 0); } @@ -239,7 +216,7 @@ public: if (iconMgr_->refIconBuffer().readyForRetrieval(ii.fsObj->template getAbstractPath<side>())) { //do a *full* refresh for *every* failed load to update partial DC updates while scrolling - refreshCell(refGrid(), currentRow, static_cast<ColumnType>(COL_TYPE_FILENAME)); + refGrid().refreshCell(currentRow, static_cast<ColumnType>(ColumnTypeRim::FILENAME)); setFailedLoad(currentRow, false); } else //not yet in buffer: mark for async. loading @@ -303,18 +280,17 @@ protected: else { //alternate background color to improve readability (while lacking cell borders) - if (getRowDisplayType(row) == DISP_TYPE_NORMAL) + if (getRowDisplayType(row) == DisplayType::NORMAL) fillBackgroundDefaultColorAlternating(dc, rect, row % 2 == 0); else clearArea(dc, rect, getBackGroundColor(row)); //draw horizontal border if required DisplayType dispTp = getRowDisplayType(row); - if (dispTp != DISP_TYPE_NORMAL && + if (dispTp != DisplayType::NORMAL && dispTp == getRowDisplayType(row + 1)) { - const wxColor colorGridLine = wxColour(192, 192, 192); //light grey - wxDCPenChanger dummy2(dc, wxPen(colorGridLine, 1, wxSOLID)); + wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), 1, wxSOLID)); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); } } @@ -330,41 +306,41 @@ protected: switch (getRowDisplayType(row)) { - case DISP_TYPE_NORMAL: + case DisplayType::NORMAL: break; - case DISP_TYPE_FOLDER: - return COLOR_GREY; - case DISP_TYPE_SYMLINK: - return COLOR_ORANGE; - case DISP_TYPE_INACTIVE: - return COLOR_NOT_ACTIVE; + case DisplayType::FOLDER: + return getColorGrey(); + case DisplayType::SYMLINK: + return getColorOrange(); + case DisplayType::INACTIVE: + return getColorNotActive(); } return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } private: - enum DisplayType + enum class DisplayType { - DISP_TYPE_NORMAL, - DISP_TYPE_FOLDER, - DISP_TYPE_SYMLINK, - DISP_TYPE_INACTIVE, + NORMAL, + FOLDER, + SYMLINK, + INACTIVE, }; DisplayType getRowDisplayType(size_t row) const { const FileSystemObject* fsObj = getRawData(row); if (!fsObj ) - return DISP_TYPE_NORMAL; + return DisplayType::NORMAL; //mark filtered rows if (!fsObj->isActive()) - return DISP_TYPE_INACTIVE; + return DisplayType::INACTIVE; if (fsObj->isEmpty<side>()) //always show not existing files/dirs/symlinks as empty - return DISP_TYPE_NORMAL; + return DisplayType::NORMAL; - DisplayType output = DISP_TYPE_NORMAL; + DisplayType output = DisplayType::NORMAL; //mark directories and symlinks struct GetRowType : public FSObjectVisitor { @@ -373,11 +349,11 @@ private: void visit(const FilePair& file) override {} void visit(const SymlinkPair& symlink) override { - result_ = DISP_TYPE_SYMLINK; + result_ = DisplayType::SYMLINK; } void visit(const FolderPair& folder) override { - result_ = DISP_TYPE_FOLDER; + result_ = DisplayType::FOLDER; } private: DisplayType& result_; @@ -400,20 +376,20 @@ private: { switch (colType_) { - case COL_TYPE_FULL_PATH: + case ColumnTypeRim::FULL_PATH: return file.isEmpty<side>() ? std::wstring() : AFS::getDisplayPath(file.getAbstractPath<side>()); - case COL_TYPE_FILENAME: + case ColumnTypeRim::FILENAME: return utfCvrtTo<std::wstring>(file.getItemName<side>()); - case COL_TYPE_REL_FOLDER: + case ColumnTypeRim::REL_FOLDER: return utfCvrtTo<std::wstring>(beforeLast(file.getPairRelativePath(), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); - case COL_TYPE_BASE_DIRECTORY: + case ColumnTypeRim::BASE_DIRECTORY: return AFS::getDisplayPath(file.base().getAbstractPath<side>()); - case COL_TYPE_SIZE: + case ColumnTypeRim::SIZE: //return file.isEmpty<side>() ? std::wstring() : utfCvrtTo<std::wstring>(file.getFileId<side>()); // -> test file id return file.isEmpty<side>() ? std::wstring() : toGuiString(file.getFileSize<side>()); - case COL_TYPE_DATE: + case ColumnTypeRim::DATE: return file.isEmpty<side>() ? std::wstring() : utcToLocalTimeString(file.getLastWriteTime<side>()); - case COL_TYPE_EXTENSION: + case ColumnTypeRim::EXTENSION: return utfCvrtTo<std::wstring>(getFileExtension(file.getItemName<side>())); } assert(false); @@ -427,19 +403,19 @@ private: { switch (colType_) { - case COL_TYPE_FULL_PATH: + case ColumnTypeRim::FULL_PATH: return symlink.isEmpty<side>() ? std::wstring() : AFS::getDisplayPath(symlink.getAbstractPath<side>()); - case COL_TYPE_FILENAME: + case ColumnTypeRim::FILENAME: return utfCvrtTo<std::wstring>(symlink.getItemName<side>()); - case COL_TYPE_REL_FOLDER: + case ColumnTypeRim::REL_FOLDER: return utfCvrtTo<std::wstring>(beforeLast(symlink.getPairRelativePath(), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); - case COL_TYPE_BASE_DIRECTORY: + case ColumnTypeRim::BASE_DIRECTORY: return AFS::getDisplayPath(symlink.base().getAbstractPath<side>()); - case COL_TYPE_SIZE: + case ColumnTypeRim::SIZE: return symlink.isEmpty<side>() ? std::wstring() : L"<" + _("Symlink") + L">"; - case COL_TYPE_DATE: + case ColumnTypeRim::DATE: return symlink.isEmpty<side>() ? std::wstring() : utcToLocalTimeString(symlink.getLastWriteTime<side>()); - case COL_TYPE_EXTENSION: + case ColumnTypeRim::EXTENSION: return utfCvrtTo<std::wstring>(getFileExtension(symlink.getItemName<side>())); } assert(false); @@ -453,19 +429,19 @@ private: { switch (colType_) { - case COL_TYPE_FULL_PATH: + case ColumnTypeRim::FULL_PATH: return folder.isEmpty<side>() ? std::wstring() : AFS::getDisplayPath(folder.getAbstractPath<side>()); - case COL_TYPE_FILENAME: + case ColumnTypeRim::FILENAME: return utfCvrtTo<std::wstring>(folder.getItemName<side>()); - case COL_TYPE_REL_FOLDER: + case ColumnTypeRim::REL_FOLDER: return utfCvrtTo<std::wstring>(beforeLast(folder.getPairRelativePath(), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); - case COL_TYPE_BASE_DIRECTORY: + case ColumnTypeRim::BASE_DIRECTORY: return AFS::getDisplayPath(folder.base().getAbstractPath<side>()); - case COL_TYPE_SIZE: + case ColumnTypeRim::SIZE: return folder.isEmpty<side>() ? std::wstring() : L"<" + _("Folder") + L">"; - case COL_TYPE_DATE: + case ColumnTypeRim::DATE: return std::wstring(); - case COL_TYPE_EXTENSION: + case ColumnTypeRim::EXTENSION: return std::wstring(); } assert(false); @@ -484,7 +460,7 @@ private: static const int GAP_SIZE = 2; - void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) override + void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override { wxRect rectTmp = rect; @@ -496,7 +472,7 @@ private: }(); //draw file icon - if (static_cast<ColumnTypeRim>(colType) == COL_TYPE_FILENAME && + if (static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::FILENAME && iconMgr_) { rectTmp.x += GAP_SIZE; @@ -535,7 +511,7 @@ private: //see repaint behavior of ::ScrollWindow() function! fileIcon = iconMgr_->refIconBuffer().getIconByExtension(ii.fsObj->template getItemName<side>()); //better than nothing } - break; //return iconMgr_->getGenericFileIcon(); + break; case IconInfo::EMPTY: break; @@ -567,11 +543,11 @@ private: } std::unique_ptr<wxDCTextColourChanger> dummy3; - if (getRowDisplayType(row) != DISP_TYPE_NORMAL) + if (getRowDisplayType(row) != DisplayType::NORMAL) dummy3 = std::make_unique<wxDCTextColourChanger>(dc, *wxBLACK); //accessibility: always set both foreground AND background colors! //draw text - if (static_cast<ColumnTypeRim>(colType) == COL_TYPE_SIZE && refGrid().GetLayoutDirection() != wxLayout_RightToLeft) + if (static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::SIZE && refGrid().GetLayoutDirection() != wxLayout_RightToLeft) { //have file size right-justified (but don't change for RTL languages) rectTmp.width -= GAP_SIZE; @@ -588,12 +564,12 @@ private: int getBestSize(wxDC& dc, size_t row, ColumnType colType) override { // Partitioning: - // ________________________________ + // _________________________________ // | gap | icon | gap | text | gap | - // -------------------------------- + // --------------------------------- int bestSize = 0; - if (static_cast<ColumnTypeRim>(colType) == COL_TYPE_FILENAME && iconMgr_) + if (static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::FILENAME && iconMgr_) bestSize += GAP_SIZE + iconMgr_->refIconBuffer().getSize(); bestSize += GAP_SIZE + dc.GetTextExtent(getValue(row, colType)).GetWidth() + GAP_SIZE; @@ -605,21 +581,22 @@ private: { switch (static_cast<ColumnTypeRim>(colType)) { - case COL_TYPE_FULL_PATH: + case ColumnTypeRim::FULL_PATH: return _("Full path"); - case COL_TYPE_FILENAME: + case ColumnTypeRim::FILENAME: return _("Name"); //= short name - case COL_TYPE_REL_FOLDER: + case ColumnTypeRim::REL_FOLDER: return _("Relative folder"); - case COL_TYPE_BASE_DIRECTORY: + case ColumnTypeRim::BASE_DIRECTORY: return _("Base folder"); - case COL_TYPE_SIZE: + case ColumnTypeRim::SIZE: return _("Size"); - case COL_TYPE_DATE: + case ColumnTypeRim::DATE: return _("Date"); - case COL_TYPE_EXTENSION: + case ColumnTypeRim::EXTENSION: return _("Extension"); } + //assert(false); may be ColumnType::NONE return std::wstring(); } @@ -793,18 +770,15 @@ private: if (markRow) { - //const wxColor COLOR_TREE_SELECTION_GRADIENT = wxColor(101, 148, 255); //H:158 S:255 V:178 - const wxColor COLOR_TREE_SELECTION_GRADIENT = getColorSelectionGradientFrom(); - wxRect rectTmp = rect; rectTmp.width /= 20; - dc.GradientFillLinear(rectTmp, COLOR_TREE_SELECTION_GRADIENT, GridDataRim<LEFT_SIDE>::getBackGroundColor(row), wxEAST); + dc.GradientFillLinear(rectTmp, getColorSelectionGradientFrom(), GridDataRim<LEFT_SIDE>::getBackGroundColor(row), wxEAST); } } } std::unordered_set<const FileSystemObject*> markedFilesAndLinks_; //mark files/symlinks directly within a container - std::unordered_set<const HierarchyObject*> markedContainer_; //mark full container including all child-objects + std::unordered_set<const HierarchyObject *> markedContainer_; //mark full container including all child-objects //DO NOT DEREFERENCE!!!! NOT GUARANTEED TO BE VALID!!! }; @@ -817,134 +791,108 @@ public: //######################################################################################################## -class GridDataMiddle : public GridDataBase +class GridDataCenter : public GridDataBase { public: - GridDataMiddle(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : + GridDataCenter(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataBase(grid, gridDataView), - highlightSyncAction_(false), toolTip(grid), //tool tip must not live longer than grid! notch(getResourceImage(L"notch").ConvertToImage()) {} - void onSelectBegin(const wxPoint& clientPos, size_t row, ColumnType colType) + void onSelectBegin() { - if (row < refGrid().getRowCount()) - { - refGrid().clearSelection(DENY_GRID_EVENT); //don't emit event, prevent recursion! - dragSelection = std::make_unique<std::pair<size_t, BlockPosition>>(row, mousePosToBlock(clientPos, row, static_cast<ColumnTypeMiddle>(colType))); - toolTip.hide(); //handle custom tooltip - } + selectionInProgress = true; + refGrid().clearSelection(DENY_GRID_EVENT); //don't emit event, prevent recursion! + toolTip.hide(); //handle custom tooltip } - void onSelectEnd(size_t rowFirst, size_t rowLast) //we cannot reuse row from "onSelectBegin": if user is holding shift, this may now be in the middle of the range! + void onSelectEnd(size_t rowFirst, size_t rowLast, HoverArea rowHover, ptrdiff_t clickInitRow) { refGrid().clearSelection(DENY_GRID_EVENT); //don't emit event, prevent recursion! //issue custom event - if (dragSelection) - { - if (rowFirst < rowLast && //may be empty? probably not in this context - rowLast <= refGrid().getRowCount()) - { + if (selectionInProgress) //don't process selections initiated by right-click + if (rowFirst < rowLast && rowLast <= refGrid().getRowCount()) //empty? probably not in this context if (wxEvtHandler* evtHandler = refGrid().GetEventHandler()) - switch (dragSelection->second) + switch (static_cast<HoverAreaCenter>(rowHover)) { - case BLOCKPOS_CHECK_BOX: - { - const FileSystemObject* fsObj = getRawData(dragSelection->first); - const bool setIncluded = fsObj ? !fsObj->isActive() : true; - - CheckRowsEvent evt(rowFirst, rowLast, setIncluded); - evtHandler->ProcessEvent(evt); - } - break; - case BLOCKPOS_LEFT: + case HoverAreaCenter::CHECK_BOX: + if (const FileSystemObject* fsObj = getRawData(clickInitRow)) + { + const bool setIncluded = !fsObj->isActive(); + CheckRowsEvent evt(rowFirst, rowLast, setIncluded); + evtHandler->ProcessEvent(evt); + } + break; + case HoverAreaCenter::DIR_LEFT: { SyncDirectionEvent evt(rowFirst, rowLast, SyncDirection::LEFT); evtHandler->ProcessEvent(evt); } break; - case BLOCKPOS_MIDDLE: + case HoverAreaCenter::DIR_NONE: { SyncDirectionEvent evt(rowFirst, rowLast, SyncDirection::NONE); evtHandler->ProcessEvent(evt); } break; - case BLOCKPOS_RIGHT: + case HoverAreaCenter::DIR_RIGHT: { SyncDirectionEvent evt(rowFirst, rowLast, SyncDirection::RIGHT); evtHandler->ProcessEvent(evt); } break; } - } - dragSelection.reset(); - } + selectionInProgress = false; //update highlight and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) wxPoint clientPos = refGrid().getMainWin().ScreenToClient(wxGetMousePosition()); onMouseMovement(clientPos); } - + void onMouseMovement(const wxPoint& clientPos) { //manage block highlighting and custom tooltip - if (!dragSelection) + if (!selectionInProgress) { - auto refreshHighlight = [&](size_t row) - { - refreshCell(refGrid(), row, static_cast<ColumnType>(COL_TYPE_CHECKBOX)); - refreshCell(refGrid(), row, static_cast<ColumnType>(COL_TYPE_SYNC_ACTION)); - }; - const wxPoint& topLeftAbs = refGrid().CalcUnscrolledPosition(clientPos); const size_t row = refGrid().getRowAtPos(topLeftAbs.y); //return -1 for invalid position, rowCount if one past the end - const Opt<ColumnType> ct = refGrid().getColumnAtPos(topLeftAbs.x); - - if (row < refGrid().getRowCount() && ct) - { - if (highlight) refreshHighlight(highlight->row_); //refresh old highlight + const Grid::ColumnPosInfo cpi = refGrid().getColumnAtPos(topLeftAbs.x); //returns ColumnType::NONE if no column at x position! - highlight = std::make_unique<MouseHighlight>(row, mousePosToBlock(clientPos, row, static_cast<ColumnTypeMiddle>(*ct))); - - refreshHighlight(highlight->row_); - - //show custom tooltip - if (refGrid().getMainWin().GetClientRect().Contains(clientPos)) //cursor might have moved outside visible client area - showToolTip(row, static_cast<ColumnTypeMiddle>(*ct), refGrid().getMainWin().ClientToScreen(clientPos)); - } + if (row < refGrid().getRowCount() && cpi.colType != ColumnType::NONE && + refGrid().getMainWin().GetClientRect().Contains(clientPos)) //cursor might have moved outside visible client area + showToolTip(row, static_cast<ColumnTypeCenter>(cpi.colType), refGrid().getMainWin().ClientToScreen(clientPos)); else - onMouseLeave(); + toolTip.hide(); } } void onMouseLeave() //wxEVT_LEAVE_WINDOW does not respect mouse capture! { - if (!dragSelection) - { - if (highlight) - { - refreshCell(refGrid(), highlight->row_, static_cast<ColumnType>(COL_TYPE_CHECKBOX)); - refreshCell(refGrid(), highlight->row_, static_cast<ColumnType>(COL_TYPE_SYNC_ACTION)); - highlight.reset(); - } - toolTip.hide(); //handle custom tooltip - } + toolTip.hide(); //handle custom tooltip } void highlightSyncAction(bool value) { highlightSyncAction_ = value; } private: + enum class HoverAreaCenter //each cell can be divided into four blocks concerning mouse selections + { + CHECK_BOX, + DIR_LEFT, + DIR_NONE, + DIR_RIGHT + }; + std::wstring getValue(size_t row, ColumnType colType) const override { if (const FileSystemObject* fsObj = getRawData(row)) - switch (static_cast<ColumnTypeMiddle>(colType)) + switch (static_cast<ColumnTypeCenter>(colType)) { - case COL_TYPE_CHECKBOX: + case ColumnTypeCenter::CHECKBOX: break; - case COL_TYPE_CMP_CATEGORY: + case ColumnTypeCenter::CMP_CATEGORY: return getSymbol(fsObj->getCategory()); - case COL_TYPE_SYNC_ACTION: + case ColumnTypeCenter::SYNC_ACTION: return getSymbol(fsObj->getSyncOperation()); } return std::wstring(); @@ -963,7 +911,7 @@ private: if (fsObj->isActive()) fillBackgroundDefaultColorAlternating(dc, rect, row % 2 == 0); else - clearArea(dc, rect, COLOR_NOT_ACTIVE); + clearArea(dc, rect, getColorNotActive()); } else clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -973,7 +921,7 @@ private: clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); } - void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) override + void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override { auto drawHighlightBackground = [&](const FileSystemObject& fsObj, const wxColor& col) { @@ -981,22 +929,21 @@ private: clearArea(dc, rect, col); }; - switch (static_cast<ColumnTypeMiddle>(colType)) + switch (static_cast<ColumnTypeCenter>(colType)) { - case COL_TYPE_CHECKBOX: + case ColumnTypeCenter::CHECKBOX: if (const FileSystemObject* fsObj = getRawData(row)) { - const bool rowHighlighted = dragSelection ? row == dragSelection->first : highlight ? row == highlight->row_ : false; - const BlockPosition highlightBlock = dragSelection ? dragSelection->second : highlight ? highlight->blockPos_ : BLOCKPOS_CHECK_BOX; + const bool drawMouseHover = static_cast<HoverAreaCenter>(rowHover) == HoverAreaCenter::CHECK_BOX; - if (rowHighlighted && highlightBlock == BLOCKPOS_CHECK_BOX) - drawBitmapRtlMirror(dc, getResourceImage(fsObj->isActive() ? L"checkboxTrueFocus" : L"checkboxFalseFocus"), rect, wxALIGN_CENTER, buffer); + if (fsObj->isActive()) + drawBitmapRtlMirror(dc, getResourceImage(drawMouseHover ? L"checkbox_true_hover" : L"checkbox_true"), rect, wxALIGN_CENTER, buffer); else //default - drawBitmapRtlMirror(dc, getResourceImage(fsObj->isActive() ? L"checkboxTrue" : L"checkboxFalse" ), rect, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, getResourceImage(drawMouseHover ? L"checkbox_false_hover" : L"checkbox_false"), rect, wxALIGN_CENTER, buffer); } break; - case COL_TYPE_CMP_CATEGORY: + case ColumnTypeCenter::CMP_CATEGORY: if (const FileSystemObject* fsObj = getRawData(row)) { if (!highlightSyncAction_) @@ -1021,54 +968,80 @@ private: } break; - case COL_TYPE_SYNC_ACTION: + case ColumnTypeCenter::SYNC_ACTION: if (const FileSystemObject* fsObj = getRawData(row)) { if (highlightSyncAction_) drawHighlightBackground(*fsObj, getBackGroundColorSyncAction(fsObj)); - const bool rowHighlighted = dragSelection ? row == dragSelection->first : highlight ? row == highlight->row_ : false; - const BlockPosition highlightBlock = dragSelection ? dragSelection->second : highlight ? highlight->blockPos_ : BLOCKPOS_CHECK_BOX; - //synchronization preview - if (rowHighlighted && highlightBlock != BLOCKPOS_CHECK_BOX) - switch (highlightBlock) - { - case BLOCKPOS_CHECK_BOX: - break; - case BLOCKPOS_LEFT: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::LEFT)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer); - break; - case BLOCKPOS_MIDDLE: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::NONE)), rect, wxALIGN_CENTER, buffer); - break; - case BLOCKPOS_RIGHT: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::RIGHT)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, buffer); - break; - } - else //default + switch (static_cast<HoverAreaCenter>(rowHover)) { - if (highlightSyncAction_) - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->getSyncOperation()), rect, wxALIGN_CENTER, buffer); - else if (fsObj->getSyncOperation() != SO_EQUAL) //don't show = in both middle columns - drawBitmapRtlMirror(dc, greyScale(getSyncOpImage(fsObj->getSyncOperation())), rect, wxALIGN_CENTER, buffer); + case HoverAreaCenter::DIR_LEFT: + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::LEFT)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer); + break; + case HoverAreaCenter::DIR_NONE: + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::NONE)), rect, wxALIGN_CENTER, buffer); + break; + case HoverAreaCenter::DIR_RIGHT: + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::RIGHT)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, buffer); + break; + + case HoverAreaCenter::CHECK_BOX: + default: //HoverArea::NONE + if (highlightSyncAction_) + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->getSyncOperation()), rect, wxALIGN_CENTER, buffer); + else if (fsObj->getSyncOperation() != SO_EQUAL) //don't show = in both middle columns + drawBitmapRtlMirror(dc, greyScale(getSyncOpImage(fsObj->getSyncOperation())), rect, wxALIGN_CENTER, buffer); + break; } } break; } } + HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override + { + if (const FileSystemObject* const fsObj = getRawData(row)) + switch (static_cast<ColumnTypeCenter>(colType)) + { + case ColumnTypeCenter::CHECKBOX: + case ColumnTypeCenter::CMP_CATEGORY: + return static_cast<HoverArea>(HoverAreaCenter::CHECK_BOX); + + case ColumnTypeCenter::SYNC_ACTION: + if (fsObj->getSyncOperation() == SO_EQUAL) //in sync-preview equal files shall be treated like a checkbox + return static_cast<HoverArea>(HoverAreaCenter::CHECK_BOX); + // cell: + // ----------------------- + // | left | middle | right| + // ----------------------- + if (0 <= cellRelativePosX) + { + if (cellRelativePosX < cellWidth / 3) + return static_cast<HoverArea>(HoverAreaCenter::DIR_LEFT); + else if (cellRelativePosX < 2 * cellWidth / 3) + return static_cast<HoverArea>(HoverAreaCenter::DIR_NONE); + else if (cellRelativePosX < cellWidth) + return static_cast<HoverArea>(HoverAreaCenter::DIR_RIGHT); + } + break; + } + return HoverArea::NONE; + } + std::wstring getColumnLabel(ColumnType colType) const override { - switch (static_cast<ColumnTypeMiddle>(colType)) + switch (static_cast<ColumnTypeCenter>(colType)) { - case COL_TYPE_CHECKBOX: + case ColumnTypeCenter::CHECKBOX: break; - case COL_TYPE_CMP_CATEGORY: + case ColumnTypeCenter::CMP_CATEGORY: return _("Category") + L" (F10)"; - case COL_TYPE_SYNC_ACTION: + case ColumnTypeCenter::SYNC_ACTION: return _("Action") + L" (F10)"; } + assert(false); return std::wstring(); } @@ -1076,13 +1049,13 @@ private: void renderColumnLabel(Grid& tree, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted) override { - switch (static_cast<ColumnTypeMiddle>(colType)) + switch (static_cast<ColumnTypeCenter>(colType)) { - case COL_TYPE_CHECKBOX: + case ColumnTypeCenter::CHECKBOX: drawColumnLabelBackground(dc, rect, false); break; - case COL_TYPE_CMP_CATEGORY: + case ColumnTypeCenter::CMP_CATEGORY: { wxRect rectInside = drawColumnLabelBorder(dc, rect); drawColumnLabelBackground(dc, rectInside, highlighted); @@ -1092,7 +1065,7 @@ private: } break; - case COL_TYPE_SYNC_ACTION: + case ColumnTypeCenter::SYNC_ACTION: { wxRect rectInside = drawColumnLabelBorder(dc, rect); drawColumnLabelBackground(dc, rectInside, highlighted); @@ -1109,12 +1082,12 @@ private: if (fsObj) { if (!fsObj->isActive()) - return COLOR_NOT_ACTIVE; + return getColorNotActive(); switch (fsObj->getSyncOperation()) //evaluate comparison result and sync direction { case SO_DO_NOTHING: - return COLOR_NOT_ACTIVE; + return getColorNotActive(); case SO_EQUAL: break; //usually white @@ -1124,7 +1097,7 @@ private: case SO_MOVE_LEFT_SOURCE: case SO_MOVE_LEFT_TARGET: case SO_COPY_METADATA_TO_LEFT: - return COLOR_SYNC_BLUE; + return getColorSyncBlue(); case SO_CREATE_NEW_RIGHT: case SO_OVERWRITE_RIGHT: @@ -1132,10 +1105,10 @@ private: case SO_MOVE_RIGHT_SOURCE: case SO_MOVE_RIGHT_TARGET: case SO_COPY_METADATA_TO_RIGHT: - return COLOR_SYNC_GREEN; + return getColorSyncGreen(); case SO_UNRESOLVED_CONFLICT: - return COLOR_YELLOW; + return getColorYellow(); } } return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -1146,180 +1119,123 @@ private: if (fsObj) { if (!fsObj->isActive()) - return COLOR_NOT_ACTIVE; + return getColorNotActive(); switch (fsObj->getCategory()) { case FILE_LEFT_SIDE_ONLY: case FILE_LEFT_NEWER: - return COLOR_SYNC_BLUE; //COLOR_CMP_BLUE; + return getColorSyncBlue(); //COLOR_CMP_BLUE; case FILE_RIGHT_SIDE_ONLY: case FILE_RIGHT_NEWER: - return COLOR_SYNC_GREEN; //COLOR_CMP_GREEN; + return getColorSyncGreen(); //COLOR_CMP_GREEN; case FILE_DIFFERENT_CONTENT: - return COLOR_CMP_RED; + return getColorCmpRed(); case FILE_EQUAL: break; //usually white case FILE_CONFLICT: case FILE_DIFFERENT_METADATA: //= sub-category of equal, but hint via background that sync direction follows conflict-setting - return COLOR_YELLOW; - //return COLOR_YELLOW_LIGHT; + return getColorYellow(); + //return getColorYellowLight(); } } return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } - enum BlockPosition //each cell can be divided into four blocks concerning mouse selections - { - BLOCKPOS_CHECK_BOX, - BLOCKPOS_LEFT, - BLOCKPOS_MIDDLE, - BLOCKPOS_RIGHT - }; - - //determine block position within cell - BlockPosition mousePosToBlock(const wxPoint& clientPos, size_t row, ColumnTypeMiddle colType) const - { - switch (static_cast<ColumnTypeMiddle>(colType)) - { - case COL_TYPE_CHECKBOX: - case COL_TYPE_CMP_CATEGORY: - break; - - case COL_TYPE_SYNC_ACTION: - { - const int absX = refGrid().CalcUnscrolledPosition(clientPos).x; - - const wxRect rect = refGrid().getCellArea(row, static_cast<ColumnType>(COL_TYPE_SYNC_ACTION)); //returns empty rect if column not found; absolute coordinates! - if (rect.width > 0 && rect.height > 0) - if (const FileSystemObject* const fsObj = getRawData(row)) - if (fsObj->getSyncOperation() != SO_EQUAL) //in sync-preview equal files shall be treated like a checkbox - // cell: - // ----------------------- - // | left | middle | right| - // ----------------------- - if (rect.GetX() <= absX) - { - if (absX < rect.GetX() + rect.GetWidth() / 3) - return BLOCKPOS_LEFT; - else if (absX < rect.GetX() + 2 * rect.GetWidth() / 3) - return BLOCKPOS_MIDDLE; - else if (absX < rect.GetX() + rect.GetWidth()) - return BLOCKPOS_RIGHT; - } - } - break; - } - return BLOCKPOS_CHECK_BOX; - } - - void showToolTip(size_t row, ColumnTypeMiddle colType, wxPoint posScreen) + void showToolTip(size_t row, ColumnTypeCenter colType, wxPoint posScreen) { if (const FileSystemObject* fsObj = getRawData(row)) { - bool showTooltipSyncAction = true; switch (colType) { - case COL_TYPE_CHECKBOX: - showTooltipSyncAction = highlightSyncAction_; - break; - case COL_TYPE_CMP_CATEGORY: - showTooltipSyncAction = false; - break; - case COL_TYPE_SYNC_ACTION: - break; - } - - if (showTooltipSyncAction) //synchronization preview - { - const wchar_t* imageName = [&]() -> const wchar_t* + case ColumnTypeCenter::CHECKBOX: + case ColumnTypeCenter::CMP_CATEGORY: { - const SyncOperation syncOp = fsObj->getSyncOperation(); - switch (syncOp) + const wchar_t* imageName = [&] { - case SO_CREATE_NEW_LEFT: - return L"so_create_left"; - case SO_CREATE_NEW_RIGHT: - return L"so_create_right"; - case SO_DELETE_LEFT: - return L"so_delete_left"; - case SO_DELETE_RIGHT: - return L"so_delete_right"; - case SO_MOVE_LEFT_SOURCE: - return L"so_move_left_source"; - case SO_MOVE_LEFT_TARGET: - return L"so_move_left_target"; - case SO_MOVE_RIGHT_SOURCE: - return L"so_move_right_source"; - case SO_MOVE_RIGHT_TARGET: - return L"so_move_right_target"; - case SO_OVERWRITE_LEFT: - return L"so_update_left"; - case SO_OVERWRITE_RIGHT: - return L"so_update_right"; - case SO_COPY_METADATA_TO_LEFT: - return L"so_move_left"; - case SO_COPY_METADATA_TO_RIGHT: - return L"so_move_right"; - case SO_DO_NOTHING: - return L"so_none"; - case SO_EQUAL: - return L"cat_equal"; - case SO_UNRESOLVED_CONFLICT: - return L"cat_conflict"; - }; - assert(false); - return L""; - }(); - const auto& img = mirrorIfRtl(getResourceImage(imageName)); - toolTip.show(getSyncOpDescription(*fsObj), posScreen, &img); - } - else - { - const wchar_t* imageName = [&]() -> const wchar_t* + const CompareFilesResult cmpRes = fsObj->getCategory(); + switch (cmpRes) + { + case FILE_LEFT_SIDE_ONLY: + return L"cat_left_only"; + case FILE_RIGHT_SIDE_ONLY: + return L"cat_right_only"; + case FILE_LEFT_NEWER: + return L"cat_left_newer"; + case FILE_RIGHT_NEWER: + return L"cat_right_newer"; + case FILE_DIFFERENT_CONTENT: + return L"cat_different"; + case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: //= sub-category of equal + return L"cat_equal"; + case FILE_CONFLICT: + return L"cat_conflict"; + } + assert(false); + return L""; + }(); + const auto& img = mirrorIfRtl(getResourceImage(imageName)); + toolTip.show(getCategoryDescription(*fsObj), posScreen, &img); + } + break; + + case ColumnTypeCenter::SYNC_ACTION: { - const CompareFilesResult cmpRes = fsObj->getCategory(); - switch (cmpRes) + const wchar_t* imageName = [&] { - case FILE_LEFT_SIDE_ONLY: - return L"cat_left_only"; - case FILE_RIGHT_SIDE_ONLY: - return L"cat_right_only"; - case FILE_LEFT_NEWER: - return L"cat_left_newer"; - case FILE_RIGHT_NEWER: - return L"cat_right_newer"; - case FILE_DIFFERENT_CONTENT: - return L"cat_different"; - case FILE_EQUAL: - case FILE_DIFFERENT_METADATA: //= sub-category of equal - return L"cat_equal"; - case FILE_CONFLICT: - return L"cat_conflict"; - } - assert(false); - return L""; - }(); - const auto& img = mirrorIfRtl(getResourceImage(imageName)); - toolTip.show(getCategoryDescription(*fsObj), posScreen, &img); + const SyncOperation syncOp = fsObj->getSyncOperation(); + switch (syncOp) + { + case SO_CREATE_NEW_LEFT: + return L"so_create_left"; + case SO_CREATE_NEW_RIGHT: + return L"so_create_right"; + case SO_DELETE_LEFT: + return L"so_delete_left"; + case SO_DELETE_RIGHT: + return L"so_delete_right"; + case SO_MOVE_LEFT_SOURCE: + return L"so_move_left_source"; + case SO_MOVE_LEFT_TARGET: + return L"so_move_left_target"; + case SO_MOVE_RIGHT_SOURCE: + return L"so_move_right_source"; + case SO_MOVE_RIGHT_TARGET: + return L"so_move_right_target"; + case SO_OVERWRITE_LEFT: + return L"so_update_left"; + case SO_OVERWRITE_RIGHT: + return L"so_update_right"; + case SO_COPY_METADATA_TO_LEFT: + return L"so_move_left"; + case SO_COPY_METADATA_TO_RIGHT: + return L"so_move_right"; + case SO_DO_NOTHING: + return L"so_none"; + case SO_EQUAL: + return L"cat_equal"; + case SO_UNRESOLVED_CONFLICT: + return L"cat_conflict"; + }; + assert(false); + return L""; + }(); + const auto& img = mirrorIfRtl(getResourceImage(imageName)); + toolTip.show(getSyncOpDescription(*fsObj), posScreen, &img); + } + break; } } else toolTip.hide(); //if invalid row... } - bool highlightSyncAction_; + bool highlightSyncAction_ = false; + bool selectionInProgress = false; - struct MouseHighlight - { - MouseHighlight(size_t row, BlockPosition blockPos) : row_(row), blockPos_(blockPos) {} - const size_t row_; - const BlockPosition blockPos_; - }; - std::unique_ptr<MouseHighlight> highlight; //current mouse highlight - std::unique_ptr<std::pair<size_t, BlockPosition>> dragSelection; //(row, block); area clicked when beginning selection Opt<wxBitmap> buffer; //avoid costs of recreating this temporal variable Tooltip toolTip; wxImage notch; @@ -1335,10 +1251,9 @@ public: GridEventManager(Grid& gridL, Grid& gridC, Grid& gridR, - GridDataMiddle& provMiddle) : - gridL_(gridL), gridC_(gridC), gridR_(gridR), scrollMaster(nullptr), - provMiddle_(provMiddle), - scrollbarUpdatePending(false) + GridDataCenter& provCenter) : + gridL_(gridL), gridC_(gridC), gridR_(gridR), + provCenter_(provCenter) { gridL_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnL), nullptr, this); gridR_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnR), nullptr, this); @@ -1394,27 +1309,31 @@ public: private: void onCenterSelectBegin(GridClickEvent& event) { - - provMiddle_.onSelectBegin(event.GetPosition(), event.row_, event.colType_); + provCenter_.onSelectBegin(); event.Skip(); } void onCenterSelectEnd(GridRangeSelectEvent& event) { if (event.positive_) - provMiddle_.onSelectEnd(event.rowFirst_, event.rowLast_); + { + if (event.mouseInitiated_) + provCenter_.onSelectEnd(event.rowFirst_, event.rowLast_, event.mouseInitiated_->hoverArea_, event.mouseInitiated_->row_); + else + provCenter_.onSelectEnd(event.rowFirst_, event.rowLast_, HoverArea::NONE, -1); + } event.Skip(); } void onCenterMouseMovement(wxMouseEvent& event) { - provMiddle_.onMouseMovement(event.GetPosition()); + provCenter_.onMouseMovement(event.GetPosition()); event.Skip(); } void onCenterMouseLeave(wxMouseEvent& event) { - provMiddle_.onMouseLeave(); + provCenter_.onMouseLeave(); event.Skip(); } @@ -1436,14 +1355,10 @@ private: int keyCode = event.GetKeyCode(); if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) { - if (keyCode == WXK_LEFT) + if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT) keyCode = WXK_RIGHT; - else if (keyCode == WXK_RIGHT) + else if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT) keyCode = WXK_LEFT; - else if (keyCode == WXK_NUMPAD_LEFT) - keyCode = WXK_NUMPAD_RIGHT; - else if (keyCode == WXK_NUMPAD_RIGHT) - keyCode = WXK_NUMPAD_LEFT; } //skip middle component when navigating via keyboard @@ -1585,11 +1500,11 @@ private: Grid& gridC_; Grid& gridR_; - const Grid* scrollMaster; //for address check only; this needn't be the grid having focus! + const Grid* scrollMaster = nullptr; //for address check only; this needn't be the grid having focus! //e.g. mouse wheel events should set window under cursor as scrollMaster, but *not* change focus - GridDataMiddle& provMiddle_; - bool scrollbarUpdatePending; + GridDataCenter& provCenter_; + bool scrollbarUpdatePending = false; }; } @@ -1598,16 +1513,16 @@ private: void gridview::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std::shared_ptr<const zen::GridView>& gridDataView) { auto provLeft_ = std::make_shared<GridDataLeft >(gridDataView, gridLeft); - auto provMiddle_ = std::make_shared<GridDataMiddle>(gridDataView, gridCenter); + auto provCenter_ = std::make_shared<GridDataCenter>(gridDataView, gridCenter); auto provRight_ = std::make_shared<GridDataRight >(gridDataView, gridRight); gridLeft .setDataProvider(provLeft_); //data providers reference grid => - gridCenter.setDataProvider(provMiddle_); //ownership must belong *exclusively* to grid! + gridCenter.setDataProvider(provCenter_); //ownership must belong *exclusively* to grid! gridRight .setDataProvider(provRight_); - auto evtMgr = std::make_shared<GridEventManager>(gridLeft, gridCenter, gridRight, *provMiddle_); + auto evtMgr = std::make_shared<GridEventManager>(gridLeft, gridCenter, gridRight, *provCenter_); provLeft_ ->holdOwnership(evtMgr); - provMiddle_->holdOwnership(evtMgr); + provCenter_->holdOwnership(evtMgr); provRight_ ->holdOwnership(evtMgr); gridCenter.enableColumnMove (false); @@ -1619,16 +1534,16 @@ void gridview::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std //gridLeft .showScrollBars(Grid::SB_SHOW_AUTOMATIC, Grid::SB_SHOW_NEVER); -> redundant: configuration happens in GridEventManager::onAlignScrollBars() //gridCenter.showScrollBars(Grid::SB_SHOW_NEVER, Grid::SB_SHOW_NEVER); - const int widthCheckbox = getResourceImage(L"checkboxTrue").GetWidth() + 4 + getResourceImage(L"notch").GetWidth(); + const int widthCheckbox = getResourceImage(L"checkbox_true").GetWidth() + 4 + getResourceImage(L"notch").GetWidth(); const int widthCategory = 30; const int widthAction = 45; gridCenter.SetSize(widthCategory + widthCheckbox + widthAction, -1); - std::vector<Grid::ColumnAttribute> attribMiddle; - attribMiddle.emplace_back(static_cast<ColumnType>(COL_TYPE_CHECKBOX ), widthCheckbox, 0, true); - attribMiddle.emplace_back(static_cast<ColumnType>(COL_TYPE_CMP_CATEGORY), widthCategory, 0, true); - attribMiddle.emplace_back(static_cast<ColumnType>(COL_TYPE_SYNC_ACTION ), widthAction, 0, true); - gridCenter.setColumnConfig(attribMiddle); + std::vector<Grid::ColumnAttribute> attribCenter; + attribCenter.emplace_back(static_cast<ColumnType>(ColumnTypeCenter::CHECKBOX ), widthCheckbox, 0, true); + attribCenter.emplace_back(static_cast<ColumnType>(ColumnTypeCenter::CMP_CATEGORY), widthCategory, 0, true); + attribCenter.emplace_back(static_cast<ColumnType>(ColumnTypeCenter::SYNC_ACTION ), widthAction, 0, true); + gridCenter.setColumnConfig(attribCenter); } @@ -1643,7 +1558,7 @@ std::vector<ColumnAttributeRim> makeConsistent(const std::vector<ColumnAttribute std::copy_if(attribs.begin(), attribs.end(), std::back_inserter(output), [&](const ColumnAttributeRim& a) { return usedTypes.insert(a.type_).second; }); - //make sure each type is existing! -> should *only* be a problem if user manually messes with globalsettings.xml + //make sure each type is existing! -> should *only* be a problem if user manually messes with GlobalSettings.xml const auto& defAttr = getDefaultColumnAttributesLeft(); std::copy_if(defAttr.begin(), defAttr.end(), std::back_inserter(output), [&](const ColumnAttributeRim& a) { return usedTypes.insert(a.type_).second; }); @@ -1654,12 +1569,9 @@ std::vector<ColumnAttributeRim> makeConsistent(const std::vector<ColumnAttribute std::vector<Grid::ColumnAttribute> gridview::convertConfig(const std::vector<ColumnAttributeRim>& attribs) { - const auto& attribClean = makeConsistent(attribs); - std::vector<Grid::ColumnAttribute> output; - std::transform(attribClean.begin(), attribClean.end(), std::back_inserter(output), - [&](const ColumnAttributeRim& ca) { return Grid::ColumnAttribute(static_cast<ColumnType>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); - + for (const ColumnAttributeRim& ca : makeConsistent(attribs)) + output.emplace_back(static_cast<ColumnType>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); return output; } @@ -1667,10 +1579,8 @@ std::vector<Grid::ColumnAttribute> gridview::convertConfig(const std::vector<Col std::vector<ColumnAttributeRim> gridview::convertConfig(const std::vector<Grid::ColumnAttribute>& attribs) { std::vector<ColumnAttributeRim> output; - - std::transform(attribs.begin(), attribs.end(), std::back_inserter(output), - [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeRim(static_cast<ColumnTypeRim>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); - + for (const Grid::ColumnAttribute& ca : attribs) + output.emplace_back(static_cast<ColumnTypeRim>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); return makeConsistent(output); } @@ -1795,8 +1705,8 @@ void gridview::setNavigationMarker(Grid& gridLeft, void gridview::highlightSyncAction(Grid& gridCenter, bool value) { - if (auto provMiddle = dynamic_cast<GridDataMiddle*>(gridCenter.getDataProvider())) - provMiddle->highlightSyncAction(value); + if (auto provCenter = dynamic_cast<GridDataCenter*>(gridCenter.getDataProvider())) + provCenter->highlightSyncAction(value); else assert(false); gridCenter.Refresh(); @@ -1838,6 +1748,7 @@ wxBitmap zen::getSyncOpImage(SyncOperation syncOp) case SO_UNRESOLVED_CONFLICT: return getResourceImage(L"cat_conflict_small"); } + assert(false); return wxNullBitmap; } @@ -1862,5 +1773,6 @@ wxBitmap zen::getCmpResultImage(CompareFilesResult cmpResult) case FILE_CONFLICT: return getResourceImage(L"cat_conflict_small"); } + assert(false); return wxNullBitmap; } diff --git a/FreeFileSync/Source/ui/folder_history_types.h b/FreeFileSync/Source/ui/folder_history_types.h deleted file mode 100644 index 42f58c79..00000000 --- a/FreeFileSync/Source/ui/folder_history_types.h +++ /dev/null @@ -1,24 +0,0 @@ -// ************************************************************************** -// * 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 gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#ifndef FOLDER_HISTORY_TYPES_H_32481457137432143214 -#define FOLDER_HISTORY_TYPES_H_32481457137432143214 - -#include <zen/zstring.h> - - -namespace zen -{ -struct ConfigHistoryItem -{ - explicit ConfigHistoryItem(const Zstring& name) : configFile(name) {} - ConfigHistoryItem() {} - Zstring configFile; - //time_t lastSyncTime; -}; -} - -#endif //FOLDER_HISTORY_TYPES_H_32481457137432143214 diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index 9cb5ce56..6b6b440f 100644 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -74,8 +74,7 @@ bool onIFileDialogAcceptFolder(HWND wnd, const Zstring& shellFolderPath) const std::wstring msg = replaceCpy(_("The selected folder %x cannot be used with FreeFileSync."), L"%x", fmtPath(shellFolderPath)) + L"\n\n" + _("Please select a folder on a local file system, network or an MTP device."); - ::MessageBox(wnd, msg.c_str(), (_("Select a folder")).c_str(), MB_ICONWARNING); - //showNotificationDialog would not support HWND parent + ::MessageBox(wnd, msg.c_str(), (_("Select a folder")).c_str(), MB_ICONWARNING); //showNotificationDialog would not support HWND parent return false; } #endif diff --git a/FreeFileSync/Source/ui/grid_view.cpp b/FreeFileSync/Source/ui/grid_view.cpp index 04d1f277..c669edf8 100644 --- a/FreeFileSync/Source/ui/grid_view.cpp +++ b/FreeFileSync/Source/ui/grid_view.cpp @@ -465,15 +465,15 @@ bool GridView::getDefaultSortDirection(ColumnTypeRim type) //true: ascending; fa { switch (type) { - case COL_TYPE_SIZE: - case COL_TYPE_DATE: + case ColumnTypeRim::SIZE: + case ColumnTypeRim::DATE: return false; - case COL_TYPE_BASE_DIRECTORY: - case COL_TYPE_FULL_PATH: - case COL_TYPE_REL_FOLDER: - case COL_TYPE_FILENAME: - case COL_TYPE_EXTENSION: + case ColumnTypeRim::BASE_DIRECTORY: + case ColumnTypeRim::FULL_PATH: + case ColumnTypeRim::REL_FOLDER: + case ColumnTypeRim::FILENAME: + case ColumnTypeRim::EXTENSION: return true; } assert(false); @@ -490,35 +490,35 @@ void GridView::sortView(ColumnTypeRim type, bool onLeft, bool ascending) switch (type) { - case COL_TYPE_FULL_PATH: + case ColumnTypeRim::FULL_PATH: if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFullPath<true, LEFT_SIDE >()); else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFullPath<true, RIGHT_SIDE>()); else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFullPath<false, LEFT_SIDE >()); else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFullPath<false, RIGHT_SIDE>()); break; - case COL_TYPE_REL_FOLDER: + case ColumnTypeRim::REL_FOLDER: if ( ascending) std::sort(sortedRef.begin(), sortedRef.end(), LessRelativeFolder<true>()); else if (!ascending) std::sort(sortedRef.begin(), sortedRef.end(), LessRelativeFolder<false>()); break; - case COL_TYPE_FILENAME: + case ColumnTypeRim::FILENAME: if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessShortFileName<true, LEFT_SIDE >()); else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessShortFileName<true, RIGHT_SIDE>()); else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessShortFileName<false, LEFT_SIDE >()); else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessShortFileName<false, RIGHT_SIDE>()); break; - case COL_TYPE_SIZE: + case ColumnTypeRim::SIZE: if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFilesize<true, LEFT_SIDE >()); else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFilesize<true, RIGHT_SIDE>()); else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFilesize<false, LEFT_SIDE >()); else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFilesize<false, RIGHT_SIDE>()); break; - case COL_TYPE_DATE: + case ColumnTypeRim::DATE: if ( ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFiletime<true, LEFT_SIDE >()); else if ( ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFiletime<true, RIGHT_SIDE>()); else if (!ascending && onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFiletime<false, LEFT_SIDE >()); else if (!ascending && !onLeft) std::sort(sortedRef.begin(), sortedRef.end(), LessFiletime<false, RIGHT_SIDE>()); break; - case COL_TYPE_EXTENSION: + case ColumnTypeRim::EXTENSION: if ( ascending && onLeft) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessExtension<true, LEFT_SIDE >()); else if ( ascending && !onLeft) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessExtension<true, RIGHT_SIDE>()); else if (!ascending && onLeft) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessExtension<false, LEFT_SIDE >()); @@ -532,7 +532,7 @@ void GridView::sortView(ColumnTypeRim type, bool onLeft, bool ascending) // if ( ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessSyncDirection<true >()); // else if (!ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), LessSyncDirection<false>()); // break; - case COL_TYPE_BASE_DIRECTORY: + case ColumnTypeRim::BASE_DIRECTORY: if ( ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), [](const RefIndex a, const RefIndex b) { return a.folderIndex < b.folderIndex; }); else if (!ascending) std::stable_sort(sortedRef.begin(), sortedRef.end(), [](const RefIndex a, const RefIndex b) { return a.folderIndex > b.folderIndex; }); break; diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index f73d7d0f..753bc9e4 100644 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -250,14 +250,14 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const fgSizer8->Fit( m_panelTopLeft ); bSizer91->Add( m_panelTopLeft, 1, wxLEFT|wxALIGN_CENTER_VERTICAL, 5 ); - m_panelTopMiddle = new wxPanel( m_panelDirectoryPairs, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panelTopCenter = new wxPanel( m_panelDirectoryPairs, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); wxBoxSizer* bSizer1771; bSizer1771 = new wxBoxSizer( wxVERTICAL ); bSizer1771->Add( 0, 0, 1, wxEXPAND, 5 ); - m_bpButtonSwapSides = new wxBitmapButton( m_panelTopMiddle, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW ); + m_bpButtonSwapSides = new wxBitmapButton( m_panelTopCenter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW ); m_bpButtonSwapSides->SetToolTip( _("Swap sides") ); bSizer1771->Add( m_bpButtonSwapSides, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxEXPAND, 5 ); @@ -265,13 +265,13 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer160; bSizer160 = new wxBoxSizer( wxHORIZONTAL ); - m_bpButtonAltCompCfg = new wxBitmapButton( m_panelTopMiddle, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 25,25 ), wxBU_AUTODRAW ); + m_bpButtonAltCompCfg = new wxBitmapButton( m_panelTopCenter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 25,25 ), wxBU_AUTODRAW ); bSizer160->Add( m_bpButtonAltCompCfg, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_bpButtonLocalFilter = new wxBitmapButton( m_panelTopMiddle, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 25,25 ), wxBU_AUTODRAW ); + m_bpButtonLocalFilter = new wxBitmapButton( m_panelTopCenter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 25,25 ), wxBU_AUTODRAW ); bSizer160->Add( m_bpButtonLocalFilter, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 2 ); - m_bpButtonAltSyncCfg = new wxBitmapButton( m_panelTopMiddle, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 25,25 ), wxBU_AUTODRAW ); + m_bpButtonAltSyncCfg = new wxBitmapButton( m_panelTopCenter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 25,25 ), wxBU_AUTODRAW ); bSizer160->Add( m_bpButtonAltSyncCfg, 0, wxALIGN_CENTER_VERTICAL, 5 ); @@ -281,10 +281,10 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer1771->Add( 0, 0, 1, wxEXPAND, 5 ); - m_panelTopMiddle->SetSizer( bSizer1771 ); - m_panelTopMiddle->Layout(); - bSizer1771->Fit( m_panelTopMiddle ); - bSizer91->Add( m_panelTopMiddle, 0, wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); + m_panelTopCenter->SetSizer( bSizer1771 ); + m_panelTopCenter->Layout(); + bSizer1771->Fit( m_panelTopCenter ); + bSizer91->Add( m_panelTopCenter, 0, wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); m_panelTopRight = new wxPanel( m_panelDirectoryPairs, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelTopRight->SetMinSize( wxSize( 1,-1 ) ); @@ -442,9 +442,9 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizerFileStatus->Add( 26, 0, 0, wxALIGN_CENTER_VERTICAL, 5 ); - m_staticTextStatusMiddle = new wxStaticText( m_panelStatusBar, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextStatusMiddle->Wrap( -1 ); - bSizerFileStatus->Add( m_staticTextStatusMiddle, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + m_staticTextStatusCenter = new wxStaticText( m_panelStatusBar, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextStatusCenter->Wrap( -1 ); + bSizerFileStatus->Add( m_staticTextStatusCenter, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); bSizerFileStatus->Add( 26, 0, 0, wxALIGN_CENTER_VERTICAL, 5 ); @@ -1110,28 +1110,6 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w wxBoxSizer* bSizer1734; bSizer1734 = new wxBoxSizer( wxHORIZONTAL ); - wxBoxSizer* bSizer1733; - bSizer1733 = new wxBoxSizer( wxVERTICAL ); - - m_checkBoxTimeShift = new wxCheckBox( m_panelComparisonSettings, wxID_ANY, _("&Ignore time shift (in hours)"), wxDefaultPosition, wxDefaultSize, 0 ); - m_checkBoxTimeShift->SetToolTip( _("Consider file times with specified offset as equal") ); - - bSizer1733->Add( m_checkBoxTimeShift, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - - m_spinCtrlTimeShift = new wxSpinCtrl( m_panelComparisonSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 70,-1 ), wxSP_ARROW_KEYS|wxSP_WRAP, 1, 26, 0 ); - m_spinCtrlTimeShift->SetToolTip( _("Consider file times with specified offset as equal") ); - - bSizer1733->Add( m_spinCtrlTimeShift, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - - m_hyperlink241 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("Handle daylight saving time"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); - bSizer1733->Add( m_hyperlink241, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - - - bSizer1734->Add( bSizer1733, 0, wxALL, 5 ); - - m_staticline44 = new wxStaticLine( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); - bSizer1734->Add( m_staticline44, 0, wxEXPAND, 5 ); - wxBoxSizer* bSizer1721; bSizer1721 = new wxBoxSizer( wxVERTICAL ); @@ -1155,7 +1133,46 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer1721->Add( m_hyperlink24, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer1734->Add( bSizer1721, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + bSizer1734->Add( bSizer1721, 0, wxALL, 5 ); + + m_staticline44 = new wxStaticLine( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer1734->Add( m_staticline44, 0, wxEXPAND, 5 ); + + wxBoxSizer* bSizer1733; + bSizer1733 = new wxBoxSizer( wxVERTICAL ); + + m_staticText112 = new wxStaticText( m_panelComparisonSettings, wxID_ANY, _("&Ignore time shift [hh:mm]"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText112->Wrap( -1 ); + bSizer1733->Add( m_staticText112, 0, wxALL, 5 ); + + m_textCtrlTimeShift = new wxTextCtrl( m_panelComparisonSettings, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + m_textCtrlTimeShift->SetToolTip( _("List of file time offsets to consider equal") ); + + bSizer1733->Add( m_textCtrlTimeShift, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + wxBoxSizer* bSizer197; + bSizer197 = new wxBoxSizer( wxHORIZONTAL ); + + m_staticText1381 = new wxStaticText( m_panelComparisonSettings, wxID_ANY, _("Example:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText1381->Wrap( -1 ); + m_staticText1381->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + + bSizer197->Add( m_staticText1381, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + m_staticText13811 = new wxStaticText( m_panelComparisonSettings, wxID_ANY, _("1, 2, 4:30"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText13811->Wrap( -1 ); + m_staticText13811->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + + bSizer197->Add( m_staticText13811, 0, wxBOTTOM|wxRIGHT, 5 ); + + + bSizer1733->Add( bSizer197, 0, 0, 5 ); + + m_hyperlink241 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("Handle daylight saving time"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + bSizer1733->Add( m_hyperlink241, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + + bSizer1734->Add( bSizer1733, 0, wxALL, 5 ); m_staticline441 = new wxStaticLine( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); bSizer1734->Add( m_staticline441, 0, wxEXPAND, 5 ); @@ -1176,7 +1193,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_panelCompSettingsHolder->SetSizer( bSizer275 ); m_panelCompSettingsHolder->Layout(); bSizer275->Fit( m_panelCompSettingsHolder ); - m_notebook->AddPage( m_panelCompSettingsHolder, _("dummy"), false ); + m_notebook->AddPage( m_panelCompSettingsHolder, _("dummy"), true ); m_panelFilterSettingsHolder = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelFilterSettingsHolder->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -1770,7 +1787,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_panelSyncSettingsHolder->SetSizer( bSizer276 ); m_panelSyncSettingsHolder->Layout(); bSizer276->Fit( m_panelSyncSettingsHolder ); - m_notebook->AddPage( m_panelSyncSettingsHolder, _("dummy"), true ); + m_notebook->AddPage( m_panelSyncSettingsHolder, _("dummy"), false ); bSizer190->Add( m_notebook, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); @@ -1800,16 +1817,17 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w // Connect Events this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( ConfigDlgGenerated::OnClose ) ); + m_listBoxFolderPair->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( ConfigDlgGenerated::onListBoxKeyEvent ), NULL, this ); m_listBoxFolderPair->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnSelectFolderPair ), NULL, this ); m_checkBoxUseLocalCmpOptions->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleLocalCompSettings ), NULL, this ); m_toggleBtnTimeSize->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnTimeSizeDouble ), NULL, this ); m_toggleBtnTimeSize->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnTimeSize ), NULL, this ); m_toggleBtnContent->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnContentDouble ), NULL, this ); m_toggleBtnContent->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnContent ), NULL, this ); - m_checkBoxTimeShift->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeCompOption ), NULL, this ); - m_hyperlink241->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpTimeShift ), NULL, this ); m_checkBoxSymlinksInclude->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeCompOption ), NULL, this ); m_hyperlink24->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpComparisonSettings ), NULL, this ); + m_textCtrlTimeShift->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( ConfigDlgGenerated::onlTimeShiftKeyDown ), NULL, this ); + m_hyperlink241->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpTimeShift ), NULL, this ); m_textCtrlInclude->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpShowExamples ), NULL, this ); m_textCtrlExclude->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); @@ -2011,7 +2029,7 @@ SftpSetupDlgGenerated::SftpSetupDlgGenerated( wxWindow* parent, wxWindowID id, c wxBoxSizer* bSizer181; bSizer181 = new wxBoxSizer( wxHORIZONTAL ); - m_staticText1381 = new wxStaticText( m_panel41, wxID_ANY, _("Examples:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText1381 = new wxStaticText( m_panel41, wxID_ANY, _("Example:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText1381->Wrap( -1 ); m_staticText1381->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index c55ece18..c9ce955a 100644 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -42,9 +42,9 @@ namespace zen { class TripleSplitter; } #include <wx/listbox.h> #include <wx/frame.h> #include <wx/tglbtn.h> -#include <wx/spinctrl.h> -#include <wx/hyperlink.h> #include <wx/radiobut.h> +#include <wx/hyperlink.h> +#include <wx/spinctrl.h> #include <wx/choice.h> #include <wx/notebook.h> #include <wx/dialog.h> @@ -102,7 +102,7 @@ protected: wxStaticText* m_staticTextResolvedPathL; wxBitmapButton* m_bpButtonAddPair; wxButton* m_buttonSelectFolderLeft; - wxPanel* m_panelTopMiddle; + wxPanel* m_panelTopCenter; wxBitmapButton* m_bpButtonSwapSides; wxStaticText* m_staticTextResolvedPathR; wxButton* m_buttonSelectFolderRight; @@ -125,7 +125,7 @@ protected: wxStaticText* m_staticTextStatusLeftFiles; wxStaticText* m_staticTextStatusLeftBytes; wxStaticLine* m_staticline9; - wxStaticText* m_staticTextStatusMiddle; + wxStaticText* m_staticTextStatusCenter; wxBoxSizer* bSizerStatusRight; wxStaticLine* m_staticline10; wxBoxSizer* bSizerStatusRightDirectories; @@ -276,14 +276,16 @@ protected: wxStaticLine* m_staticline42; wxTextCtrl* m_textCtrlCompVarDescription; wxStaticLine* m_staticline33; - wxCheckBox* m_checkBoxTimeShift; - wxSpinCtrl* m_spinCtrlTimeShift; - wxHyperlinkCtrl* m_hyperlink241; - wxStaticLine* m_staticline44; wxCheckBox* m_checkBoxSymlinksInclude; wxRadioButton* m_radioBtnSymlinksFollow; wxRadioButton* m_radioBtnSymlinksDirect; wxHyperlinkCtrl* m_hyperlink24; + wxStaticLine* m_staticline44; + wxStaticText* m_staticText112; + wxTextCtrl* m_textCtrlTimeShift; + wxStaticText* m_staticText1381; + wxStaticText* m_staticText13811; + wxHyperlinkCtrl* m_hyperlink241; wxStaticLine* m_staticline441; wxStaticLine* m_staticline331; wxPanel* m_panelFilterSettingsHolder; @@ -380,6 +382,7 @@ protected: // Virtual event handlers, overide them in your derived class virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onListBoxKeyEvent( wxKeyEvent& event ) { event.Skip(); } virtual void OnSelectFolderPair( wxCommandEvent& event ) { event.Skip(); } virtual void OnToggleLocalCompSettings( wxCommandEvent& event ) { event.Skip(); } virtual void OnTimeSizeDouble( wxMouseEvent& event ) { event.Skip(); } @@ -387,8 +390,9 @@ protected: virtual void OnContentDouble( wxMouseEvent& event ) { event.Skip(); } virtual void OnContent( wxCommandEvent& event ) { event.Skip(); } virtual void OnChangeCompOption( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpTimeShift( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnHelpComparisonSettings( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onlTimeShiftKeyDown( wxKeyEvent& event ) { event.Skip(); } + virtual void OnHelpTimeShift( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnChangeFilterOption( wxCommandEvent& event ) { event.Skip(); } virtual void OnHelpShowExamples( wxHyperlinkEvent& event ) { event.Skip(); } virtual void OnFilterReset( wxCommandEvent& event ) { event.Skip(); } diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index 56b73506..ebc88ca8 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -20,9 +20,7 @@ using namespace zen; using namespace xmlAccess; -StatusHandlerTemporaryPanel::StatusHandlerTemporaryPanel(MainDialog& dlg) : - mainDlg(dlg), - ignoreErrors(false) +StatusHandlerTemporaryPanel::StatusHandlerTemporaryPanel(MainDialog& dlg) : mainDlg(dlg) { { #ifdef ZEN_WIN @@ -55,7 +53,7 @@ StatusHandlerTemporaryPanel::StatusHandlerTemporaryPanel(MainDialog& dlg) : wxAuiPaneInfoArray& paneArray = mainDlg.auiMgr.GetAllPanes(); - const bool statusRowTaken = [&]() -> bool + const bool statusRowTaken = [&] { for (size_t i = 0; i < paneArray.size(); ++i) { @@ -151,7 +149,7 @@ void StatusHandlerTemporaryPanel::initNewPhase(int objectsTotal, std::int64_t da ProcessCallback::Response StatusHandlerTemporaryPanel::reportError(const std::wstring& errorMessage, size_t retryNumber) { //no need to implement auto-retry here: 1. user is watching 2. comparison is fast - //=> similar behavior like "ignoreErrors" which does not honor sync settings + //=> similar behavior like "ignoreErrors" which is also not used for the comparison phase in GUI mode if (ignoreErrors) return ProcessCallback::IGNORE_ERROR; diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h index 1483856b..61dad51e 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.h +++ b/FreeFileSync/Source/ui/gui_status_handler.h @@ -41,7 +41,7 @@ private: void OnAbortCompare(wxCommandEvent& event); //handle abort button click MainDialog& mainDlg; - bool ignoreErrors; + bool ignoreErrors = false; }; diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index a32b4870..1476d230 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -252,7 +252,7 @@ public: folderSelectorRight.Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); mainDialog.m_panelTopLeft ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); - mainDialog.m_panelTopMiddle->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); + mainDialog.m_panelTopCenter->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); mainDialog.m_panelTopRight ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, &mainDialog); } @@ -394,17 +394,19 @@ void MainDialog::create(const Zstring& globalConfigFile) if (fileExists(globalConfigFile)) //else: globalCfg already has default values globalSettings = loadGlobalConfig(globalConfigFile); - std::vector<Zstring> filepaths = globalSettings.gui.lastUsedConfigFiles; //2. now try last used files + std::vector<Zstring> cfgFilePaths; + for (const ConfigFileItem& item : globalSettings.gui.lastUsedConfigFiles) + cfgFilePaths.push_back(item.filePath_); //------------------------------------------------------------------------------------------ //check existence of all files in parallel: GetFirstResult<FalseType> firstMissingDir; - for (const Zstring& filepath : filepaths) - firstMissingDir.addJob([filepath] () -> Opt<FalseType> + for (const Zstring& filePath : cfgFilePaths) + firstMissingDir.addJob([filePath] () -> Opt<FalseType> { - assert(!filepath.empty()); - if (filepath.empty() /*ever empty??*/ || !fileExists(filepath)) + assert(!filePath.empty()); + if (filePath.empty() /*ever empty??*/ || !fileExists(filePath)) return FalseType(); return NoValue(); }); @@ -413,18 +415,18 @@ void MainDialog::create(const Zstring& globalConfigFile) const bool allFilesExist = firstMissingDir.timedWait(std::chrono::milliseconds(500)) && //false: time elapsed !firstMissingDir.get(); //no missing if (!allFilesExist) - filepaths.clear(); //we do NOT want to show an error due to last config file missing on application start! + cfgFilePaths.clear(); //we do NOT want to show an error due to last config file missing on application start! //------------------------------------------------------------------------------------------ - if (filepaths.empty()) + if (cfgFilePaths.empty()) { if (zen::fileExists(lastRunConfigName())) //3. try to load auto-save config - filepaths.push_back(lastRunConfigName()); + cfgFilePaths.push_back(lastRunConfigName()); } XmlGuiConfig guiCfg; //structure to receive gui settings with default values - if (filepaths.empty()) + if (cfgFilePaths.empty()) { //add default exclusion filter: this is only ever relevant when creating new configurations! //a default XmlGuiConfig does not need these user-specific exclusions! @@ -437,7 +439,7 @@ void MainDialog::create(const Zstring& globalConfigFile) try { std::wstring warningMsg; - readAnyConfig(filepaths, guiCfg, warningMsg); //throw FileError + readAnyConfig(cfgFilePaths, guiCfg, warningMsg); //throw FileError if (!warningMsg.empty()) showNotificationDialog(nullptr, DialogInfoType::WARNING, PopupDialogCfg().setDetailInstructions(warningMsg)); @@ -450,7 +452,7 @@ void MainDialog::create(const Zstring& globalConfigFile) //------------------------------------------------------------------------------------------ - create(globalConfigFile, &globalSettings, guiCfg, filepaths, false); + create(globalConfigFile, &globalSettings, guiCfg, cfgFilePaths, false); } @@ -609,19 +611,19 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, //---------------------------------------------------------------------------------- //sort grids - m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickL ), nullptr, this); - m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickC ), nullptr, this); - m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickR ), nullptr, this); + m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(MainDialog::onGridLabelLeftClickL), nullptr, this); + m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(MainDialog::onGridLabelLeftClickC), nullptr, this); + m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(MainDialog::onGridLabelLeftClickR), nullptr, this); - m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextL ), nullptr, this); - m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextC ), nullptr, this); - m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextR ), nullptr, this); + m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(MainDialog::onGridLabelContextL ), nullptr, this); + m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(MainDialog::onGridLabelContextC ), nullptr, this); + m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(MainDialog::onGridLabelContextR ), nullptr, this); //grid context menu - m_gridMainL->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextL), nullptr, this); - m_gridMainC->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextC), nullptr, this); - m_gridMainR->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextR), nullptr, this); - m_gridNavi ->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onNaviGridContext ), nullptr, this); + m_gridMainL->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextL), nullptr, this); + m_gridMainC->Connect(EVENT_GRID_MOUSE_RIGHT_DOWN, GridClickEventHandler(MainDialog::onMainGridContextC), nullptr, this); + m_gridMainR->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextR), nullptr, this); + m_gridNavi ->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onNaviGridContext ), nullptr, this); m_gridMainL->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onGridDoubleClickL), nullptr, this ); m_gridMainR->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onGridDoubleClickR), nullptr, this ); @@ -693,13 +695,13 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, } //create language selection menu - for (const ExistingTranslations::Entry& entry : ExistingTranslations::get()) + for (const TranslationInfo& ti : getExistingTranslations()) { - wxMenuItem* newItem = new wxMenuItem(m_menuLanguages, wxID_ANY, entry.languageName); - newItem->SetBitmap(getResourceImage(entry.languageFlag)); + wxMenuItem* newItem = new wxMenuItem(m_menuLanguages, wxID_ANY, ti.languageName); + newItem->SetBitmap(getResourceImage(ti.languageFlag)); //map menu item IDs with language IDs: evaluated when processing event handler - languageMenuItemMap.emplace(newItem->GetId(), entry.languageID); + languageMenuItemMap.emplace(newItem->GetId(), ti.languageID); //connect event this->Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnMenuLanguageSwitch), nullptr, this); @@ -727,7 +729,6 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, //init grid settings gridview::init(*m_gridMainL, *m_gridMainC, *m_gridMainR, gridDataView); treeview::init(*m_gridNavi, treeDataView); - //config_history::init(*m_gridConfigHistory, lastRunConfigName()); //initialize and load configuration setGlobalCfgOnInit(globalSettings); @@ -889,7 +890,7 @@ void MainDialog::onQueryEndSession() using namespace xmlAccess; try { writeConfig(getGlobalCfgBeforeExit(), globalConfigFile_); } - catch (const FileError&) {} //we try our best do to something useful in this extreme situation - no reason to notify or even log errors here! + catch (const FileError&) {} //we try our best to do something useful in this extreme situation - no reason to notify or even log errors here! try { writeConfig(getConfig(), lastRunConfigName()); } catch (const FileError&) {} @@ -967,17 +968,16 @@ void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSe //-------------------------------------------------------------------------------- //load list of last used configuration files - //config_history::setItems(*m_gridConfigHistory, globalSettings.gui.lastUsedConfigFiles2); - - std::vector<Zstring> cfgFileNames; - std::transform(globalSettings.gui.cfgFileHistory.rbegin(), globalSettings.gui.cfgFileHistory.rend(), std::back_inserter(cfgFileNames), - [](const ConfigHistoryItem& item) { return item.configFile; }); + std::vector<Zstring> cfgFilePaths; + for (const xmlAccess::ConfigFileItem& item : globalSettings.gui.cfgFileHistory) + cfgFilePaths.push_back(item.filePath_); + std::reverse(cfgFilePaths.begin(), cfgFilePaths.end()); //list is stored with last used files first in xml, however addFileToCfgHistory() needs them last!!! - cfgFileNames.push_back(lastRunConfigName()); //make sure <Last session> is always part of history list (if existing) - addFileToCfgHistory(cfgFileNames); + cfgFilePaths.push_back(lastRunConfigName()); //make sure <Last session> is always part of history list (if existing) + addFileToCfgHistory(cfgFilePaths); - removeObsoleteCfgHistoryItems(cfgFileNames); //remove non-existent items (we need this only on startup) + removeObsoleteCfgHistoryItems(cfgFilePaths); //remove non-existent items (we need this only on startup) //-------------------------------------------------------------------------------- //load list of last used folders @@ -1043,17 +1043,20 @@ xmlAccess::XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit() else assert(false); - //sort by last use; put most recent items *first* (looks better in xml than the reverse) - std::vector<ConfigHistoryItem> history; - std::transform(historyDetail.rbegin(), historyDetail.rend(), std::back_inserter(history), [](const std::pair<const int, Zstring>& item) { return ConfigHistoryItem(item.second); }); + //sort by last use; put most recent items *first* (looks better in xml than reverted) + std::vector<xmlAccess::ConfigFileItem> history; + for (const auto& item : historyDetail) + history.emplace_back(item.second); + std::reverse(history.begin(), history.end()); if (history.size() > globalSettings.gui.cfgFileHistMax) //erase oldest elements history.resize(globalSettings.gui.cfgFileHistMax); globalSettings.gui.cfgFileHistory = history; //-------------------------------------------------------------------------------- - globalSettings.gui.lastUsedConfigFiles = activeConfigFiles; - //globalSettings.gui.lastUsedConfigFiles2 = config_history::getItems(*m_gridConfigHistory); + globalSettings.gui.lastUsedConfigFiles.clear(); + for (const Zstring& cfgFilePath : activeConfigFiles) + globalSettings.gui.lastUsedConfigFiles.emplace_back(cfgFilePath); //write list of last used folders globalSettings.gui.folderHistoryLeft = folderHistoryLeft ->getList(); @@ -1504,18 +1507,18 @@ void MainDialog::setStatusBarFileStatistics(size_t filesOnLeftView, setText(*m_staticTextStatusRightFiles, _P("1 file", "%x files", filesOnRightView)); setText(*m_staticTextStatusRightBytes, L"(" + filesizeToShortString(filesizeRightView) + L")"); //------------------------------------------------------------------------------ - wxString statusMiddleNew; + wxString statusCenterNew; if (gridDataView->rowsTotal() > 0) { - statusMiddleNew = _P("Showing %y of 1 row", "Showing %y of %x rows", gridDataView->rowsTotal()); - replace(statusMiddleNew, L"%y", toGuiString(gridDataView->rowsOnView())); //%x is already used as plural form placeholder! + 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! } //fill middle text (considering flashStatusInformation()) if (oldStatusMsgs.empty()) - setText(*m_staticTextStatusMiddle, statusMiddleNew); + setText(*m_staticTextStatusCenter, statusCenterNew); else - oldStatusMsgs.front() = statusMiddleNew; + oldStatusMsgs.front() = statusCenterNew; m_panelStatusBar->Layout(); } @@ -1539,11 +1542,11 @@ void MainDialog::setStatusBarFileStatistics(size_t filesOnLeftView, void MainDialog::flashStatusInformation(const wxString& text) { - oldStatusMsgs.push_back(m_staticTextStatusMiddle->GetLabel()); + oldStatusMsgs.push_back(m_staticTextStatusCenter->GetLabel()); - m_staticTextStatusMiddle->SetLabel(text); - m_staticTextStatusMiddle->SetForegroundColour(wxColour(31, 57, 226)); //highlight color: blue - m_staticTextStatusMiddle->SetFont(m_staticTextStatusMiddle->GetFont().Bold()); + m_staticTextStatusCenter->SetLabel(text); + m_staticTextStatusCenter->SetForegroundColour(wxColor(31, 57, 226)); //highlight color: blue + m_staticTextStatusCenter->SetFont(m_staticTextStatusCenter->GetFont().Bold()); m_panelStatusBar->Layout(); //if (needLayoutUpdate) auiMgr.Update(); -> not needed here, this is called anyway in updateGui() @@ -1562,12 +1565,12 @@ void MainDialog::restoreStatusInformation() if (oldStatusMsgs.empty()) //restore original status text { - m_staticTextStatusMiddle->SetLabel(oldMsg); - m_staticTextStatusMiddle->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //reset color + m_staticTextStatusCenter->SetLabel(oldMsg); + m_staticTextStatusCenter->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //reset color - wxFont fnt = m_staticTextStatusMiddle->GetFont(); + wxFont fnt = m_staticTextStatusCenter->GetFont(); fnt.SetWeight(wxFONTWEIGHT_NORMAL); - m_staticTextStatusMiddle->SetFont(fnt); + m_staticTextStatusCenter->SetFont(fnt); m_panelStatusBar->Layout(); } @@ -1729,14 +1732,10 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event) int keyCode = event.GetKeyCode(); if (m_gridNavi->GetLayoutDirection() == wxLayout_RightToLeft) { - if (keyCode == WXK_LEFT) + if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT) keyCode = WXK_RIGHT; - else if (keyCode == WXK_RIGHT) + else if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT) keyCode = WXK_LEFT; - else if (keyCode == WXK_NUMPAD_LEFT) - keyCode = WXK_NUMPAD_RIGHT; - else if (keyCode == WXK_NUMPAD_RIGHT) - keyCode = WXK_NUMPAD_LEFT; } if (event.ControlDown()) @@ -1797,14 +1796,10 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) int keyCode = event.GetKeyCode(); if (grid.GetLayoutDirection() == wxLayout_RightToLeft) { - if (keyCode == WXK_LEFT) + if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT) keyCode = WXK_RIGHT; - else if (keyCode == WXK_RIGHT) + else if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT) keyCode = WXK_LEFT; - else if (keyCode == WXK_NUMPAD_LEFT) - keyCode = WXK_NUMPAD_RIGHT; - else if (keyCode == WXK_NUMPAD_RIGHT) - keyCode = WXK_NUMPAD_LEFT; } if (event.ControlDown()) @@ -1970,7 +1965,7 @@ void MainDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without !isComponentOf(focus, m_listBoxHistory) && //don't propagate if selecting config !isComponentOf(focus, m_panelSearch ) && !isComponentOf(focus, m_panelTopLeft ) && //don't propagate if changing directory fields - !isComponentOf(focus, m_panelTopMiddle) && + !isComponentOf(focus, m_panelTopCenter) && !isComponentOf(focus, m_panelTopRight ) && !isComponentOf(focus, m_scrolledWindowFolderPairs) && m_gridMainL->IsEnabled()) @@ -2029,10 +2024,7 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) std::unordered_set<const FileSystemObject*> markedFilesAndLinks; //mark files/symlinks directly std::unordered_set<const HierarchyObject*> markedContainer; //mark full container including child-objects - const std::vector<size_t>& selection = m_gridNavi->getSelectedRows(); - std::for_each(selection.begin(), selection.end(), - [&](size_t row) - { + for (size_t row : m_gridNavi->getSelectedRows()) if (std::unique_ptr<TreeView::Node> node = treeDataView->getLine(row)) { if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) @@ -2042,7 +2034,6 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) else if (const TreeView::FilesNode* files = dynamic_cast<const TreeView::FilesNode*>(node.get())) markedFilesAndLinks.insert(files->filesAndLinks_.begin(), files->filesAndLinks_.end()); } - }); gridview::setNavigationMarker(*m_gridMainL, std::move(markedFilesAndLinks), std::move(markedContainer)); @@ -2119,9 +2110,9 @@ void MainDialog::onNaviGridContext(GridClickEvent& event) if (!selection.empty()) { if (m_bpButtonShowExcluded->isActive() && !selection[0]->isActive()) - menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, &getResourceImage(L"checkboxTrue")); + menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, &getResourceImage(L"checkbox_true")); else - menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, &getResourceImage(L"checkboxFalse")); + menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, &getResourceImage(L"checkbox_false")); } else menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); @@ -2252,9 +2243,9 @@ void MainDialog::onMainGridContextRim(bool leftSide) if (!selection.empty()) { if (m_bpButtonShowExcluded->isActive() && !selection[0]->isActive()) - menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, &getResourceImage(L"checkboxTrue")); + menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, &getResourceImage(L"checkbox_true")); else - menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, &getResourceImage(L"checkboxFalse")); + menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, &getResourceImage(L"checkbox_false")); } else menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); @@ -2394,7 +2385,7 @@ void MainDialog::filterItems(const std::vector<FileSystemObject*>& selection, bo } -void MainDialog::onGridLabelContextC(GridClickEvent& event) +void MainDialog::onGridLabelContextC(GridLabelClickEvent& event) { ContextMenu menu; @@ -2406,11 +2397,13 @@ void MainDialog::onGridLabelContextC(GridClickEvent& event) } -void MainDialog::onGridLabelContextL(GridClickEvent& event) +void MainDialog::onGridLabelContextL(GridLabelClickEvent& event) { onGridLabelContext(*m_gridMainL, static_cast<ColumnTypeRim>(event.colType_), getDefaultColumnAttributesLeft()); } -void MainDialog::onGridLabelContextR(GridClickEvent& event) + + +void MainDialog::onGridLabelContextR(GridLabelClickEvent& event) { onGridLabelContext(*m_gridMainR, static_cast<ColumnTypeRim>(event.colType_), getDefaultColumnAttributesRight()); } @@ -2436,7 +2429,7 @@ void MainDialog::onGridLabelContext(Grid& grid, ColumnTypeRim type, const std::v if (const GridData* prov = grid.getDataProvider()) for (const Grid::ColumnAttribute& ca : grid.getColumnConfig()) menu.addCheckBox(prov->getColumnLabel(ca.type_), [ca, toggleColumn] { toggleColumn(ca.type_); }, - ca.visible_, ca.type_ != static_cast<ColumnType>(COL_TYPE_FILENAME)); //do not allow user to hide file name column! + ca.visible_, ca.type_ != static_cast<ColumnType>(ColumnTypeRim::FILENAME)); //do not allow user to hide file name column! //---------------------------------------------------------------------------------------------- menu.addSeparator(); @@ -2468,7 +2461,7 @@ void MainDialog::onGridLabelContext(Grid& grid, ColumnTypeRim type, const std::v addSizeEntry(L" " + _("Medium"), xmlAccess::ICON_SIZE_MEDIUM); addSizeEntry(L" " + _("Large" ), xmlAccess::ICON_SIZE_LARGE ); //---------------------------------------------------------------------------------------------- - if (type == COL_TYPE_DATE) + if (type == ColumnTypeRim::DATE) { menu.addSeparator(); @@ -3229,18 +3222,10 @@ void MainDialog::onSetSyncDirection(SyncDirectionEvent& event) } -void MainDialog::setLastUsedConfig(const Zstring& filepath, const xmlAccess::XmlGuiConfig& guiConfig) -{ - std::vector<Zstring> filepaths; - filepaths.push_back(filepath); - setLastUsedConfig(filepaths, guiConfig); -} - - -void MainDialog::setLastUsedConfig(const std::vector<Zstring>& filepaths, +void MainDialog::setLastUsedConfig(const std::vector<Zstring>& cfgFilePaths, const xmlAccess::XmlGuiConfig& guiConfig) { - activeConfigFiles = filepaths; + activeConfigFiles = cfgFilePaths; lastConfigurationSaved = guiConfig; addFileToCfgHistory(activeConfigFiles); //put filepath on list of last used config files @@ -3553,7 +3538,7 @@ void MainDialog::initViewFilterButtons() initButton(*m_bpButtonShowUpdateRight, "so_update_right", _("Show files that will be updated on the right side")); initButton(*m_bpButtonShowDoNothing, "so_none", _("Show files that won't be copied")); - initButton(*m_bpButtonShowExcluded, "checkboxFalse", _("Show filtered or temporarily excluded files")); + initButton(*m_bpButtonShowExcluded, "checkbox_false", _("Show filtered or temporarily excluded files")); } @@ -3943,19 +3928,19 @@ void MainDialog::onGridLabelLeftClick(bool onLeft, ColumnTypeRim type) updateGui(); //refresh gridDataView } -void MainDialog::onGridLabelLeftClickL(GridClickEvent& event) +void MainDialog::onGridLabelLeftClickL(GridLabelClickEvent& event) { onGridLabelLeftClick(true, static_cast<ColumnTypeRim>(event.colType_)); } -void MainDialog::onGridLabelLeftClickR(GridClickEvent& event) +void MainDialog::onGridLabelLeftClickR(GridLabelClickEvent& event) { onGridLabelLeftClick(false, static_cast<ColumnTypeRim>(event.colType_)); } -void MainDialog::onGridLabelLeftClickC(GridClickEvent& event) +void MainDialog::onGridLabelLeftClickC(GridLabelClickEvent& event) { //sorting middle grid is more or less useless: therefore let's toggle view instead! setViewTypeSyncAction(!m_bpButtonViewTypeSyncAction->isActive()); //toggle view @@ -4480,7 +4465,7 @@ void MainDialog::updateGuiForFolderPair() setImage(*m_bpButtonSwapSides, getResourceImage(showLocalCfgFirstPair ? L"swap_slim" : L"swap")); //update sub-panel sizes for calculations below!!! - m_panelTopMiddle->GetSizer()->SetSizeHints(m_panelTopMiddle); //~=Fit() + SetMinSize() + m_panelTopCenter->GetSizer()->SetSizeHints(m_panelTopCenter); //~=Fit() + SetMinSize() int addPairMinimalHeight = 0; int addPairOptimalHeight = 0; @@ -4495,7 +4480,7 @@ void MainDialog::updateGuiForFolderPair() } const int firstPairHeight = std::max(m_panelDirectoryPairs->ClientToWindowSize(m_panelTopLeft ->GetSize()).GetHeight(), //include m_panelDirectoryPairs window borders! - m_panelDirectoryPairs->ClientToWindowSize(m_panelTopMiddle->GetSize()).GetHeight()); // + m_panelDirectoryPairs->ClientToWindowSize(m_panelTopCenter->GetSize()).GetHeight()); // //######################################################################################################################## //wxAUI hack: set minimum height to desired value, then call wxAuiPaneInfo::Fixed() to apply it @@ -4678,27 +4663,27 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) //write header auto provLeft = m_gridMainL->getDataProvider(); - auto provMiddle = m_gridMainC->getDataProvider(); + auto provCenter = m_gridMainC->getDataProvider(); auto provRight = m_gridMainR->getDataProvider(); auto colAttrLeft = m_gridMainL->getColumnConfig(); - auto colAttrMiddle = m_gridMainC->getColumnConfig(); + auto colAttrCenter = m_gridMainC->getColumnConfig(); auto colAttrRight = m_gridMainR->getColumnConfig(); erase_if(colAttrLeft , [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); - erase_if(colAttrMiddle, [](const Grid::ColumnAttribute& ca) { return !ca.visible_ || static_cast<ColumnTypeMiddle>(ca.type_) == COL_TYPE_CHECKBOX; }); + erase_if(colAttrCenter, [](const Grid::ColumnAttribute& ca) { return !ca.visible_ || static_cast<ColumnTypeCenter>(ca.type_) == ColumnTypeCenter::CHECKBOX; }); erase_if(colAttrRight , [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); - if (provLeft && provMiddle && provRight) + if (provLeft && provCenter && provRight) { for (const Grid::ColumnAttribute& ca : colAttrLeft) { header += fmtValue(provLeft->getColumnLabel(ca.type_)); header += CSV_SEP; } - for (const Grid::ColumnAttribute& ca : colAttrMiddle) + for (const Grid::ColumnAttribute& ca : colAttrCenter) { - header += fmtValue(provMiddle->getColumnLabel(ca.type_)); + header += fmtValue(provCenter->getColumnLabel(ca.type_)); header += CSV_SEP; } if (!colAttrRight.empty()) @@ -4732,24 +4717,23 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) { Utf8String tmp; - std::for_each(colAttrLeft.begin(), colAttrLeft.end(), - [&](const Grid::ColumnAttribute& ca) + for (const Grid::ColumnAttribute& ca : colAttrLeft) { tmp += fmtValue(provLeft->getValue(row, ca.type_)); tmp += CSV_SEP; - }); - std::for_each(colAttrMiddle.begin(), colAttrMiddle.end(), - [&](const Grid::ColumnAttribute& ca) + } + + for (const Grid::ColumnAttribute& ca : colAttrCenter) { - tmp += fmtValue(provMiddle->getValue(row, ca.type_)); + tmp += fmtValue(provCenter->getValue(row, ca.type_)); tmp += CSV_SEP; - }); - std::for_each(colAttrRight.begin(), colAttrRight.end(), - [&](const Grid::ColumnAttribute& ca) + } + + for (const Grid::ColumnAttribute& ca : colAttrRight) { tmp += fmtValue(provRight->getValue(row, ca.type_)); tmp += CSV_SEP; - }); + } tmp += '\n'; replace(tmp, '\n', LINE_BREAK); @@ -4848,11 +4832,11 @@ void MainDialog::OnShowHelp(wxCommandEvent& event) //######################################################################################################### //language selection -void MainDialog::switchProgramLanguage(int langID) +void MainDialog::switchProgramLanguage(wxLanguage langId) { //create new dialog with respect to new language xmlAccess::XmlGlobalSettings newGlobalCfg = getGlobalCfgBeforeExit(); - newGlobalCfg.programLanguage = langID; + newGlobalCfg.programLanguage = langId; //show new dialog, then delete old one MainDialog::create(globalConfigFile_, &newGlobalCfg, getConfig(), activeConfigFiles, false); @@ -4866,7 +4850,7 @@ void MainDialog::switchProgramLanguage(int langID) void MainDialog::OnMenuLanguageSwitch(wxCommandEvent& event) { - std::map<MenuItemID, LanguageID>::const_iterator it = languageMenuItemMap.find(event.GetId()); + auto it = languageMenuItemMap.find(event.GetId()); if (it != languageMenuItemMap.end()) switchProgramLanguage(it->second); } diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index 1624e8c6..71ed5858 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -65,8 +65,8 @@ private: friend class PanelMoveWindow; //configuration load/save - void setLastUsedConfig(const Zstring& filepath, const xmlAccess::XmlGuiConfig& guiConfig); - void setLastUsedConfig(const std::vector<Zstring>& filepaths, const xmlAccess::XmlGuiConfig& guiConfig); + void setLastUsedConfig(const Zstring& cfgFilePath, const xmlAccess::XmlGuiConfig& guiConfig) { setLastUsedConfig(std::vector<Zstring>({ cfgFilePath }), guiConfig); } + void setLastUsedConfig(const std::vector<Zstring>& cfgFilePaths, const xmlAccess::XmlGuiConfig& guiConfig); xmlAccess::XmlGuiConfig getConfig() const; void setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std::vector<Zstring>& referenceFiles); @@ -169,14 +169,14 @@ private: void onGridDoubleClickR(zen::GridClickEvent& event); void onGridDoubleClickRim(size_t row, bool leftSide); - void onGridLabelLeftClickC(zen::GridClickEvent& event); - void onGridLabelLeftClickL(zen::GridClickEvent& event); - void onGridLabelLeftClickR(zen::GridClickEvent& event); + void onGridLabelLeftClickL(zen::GridLabelClickEvent& event); + void onGridLabelLeftClickC(zen::GridLabelClickEvent& event); + void onGridLabelLeftClickR(zen::GridLabelClickEvent& event); void onGridLabelLeftClick(bool onLeft, zen::ColumnTypeRim type); - void onGridLabelContextL(zen::GridClickEvent& event); - void onGridLabelContextC(zen::GridClickEvent& event); - void onGridLabelContextR(zen::GridClickEvent& event); + void onGridLabelContextL(zen::GridLabelClickEvent& event); + void onGridLabelContextC(zen::GridLabelClickEvent& event); + void onGridLabelContextR(zen::GridLabelClickEvent& event); void onGridLabelContext(zen::Grid& grid, zen::ColumnTypeRim type, const std::vector<zen::ColumnAttributeRim>& defaultColumnAttributes); void OnToggleViewType (wxCommandEvent& event) override; @@ -260,13 +260,12 @@ private: void OnMenuLanguageSwitch(wxCommandEvent& event); - void switchProgramLanguage(int langID); + void switchProgramLanguage(wxLanguage langId); void clearGrid(ptrdiff_t pos = -1); typedef int MenuItemID; - typedef int LanguageID; - std::map<MenuItemID, LanguageID> languageMenuItemMap; //needed to attach menu item events + std::map<MenuItemID, wxLanguage> languageMenuItemMap; //needed to attach menu item events //*********************************************** //application variables are stored here: diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index 2ee83ba7..22099337 100644 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -53,6 +53,8 @@ const int WINDOW_BYTES_PER_SEC = 5000; // const int GAUGE_FULL_RANGE = 50000; +inline wxColor getColorGridLine() { return { 192, 192, 192 }; } //light grey + //don't use wxStopWatch for long-running measurements: internally it uses ::QueryPerformanceCounter() which can overflow after only a few days: //https://sourceforge.net/p/freefilesync/discussion/help/thread/5d62339e @@ -560,14 +562,12 @@ public: return std::wstring(); } - void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) override + void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override { wxRect rectTmp = rect; //-------------- draw item separation line ----------------- - const wxColor colorGridLine = wxColour(192, 192, 192); //light grey - - wxDCPenChanger dummy2(dc, wxPen(colorGridLine, 1, wxSOLID)); + wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), 1, wxSOLID)); const bool drawBottomLine = [&] //don't separate multi-line messages { if (msgView_) diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index 3517e3db..2e5f1c93 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -68,19 +68,19 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) //m_animCtrlWink->Play(); //create language credits - for (const ExistingTranslations::Entry& trans : ExistingTranslations::get()) + for (const TranslationInfo& ti : getExistingTranslations()) { //flag - wxStaticBitmap* staticBitmapFlag = new wxStaticBitmap(m_scrolledWindowTranslators, wxID_ANY, getResourceImage(trans.languageFlag), wxDefaultPosition, wxSize(-1, 11), 0); + wxStaticBitmap* staticBitmapFlag = new wxStaticBitmap(m_scrolledWindowTranslators, wxID_ANY, getResourceImage(ti.languageFlag), wxDefaultPosition, wxSize(-1, 11), 0); fgSizerTranslators->Add(staticBitmapFlag, 0, wxALIGN_CENTER); //translator name - wxStaticText* staticTextTranslator = new wxStaticText(m_scrolledWindowTranslators, wxID_ANY, trans.translatorName, wxDefaultPosition, wxDefaultSize, 0); + wxStaticText* staticTextTranslator = new wxStaticText(m_scrolledWindowTranslators, wxID_ANY, ti.translatorName, wxDefaultPosition, wxDefaultSize, 0); staticTextTranslator->Wrap(-1); fgSizerTranslators->Add(staticTextTranslator, 0, wxALIGN_CENTER_VERTICAL); - staticBitmapFlag ->SetToolTip(trans.languageName); - staticTextTranslator->SetToolTip(trans.languageName); + staticBitmapFlag ->SetToolTip(ti.languageName); + staticTextTranslator->SetToolTip(ti.languageName); } fgSizerTranslators->Fit(m_scrolledWindowTranslators); diff --git a/FreeFileSync/Source/ui/sorting.h b/FreeFileSync/Source/ui/sorting.h index 27dd605e..7f3eb6b6 100644 --- a/FreeFileSync/Source/ui/sorting.h +++ b/FreeFileSync/Source/ui/sorting.h @@ -20,7 +20,7 @@ struct CompileTimeReminder : public FSObjectVisitor void visit(const FilePair& file ) override {} void visit(const SymlinkPair& symlink) override {} void visit(const FolderPair& folder ) override {} -} checkDymanicCasts; //just a compile-time reminder to manually check dynamic casts in this file when needed +} checkDymanicCasts; //just a compile-time reminder to manually check dynamic casts in this file if ever needed } diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index d192401a..014dde8f 100644 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -51,6 +51,7 @@ private: void OnClose (wxCloseEvent& event) override { EndModal(ReturnSyncConfig::BUTTON_CANCEL); } void onLocalKeyEvent(wxKeyEvent& event); + void onListBoxKeyEvent(wxKeyEvent& event) override; void OnSelectFolderPair(wxCommandEvent& event) override; enum class ConfigTypeImage @@ -73,6 +74,7 @@ private: void OnTimeSizeDouble (wxMouseEvent& event) override; void OnContentDouble (wxMouseEvent& event) override; void OnChangeCompOption (wxCommandEvent& event) override { updateCompGui(); } + void onlTimeShiftKeyDown (wxKeyEvent& event) override; std::shared_ptr<const CompConfig> getCompConfig() const; void setCompConfig(std::shared_ptr<const CompConfig> compCfg); @@ -367,6 +369,56 @@ void ConfigDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events witho } +void ConfigDialog::onListBoxKeyEvent(wxKeyEvent& event) +{ + int keyCode = event.GetKeyCode(); + if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) + { + if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT) + keyCode = WXK_RIGHT; + else if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT) + keyCode = WXK_LEFT; + } + + switch (keyCode) + { + case WXK_LEFT: + case WXK_NUMPAD_LEFT: + switch (static_cast<SyncConfigPanel>(m_notebook->GetSelection())) + { + case SyncConfigPanel::COMPARISON: + break; + case SyncConfigPanel::FILTER: + m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::COMPARISON)); + break; + case SyncConfigPanel::SYNC: + m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::FILTER)); + break; + } + m_listBoxFolderPair->SetFocus(); //needed! wxNotebook::ChangeSelection() leads to focus change! + return; //handled! + + case WXK_RIGHT: + case WXK_NUMPAD_RIGHT: + switch (static_cast<SyncConfigPanel>(m_notebook->GetSelection())) + { + case SyncConfigPanel::COMPARISON: + m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::FILTER)); + break; + case SyncConfigPanel::FILTER: + m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::SYNC)); + break; + case SyncConfigPanel::SYNC: + break; + } + m_listBoxFolderPair->SetFocus(); + return; //handled! + } + + event.Skip(); +} + + void ConfigDialog::OnSelectFolderPair(wxCommandEvent& event) { assert(!m_listBoxFolderPair->HasMultipleSelection()); //single-choice! @@ -401,6 +453,18 @@ void ConfigDialog::OnContentDouble(wxMouseEvent& event) } +void ConfigDialog::onlTimeShiftKeyDown(wxKeyEvent& event) +{ + const int keyCode = event.GetKeyCode(); + + //ignore invalid input: basically only numeric keys + navigation + text edit keys should be allowed, but let's not hard-code too much... + if ('A' <= keyCode && keyCode <= 'Z') + return; + + event.Skip(); +} + + std::shared_ptr<const CompConfig> ConfigDialog::getCompConfig() const { if (!m_checkBoxUseLocalCmpOptions->GetValue()) @@ -409,7 +473,7 @@ std::shared_ptr<const CompConfig> ConfigDialog::getCompConfig() const CompConfig compCfg; compCfg.compareVar = localCmpVar; compCfg.handleSymlinks = !m_checkBoxSymlinksInclude->GetValue() ? SYMLINK_EXCLUDE : m_radioBtnSymlinksDirect->GetValue() ? SYMLINK_DIRECT : SYMLINK_FOLLOW; - compCfg.optTimeShiftHours = m_checkBoxTimeShift->GetValue() ? m_spinCtrlTimeShift->GetValue() : 0; + compCfg.ignoreTimeShiftMinutes = fromTimeShiftPhrase(copyStringTo<std::wstring>(m_textCtrlTimeShift->GetValue())); return std::make_shared<const CompConfig>(compCfg); } @@ -440,8 +504,7 @@ void ConfigDialog::setCompConfig(std::shared_ptr<const CompConfig> compCfg) break; } - m_checkBoxTimeShift->SetValue(compCfg->optTimeShiftHours != 0); - m_spinCtrlTimeShift->SetValue(compCfg->optTimeShiftHours == 0 ? 1 : compCfg->optTimeShiftHours); + m_textCtrlTimeShift->ChangeValue(toTimeShiftPhrase(compCfg->ignoreTimeShiftMinutes)); updateCompGui(); } @@ -483,8 +546,6 @@ void ConfigDialog::updateCompGui() //active variant description: setText(*m_textCtrlCompVarDescription, L"\n" + getCompVariantDescription(localCmpVar)); - m_spinCtrlTimeShift->Enable(m_checkBoxTimeShift->GetValue()); - m_radioBtnSymlinksDirect->Enable(m_checkBoxSymlinksInclude->GetValue()); m_radioBtnSymlinksFollow->Enable(m_checkBoxSymlinksInclude->GetValue()); } diff --git a/FreeFileSync/Source/ui/tray_icon.cpp b/FreeFileSync/Source/ui/tray_icon.cpp index 54dbbfff..4833fbcb 100644 --- a/FreeFileSync/Source/ui/tray_icon.cpp +++ b/FreeFileSync/Source/ui/tray_icon.cpp @@ -99,7 +99,7 @@ wxIcon generateProgressIcon(const wxImage& logo, double fraction) //generate ico } //fill yellow remainder - fillRange(genImage, startFillPixel, pixelCount, wxColour(240, 200, 0)); + fillRange(genImage, startFillPixel, pixelCount, wxColor(240, 200, 0)); buffer.second.CopyFromBitmap(wxBitmap(genImage)); } diff --git a/FreeFileSync/Source/ui/tree_view.cpp b/FreeFileSync/Source/ui/tree_view.cpp index c74c836a..e9798bf4 100644 --- a/FreeFileSync/Source/ui/tree_view.cpp +++ b/FreeFileSync/Source/ui/tree_view.cpp @@ -297,15 +297,15 @@ void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeNavi colu switch (columnType) { - case COL_TYPE_NAVI_BYTES: + case ColumnTypeNavi::BYTES: std::sort(items.begin(), items.end(), makeSortDirection(lessBytes, Int2Type<ascending>())); break; - case COL_TYPE_NAVI_DIRECTORY: + case ColumnTypeNavi::DIRECTORY: std::sort(items.begin(), items.end(), LessShortName<ascending>()); break; - case COL_TYPE_NAVI_ITEM_COUNT: + case ColumnTypeNavi::ITEM_COUNT: std::sort(items.begin(), items.end(), makeSortDirection(lessCount, Int2Type<ascending>())); break; } @@ -465,11 +465,11 @@ bool TreeView::getDefaultSortDirection(ColumnTypeNavi colType) { switch (colType) { - case COL_TYPE_NAVI_BYTES: + case ColumnTypeNavi::BYTES: return false; - case COL_TYPE_NAVI_DIRECTORY: + case ColumnTypeNavi::DIRECTORY: return true; - case COL_TYPE_NAVI_ITEM_COUNT: + case ColumnTypeNavi::ITEM_COUNT: return false; } assert(false); @@ -735,31 +735,49 @@ std::unique_ptr<TreeView::Node> TreeView::getLine(size_t row) const namespace { -const wxColour COLOR_LEVEL0(0xcc, 0xcc, 0xff); -const wxColour COLOR_LEVEL1(0xcc, 0xff, 0xcc); -const wxColour COLOR_LEVEL2(0xff, 0xff, 0x99); +//let's NOT create wxWidgets objects statically: +inline wxColor getColorPercentBorder () { return { 198, 198, 198 }; } +inline wxColor getColorPercentBackground() { return { 0xf8, 0xf8, 0xf8 }; } -const wxColour COLOR_LEVEL3(0xcc, 0xcc, 0xcc); -const wxColour COLOR_LEVEL4(0xff, 0xcc, 0xff); -const wxColour COLOR_LEVEL5(0x99, 0xff, 0xcc); +inline wxColor getColorTreeSelectionGradientFrom() { return getColorSelectionGradientFrom(); } +inline wxColor getColorTreeSelectionGradientTo () { return getColorSelectionGradientTo (); } -const wxColour COLOR_LEVEL6(0xcc, 0xcc, 0x99); -const wxColour COLOR_LEVEL7(0xff, 0xcc, 0xcc); -const wxColour COLOR_LEVEL8(0xcc, 0xff, 0x99); - -const wxColour COLOR_LEVEL9 (0xff, 0xff, 0xcc); -const wxColour COLOR_LEVEL10(0xcc, 0xff, 0xff); -const wxColour COLOR_LEVEL11(0xff, 0xcc, 0x99); +const int iconSizeSmall = IconBuffer::getSize(IconBuffer::SIZE_SMALL); -const wxColour COLOR_PERCENTAGE_BORDER (198, 198, 198); -const wxColour COLOR_PERCENTAGE_BACKGROUND(0xf8, 0xf8, 0xf8); -//const wxColor COLOR_TREE_SELECTION_GRADIENT_FROM = wxColor( 89, 255, 99); //green: HSV: 88, 255, 172 -//const wxColor COLOR_TREE_SELECTION_GRADIENT_TO = wxColor(225, 255, 227); // HSV: 88, 255, 240 -const wxColor COLOR_TREE_SELECTION_GRADIENT_FROM = getColorSelectionGradientFrom(); -const wxColor COLOR_TREE_SELECTION_GRADIENT_TO = getColorSelectionGradientTo (); +wxColor getColorForLevel(size_t level) +{ + switch (level % 12) + { + case 0: + return { 0xcc, 0xcc, 0xff }; + case 1: + return { 0xcc, 0xff, 0xcc }; + case 2: + return { 0xff, 0xff, 0x99 }; + case 3: + return { 0xcc, 0xcc, 0xcc }; + case 4: + return { 0xff, 0xcc, 0xff }; + case 5: + return { 0x99, 0xff, 0xcc }; + case 6: + return { 0xcc, 0xcc, 0x99 }; + case 7: + return { 0xff, 0xcc, 0xcc }; + case 8: + return { 0xcc, 0xff, 0x99 }; + case 9: + return { 0xff, 0xff, 0xcc }; + case 10: + return { 0xcc, 0xff, 0xff }; + case 11: + return { 0xff, 0xcc, 0x99 }; + } + assert(false); + return *wxBLACK; +} -const int iconSizeSmall = IconBuffer::getSize(IconBuffer::SIZE_SMALL); class GridDataNavi : private wxEvtHandler, public GridData { @@ -768,14 +786,14 @@ public: rootBmp(getResourceImage(L"rootFolder").ConvertToImage().Scale(iconSizeSmall, iconSizeSmall, wxIMAGE_QUALITY_HIGH)), widthNodeIcon(iconSizeSmall), widthLevelStep(widthNodeIcon), - widthNodeStatus(getResourceImage(L"nodeExpanded").GetWidth()), + widthNodeStatus(getResourceImage(L"node_expanded").GetWidth()), grid_(grid) { grid.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GridDataNavi::onKeyDown), nullptr, this); - grid.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler(GridDataNavi::onMouseLeft ), nullptr, this); - grid.Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(GridDataNavi::onMouseLeftDouble ), nullptr, this); - grid.Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(GridDataNavi::onGridLabelContext), nullptr, this ); - grid.Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(GridDataNavi::onGridLabelLeftClick ), nullptr, this ); + grid.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler (GridDataNavi::onMouseLeft ), nullptr, this); + grid.Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler (GridDataNavi::onMouseLeftDouble ), nullptr, this); + grid.Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(GridDataNavi::onGridLabelContext ), nullptr, this); + grid.Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(GridDataNavi::onGridLabelLeftClick), nullptr, this); } void setShowPercentage(bool value) { showPercentBar = value; grid_.Refresh(); } @@ -788,11 +806,11 @@ private: { switch (static_cast<ColumnTypeNavi>(colType)) { - case COL_TYPE_NAVI_BYTES: - case COL_TYPE_NAVI_ITEM_COUNT: + case ColumnTypeNavi::BYTES: + case ColumnTypeNavi::ITEM_COUNT: break; - case COL_TYPE_NAVI_DIRECTORY: + case ColumnTypeNavi::DIRECTORY: if (treeDataView_) if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) @@ -817,10 +835,10 @@ private: if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) switch (static_cast<ColumnTypeNavi>(colType)) { - case COL_TYPE_NAVI_BYTES: + case ColumnTypeNavi::BYTES: return filesizeToShortString(node->bytes_); - case COL_TYPE_NAVI_DIRECTORY: + case ColumnTypeNavi::DIRECTORY: if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) return root->displayName_; else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get())) @@ -829,7 +847,7 @@ private: return _("Files"); break; - case COL_TYPE_NAVI_ITEM_COUNT: + case ColumnTypeNavi::ITEM_COUNT: return toGuiString(node->itemCount_); } } @@ -859,12 +877,17 @@ private: static const int GAP_SIZE = 2; + enum class HoverAreaNavi + { + NODE, + }; + void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override { if (enabled) { if (selected) - dc.GradientFillLinear(rect, COLOR_TREE_SELECTION_GRADIENT_FROM, COLOR_TREE_SELECTION_GRADIENT_TO, wxEAST); + dc.GradientFillLinear(rect, getColorTreeSelectionGradientFrom(), getColorTreeSelectionGradientTo(), wxEAST); //ignore focus else clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -873,7 +896,7 @@ private: clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); } - void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) override + void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override { //wxRect rectTmp= drawCellBorder(dc, rect); wxRect rectTmp = rect; @@ -882,9 +905,9 @@ private: // ________________________________________________________________________________ // | space | gap | percentage bar | 2 x gap | node status | gap |icon | gap | rest | // -------------------------------------------------------------------------------- - // -> synchronize renderCell() <-> getBestSize() <-> onMouseLeft() + // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() - if (static_cast<ColumnTypeNavi>(colType) == COL_TYPE_NAVI_DIRECTORY && treeDataView_) + if (static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::DIRECTORY && treeDataView_) { if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) { @@ -907,45 +930,16 @@ private: //percentage bar if (showPercentBar) { - const wxColour brushCol = [&]() -> wxColour - { - switch (node->level_ % 12) - { - case 0: - return COLOR_LEVEL0; - case 1: - return COLOR_LEVEL1; - case 2: - return COLOR_LEVEL2; - case 3: - return COLOR_LEVEL3; - case 4: - return COLOR_LEVEL4; - case 5: - return COLOR_LEVEL5; - case 6: - return COLOR_LEVEL6; - case 7: - return COLOR_LEVEL7; - case 8: - return COLOR_LEVEL8; - case 9: - return COLOR_LEVEL9; - case 10: - return COLOR_LEVEL10; - default: - return COLOR_LEVEL11; - } - }(); const wxRect areaPerc(rectTmp.x, rectTmp.y + 2, WIDTH_PERCENTAGE_BAR, rectTmp.height - 4); { //clear background - wxDCPenChanger dummy (dc, COLOR_PERCENTAGE_BORDER); - wxDCBrushChanger dummy2(dc, COLOR_PERCENTAGE_BACKGROUND); + wxDCPenChanger dummy (dc, getColorPercentBorder()); + wxDCBrushChanger dummy2(dc, getColorPercentBackground()); dc.DrawRectangle(areaPerc); //inner area + const wxColor brushCol = getColorForLevel(node->level_); dc.SetPen (brushCol); dc.SetBrush(brushCol); @@ -976,13 +970,14 @@ private: drawBitmapRtlMirror(dc, bmp, rectStat, wxALIGN_CENTER, buffer); }; + const bool drawMouseHover = static_cast<HoverAreaNavi>(rowHover) == HoverAreaNavi::NODE; switch (node->status_) { case TreeView::STATUS_EXPANDED: - drawStatus(L"nodeExpanded"); + drawStatus(drawMouseHover ? L"node_expanded_hover" : L"node_expanded"); break; case TreeView::STATUS_REDUCED: - drawStatus(L"nodeReduced"); + drawStatus(drawMouseHover ? L"node_reduced_hover" : L"node_reduced"); break; case TreeView::STATUS_EMPTY: break; @@ -1005,12 +1000,10 @@ private: else if (dynamic_cast<const TreeView::FilesNode*>(node.get())) nodeIcon = fileIcon; - if (isActive) - drawBitmapRtlNoMirror(dc, nodeIcon, rectTmp, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer); + if (!isActive) + nodeIcon = wxBitmap(nodeIcon.ConvertToImage().ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3)); //treat all channels equally! - else - drawBitmapRtlNoMirror(dc, wxBitmap(nodeIcon.ConvertToImage().ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3)), //treat all channels equally! - rectTmp, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer); + drawBitmapRtlNoMirror(dc, nodeIcon, rectTmp, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer); rectTmp.x += widthNodeIcon + GAP_SIZE; rectTmp.width -= widthNodeIcon + GAP_SIZE; @@ -1027,8 +1020,8 @@ private: int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL; //have file size and item count right-justified (but don't change for RTL languages) - if ((static_cast<ColumnTypeNavi>(colType) == COL_TYPE_NAVI_BYTES || - static_cast<ColumnTypeNavi>(colType) == COL_TYPE_NAVI_ITEM_COUNT) && grid_.GetLayoutDirection() != wxLayout_RightToLeft) + if ((static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::BYTES || + static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::ITEM_COUNT) && grid_.GetLayoutDirection() != wxLayout_RightToLeft) { rectTmp.width -= 2 * GAP_SIZE; alignment = wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL; @@ -1045,9 +1038,9 @@ private: int getBestSize(wxDC& dc, size_t row, ColumnType colType) override { - // -> synchronize renderCell() <-> getBestSize() <-> onMouseLeft() + // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() - if (static_cast<ColumnTypeNavi>(colType) == COL_TYPE_NAVI_DIRECTORY && treeDataView_) + if (static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::DIRECTORY && treeDataView_) { if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) return node->level_ * widthLevelStep + GAP_SIZE + (showPercentBar ? WIDTH_PERCENTAGE_BAR + 2 * GAP_SIZE : 0) + widthNodeStatus + GAP_SIZE @@ -1061,15 +1054,40 @@ private: 2 * GAP_SIZE; //include gap from right! } + HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override + { + switch (static_cast<ColumnTypeNavi>(colType)) + { + case ColumnTypeNavi::BYTES: + case ColumnTypeNavi::ITEM_COUNT: + break; + + case ColumnTypeNavi::DIRECTORY: + if (treeDataView_) + if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) + { + const int tolerance = 2; + const int nodeStatusXFirst = -tolerance + static_cast<int>(node->level_) * widthLevelStep + GAP_SIZE + (showPercentBar ? WIDTH_PERCENTAGE_BAR + 2 * GAP_SIZE : 0); + const int nodeStatusXLast = (nodeStatusXFirst + tolerance) + widthNodeStatus + tolerance; + // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() + + if (nodeStatusXFirst <= cellRelativePosX && cellRelativePosX < nodeStatusXLast) + return static_cast<HoverArea>(HoverAreaNavi::NODE); + } + break; + } + return HoverArea::NONE; + } + std::wstring getColumnLabel(ColumnType colType) const override { switch (static_cast<ColumnTypeNavi>(colType)) { - case COL_TYPE_NAVI_BYTES: + case ColumnTypeNavi::BYTES: return _("Size"); - case COL_TYPE_NAVI_DIRECTORY: + case ColumnTypeNavi::DIRECTORY: return _("Name"); - case COL_TYPE_NAVI_ITEM_COUNT: + case ColumnTypeNavi::ITEM_COUNT: return _("Items"); } return std::wstring(); @@ -1077,37 +1095,20 @@ private: void onMouseLeft(GridClickEvent& event) { - if (treeDataView_) + switch (static_cast<HoverAreaNavi>(event.hoverArea_)) { - bool clickOnNodeStatus = false; - if (static_cast<ColumnTypeNavi>(event.colType_) == COL_TYPE_NAVI_DIRECTORY) - if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(event.row_)) - { - const int absX = grid_.CalcUnscrolledPosition(event.GetPosition()).x; - const wxRect cellArea = grid_.getCellArea(event.row_, event.colType_); - if (cellArea.width > 0 && cellArea.height > 0) + case HoverAreaNavi::NODE: + if (treeDataView_) + switch (treeDataView_->getStatus(event.row_)) { - const int tolerance = 1; - const int xNodeStatusFirst = -tolerance + cellArea.x + static_cast<int>(node->level_) * widthLevelStep + GAP_SIZE + (showPercentBar ? WIDTH_PERCENTAGE_BAR + 2 * GAP_SIZE : 0); - const int xNodeStatusLast = (xNodeStatusFirst + tolerance) + widthNodeStatus + tolerance; - // -> synchronize renderCell() <-> getBestSize() <-> onMouseLeft() - - if (xNodeStatusFirst <= absX && absX < xNodeStatusLast) - clickOnNodeStatus = true; + case TreeView::STATUS_EXPANDED: + return reduceNode(event.row_); + case TreeView::STATUS_REDUCED: + return expandNode(event.row_); + case TreeView::STATUS_EMPTY: + break; } - } - //-------------------------------------------------------------------------------------------------- - - if (clickOnNodeStatus) - switch (treeDataView_->getStatus(event.row_)) - { - case TreeView::STATUS_EXPANDED: - return reduceNode(event.row_); - case TreeView::STATUS_REDUCED: - return expandNode(event.row_); - case TreeView::STATUS_EMPTY: - break; - } + break; } event.Skip(); } @@ -1132,20 +1133,16 @@ private: int keyCode = event.GetKeyCode(); if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) { - if (keyCode == WXK_LEFT) + if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT) keyCode = WXK_RIGHT; - else if (keyCode == WXK_RIGHT) + else if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT) keyCode = WXK_LEFT; - else if (keyCode == WXK_NUMPAD_LEFT) - keyCode = WXK_NUMPAD_RIGHT; - else if (keyCode == WXK_NUMPAD_RIGHT) - keyCode = WXK_NUMPAD_LEFT; } const size_t rowCount = grid_.getRowCount(); if (rowCount == 0) return; - size_t row = grid_.getGridCursor(); + const size_t row = grid_.getGridCursor(); if (event.ShiftDown()) ; else if (event.ControlDown()) @@ -1191,21 +1188,21 @@ private: event.Skip(); } - void onGridLabelContext(GridClickEvent& event) + void onGridLabelContext(GridLabelClickEvent& event) { ContextMenu menu; //-------------------------------------------------------------------------------------------------------- menu.addCheckBox(_("Percentage"), [this] { setShowPercentage(!getShowPercentage()); }, getShowPercentage()); //-------------------------------------------------------------------------------------------------------- - auto toggleColumn = [&](const Grid::ColumnAttribute& ca) + auto toggleColumn = [&](ColumnType ct) { auto colAttr = grid_.getColumnConfig(); - for (auto it = colAttr.begin(); it != colAttr.end(); ++it) - if (it->type_ == ca.type_) + for (Grid::ColumnAttribute& ca : colAttr) + if (ca.type_ == ct) { - it->visible_ = !ca.visible_; + ca.visible_ = !ca.visible_; grid_.setColumnConfig(colAttr); return; } @@ -1213,8 +1210,8 @@ private: for (const Grid::ColumnAttribute& ca : grid_.getColumnConfig()) { - menu.addCheckBox(getColumnLabel(ca.type_), [ca, toggleColumn]() { toggleColumn(ca); }, - ca.visible_, ca.type_ != static_cast<ColumnType>(COL_TYPE_NAVI_DIRECTORY)); //do not allow user to hide file name column! + menu.addCheckBox(getColumnLabel(ca.type_), [ca, toggleColumn] { toggleColumn(ca.type_); }, + ca.visible_, ca.type_ != static_cast<ColumnType>(ColumnTypeNavi::DIRECTORY)); //do not allow user to hide file name column! } //-------------------------------------------------------------------------------------------------------- menu.addSeparator(); @@ -1231,7 +1228,7 @@ private: //event.Skip(); } - void onGridLabelLeftClick(GridClickEvent& event) + void onGridLabelLeftClick(GridLabelClickEvent& event) { if (treeDataView_) { @@ -1328,12 +1325,9 @@ std::vector<ColumnAttributeNavi> makeConsistent(const std::vector<ColumnAttribut std::vector<Grid::ColumnAttribute> treeview::convertConfig(const std::vector<ColumnAttributeNavi>& attribs) { - const auto& attribClean = makeConsistent(attribs); - std::vector<Grid::ColumnAttribute> output; - std::transform(attribClean.begin(), attribClean.end(), std::back_inserter(output), - [&](const ColumnAttributeNavi& ca) { return Grid::ColumnAttribute(static_cast<ColumnType>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); - + for (const ColumnAttributeNavi& ca : makeConsistent(attribs)) + output.emplace_back(static_cast<ColumnType>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); return output; } @@ -1341,9 +1335,7 @@ std::vector<Grid::ColumnAttribute> treeview::convertConfig(const std::vector<Col std::vector<ColumnAttributeNavi> treeview::convertConfig(const std::vector<Grid::ColumnAttribute>& attribs) { std::vector<ColumnAttributeNavi> output; - - std::transform(attribs.begin(), attribs.end(), std::back_inserter(output), - [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeNavi(static_cast<ColumnTypeNavi>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); - + for (const Grid::ColumnAttribute& ca : attribs) + output.emplace_back(static_cast<ColumnTypeNavi>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); return makeConsistent(output); } diff --git a/FreeFileSync/Source/ui/tree_view.h b/FreeFileSync/Source/ui/tree_view.h index d24b37dd..1b74a661 100644 --- a/FreeFileSync/Source/ui/tree_view.h +++ b/FreeFileSync/Source/ui/tree_view.h @@ -20,9 +20,7 @@ namespace zen class TreeView { public: - TreeView() : - sortColumn(defaultValueLastSortColumn), - sortAscending(defaultValueLastSortAscending) {} + TreeView() {} void setData(FolderComparison& newData); //set data, taking (partial) ownership @@ -109,20 +107,13 @@ private: struct Container { - Container() : - bytesGross(), - bytesNet(), - itemCountGross(), - itemCountNet(), - firstFileId(nullptr) {} - - std::uint64_t bytesGross; - std::uint64_t bytesNet; //bytes for files on view in this directory only - int itemCountGross; - int itemCountNet; //number of files on view for in this directory only + std::uint64_t bytesGross = 0; + std::uint64_t bytesNet = 0; //bytes for files on view in this directory only + int itemCountGross = 0; + int itemCountNet = 0; //number of files on view for in this directory only std::vector<DirNodeImpl> subDirs; - FileSystemObject::ObjectId firstFileId; //weak pointer to first FilePair or SymlinkPair + FileSystemObject::ObjectId firstFileId = nullptr; //weak pointer to first FilePair or SymlinkPair //- "compress" algorithm may hide file nodes for directories with a single included file, i.e. itemCountGross == itemCountNet == 1 //- a HierarchyObject* would be a better fit, but we need weak pointer semantics! //- a std::vector<FileSystemObject::ObjectId> would be a better design, but we don't want a second memory structure as large as custom grid! @@ -130,13 +121,11 @@ private: struct DirNodeImpl : public Container { - DirNodeImpl() : objId(nullptr) {} - FileSystemObject::ObjectId objId; //weak pointer to FolderPair + FileSystemObject::ObjectId objId = nullptr; //weak pointer to FolderPair }; struct RootNodeImpl : public Container { - RootNodeImpl() {} std::shared_ptr<BaseFolderPair> baseFolder; std::wstring displayName; }; @@ -153,7 +142,7 @@ private: TreeLine(unsigned int level, int percent, const Container* node, enum NodeType type) : level_(level), percent_(percent), node_(node), type_(type) {} unsigned int level_; - int percent_; //[0, 100] + int percent_; //[0, 100] const Container* node_; // NodeType type_; //we increase size of "flatTree" using C-style types rather than have a polymorphic "folderCmpView" }; @@ -179,8 +168,8 @@ private: | */ std::vector<std::shared_ptr<BaseFolderPair>> folderCmp; //full raw data - ColumnTypeNavi sortColumn; - bool sortAscending; + ColumnTypeNavi sortColumn = defaultValueLastSortColumn; + bool sortAscending = defaultValueLastSortAscending; }; diff --git a/FreeFileSync/Source/ui/triple_splitter.cpp b/FreeFileSync/Source/ui/triple_splitter.cpp index 22e9b5d6..8e527128 100644 --- a/FreeFileSync/Source/ui/triple_splitter.cpp +++ b/FreeFileSync/Source/ui/triple_splitter.cpp @@ -19,19 +19,17 @@ const int SASH_SIZE = 10; const double SASH_GRAVITY = 0.5; //value within [0, 1]; 1 := resize left only, 0 := resize right only const int CHILD_WINDOW_MIN_SIZE = 50; //min. size of managed windows -const wxColor COLOR_SASH_GRADIENT_FROM = wxColour(192, 192, 192); //light grey -const wxColor COLOR_SASH_GRADIENT_TO = *wxWHITE; +//let's NOT create wxWidgets objects statically: +inline wxColor getColorSashGradientFrom() { return { 192, 192, 192 }; } //light grey +inline wxColor getColorSashGradientTo () { return *wxWHITE; } } + TripleSplitter::TripleSplitter(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, - long style) : wxWindow(parent, id, pos, size, style | wxTAB_TRAVERSAL), //tab between windows - centerOffset(0), - windowL(nullptr), - windowC(nullptr), - windowR(nullptr) + long style) : wxWindow(parent, id, pos, size, style | wxTAB_TRAVERSAL) //tab between windows { Connect(wxEVT_PAINT, wxPaintEventHandler(TripleSplitter::onPaintEvent ), nullptr, this); Connect(wxEVT_SIZE, wxSizeEventHandler (TripleSplitter::onSizeEvent ), nullptr, this); @@ -139,11 +137,11 @@ void TripleSplitter::drawSash(wxDC& dc) { const int sash2ndHalf = 3; rect.width -= sash2ndHalf; - dc.GradientFillLinear(rect, COLOR_SASH_GRADIENT_FROM, COLOR_SASH_GRADIENT_TO, wxEAST); + dc.GradientFillLinear(rect, getColorSashGradientFrom(), getColorSashGradientTo(), wxEAST); rect.x += rect.width; rect.width = sash2ndHalf; - dc.GradientFillLinear(rect, COLOR_SASH_GRADIENT_FROM, COLOR_SASH_GRADIENT_TO, wxWEST); + dc.GradientFillLinear(rect, getColorSashGradientFrom(), getColorSashGradientTo(), wxWEST); static_assert(SASH_SIZE > sash2ndHalf, ""); }; diff --git a/FreeFileSync/Source/ui/triple_splitter.h b/FreeFileSync/Source/ui/triple_splitter.h index 9bc54acc..5db1f8b8 100644 --- a/FreeFileSync/Source/ui/triple_splitter.h +++ b/FreeFileSync/Source/ui/triple_splitter.h @@ -78,11 +78,11 @@ private: class SashMove; std::unique_ptr<SashMove> activeMove; - int centerOffset; //offset to add after "gravity" stretching + int centerOffset = 0; //offset to add after "gravity" stretching - wxWindow* windowL; - wxWindow* windowC; - wxWindow* windowR; + wxWindow* windowL = nullptr; + wxWindow* windowC = nullptr; + wxWindow* windowR = nullptr; }; } diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp index 91df1f17..fa33c462 100644 --- a/FreeFileSync/Source/ui/version_check.cpp +++ b/FreeFileSync/Source/ui/version_check.cpp @@ -12,15 +12,13 @@ #include <zen/build_info.h> #include <zen/basic_math.h> #include <zen/thread.h> //std::thread::id -//#include <wx/timer.h> -//#include <wx/utils.h> #include <wx+/popup_dlg.h> #include "version_id.h" #ifdef ZEN_WIN #include <zen/win.h> //tame wininet include #include <zen/win_ver.h> -#include <zen/com_tools.h> + #include <zen/com_tools.h> #include <wininet.h> #elif defined ZEN_MAC @@ -421,7 +419,6 @@ bool zen::shouldRunPeriodicUpdateCheck(time_t lastUpdateCheck) { if (updateCheckActive(lastUpdateCheck)) { - static_assert(sizeof(time_t) >= 8, "Still using 32-bit time_t? WTF!!"); const time_t now = std::time(nullptr); return numeric::dist(now, lastUpdateCheck) >= 7 * 24 * 3600; //check weekly } diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index 955f886f..8bbd34a8 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace zen { -const wchar_t ffsVersion[] = L"7.6"; //internal linkage! +const wchar_t ffsVersion[] = L"7.7"; //internal linkage! const wchar_t FFS_VERSION_SEPARATOR = L'.'; } diff --git a/wx+/font_size.h b/wx+/font_size.h index 2302e056..2858afb6 100644 --- a/wx+/font_size.h +++ b/wx+/font_size.h @@ -69,7 +69,7 @@ void setMainInstructionFont(wxWindow& control) 0, // _In_ int iStateId, TMT_TEXTCOLOR, // _In_ int iPropId, &cr) == S_OK) // _Out_ COLORREF *pColor - control.SetForegroundColour(wxColour(cr)); + control.SetForegroundColour(wxColor(cr)); } #elif defined ZEN_LINUX //https://developer.gnome.org/hig-book/3.2/hig-book.html#alert-text diff --git a/wx+/graph.cpp b/wx+/graph.cpp index bf1f1567..f39d29f5 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -19,9 +19,6 @@ using namespace zen; const wxEventType zen::wxEVT_GRAPH_SELECTION = wxNewEventType(); -//for some buggy reason MSVC isn't able to use a temporary as a default argument -const std::shared_ptr<LabelFormatter> Graph2D::MainAttributes::defaultFormat = std::make_shared<DecimalNumberFormatter>(); - double zen::nextNiceNumber(double blockSize) //round to next number which is a convenient to read block size { @@ -48,25 +45,25 @@ wxColor getDefaultColor(size_t pos) switch (pos % 10) { case 0: - return wxColor(0, 69, 134); //blue + return { 0, 69, 134 }; //blue case 1: - return wxColor(255, 66, 14); //red + return { 255, 66, 14 }; //red case 2: - return wxColor(255, 211, 32); //yellow + return { 255, 211, 32 }; //yellow case 3: - return wxColor(87, 157, 28); //green + return { 87, 157, 28 }; //green case 4: - return wxColor(126, 0, 33); //royal + return { 126, 0, 33 }; //royal case 5: - return wxColor(131, 202, 255); //light blue + return { 131, 202, 255 }; //light blue case 6: - return wxColor(49, 64, 4); //dark green + return { 49, 64, 4 }; //dark green case 7: - return wxColor(174, 207, 0); //light green + return { 174, 207, 0 }; //light green case 8: - return wxColor(75, 31, 111); //purple + return { 75, 31, 111 }; //purple case 9: - return wxColor(255, 149, 14); //orange + return { 255, 149, 14 }; //orange } assert(false); return *wxBLACK; @@ -574,7 +571,7 @@ void Graph2D::render(wxDC& dc) const { //paint graph background (excluding label area) - wxDCPenChanger dummy (dc, wxColour(130, 135, 144)); //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal... + wxDCPenChanger dummy (dc, wxColor(130, 135, 144)); //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal... 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 diff --git a/wx+/graph.h b/wx+/graph.h index 7b61858d..b9873bd8 100644 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -179,18 +179,18 @@ public: { public: CurveAttributes() {} //required by GCC - CurveAttributes& setColor (const wxColour& col) { color = col; autoColor = false; return *this; } - CurveAttributes& fillCurveArea(const wxColour& col) { fillColor = col; drawCurveArea = true; return *this; } + CurveAttributes& setColor (const wxColor& col) { color = col; autoColor = false; return *this; } + CurveAttributes& fillCurveArea(const wxColor& col) { fillColor = col; drawCurveArea = true; return *this; } CurveAttributes& setLineWidth(size_t width) { lineWidth = static_cast<int>(width); return *this; } private: friend class Graph2D; bool autoColor = true; - wxColour color; + wxColor color; bool drawCurveArea = false; - wxColour fillColor; + wxColor fillColor; int lineWidth = 2; }; @@ -239,16 +239,14 @@ public: MainAttributes& setAutoSize() { minXauto = maxXauto = minYauto = maxYauto = true; return *this; } - static const std::shared_ptr<LabelFormatter> defaultFormat; - - MainAttributes& setLabelX(PosLabelX posX, size_t height = 25, const std::shared_ptr<LabelFormatter>& newLabelFmt = defaultFormat) + MainAttributes& setLabelX(PosLabelX posX, size_t height = 25, std::shared_ptr<LabelFormatter> newLabelFmt = std::make_shared<DecimalNumberFormatter>()) { labelposX = posX; xLabelHeight = static_cast<int>(height); labelFmtX = newLabelFmt; return *this; } - MainAttributes& setLabelY(PosLabelY posY, size_t width = 60, const std::shared_ptr<LabelFormatter>& newLabelFmt = defaultFormat) + MainAttributes& setLabelY(PosLabelY posY, size_t width = 60, std::shared_ptr<LabelFormatter> newLabelFmt = std::make_shared<DecimalNumberFormatter>()) { labelposY = posY; yLabelWidth = static_cast<int>(width); @@ -258,7 +256,7 @@ public: MainAttributes& setCornerText(const wxString& txt, PosCorner pos) { cornerTexts[pos] = txt; return *this; } - MainAttributes& setBackgroundColor(const wxColour& col) { backgroundColor = col; return *this; } + MainAttributes& setBackgroundColor(const wxColor& col) { backgroundColor = col; return *this; } MainAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; } @@ -285,7 +283,7 @@ public: std::map<PosCorner, wxString> cornerTexts; - wxColour backgroundColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + wxColor backgroundColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SelMode mouseSelMode = SELECT_RECTANGLE; }; void setAttributes(const MainAttributes& newAttr) { attr = newAttr; Refresh(); } diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 26186a09..56556797 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -26,8 +26,11 @@ using namespace zen; -wxColor zen::getColorSelectionGradientFrom() { return wxColor(137, 172, 255); } //blue: HSL: 158, 255, 196 HSV: 222, 0.46, 1 -wxColor zen::getColorSelectionGradientTo () { return wxColor(225, 234, 255); } // HSL: 158, 255, 240 HSV: 222, 0.12, 1 +wxColor zen::getColorSelectionGradientFrom() { return { 137, 172, 255 }; } //blue: HSL: 158, 255, 196 HSV: 222, 0.46, 1 +wxColor zen::getColorSelectionGradientTo () { return { 225, 234, 255 }; } // HSL: 158, 255, 240 HSV: 222, 0.12, 1 + +const int GridData::COLUMN_GAP_LEFT = 4; + void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) { @@ -36,43 +39,41 @@ void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) dc.DrawRectangle(rect); } -const int GridData::COLUMN_GAP_LEFT = 4; namespace { +//let's NOT create wxWidgets objects statically: //------------------------------ Grid Parameters -------------------------------- -wxColor getColorLabelText() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); } +inline wxColor getColorLabelText() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); } +inline wxColor getColorGridLine() { return { 192, 192, 192 }; } //light grey -wxColor getColorLabelGradientFrom () { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } -wxColor getColorLabelGradientTo () { return wxColour(200, 200, 200); } //light grey +inline wxColor getColorLabelGradientFrom() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } +inline wxColor getColorLabelGradientTo () { return { 200, 200, 200 }; } //light grey -wxColor getColorLabelGradientFocusFrom() { return getColorLabelGradientFrom(); } -wxColor getColorLabelGradientFocusTo () { return getColorSelectionGradientFrom(); } +inline wxColor getColorLabelGradientFocusFrom() { return getColorLabelGradientFrom(); } +inline wxColor getColorLabelGradientFocusTo () { return getColorSelectionGradientFrom(); } -const double MOUSE_DRAG_ACCELERATION = 1.5; //unit: [rows / (pixel * sec)] -> same value as Explorer! +const double MOUSE_DRAG_ACCELERATION = 1.5; //unit: [rows / (pixel * sec)] -> same value like Explorer! const int DEFAULT_COL_LABEL_BORDER = 6; //top + bottom border in addition to label height -//const int COLUMN_LABEL_BORDER = GridData::COLUMN_GAP_LEFT; const int COLUMN_MOVE_DELAY = 5; //unit: [pixel] (from Explorer) const int COLUMN_MIN_WIDTH = 40; //only honored when resizing manually! const int ROW_LABEL_BORDER = 3; const int COLUMN_RESIZE_TOLERANCE = 6; //unit [pixel] const int COLUMN_FILL_GAP_TOLERANCE = 10; //enlarge column to fill full width when resizing -const wxColor colorGridLine = wxColour(192, 192, 192); //light grey - const bool fillGapAfterColumns = true; //draw rows/column label to fill full window width; may become an instance variable some time? } //---------------------------------------------------------------------------------------------------------------- const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOUBLE = wxNewEventType(); -const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_LEFT = wxNewEventType(); -const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_RIGHT = wxNewEventType(); -const wxEventType zen::EVENT_GRID_COL_RESIZE = wxNewEventType(); const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOWN = wxNewEventType(); const wxEventType zen::EVENT_GRID_MOUSE_LEFT_UP = wxNewEventType(); const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_DOWN = wxNewEventType(); const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_UP = wxNewEventType(); const wxEventType zen::EVENT_GRID_SELECT_RANGE = wxNewEventType(); +const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_LEFT = wxNewEventType(); +const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_RIGHT = wxNewEventType(); +const wxEventType zen::EVENT_GRID_COL_RESIZE = wxNewEventType(); //---------------------------------------------------------------------------------------------------------------- void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) @@ -81,7 +82,7 @@ void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool } -void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) +void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) { wxRect rectTmp = drawCellBorder(dc, rect); @@ -99,7 +100,7 @@ int GridData::getBestSize(wxDC& dc, size_t row, ColumnType colType) wxRect GridData::drawCellBorder(wxDC& dc, const wxRect& rect) //returns remaining rectangle { - wxDCPenChanger dummy2(dc, wxPen(colorGridLine, 1, wxSOLID)); + wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), 1, wxSOLID)); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight()); dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1)); @@ -298,7 +299,7 @@ protected: //wxWidgets bug: tooltip multiline property is defined by first tooltip text containing newlines or not (same is true for maximum width) if (!tt) SetToolTip(new wxToolTip(L"a b\n\ - a b")); //ugly, but is working (on Windows) + a b")); //ugly, but working (on Windows) tt = GetToolTip(); //should be bound by now assert(tt); if (tt) @@ -447,14 +448,16 @@ public: return -1; } - int getRowHeight() const { return std::max(1, rowHeight); } //guarantees to return size >= 1 ! - void setRowHeight(int height) { rowHeight = height; } + int getRowHeight() const { return rowHeight; } //guarantees to return size >= 1 ! + void setRowHeight(int height) { assert(height > 0); rowHeight = std::max(1, height); } - wxRect getRowLabelArea(ptrdiff_t row) const + wxRect getRowLabelArea(size_t row) const //returns empty rect if row not found { assert(GetClientAreaOrigin() == wxPoint()); - return wxRect(wxPoint(0, rowHeight * row), - wxSize(GetClientSize().GetWidth(), rowHeight)); + if (row < refParent().getRowCount()) + return wxRect(wxPoint(0, rowHeight * row), + wxSize(GetClientSize().GetWidth(), rowHeight)); + return wxRect(); } std::pair<ptrdiff_t, ptrdiff_t> getRowsOnClient(const wxRect& clientRect) const //returns range [begin, end) @@ -503,8 +506,8 @@ private: auto rowRange = getRowsOnClient(rect); //returns range [begin, end) for (auto row = rowRange.first; row < rowRange.second; ++row) { - wxRect singleLabelArea = getRowLabelArea(row); - if (singleLabelArea.GetHeight() > 0) + wxRect singleLabelArea = getRowLabelArea(row); //returns empty rect if row not found + if (singleLabelArea.height > 0) { singleLabelArea.y = refParent().CalcScrolledPosition(singleLabelArea.GetTopLeft()).y; drawRowLabel(dc, singleLabelArea, row); @@ -653,7 +656,7 @@ private: const int clientWidth = GetClientSize().GetWidth(); //need reliable, stable width in contrast to rect.width if (totalWidth < clientWidth) - drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(clientWidth - totalWidth, colLabelHeight)), absWidths.size(), DUMMY_COLUMN_TYPE); + drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(clientWidth - totalWidth, colLabelHeight)), absWidths.size(), ColumnType::NONE); } } @@ -661,9 +664,9 @@ private: { if (auto dataView = refParent().getDataProvider()) { - const bool isHighlighted = activeResizing ? col == activeResizing->getColumn () : //highlight column on mouse-over - activeMove ? col == activeMove ->getColumnFrom() : - highlightCol ? col == *highlightCol : + const bool isHighlighted = activeResizing ? col == activeResizing ->getColumn () : //highlight column on mouse-over + activeClickOrMove ? col == activeClickOrMove->getColumnFrom() : + highlightCol ? col == *highlightCol : false; RecursiveDcClipper clip(dc, rect); @@ -671,11 +674,11 @@ private: //draw move target location if (refParent().allowColumnMove) - if (activeMove && activeMove->isRealMove()) + if (activeClickOrMove && activeClickOrMove->isRealMove()) { - if (col + 1 == activeMove->refColumnTo()) //handle pos 1, 2, .. up to "at end" position + if (col + 1 == activeClickOrMove->refColumnTo()) //handle pos 1, 2, .. up to "at end" position dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight() + wxPoint(-2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); - else if (col == activeMove->refColumnTo() && col == 0) //pos 0 + else if (col == activeClickOrMove->refColumnTo() && col == 0) //pos 0 dc.GradientFillLinear(wxRect(rect.GetTopLeft(), rect.GetBottomLeft() + wxPoint(2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); } } @@ -687,7 +690,7 @@ private: refParent().getMainWin().SetFocus(); activeResizing.reset(); - activeMove.reset(); + activeClickOrMove.reset(); if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) { @@ -698,7 +701,7 @@ private: activeResizing = std::make_unique<ColumnResizing>(*this, action->col, *colWidth, event.GetPosition().x); } else //a move or single click - activeMove = std::make_unique<ColumnMove>(*this, action->col, event.GetPosition().x); + activeClickOrMove = std::make_unique<ColumnMove>(*this, action->col, event.GetPosition().x); } event.Skip(); } @@ -707,14 +710,14 @@ private: { activeResizing.reset(); //nothing else to do, actual work done by onMouseMovement() - if (activeMove) + if (activeClickOrMove) { - if (activeMove->isRealMove()) + if (activeClickOrMove->isRealMove()) { if (refParent().allowColumnMove) { - const auto colFrom = activeMove->getColumnFrom(); - auto colTo = activeMove->refColumnTo(); + const size_t colFrom = activeClickOrMove->getColumnFrom(); + size_t colTo = activeClickOrMove->refColumnTo(); if (colTo > colFrom) //simulate "colFrom" deletion --colTo; @@ -724,10 +727,10 @@ private: } else //notify single label click { - if (const Opt<ColumnType> colType = refParent().colToType(activeMove->getColumnFrom())) - sendEventNow(GridClickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, event, -1, *colType)); + if (const Opt<ColumnType> colType = refParent().colToType(activeClickOrMove->getColumnFrom())) + sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, event, *colType)); } - activeMove.reset(); + activeClickOrMove.reset(); } refParent().updateWindowSizes(); //looks strange if done during onMouseMovement() @@ -738,7 +741,7 @@ private: void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override { activeResizing.reset(); - activeMove.reset(); + activeClickOrMove.reset(); Refresh(); //event.Skip(); -> we DID handle it! } @@ -770,22 +773,22 @@ private: refParent().setColumnWidth(newWidth, col, ALLOW_GRID_EVENT); //check if there's a small gap after last column, if yes, fill it - int gapWidth = GetClientSize().GetWidth() - refParent().getColWidthsSum(GetClientSize().GetWidth()); + const int gapWidth = GetClientSize().GetWidth() - refParent().getColWidthsSum(GetClientSize().GetWidth()); if (std::abs(gapWidth) < COLUMN_FILL_GAP_TOLERANCE) refParent().setColumnWidth(newWidth + gapWidth, col, ALLOW_GRID_EVENT); refParent().Refresh(); //refresh columns on main grid as well! } - else if (activeMove) + else if (activeClickOrMove) { const int clientPosX = event.GetPosition().x; - if (std::abs(clientPosX - activeMove->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click) + if (std::abs(clientPosX - activeClickOrMove->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click) { - activeMove->setRealMove(); + activeClickOrMove->setRealMove(); const ptrdiff_t col = refParent().clientPosToMoveTargetColumn(event.GetPosition()); if (col >= 0) - activeMove->refColumnTo() = col; + activeClickOrMove->refColumnTo() = col; } } else @@ -806,13 +809,13 @@ private: } } - //update tooltip const std::wstring toolTip = [&] { const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); - if (const Opt<ColumnType> ct = refParent().getColumnAtPos(absPos.x)) + const ColumnType colType = refParent().getColumnAtPos(absPos.x).colType; //returns ColumnType::NONE if no column at x position! + if (colType != ColumnType::NONE) if (auto prov = refParent().getDataProvider()) - return prov->getToolTip(*ct); + return prov->getToolTip(colType); return std::wstring(); }(); setToolTip(toolTip); @@ -833,19 +836,19 @@ private: if (const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) { if (const Opt<ColumnType> colType = refParent().colToType(action->col)) - sendEventNow(GridClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, -1, *colType)); //notify right click + sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, *colType)); //notify right click else assert(false); } else //notify right click (on free space after last column) if (fillGapAfterColumns) - sendEventNow(GridClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, -1, DUMMY_COLUMN_TYPE)); + sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, ColumnType::NONE)); event.Skip(); } std::unique_ptr<ColumnResizing> activeResizing; - std::unique_ptr<ColumnMove> activeMove; + std::unique_ptr<ColumnMove> activeClickOrMove; Opt<size_t> highlightCol; //column during mouse-over }; @@ -892,17 +895,6 @@ private: wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels - const int rowHeight = rowLabelWin_.getRowHeight(); - - //why again aren't we using RowLabelWin::getRowsOnClient() here? - const wxPoint topLeft = refParent().CalcUnscrolledPosition(rect.GetTopLeft()); - const wxPoint bottomRight = refParent().CalcUnscrolledPosition(rect.GetBottomRight()); - - const int rowFirst = std::max(topLeft .y / rowHeight, 0); // [rowFirst, rowLast) - const int rowLast = std::min(bottomRight.y / rowHeight + 1, static_cast<int>(refParent().getRowCount())); - - wxPoint cellAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates - std::vector<ColumnWidth> absWidths = refParent().getColWidths(); //resolve stretched widths { int totalRowWidth = 0; @@ -917,8 +909,12 @@ private: { RecursiveDcClipper dummy2(dc, rect); //do NOT draw background on cells outside of invalidated rect invalidating foreground text! + wxPoint cellAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates + const int rowHeight = rowLabelWin_.getRowHeight(); + const auto rowRange = rowLabelWin_.getRowsOnClient(rect); //returns range [begin, end) + //draw background lines - for (int row = rowFirst; row < rowLast; ++row) + for (auto row = rowRange.first; row < rowRange.second; ++row) { const wxRect rowRect(cellAreaTL + wxPoint(0, row * rowHeight), wxSize(totalRowWidth, rowHeight)); RecursiveDcClipper dummy3(dc, rowRect); @@ -932,11 +928,11 @@ private: return; //done if (cellAreaTL.x + cw.width_ > rect.x) - for (int row = rowFirst; row < rowLast; ++row) + for (auto row = rowRange.first; row < rowRange.second; ++row) { const wxRect cellRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, cw.width_, rowHeight); RecursiveDcClipper dummy3(dc, cellRect); - prov->renderCell(dc, cellRect, row, cw.type_, refParent().IsThisEnabled(), drawAsSelected(row)); + prov->renderCell(dc, cellRect, row, cw.type_, refParent().IsThisEnabled(), drawAsSelected(row), getRowHoverToDraw(row)); } cellAreaTL.x += cw.width_; } @@ -944,6 +940,18 @@ private: } } + HoverArea getRowHoverToDraw(ptrdiff_t row) const + { + if (activeSelection) + { + if (activeSelection->getFirstClick().row_ == row) + return activeSelection->getFirstClick().hoverArea_; + } + else if (highlight.row == row) + return highlight.rowHover; + return HoverArea::NONE; + } + bool drawAsSelected(size_t row) const { if (activeSelection) //check if user is currently selecting with mouse @@ -964,14 +972,14 @@ private: void onMouseLeftDouble(wxMouseEvent& event) override { - const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); - const auto row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - if (row >= 0) + if (auto prov = refParent().getDataProvider()) { - const Opt<ColumnType> ct = refParent().getColumnAtPos(absPos.x); - const ColumnType colType = ct ? *ct : DUMMY_COLUMN_TYPE; + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! + const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); //client is interested in all double-clicks, even those outside of the grid! - sendEventNow(GridClickEvent(EVENT_GRID_MOUSE_LEFT_DOUBLE, event, row, colType)); + sendEventNow(GridClickEvent(EVENT_GRID_MOUSE_LEFT_DOUBLE, event, row, rowHover)); } event.Skip(); } @@ -981,36 +989,37 @@ private: if (wxWindow::FindFocus() != this) //doesn't seem to happen automatically for right mouse button SetFocus(); - const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); - const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! + assert(row >= 0); if (row >= 0) - { - const Opt<ColumnType> ct = refParent().getColumnAtPos(absPos.x); - const ColumnType colType = ct ? *ct : DUMMY_COLUMN_TYPE; - - if (!event.RightDown() || !refParent().isSelected(row)) //do NOT start a new selection if user right-clicks on a selected area! + if (auto prov = refParent().getDataProvider()) { - if (event.ControlDown()) - activeSelection = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row)); - else if (event.ShiftDown()) - { - activeSelection = std::make_unique<MouseSelection>(*this, selectionAnchor, true); - refParent().clearSelection(ALLOW_GRID_EVENT); - } - else + const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, rowHover); + + if (!event.RightDown() || !refParent().isSelected(row)) //do NOT start a new selection if user right-clicks on a selected area! { - activeSelection = std::make_unique<MouseSelection>(*this, row, true); - refParent().clearSelection(ALLOW_GRID_EVENT); + if (event.ControlDown()) + activeSelection = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row), mouseEvent); + else if (event.ShiftDown()) + { + activeSelection = std::make_unique<MouseSelection>(*this, selectionAnchor, true, mouseEvent); + refParent().clearSelection(ALLOW_GRID_EVENT); + } + else + { + activeSelection = std::make_unique<MouseSelection>(*this, row, true, mouseEvent); + refParent().clearSelection(ALLOW_GRID_EVENT); + } } - } - - //notify event *after* potential "clearSelection(true)" above: a client should first receive a GridRangeSelectEvent for clearing the grid, if necessary, - //then GridClickEvent and the associated GridRangeSelectEvent one after the other - GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, colType); - sendEventNow(mouseEvent); + //notify event *after* potential "clearSelection(true)" above: a client should first receive a GridRangeSelectEvent for clearing the grid, if necessary, + //then GridClickEvent and the associated GridRangeSelectEvent one after the other + sendEventNow(mouseEvent); - Refresh(); - } + Refresh(); + } event.Skip(); //allow changing focus } @@ -1038,19 +1047,25 @@ private: refParent().selectRangeAndNotify(activeSelection->getStartRow (), //from activeSelection->getCurrentRow(), //to - activeSelection->isPositiveSelect()); + activeSelection->isPositiveSelect(), + &activeSelection->getFirstClick()); activeSelection.reset(); } - //this one may point to row which is not in visible area! - const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); - - const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - const Opt<ColumnType> ct = refParent().getColumnAtPos(absPos.x); - const ColumnType colType = ct ? *ct : DUMMY_COLUMN_TYPE; //we probably should notify even if colInfo is invalid! + if (auto prov = refParent().getDataProvider()) + { + //this one may point to row which is not in visible area! + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! + const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + //notify click event after the range selection! e.g. this makes sure the selection is applied before showing a context menu + sendEventNow(GridClickEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, event, row, rowHover)); + } - //notify click event after the range selection! e.g. this makes sure the selection is applied before showing a context menu - sendEventNow(GridClickEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, event, row, colType)); + //update highlight and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) + event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)! + onMouseMovement(event); Refresh(); event.Skip(); //allow changing focus @@ -1059,41 +1074,61 @@ private: void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override { activeSelection.reset(); + highlight.row = -1; Refresh(); //event.Skip(); -> we DID handle it! } void onMouseMovement(wxMouseEvent& event) override { - if (activeSelection) - activeSelection->evalMousePos(); //eval on both mouse movement + timer event! - - //change tooltip - const std::wstring toolTip = [&] + if (auto prov = refParent().getDataProvider()) { const ptrdiff_t rowCount = refParent().getRowCount(); - const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! + const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); - const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - const Opt<ColumnType> ct = refParent().getColumnAtPos(absPos.x); - if (ct && 0 <= row && row < rowCount) - if (auto prov = refParent().getDataProvider()) - return prov->getToolTip(row, *ct); - return std::wstring(); - }(); + const std::wstring toolTip = [&] + { + if (cpi.colType != ColumnType::NONE && 0 <= row && row < rowCount) + return prov->getToolTip(row, cpi.colType); + return std::wstring(); + }(); + setToolTip(toolTip); //show even during mouse selection! + + if (activeSelection) + activeSelection->evalMousePos(); //call on both mouse movement + timer event! + else + { + refreshHighlight(highlight); + highlight.row = row; + highlight.rowHover = rowHover; + refreshHighlight(highlight); //multiple Refresh() calls are condensed into single one! + } + } + event.Skip(); + } - setToolTip(toolTip); + void onLeaveWindow(wxMouseEvent& event) override //wxEVT_LEAVE_WINDOW does not respect mouse capture! + { + if (!activeSelection) + { + refreshHighlight(highlight); + highlight.row = -1; + } event.Skip(); } + void onFocus(wxFocusEvent& event) override { Refresh(); event.Skip(); } class MouseSelection : private wxEvtHandler { public: - MouseSelection(MainWin& wnd, size_t rowStart, bool positiveSelect) : - wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positiveSelect) + MouseSelection(MainWin& wnd, size_t rowStart, bool positiveSelect, const GridClickEvent& firstClick) : + wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positiveSelect), firstClick_(firstClick) { wnd_.CaptureMouse(); timer.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this); @@ -1105,13 +1140,14 @@ private: size_t getStartRow () const { return rowStart_; } size_t getCurrentRow () const { return rowCurrent_; } bool isPositiveSelect() const { return positiveSelect_; } //are we selecting or unselecting? + const GridClickEvent& getFirstClick() const { return firstClick_; } void evalMousePos() { double deltaTime = 0; if (ticksPerSec_ > 0) { - const TickVal now = getTicks(); //isValid() on error + const TickVal now = getTicks(); //!isValid() on error deltaTime = static_cast<double>(dist(tickCountLast, now)) / ticksPerSec_; //unit: [sec] tickCountLast = now; } @@ -1146,27 +1182,25 @@ private: autoScroll(overlapPixX, toScrollX); autoScroll(overlapPixY, toScrollY); - if (toScrollX != 0 || toScrollY != 0) + if (static_cast<int>(toScrollX) != 0 || static_cast<int>(toScrollY) != 0) { wnd_.refParent().scrollDelta(static_cast<int>(toScrollX), static_cast<int>(toScrollY)); // toScrollX -= static_cast<int>(toScrollX); //rounds down for positive numbers, up for negative, toScrollY -= static_cast<int>(toScrollY); //exactly what we want } - { - //select current row *after* scrolling - wxPoint clientPosTrimmed = clientPos; - numeric::clamp(clientPosTrimmed.y, 0, clientSize.GetHeight() - 1); //do not select row outside client window! - - const wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed); - const ptrdiff_t newRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - if (newRow >= 0) - if (rowCurrent_ != newRow) - { - rowCurrent_ = newRow; - wnd_.Refresh(); - } - } + //select current row *after* scrolling + wxPoint clientPosTrimmed = clientPos; + numeric::clamp(clientPosTrimmed.y, 0, clientSize.GetHeight() - 1); //do not select row outside client window! + + const wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed); + const ptrdiff_t newRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + if (newRow >= 0) + if (rowCurrent_ != newRow) + { + rowCurrent_ = newRow; + wnd_.Refresh(); + } } private: @@ -1176,13 +1210,20 @@ private: const size_t rowStart_; ptrdiff_t rowCurrent_; const bool positiveSelect_; + const GridClickEvent firstClick_; wxTimer timer; - double toScrollX = 0; //count outstanding scroll units to scroll while dragging mouse + double toScrollX = 0; //count outstanding scroll unit fractions while dragging mouse double toScrollY = 0; // TickVal tickCountLast = getTicks(); const std::int64_t ticksPerSec_ = ticksPerSec(); }; + struct MouseHighlight + { + ptrdiff_t row = -1; + HoverArea rowHover = HoverArea::NONE; + }; + void ScrollWindow(int dx, int dy, const wxRect* rect) override { wxWindow::ScrollWindow(dx, dy, rect); @@ -1213,10 +1254,26 @@ private: rowLabelWin_.Update(); //update while dragging scroll thumb } + void refreshRow(size_t row) + { + const wxRect& rowArea = rowLabelWin_.getRowLabelArea(row); //returns empty rect if row not found + const wxPoint topLeft = refParent().CalcScrolledPosition(wxPoint(0, rowArea.y)); //absolute -> client coordinates + wxRect cellArea(topLeft, wxSize(refParent().getColWidthsSum(GetClientSize().GetWidth()), rowArea.height)); + RefreshRect(cellArea, false); + } + + void refreshHighlight(const MouseHighlight& hl) + { + const ptrdiff_t rowCount = refParent().getRowCount(); + if (0 <= hl.row && hl.row < rowCount && hl.rowHover != HoverArea::NONE) //no highlight? => NOP! + refreshRow(hl.row); + } + RowLabelWin& rowLabelWin_; ColLabelWin& colLabelWin_; std::unique_ptr<MouseSelection> activeSelection; //bound while user is selecting with mouse + MouseHighlight highlight; //current mouse highlight (superseeded by activeSelection if available) ptrdiff_t cursorRow = 0; size_t selectionAnchor = 0; @@ -1427,14 +1484,10 @@ void Grid::onKeyDown(wxKeyEvent& event) int keyCode = event.GetKeyCode(); if (GetLayoutDirection() == wxLayout_RightToLeft) { - if (keyCode == WXK_LEFT) + if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT) keyCode = WXK_RIGHT; - else if (keyCode == WXK_RIGHT) + else if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT) keyCode = WXK_LEFT; - else if (keyCode == WXK_NUMPAD_LEFT) - keyCode = WXK_NUMPAD_RIGHT; - else if (keyCode == WXK_NUMPAD_RIGHT) - keyCode = WXK_NUMPAD_LEFT; } const ptrdiff_t rowCount = getRowCount(); @@ -1547,7 +1600,7 @@ void Grid::onKeyDown(wxKeyEvent& event) case 'A': //Ctrl + A - select all if (event.ControlDown()) - selectRangeAndNotify(0, rowCount); + selectRangeAndNotify(0, rowCount, true /*positive*/, nullptr /*mouseInitiated*/); break; case WXK_NUMPAD_ADD: //CTRL + '+' - auto-size all @@ -1581,7 +1634,7 @@ void Grid::selectAllRows(GridEventPolicy rangeEventPolicy) if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction { - GridRangeSelectEvent selEvent(0, getRowCount(), true); + GridRangeSelectEvent selEvent(0, getRowCount(), true, nullptr); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(selEvent); } @@ -1595,7 +1648,7 @@ void Grid::clearSelection(GridEventPolicy rangeEventPolicy) if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction { - GridRangeSelectEvent unselectionEvent(0, getRowCount(), false); + GridRangeSelectEvent unselectionEvent(0, getRowCount(), false, nullptr); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(unselectionEvent); } @@ -1645,7 +1698,7 @@ void Grid::Refresh(bool eraseBackground, const wxRect* rect) updateWindowSizes(); } - if (selection.size() != rowCountNew) //clear selection only when needed (consider setSelectedRows()) + if (selection.maxSize() != rowCountNew) //clear selection only when needed (consider setSelectedRows()) selection.init(rowCountNew); wxScrolledWindow::Refresh(eraseBackground, rect); @@ -1667,8 +1720,11 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) std::vector<VisibleColumn> visCols; for (const ColumnAttribute& ca : attr) + { + assert(ca.type_ != ColumnType::NONE); if (ca.visible_) visCols.emplace_back(ca.type_, ca.offset_, ca.stretch_); + } //"ownership" of visible columns is now within Grid visibleCols = visCols; @@ -1742,34 +1798,16 @@ void Grid::showScrollBars(Grid::ScrollBarStatus horizontal, Grid::ScrollBarStatu #endif updateWindowSizes(); +} +#if defined ZEN_WIN || defined ZEN_MAC +void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) +{ /* wxWidgets >= 2.9 ShowScrollbars() is next to useless since it doesn't honor wxSHOW_SB_ALWAYS on OS X, so let's ditch it and avoid more non-portability surprises - - #if wxCHECK_VERSION(2, 9, 0) - auto mapStatus = [](ScrollBarStatus sbStatus) -> wxScrollbarVisibility - { - switch (sbStatus) - { - case SB_SHOW_AUTOMATIC: - return wxSHOW_SB_DEFAULT; - case SB_SHOW_ALWAYS: - return wxSHOW_SB_ALWAYS; - case SB_SHOW_NEVER: - return wxSHOW_SB_NEVER; - } - assert(false); - return wxSHOW_SB_DEFAULT; - }; - ShowScrollbars(mapStatus(horizontal), mapStatus(vertical)); - #endif */ -} -#if defined ZEN_WIN || defined ZEN_MAC -void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) -{ ScrollBarStatus sbStatus = SB_SHOW_AUTOMATIC; if (orientation == wxHORIZONTAL) sbStatus = showScrollbarX; @@ -1806,24 +1844,6 @@ wxWindow& Grid::getMainWin () { return *mainWin_; } const wxWindow& Grid::getMainWin() const { return *mainWin_; } -wxRect Grid::getColumnLabelArea(ColumnType colType) const -{ - std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths - - auto iterCol = std::find_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }); - if (iterCol != absWidths.end()) - { - ptrdiff_t posX = 0; - for (auto it = absWidths.begin(); it != iterCol; ++it) - posX += it->width_; - - return wxRect(wxPoint(posX, 0), wxSize(iterCol->width_, colLabelHeight)); - } - - return wxRect(); -} - - Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const { const int absPosX = CalcUnscrolledPosition(pos).x; @@ -1862,7 +1882,7 @@ void Grid::moveColumn(size_t colFrom, size_t colTo) colTo < visibleCols.size() && colTo != colFrom) { - const auto colAtt = visibleCols[colFrom]; + const VisibleColumn colAtt = visibleCols[colFrom]; visibleCols.erase (visibleCols.begin() + colFrom); visibleCols.insert(visibleCols.begin() + colTo, colAtt); } @@ -1871,35 +1891,35 @@ void Grid::moveColumn(size_t colFrom, size_t colTo) ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos) const { - std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths const int absPosX = CalcUnscrolledPosition(pos).x; - int accuWidth = 0; - for (auto iterCol = absWidths.begin(); iterCol != absWidths.end(); ++iterCol) + int accWidth = 0; + std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths + for (auto itCol = absWidths.begin(); itCol != absWidths.end(); ++itCol) { - const int width = iterCol->width_; //beware dreaded unsigned conversions! - accuWidth += width; + const int width = itCol->width_; //beware dreaded unsigned conversions! + accWidth += width; - if (absPosX < accuWidth - width / 2) - return iterCol - absWidths.begin(); + if (absPosX < accWidth - width / 2) + return itCol - absWidths.begin(); } return absWidths.size(); } -Opt<ColumnType> Grid::colToType(size_t col) const +ColumnType Grid::colToType(size_t col) const { if (col < visibleCols.size()) return visibleCols[col].type_; - return NoValue(); + return ColumnType::NONE; } ptrdiff_t Grid::getRowAtPos(int posY) const { return rowLabelWin_->getRowAtPos(posY); } -Opt<ColumnType> Grid::getColumnAtPos(int posX) const +Grid::ColumnPosInfo Grid::getColumnAtPos(int posX) const { if (posX >= 0) { @@ -1908,18 +1928,44 @@ Opt<ColumnType> Grid::getColumnAtPos(int posX) const { accWidth += cw.width_; if (posX < accWidth) - return cw.type_; + return { cw.type_, posX + cw.width_ - accWidth, cw.width_ }; } } - return NoValue(); + return { ColumnType::NONE, 0, 0 }; } -wxRect Grid::getCellArea(size_t row, ColumnType colType) const +wxRect Grid::getColumnLabelArea(ColumnType colType) const { - const wxRect& colArea = getColumnLabelArea(colType); - const wxRect& rowArea = rowLabelWin_->getRowLabelArea(row); - return wxRect(wxPoint(colArea.x, rowArea.y), wxSize(colArea.width, rowArea.height)); + std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths + + //colType is not unique in general, but *this* function expects it! + assert(std::count_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }) <= 1); + + auto itCol = std::find_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }); + if (itCol != absWidths.end()) + { + ptrdiff_t posX = 0; + for (auto it = absWidths.begin(); it != itCol; ++it) + posX += it->width_; + + return wxRect(wxPoint(posX, 0), wxSize(itCol->width_, colLabelHeight)); + } + return wxRect(); +} + + +void Grid::refreshCell(size_t row, ColumnType colType) +{ + const wxRect& colArea = getColumnLabelArea(colType); //returns empty rect if column not found + const wxRect& rowArea = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found + if (colArea.height > 0 && rowArea.height > 0) + { + const wxPoint topLeft = CalcScrolledPosition(wxPoint(colArea.x, rowArea.y)); //absolute -> client coordinates + const wxRect cellArea(topLeft, wxSize(colArea.width, rowArea.height)); + + getMainWin().RefreshRect(cellArea, false); + } } @@ -1929,7 +1975,7 @@ void Grid::setGridCursor(size_t row) makeRowVisible(row); selection.clear(); //clear selection, do NOT fire event - selectRangeAndNotify(row, row); //set new selection + fire event + selectRangeAndNotify(row, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event mainWin_->Refresh(); rowLabelWin_->Refresh(); //row labels! (Kubuntu) @@ -1944,7 +1990,7 @@ void Grid::selectWithCursor(ptrdiff_t row) makeRowVisible(row); selection.clear(); //clear selection, do NOT fire event - selectRangeAndNotify(anchorRow, row); //set new selection + fire event + selectRangeAndNotify(anchorRow, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event mainWin_->Refresh(); rowLabelWin_->Refresh(); @@ -1953,7 +1999,7 @@ void Grid::selectWithCursor(ptrdiff_t row) void Grid::makeRowVisible(size_t row) { - const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if column not found + const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found if (labelRect.height > 0) { int scrollPosX = 0; @@ -1966,16 +2012,16 @@ void Grid::makeRowVisible(size_t row) const int clientPosY = CalcScrolledPosition(labelRect.GetTopLeft()).y; if (clientPosY < 0) { - const int scrollPosY = labelRect.GetTopLeft().y / pixelsPerUnitY; + const int scrollPosY = labelRect.y / pixelsPerUnitY; Scroll(scrollPosX, scrollPosY); updateWindowSizes(); //may show horizontal scroll bar } - else if (clientPosY + labelRect.GetHeight() > rowLabelWin_->GetClientSize().GetHeight()) + else if (clientPosY + labelRect.height > rowLabelWin_->GetClientSize().GetHeight()) { auto execScroll = [&](int clientHeight) { - const int scrollPosY = std::ceil((labelRect.GetTopLeft().y - clientHeight + - labelRect.GetHeight()) / static_cast<double>(pixelsPerUnitY)); + const int scrollPosY = std::ceil((labelRect.y - clientHeight + + labelRect.height) / static_cast<double>(pixelsPerUnitY)); Scroll(scrollPosX, scrollPosY); updateWindowSizes(); //may show horizontal scroll bar }; @@ -1992,7 +2038,7 @@ void Grid::makeRowVisible(size_t row) } -void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive) +void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated) { //sort + convert to half-open range auto rowFirst = std::min(rowFrom, rowTo); @@ -2005,7 +2051,7 @@ void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positiv selection.selectRange(rowFirst, rowLast, positive); //notify event - GridRangeSelectEvent selectionEvent(rowFirst, rowLast, positive); + GridRangeSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseInitiated); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(selectionEvent); @@ -2015,14 +2061,14 @@ void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positiv void Grid::scrollTo(size_t row) { - const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if column not found + const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found if (labelRect.height > 0) { int pixelsPerUnitY = 0; GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY); if (pixelsPerUnitY > 0) { - const int scrollPosYNew = labelRect.GetTopLeft().y / pixelsPerUnitY; + const int scrollPosYNew = labelRect.y / pixelsPerUnitY; int scrollPosXOld = 0; int scrollPosYOld = 0; GetViewStart(&scrollPosXOld, &scrollPosYOld); @@ -18,13 +18,10 @@ namespace zen { -typedef enum { DUMMY_COLUMN_TYPE = static_cast<unsigned int>(-1) } ColumnType; - -//----- events ------------------------------------------------------------------------ -extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridClickEvent -extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_RIGHT; // -extern const wxEventType EVENT_GRID_COL_RESIZE; //generates: GridColumnResizeEvent +enum class ColumnType { NONE = -1 }; //user-defiend column type +enum class HoverArea { NONE = -1 }; //user-defined area for mouse selections for a given row (may span multiple columns or split a single column into multiple areas) +//------------------------ events ------------------------------------------------ extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOUBLE; // extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOWN; // extern const wxEventType EVENT_GRID_MOUSE_LEFT_UP; //generates: GridClickEvent @@ -34,48 +31,63 @@ extern const wxEventType EVENT_GRID_MOUSE_RIGHT_UP; // extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridRangeSelectEvent //NOTE: neither first nor second row need to match EVENT_GRID_MOUSE_LEFT_DOWN/EVENT_GRID_MOUSE_LEFT_UP: user holding SHIFT; moving out of window... +extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridLabelClickEvent +extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_RIGHT; // +extern const wxEventType EVENT_GRID_COL_RESIZE; //generates: GridColumnResizeEvent + //example: wnd.Connect(EVENT_GRID_COL_LABEL_LEFT_CLICK, GridClickEventHandler(MyDlg::OnLeftClick), nullptr, this); struct GridClickEvent : public wxMouseEvent { - GridClickEvent(wxEventType et, const wxMouseEvent& me, ptrdiff_t row, ColumnType colType) : wxMouseEvent(me), row_(row), colType_(colType) { SetEventType(et); } + GridClickEvent(wxEventType et, const wxMouseEvent& me, ptrdiff_t row, HoverArea hoverArea) : + wxMouseEvent(me), row_(row), hoverArea_(hoverArea) { SetEventType(et); } wxEvent* Clone() const override { return new GridClickEvent(*this); } const ptrdiff_t row_; //-1 for invalid position, >= rowCount if out of range - const ColumnType colType_; //may be DUMMY_COLUMN_TYPE -}; - -struct GridColumnResizeEvent : public wxCommandEvent -{ - GridColumnResizeEvent(int offset, ColumnType colType) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), offset_(offset) {} - wxEvent* Clone() const override { return new GridColumnResizeEvent(*this); } - - const ColumnType colType_; - const int offset_; + const HoverArea hoverArea_; //may be HoverArea::NONE }; struct GridRangeSelectEvent : public wxCommandEvent { - GridRangeSelectEvent(size_t rowFirst, size_t rowLast, bool positive) : wxCommandEvent(EVENT_GRID_SELECT_RANGE), positive_(positive), rowFirst_(rowFirst), rowLast_(rowLast) { assert(rowFirst <= rowLast); } + GridRangeSelectEvent(size_t rowFirst, size_t rowLast, bool positive, const GridClickEvent* mouseInitiated) : + wxCommandEvent(EVENT_GRID_SELECT_RANGE), rowFirst_(rowFirst), rowLast_(rowLast), positive_(positive), + mouseInitiated_(mouseInitiated ? *mouseInitiated : Opt<GridClickEvent>()) { assert(rowFirst <= rowLast); } wxEvent* Clone() const override { return new GridRangeSelectEvent(*this); } - const bool positive_; //"false" when clearing selection! const size_t rowFirst_; //selected range: [rowFirst_, rowLast_) const size_t rowLast_; + const bool positive_; //"false" when clearing selection! + Opt<GridClickEvent> mouseInitiated_; //filled unless selection was performed via keyboard shortcuts or is result of Grid::clearSelection() +}; + +struct GridLabelClickEvent : public wxMouseEvent +{ + GridLabelClickEvent(wxEventType et, const wxMouseEvent& me, ColumnType colType) : wxMouseEvent(me), colType_(colType) { SetEventType(et); } + wxEvent* Clone() const override { return new GridLabelClickEvent(*this); } + + const ColumnType colType_; //may be ColumnType::NONE }; -typedef void (wxEvtHandler::*GridClickEventFunction )(GridClickEvent&); -typedef void (wxEvtHandler::*GridColumnResizeEventFunction)(GridColumnResizeEvent&); -typedef void (wxEvtHandler::*GridRangeSelectEventFunction )(GridRangeSelectEvent&); -#define GridClickEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func) +struct GridColumnResizeEvent : public wxCommandEvent +{ + GridColumnResizeEvent(int offset, ColumnType colType) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), offset_(offset) {} + wxEvent* Clone() const override { return new GridColumnResizeEvent(*this); } + + const ColumnType colType_; + const int offset_; +}; + +using GridClickEventFunction = void (wxEvtHandler::*)(GridClickEvent&); +using GridRangeSelectEventFunction = void (wxEvtHandler::*)(GridRangeSelectEvent&); +using GridLabelClickEventFunction = void (wxEvtHandler::*)(GridLabelClickEvent&); +using GridColumnResizeEventFunction = void (wxEvtHandler::*)(GridColumnResizeEvent&); -#define GridColumnResizeEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridColumnResizeEventFunction, &func) +#define GridClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func) +#define GridRangeSelectEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridRangeSelectEventFunction, &func) +#define GridLabelClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridLabelClickEventFunction, &func) +#define GridColumnResizeEventHandler(func)(wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridColumnResizeEventFunction, &func) -#define GridRangeSelectEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridRangeSelectEventFunction, &func) //------------------------------------------------------------------------------------------------------------ class Grid; wxColor getColorSelectionGradientFrom(); @@ -90,12 +102,13 @@ public: virtual size_t getRowCount() const = 0; - //grid area + //cell area virtual std::wstring getValue(size_t row, ColumnType colType) const = 0; virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected); //default implementation - virtual void renderCell (wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected); // - virtual int getBestSize (wxDC& dc, size_t row, ColumnType colType ); //must correspond to renderCell()! + virtual void renderCell (wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover); + virtual int getBestSize (wxDC& dc, size_t row, ColumnType colType); //must correspond to renderCell()! virtual std::wstring getToolTip (size_t row, ColumnType colType) const { return std::wstring(); } + virtual HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) { return HoverArea::NONE; } //label area virtual std::wstring getColumnLabel(ColumnType colType) const = 0; @@ -105,15 +118,16 @@ public: static const int COLUMN_GAP_LEFT; //for left-aligned text protected: //optional helper routines - static wxRect drawCellBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle - static void drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, const wxColor& backgroundColor); - static void drawCellText (wxDC& dc, const wxRect& rect, const std::wstring& text, bool enabled, int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + static wxRect drawCellBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle + static void drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, const wxColor& backgroundColor); + static void drawCellText (wxDC& dc, const wxRect& rect, const std::wstring& text, bool enabled, int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - static wxRect drawColumnLabelBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle - static void drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted); - static void drawColumnLabelText (wxDC& dc, const wxRect& rect, const std::wstring& text); + static wxRect drawColumnLabelBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle + static void drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted); + static void drawColumnLabelText (wxDC& dc, const wxRect& rect, const std::wstring& text); }; + enum GridEventPolicy { ALLOW_GRID_EVENT, @@ -179,9 +193,16 @@ public: const wxWindow& getMainWin() const; ptrdiff_t getRowAtPos(int posY) const; //return -1 for invalid position, >= rowCount if out of range; absolute coordinates! - Opt<ColumnType> getColumnAtPos(int posX) const; - wxRect getCellArea(size_t row, ColumnType colType) const; //returns empty rect if column not found; absolute coordinates! + struct ColumnPosInfo + { + ColumnType colType; //ColumnType::NONE no column at x position! + int cellRelativePosX; + int colWidth; + }; + ColumnPosInfo getColumnAtPos(int posX) const; //absolute position! + + void refreshCell(size_t row, ColumnType colType); void enableColumnMove (bool value) { allowColumnMove = value; } void enableColumnResize(bool value) { allowColumnResize = value; } @@ -231,7 +252,7 @@ private: public: void init(size_t rowCount) { rowSelectionValue.resize(rowCount); clear(); } - size_t size() const { return rowSelectionValue.size(); } + size_t maxSize() const { return rowSelectionValue.size(); } std::vector<size_t> get() const { @@ -294,7 +315,7 @@ private: wxRect getColumnLabelArea(ColumnType colType) const; //returns empty rect if column not found - void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive = true); //select inclusive range [rowFrom, rowTo] + notify event! + void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated); //select inclusive range [rowFrom, rowTo] + notify event! bool isSelected(size_t row) const { return selection.isSelected(row); } @@ -307,7 +328,7 @@ private: void moveColumn(size_t colFrom, size_t colTo); ptrdiff_t clientPosToMoveTargetColumn(const wxPoint& pos) const; //return < 0 on error - Opt<ColumnType> colToType(size_t col) const; + ColumnType colToType(size_t col) const; //returns ColumnType::NONE on error /* Visual layout: diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index 98cdc7a8..4ba62bb9 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -50,12 +50,18 @@ public: } void init(const Zstring& filepath); + void cleanup() + { + bitmaps.clear(); + anims.clear(); + } const wxBitmap& getImage (const wxString& name) const; const wxAnimation& getAnimation(const wxString& name) const; private: GlobalResources() {} + ~GlobalResources() { assert(bitmaps.empty() && anims.empty()); } //don't leave wxWidgets objects for static destruction! GlobalResources (const GlobalResources&) = delete; GlobalResources& operator=(const GlobalResources&) = delete; @@ -127,6 +133,7 @@ const wxAnimation& GlobalResources::getAnimation(const wxString& name) const void zen::initResourceImages(const Zstring& filepath) { GlobalResources::instance().init(filepath); } +void zen::cleanupResourceImages() { GlobalResources::instance().cleanup(); } const wxBitmap& zen::getResourceImage(const wxString& name) { return GlobalResources::instance().getImage(name); } diff --git a/wx+/image_resources.h b/wx+/image_resources.h index 30f89848..61623614 100644 --- a/wx+/image_resources.h +++ b/wx+/image_resources.h @@ -14,6 +14,7 @@ namespace zen { void initResourceImages(const Zstring& filepath); //pass resources .zip file at application startup +void cleanupResourceImages(); const wxBitmap& getResourceImage (const wxString& name); const wxAnimation& getResourceAnimation(const wxString& name); diff --git a/wx+/image_tools.h b/wx+/image_tools.h index 5e99653c..554e7b49 100644 --- a/wx+/image_tools.h +++ b/wx+/image_tools.h @@ -50,7 +50,7 @@ void convertToVanillaImage(wxImage& img); //add alpha channel if missing + remov //wxColor gradient(const wxColor& from, const wxColor& to, double fraction); //maps fraction within [0, 1] to an intermediate color -//wxColour hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1] +//wxColor hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1] @@ -208,7 +208,7 @@ wxColor gradient(const wxColor& from, const wxColor& to, double fraction) /* inline -wxColour hsvColor(double h, double s, double v) //h within [0, 360), s, v within [0, 1] +wxColor hsvColor(double h, double s, double v) //h within [0, 360), s, v within [0, 1] { //http://de.wikipedia.org/wiki/HSV-Farbraum @@ -238,17 +238,17 @@ wxColour hsvColor(double h, double s, double v) //h within [0, 360), s, v within switch (h_i) { case 0: - return wxColour(vi, t, p); + return wxColor(vi, t, p); case 1: - return wxColour(q, vi, p); + return wxColor(q, vi, p); case 2: - return wxColour(p, vi, t); + return wxColor(p, vi, t); case 3: - return wxColour(p, q, vi); + return wxColor(p, q, vi); case 4: - return wxColour(t, p, vi); + return wxColor(t, p, vi); case 5: - return wxColour(vi, p, q); + return wxColor(vi, p, q); } assert(false); return *wxBLACK; diff --git a/wx+/tooltip.h b/wx+/tooltip.h index 8ddba819..06953361 100644 --- a/wx+/tooltip.h +++ b/wx+/tooltip.h @@ -15,8 +15,7 @@ namespace zen class Tooltip { public: - Tooltip(wxWindow& parent) : //parent needs to live at least as long as this instance! - tipWindow(nullptr), parent_(parent) {} + Tooltip(wxWindow& parent) : parent_(parent) {} //parent needs to live at least as long as this instance! void show(const wxString& text, wxPoint mousePos, //absolute screen coordinates @@ -25,7 +24,7 @@ public: private: class TooltipDialogGenerated; - TooltipDialogGenerated* tipWindow; + TooltipDialogGenerated* tipWindow = nullptr; wxWindow& parent_; }; } diff --git a/zen/basic_math.h b/zen/basic_math.h index 8b745caf..a15d811a 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -38,7 +38,7 @@ template <class T> T clampCpy(const T& val, const T& minVal, const T& maxVal); template <class T, class InputIterator> //precondition: range must be sorted! -auto nearMatch(const T& val, InputIterator first, InputIterator last) -> typename std::iterator_traits<InputIterator>::value_type; +auto nearMatch(const T& val, InputIterator first, InputIterator last); template <class T> bool isNull(T value); @@ -195,10 +195,10 @@ std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, Input */ template <class T, class InputIterator> inline -auto nearMatch(const T& val, InputIterator first, InputIterator last) -> typename std::iterator_traits<InputIterator>::value_type +auto nearMatch(const T& val, InputIterator first, InputIterator last) { if (first == last) - return 0; + return static_cast<decltype(*first)>(0); assert(std::is_sorted(first, last)); InputIterator it = std::lower_bound(first, last, val); diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 2ca373aa..a66cbc6b 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -828,7 +828,7 @@ void setFileTimeRaw(const Zstring& filePath, throw FileError(errorMsg, formatSystemError(L"SetFileTime", ec)); } } -#ifndef NDEBUG //verify written data: mainly required to check consistency of DST hack +#ifndef NDEBUG //verify written data FILETIME creationTimeDbg = {}; FILETIME lastWriteTimeDbg = {}; @@ -1347,9 +1347,9 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, bool copyFilePermissions) { #ifdef ZEN_WIN -auto getErrorMsg = [](const Zstring& path){ return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(path)); }; + auto getErrorMsg = [](const Zstring& path) { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(path)); }; -//special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS! + //special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS! Zstring dirTmp = removeLongPathPrefix(endsWith(targetPath, FILE_NAME_SEPARATOR) ? beforeLast(targetPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) : targetPath); @@ -1359,7 +1359,7 @@ auto getErrorMsg = [](const Zstring& path){ return replaceCpy(_("Cannot create d dirTmp += FILE_NAME_SEPARATOR; //we do not support "C:" to represent a relative path! const DWORD ec = somethingExists(dirTmp) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; //don't use dirExists() => harmonize with ErrorTargetExisting! - const std::wstring errorDescr = formatSystemError(L"CreateDirectory", ec); + const std::wstring errorDescr = formatSystemError(L"CreateDirectory", ec); if (ec == ERROR_ALREADY_EXISTS) throw ErrorTargetExisting(getErrorMsg(dirTmp), errorDescr); @@ -1412,7 +1412,7 @@ auto getErrorMsg = [](const Zstring& path){ return replaceCpy(_("Cannot create d if (!sourcePath.empty()) if (::stat(sourcePath.c_str(), &dirInfo) == 0) { - mode = dirInfo.st_mode; //analog to "cp" which copies "mode" (considering umask) by default + mode = dirInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items } //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions @@ -1783,8 +1783,8 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw //create targetFile and open it for writing HANDLE hFileTarget = ::CreateFile(applyLongPathPrefix(targetFile).c_str(), //_In_ LPCTSTR lpFileName, - GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess, - //read access required for FSCTL_SET_COMPRESSION + GENERIC_READ | GENERIC_WRITE | DELETE, //_In_ DWORD dwDesiredAccess, + //GENERIC_READ required for FSCTL_SET_COMPRESSION, DELETE for ::SetFileInformationByHandle(),FileDispositionInfo FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, //FILE_SHARE_DELETE is required to rename file while handle is open! nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, @@ -1808,10 +1808,27 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw throw FileError(errorMsg, errorDescr); } +#ifndef ZEN_WIN_VISTA_AND_LATER ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); } - catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget + catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget +#endif + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileTarget)); +#ifdef ZEN_WIN_VISTA_AND_LATER + //no need for ::DeleteFile(), we already have an open handle! Maybe this also prevents needless buffer-flushing in ::CloseHandle()??? Anyway, same behavior like ::CopyFileEx() + ZEN_ON_SCOPE_FAIL + ( + FILE_DISPOSITION_INFO di = {}; + di.DeleteFile = true; + if (!::SetFileInformationByHandle(hFileTarget, //_In_ HANDLE hFile, + FileDispositionInfo, //_In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + &di, //_In_ LPVOID lpFileInformation, + sizeof(di))) //_In_ DWORD dwBufferSize + assert(false); + ); +#endif + //---------------------------------------------------------------------- BY_HANDLE_FILE_INFORMATION fileInfoTarget = {}; if (!::GetFileInformationByHandle(hFileTarget, &fileInfoTarget)) @@ -1820,7 +1837,7 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw //return up-to-date file attributes InSyncAttributes newAttrib; newAttrib.fileSize = get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh); - newAttrib.modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); //no DST hack (yet) + newAttrib.modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); newAttrib.sourceFileId = extractFileId(fileInfoSource); newAttrib.targetFileId = extractFileId(fileInfoTarget); diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 5e8b7a1d..b385ce33 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -79,11 +79,11 @@ FileInput::FileInput(const Zstring& filepath) : //throw FileError, ErrorFileLock auto createHandle = [&](DWORD dwShareMode) { return ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, - GENERIC_READ, //_In_ DWORD dwDesiredAccess, - dwShareMode, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - FILE_FLAG_SEQUENTIAL_SCAN //_In_ DWORD dwFlagsAndAttributes, + GENERIC_READ, //_In_ DWORD dwDesiredAccess, + dwShareMode, //_In_ DWORD dwShareMode, + nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, + FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes, /* possible values: (Reference http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx#caching_behavior) FILE_FLAG_NO_BUFFERING FILE_FLAG_RANDOM_ACCESS @@ -108,7 +108,7 @@ FileInput::FileInput(const Zstring& filepath) : //throw FileError, ErrorFileLock for FFS most comparisons are probably between different disks => let's use FILE_FLAG_SEQUENTIAL_SCAN */ - | FILE_FLAG_BACKUP_SEMANTICS, + FILE_FLAG_BACKUP_SEMANTICS, nullptr); //_In_opt_ HANDLE hTemplateFile }; fileHandle = createHandle(FILE_SHARE_READ | FILE_SHARE_DELETE); @@ -249,8 +249,8 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw Fil nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, dwCreationDisposition, //_In_ DWORD dwCreationDisposition, dwFlagsAndAttributes | - FILE_FLAG_SEQUENTIAL_SCAN //_In_ DWORD dwFlagsAndAttributes, - | FILE_FLAG_BACKUP_SEMANTICS, + FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes, + FILE_FLAG_BACKUP_SEMANTICS, nullptr); //_In_opt_ HANDLE hTemplateFile }; @@ -298,7 +298,7 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw Fil //checkForUnsupportedType(filepath); -> not needed, open() + O_WRONLY should fail fast fileHandle = ::open(filepath.c_str(), O_WRONLY | O_CREAT | (access == FileOutput::ACC_CREATE_NEW ? O_EXCL : O_TRUNC), - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666 if (fileHandle == -1) { const int ec = errno; //copy before making other system calls! diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index e9c686aa..cfaaa329 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -194,7 +194,7 @@ class IntegerFormat { public: static const NUMBERFMT& get() { return getInst().fmt; } - static bool isValid() { return getInst().valid_; } + static bool isValid() { return getInst().valid; } private: static const IntegerFormat& getInst() @@ -202,7 +202,7 @@ private: #if defined _MSC_VER && _MSC_VER < 1900 #error function scope static initialization is not yet thread-safe! #endif - static IntegerFormat inst; + static const IntegerFormat inst; return inst; } @@ -229,14 +229,14 @@ private: else grouping += L'0'; fmt.Grouping = stringTo<UINT>(grouping); - valid_ = true; + valid = true; } } NUMBERFMT fmt = {}; std::wstring thousandSep; std::wstring decimalSep; - bool valid_ = false; + bool valid = false; }; } #endif @@ -99,7 +99,7 @@ std::wstring translate(const std::wstring& singular, const std::wstring& plural, inline -std::unique_ptr<const TranslationHandler>& globalHandler() +std::unique_ptr<const TranslationHandler>& globalTranslationHandler() { static std::unique_ptr<const TranslationHandler> inst; //external linkage even in header! return inst; @@ -108,11 +108,11 @@ std::unique_ptr<const TranslationHandler>& globalHandler() inline -void setTranslator(std::unique_ptr<const TranslationHandler>&& newHandler) { implementation::globalHandler() = std::move(newHandler); } +void setTranslator(std::unique_ptr<const TranslationHandler>&& newHandler) { implementation::globalTranslationHandler() = std::move(newHandler); } inline -const TranslationHandler* getTranslator() { return implementation::globalHandler().get(); } +const TranslationHandler* getTranslator() { return implementation::globalTranslationHandler().get(); } } #endif //I18_N_H_3843489325044253425456 diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 791764de..1345447e 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -73,11 +73,11 @@ public: ~ScopeGuard() noexcept(runMode != ScopeGuardRunMode::ON_SUCCESS) { + if (!dismissed) + { #ifdef _MSC_VER #pragma warning(suppress: 4127) //"conditional expression is constant" #endif - if (!dismissed) - { if (runMode != ScopeGuardRunMode::ON_EXIT) { const bool failed = getUncaughtExceptionCount() > exeptionCount; @@ -85,6 +85,9 @@ public: return; } +#ifdef _MSC_VER + #pragma warning(suppress: 4127) //"conditional expression is constant" +#endif if (runMode == ScopeGuardRunMode::ON_SUCCESS) fun_(); //throw X else diff --git a/zen/thread.h b/zen/thread.h index bb6e7901..700d42dc 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -13,7 +13,7 @@ #include "type_traits.h" #include "optional.h" #ifdef ZEN_WIN -#include "win.h" + #include "win.h" #endif @@ -63,7 +63,7 @@ template <class Rep, class Period> void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime); //throw ThreadInterruption #ifdef ZEN_WIN -void setCurrentThreadName(const char* threadName); + void setCurrentThreadName(const char* threadName); #endif //------------------------------------------------------------------------------------------ @@ -421,10 +421,10 @@ void InterruptibleThread::interrupt() { intStatus_->interrupt(); } #pragma pack(push,8) struct THREADNAME_INFO { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. }; #pragma pack(pop) @@ -432,18 +432,18 @@ struct THREADNAME_INFO inline void setCurrentThreadName(const char* threadName) { -const DWORD MS_VC_EXCEPTION = 0x406D1388; - -THREADNAME_INFO info = {}; - info.dwType = 0x1000; - info.szName = threadName; - info.dwThreadID = GetCurrentThreadId(); - - __try - { - ::RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), reinterpret_cast<ULONG_PTR*>(&info)); - } - __except(EXCEPTION_EXECUTE_HANDLER){} + const DWORD MS_VC_EXCEPTION = 0x406D1388; + + THREADNAME_INFO info = {}; + info.dwType = 0x1000; + info.szName = threadName; + info.dwThreadID = GetCurrentThreadId(); + + __try + { + ::RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), reinterpret_cast<ULONG_PTR*>(&info)); + } + __except (EXCEPTION_EXECUTE_HANDLER) {} } #endif } diff --git a/zen/type_traits.h b/zen/type_traits.h index ac7f1a42..c0da43ed 100644 --- a/zen/type_traits.h +++ b/zen/type_traits.h @@ -38,11 +38,8 @@ struct ResultType }; //Herb Sutter's signedness conversion helpers: http://herbsutter.com/2013/06/13/gotw-93-solution-auto-variables-part-2/ -template<class T> inline -typename std::make_signed<T>::type makeSigned(T t) { return static_cast<std::make_signed_t<T>>(t); } - -template<class T> inline -typename std::make_unsigned<T>::type makeUnsigned(T t) { return static_cast<std::make_unsigned_t<T>>(t); } +template<class T> inline auto makeSigned (T t) { return static_cast<std::make_signed_t <T>>(t); } +template<class T> inline auto makeUnsigned(T t) { return static_cast<std::make_unsigned_t<T>>(t); } //################# Built-in Types ######################## //Example: "IsSignedInt<int>::value" evaluates to "true" @@ -62,7 +59,7 @@ template <class T> struct IsArithmetic; //IsInteger or IsFloat /* Detect data or function members of a class by name: ZEN_INIT_DETECT_MEMBER + HasMember_ Example: 1. ZEN_INIT_DETECT_MEMBER(c_str); - 2. HasMember_c_str<T>::value -> use as boolean + 2. HasMember_c_str<T>::value -> use boolean */ /* Detect data or function members of a class by name *and* type: ZEN_INIT_DETECT_MEMBER2 + HasMember_ |