diff options
author | Daniel Wilhelm <shieldwed@outlook.com> | 2018-05-09 00:07:47 +0200 |
---|---|---|
committer | Daniel Wilhelm <shieldwed@outlook.com> | 2018-05-09 00:07:47 +0200 |
commit | 48c8efc58c9eb41da96b053806deb395d2e66443 (patch) | |
tree | a6da12e987ad778bafe6da7069c7fa8b1e761c68 | |
parent | 9.6 (diff) | |
download | FreeFileSync-48c8efc58c9eb41da96b053806deb395d2e66443.tar.gz FreeFileSync-48c8efc58c9eb41da96b053806deb395d2e66443.tar.bz2 FreeFileSync-48c8efc58c9eb41da96b053806deb395d2e66443.zip |
9.7
53 files changed, 3155 insertions, 2104 deletions
diff --git a/Changelog.txt b/Changelog.txt index acf06a4d..60be1b62 100755 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,5 +1,22 @@ -FreeFileSync 9.6 ----------------- +FreeFileSync 9.7 [2018-01-12] +----------------------------- +New configuration management panel +New column showing days since last sync +Support starting FreeFileSync via Windows Send To +Minimized memory operations for I/O buffer +Allow multiple config selections on Linux +New command line option -DirPair +Fixed ENTER key not working for most dialogs (macOS) +Show only one warning about failed directory locks +Show correct synchronization time when resuming from system sleep +Don't resolve symlinks that are dropped via mouse +Detect and notify LCMapString compatibility mode bug +Fixed incorrect file permissions within macOS bundle +Fixed wrong results dialog panel selection (Linux) + + +FreeFileSync 9.6 [2017-12-07] +----------------------------- New installation command line option /disable_updates Fixed crash when closing main dialog during sync Fixed RealTimeSync crash after recursive mutex locking diff --git a/FreeFileSync/Build/Help/FreeFileSync.hhc b/FreeFileSync/Build/Help/FreeFileSync.hhc index 194d8951..1b2ba311 100755 --- a/FreeFileSync/Build/Help/FreeFileSync.hhc +++ b/FreeFileSync/Build/Help/FreeFileSync.hhc @@ -20,7 +20,7 @@ <param name="Local" value="html\freefilesync.html"> </OBJECT> <LI> <OBJECT type="text/sitemap"> - <param name="Name" value="Command Line Usage"> + <param name="Name" value="Command Line"> <param name="Local" value="html\command-line.html"> </OBJECT> <LI> <OBJECT type="text/sitemap"> diff --git a/FreeFileSync/Build/Help/html/command-line.html b/FreeFileSync/Build/Help/html/command-line.html index 3bde31e8..37ebf902 100755 --- a/FreeFileSync/Build/Help/html/command-line.html +++ b/FreeFileSync/Build/Help/html/command-line.html @@ -84,12 +84,11 @@ <h2>3. Customize an existing configuration</h2> <p> - You can replace the directories of a given ffs_gui or ffs_batch configuration file by using the <span class="command-line">-LeftDir</span> - and <span class="command-line">-RightDir</span> parameters: + You can replace the directories of a given ffs_gui or ffs_batch configuration file by using the <span class="command-line">-DirPair</span> parameter: </p> <div class="greybox"> - <div class="command-line">FreeFileSync.exe "D:\Manual Backup.ffs_gui" -leftdir C:\NewSource -rightdir D:\NewTarget</div> + <div class="command-line">FreeFileSync.exe "D:\Manual Backup.ffs_gui" -dirpair C:\NewSource D:\NewTarget</div> </div> <br> diff --git a/FreeFileSync/Build/Help/html/tips-and-tricks.html b/FreeFileSync/Build/Help/html/tips-and-tricks.html index 06b69e33..838ae087 100755 --- a/FreeFileSync/Build/Help/html/tips-and-tricks.html +++ b/FreeFileSync/Build/Help/html/tips-and-tricks.html @@ -12,15 +12,15 @@ <div class="tip" id="single-click-settings-change"> Change settings with a single mouse click: Press and hold the right mouse button until the context menu is shown, then release while over the selection: </div> - <img style="vertical-align: top;" src="../images/com-settings-context.png" alt="Comparison settings context menu"> - <img style="vertical-align: top;" src="../images/filter-context.png" alt="Filter context menu"> - <img style="vertical-align: top;" src="../images/sync-settings-context.png" alt="Synchronization settings context menu"><br> + <img style="vertical-align: top;" src="../images/com-settings-context.png" class="screen-snippet" alt="Comparison settings context menu"> + <img style="vertical-align: top;" src="../images/filter-context.png" class="screen-snippet" alt="Filter context menu"> + <img style="vertical-align: top;" src="../images/sync-settings-context.png" class="screen-snippet" alt="Synchronization settings context menu"><br> <div class="separation_line"></div> <div class="tip" id="select-multiple-configurations"> Select multiple configurations at a time: </div> - <img style="float:left; margin-right:10px" src="../images/config-multiple-selection.png" alt="Select multiple configurations"> + <img style="float:left; margin-right:10px" src="../images/config-multiple-selection.png" class="screen-snippet" alt="Select multiple configurations"> 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> @@ -28,86 +28,86 @@ <div class="tip" id="start-comparison-directly"> Start comparison directly by double-clicking on a configuration: </div> - <img src="../images/config-double-click.png" alt="Double-click on configuration"> + <img src="../images/config-double-click.png" class="screen-snippet" alt="Double-click on configuration"> <div class="separation_line"></div> <div class="tip" id="sync-multiple-folders"> Synchronize multiple folder pairs at a time with different configurations: </div> - <img src="../images/add-folder-pair.png" alt="Add folder pair"> + <img src="../images/add-folder-pair.png" class="screen-snippet" alt="Add folder pair"> <div class="separation_line"></div> <div class="tip" id="start-sync-directly"> Start synchronization directly without clicking on compare first: </div> - <img src="../images/direct-synchronize.png" alt="Start synchronization directly"> + <img src="../images/direct-synchronize.png" class="screen-snippet" alt="Start synchronization directly"> <div class="separation_line"></div> <div class="tip" id="mouse-window-drag"> Move a window by clicking on a free area and holding the mouse button: </div> - <img src="../images/dialog-drag-move.png" alt="Move dialog via mouse"> + <img src="../images/dialog-drag-move.png" class="screen-snippet" alt="Move dialog via mouse"> <div class="separation_line"></div> <div class="tip" id="open-config-from-explorer"> Open a batch configuration for edit via the Windows Explorer context menu: </div> - <img src="../images/explorer-context.png" alt="Explorer context menu"> + <img src="../images/explorer-context.png" class="screen-snippet" alt="Explorer context menu"> <div class="separation_line"></div> <div class="tip" id="two-folder-drop"> Drag and drop two folders at a time from Windows Explorer to fill a folder pair in one go: </div> - <img src="../images/two-folder-drop.png" alt="Two-folder drop"> + <img src="../images/two-folder-drop.png" class="screen-snippet" alt="Two-folder drop"> <div class="separation_line"></div> <div class="tip" id="copy-to-alternate-folder"> Copy files selected on the main dialog to an alternate folder and thereby save a "diff": </div> - <img src="../images/copy-alternative-path.png" alt="Copy to alternative path"> + <img src="../images/copy-alternative-path.png" class="screen-snippet" alt="Copy to alternative path"> <div class="separation_line"></div> <div class="tip" id="variable-drive-letter"> Use a volume name instead of a drive letter: </div> - <img src="../images/path-by-volume-name.png" alt="Drive letter by volume name"> + <img src="../images/path-by-volume-name.png" class="screen-snippet" alt="Drive letter by volume name"> <div class="separation_line"></div> <div class="tip" id="show-thumbnails"> Show thumbnail icons via the column header context menu: </div> - <img src="../images/show-thumbnails.png" alt="Show thumbnail icons"> + <img src="../images/show-thumbnails.png" class="screen-snippet" alt="Show thumbnail icons"> <div class="separation_line"></div> <div class="tip" id="save-view-filter"> Save the current view filter selection as default: </div> - <img src="../images/view-filter-default.png" alt="Save view filter settings"> + <img src="../images/view-filter-default.png" class="screen-snippet" alt="Save view filter settings"> <div class="separation_line"></div> <div class="tip" id="remove-local-settings"> Remove local settings from individual folder pairs: </div> - <img src="../images/remove-local-settings.png" alt="Remove local settings"> + <img src="../images/remove-local-settings.png" class="screen-snippet" alt="Remove local settings"> <div class="separation_line"></div> <div class="tip" id="remove-obsolete-paths"> 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"> + <img src="../images/remove-drop-down-path.png" class="screen-snippet" alt="Remove drop-down path"> <div class="separation_line"></div> <div class="tip" id="select-time-span"> Select a time span for files to include via the date column context menu: </div> - <img src="../images/select-time-span.png" alt="Select time span"> + <img src="../images/select-time-span.png" class="screen-snippet" alt="Select time span"> <div class="separation_line"></div> <div class="tip" id="double-click-dialog-confirm"> Double-click on comparison and synchronization variants to confirm the dialog: </div> - <img src="../images/comparison-variant-double-click.png" alt="Double-click comparison variant"> - <img src="../images/synchronization-variant-double-click.png" alt="Double-click synchronization variant"> + <img src="../images/comparison-variant-double-click.png" class="screen-snippet" alt="Double-click comparison variant"> + <img src="../images/synchronization-variant-double-click.png" class="screen-snippet" alt="Double-click synchronization variant"> <br> </body> </html> diff --git a/FreeFileSync/Build/Help/images/command-line-syntax.png b/FreeFileSync/Build/Help/images/command-line-syntax.png Binary files differindex 53f47122..f0d9878d 100755 --- a/FreeFileSync/Build/Help/images/command-line-syntax.png +++ b/FreeFileSync/Build/Help/images/command-line-syntax.png diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng index c39b0536..acc4da40 100755 --- a/FreeFileSync/Build/Languages/german.lng +++ b/FreeFileSync/Build/Languages/german.lng @@ -64,6 +64,9 @@ <source>Syntax error</source> <target>Syntaxfehler</target> +<source>A left and a right directory path are expected after %x.</source> +<target>Ein linker und rechter Verzeichnispfad werden nach %x erwartet.</target> + <source>Cannot find file %x.</source> <target>Die Datei %x wurde nicht gefunden.</target> @@ -456,8 +459,8 @@ Tatsächlich: %y bytes <source>Error parsing file %x, row %y, column %z.</source> <target>Fehler beim Auswerten der Datei %x, Zeile %y, Spalte %z.</target> -<source>Cannot set directory lock for %x.</source> -<target>Die Verzeichnissperre für %x kann nicht gesetzt werden.</target> +<source>Cannot set directory locks for the following folders:</source> +<target>Die Verzeichnissperren können für die folgenden Ordner nicht gesetzt werden:</target> <source> <pluralform>1 thread</pluralform> @@ -797,6 +800,27 @@ Die Befehlszeile wird ausgelöst, wenn: <source>Serious Error</source> <target>Schwerer Fehler</target> +<source>Last session</source> +<target>Letzte Sitzung</target> + +<source>Today</source> +<target>Heute</target> + +<source> +<pluralform>1 day</pluralform> +<pluralform>%x days</pluralform> +</source> +<target> +<pluralform>1 Tag</pluralform> +<pluralform>%x Tage</pluralform> +</target> + +<source>Name</source> +<target>Name</target> + +<source>Last sync</source> +<target>Letzte Ausführung</target> + <source>Folder</source> <target>Ordner</target> @@ -1324,6 +1348,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Activate offline</source> <target>Offline aktivieren</target> +<source>Highlight configurations that have not been run for more than the following number of days:</source> +<target>Konfigurationen hervorheben, die seit mehr als die folgende Anzahl an Tagen nicht mehr ausgeführt wurden:</target> + <source>Save as a Batch Job</source> <target>Als Batchauftrag speichern</target> @@ -1342,6 +1369,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>FreeFileSync Donation Edition</source> <target>FreeFileSync Spendenversion</target> +<source>Highlight Configurations</source> +<target>Konfigurationen hervorheben</target> + <source>&Options</source> <target>&Optionen</target> @@ -1465,9 +1495,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Select time span...</source> <target>Zeitspanne auswählen...</target> -<source>Last session</source> -<target>Letzte Sitzung</target> - <source>Folder Comparison and Synchronization</source> <target>Ordnervergleich und Synchronisation</target> @@ -1486,8 +1513,11 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Do&n't save</source> <target>&Nicht speichern</target> -<source>Remove entry from list</source> -<target>Eintrag aus Liste entfernen</target> +<source>Hide configuration</source> +<target>Konfiguration ausblenden</target> + +<source>Highlight...</source> +<target>Hervorheben...</target> <source>Clear filter</source> <target>Filter löschen</target> @@ -1699,9 +1729,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Synchronization</source> <target>Synchronisation</target> -<source>Today</source> -<target>Heute</target> - <source>This week</source> <target>Diese Woche</target> @@ -1771,9 +1798,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Files</source> <target>Dateien</target> -<source>Name</source> -<target>Name</target> - <source>Percentage</source> <target>Prozent</target> @@ -1885,15 +1909,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <pluralform>%x Stunden</pluralform> </target> -<source> -<pluralform>1 day</pluralform> -<pluralform>%x days</pluralform> -</source> -<target> -<pluralform>1 Tag</pluralform> -<pluralform>%x Tage</pluralform> -</target> - <source>Cannot set privilege %x.</source> <target>Das Privileg %x kann nicht gesetzt werden.</target> @@ -1960,6 +1975,9 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Start menu</source> <target>Startmenü</target> +<source>Send To</source> +<target>Senden an</target> + <source>Registering FreeFileSync file extensions</source> <target>Registriere FreeFileSync Dateiendungen</target> @@ -1987,6 +2005,6 @@ Dadurch wird ein konsistenter Datenstand auch bei schweren Fehlern garantiert. <source>Please choose the local installation type or select a different folder for installation.</source> <target>Bitte wählen Sie den lokalen Installationstyp oder einen anderen Ordner für die Installation.</target> -<source>The silent installation mode is only available in the FreeFileSync Donation Edition.</source> -<target>Der stille Installationsmodus is nur in der FreeFileSync Spendenversion verfügbar.</target> +<source>The %x installation option is only available in the FreeFileSync Donation Edition.</source> +<target>Die %x Installationsoption ist nur in der FreeFileSync Spendenversion verfügbar.</target> diff --git a/FreeFileSync/Build/Languages/slovenian.lng b/FreeFileSync/Build/Languages/slovenian.lng index 28f43e2a..81d99194 100755 --- a/FreeFileSync/Build/Languages/slovenian.lng +++ b/FreeFileSync/Build/Languages/slovenian.lng @@ -1,6 +1,6 @@ <header> <language>Slovenščina</language> - <translator>Karlo Konc</translator> + <translator>dr.Vinko Kastelic</translator> <locale>sl_SI</locale> <image>flag_slovenia.png</image> <plural_count>4</plural_count> @@ -8,19 +8,19 @@ </header> <source>Both sides have changed since last synchronization.</source> -<target>Obe strani sta se spremenili od zadnje sinhronizacije.</target> +<target>Obe strani sta spremenjeni po zadnji sinhronizaciji.</target> <source>Cannot determine sync-direction:</source> <target>Ne morem določiti sinhronizacijske smeri:</target> <source>No change since last synchronization.</source> -<target>Ni sprememb od zadnje sinhronizacije.</target> +<target>Ni sprememb po zadnji sinhronizaciji.</target> <source>The database entry is not in sync considering current settings.</source> -<target>Glede na trenutne nastavitve vnos v podatkovni bazi ni sinhroniziran.</target> +<target>Vnos podatkovne zbirke ni sinhroniziran ob upoštevanju trenutnih nastavitev.</target> <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> -<target>Nastavljanje privzetih smeri sinhronizacije: Stare datoteke bodo prepisane z novimi datotekami.</target> +<target>Nastavitve privzetih smeri sinhronizacije: Stare datoteke bodo prepisane z novimi datotekami.</target> <source>Creating file %x</source> <target>Ustvarjam datoteko %x</target> @@ -53,7 +53,7 @@ <target>Preverjam razpoložljivost koša za mapo %x...</target> <source>The recycle bin is not supported by the following folders. Deleted or overwritten files will not be able to be restored:</source> -<target></target> +<target>Koš ne podpira sledečih map. Izbrisane ali prepisane datoteke ne bo mogoče obnoviti:</target> <source>An exception occurred</source> <target>Prišlo je do izjeme</target> @@ -64,6 +64,9 @@ <source>Syntax error</source> <target>Sintaktična napaka</target> +<source>A left and a right directory path are expected after %x.</source> +<target>Levo in desno pot imenika se pričakuje po %x.</target> + <source>Cannot find file %x.</source> <target>Ne najdem datoteke %x.</target> @@ -77,10 +80,10 @@ <target>Vnešeno je neenako število levih in desnih imenikov.</target> <source>The config file must not contain settings at directory pair level when directories are set via command line.</source> -<target>Konfiguracijska datoteka ne sme vsebovati nastavitev na ravni imeniških parov, ko so imeniki nastavljeni prek ukazne vrstice.</target> +<target>Konfiguracijska datoteka ne sme vsebovati nastavitev na ravni imeniških parov, ko so imeniki nastavljeni preko ukazne vrstice.</target> <source>Directories cannot be set for more than one configuration file.</source> -<target>Imeniki ne morejo biti nastavljeni za več kot eno konfiguracijsko datoteko.</target> +<target>Imenikov ni mogoče nastaviti za več kot eno nastavitveno datoteko.</target> <source>Command line</source> <target>Ukazna vrstica</target> @@ -98,13 +101,13 @@ <target>datoteka z globalnimi konfiguracijami:</target> <source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> -<target>Poljubno število FreeFileSync .ffs_gui in/ali .ffs_batch konfigracijskih datotek.</target> +<target>Poljubno število FreeFileSync .ffs_gui in/ali .ffs_batch nastavitvenih datotek.</target> <source>Any number of alternative directory pairs for at most one config file.</source> <target>Poljubno število alternativnih parov imenikov za največ eno nastavitveno datoteko.</target> <source>Open the selected configuration for editing only without executing it.</source> -<target></target> +<target>Odprite izbrano nastavitev samo za urejanje, ne da bi jo izvedli.</target> <source>Path to an alternate GlobalSettings.xml file.</source> <target>Pot do alternativne datoteke GlobalSettings.xml.</target> @@ -113,7 +116,7 @@ <target>Ne najdem naslednjih map:</target> <source>If this error is ignored the folders will be considered empty. Missing folders are created automatically when needed.</source> -<target>V primeru ignoriranja te napake bo mapa smatrana kot prazna. Manjkajoče mape se ustvarijo avtomatično, ko je to potrebno.</target> +<target>V primeru prezrtja te napake bo mapa smatrana kot prazna. Manjkajoče mape se po potrebi ustvarijo samodejno.</target> <source>File %x has an invalid date.</source> <target>Datoteka %x ima neveljaven datum.</target> @@ -122,16 +125,16 @@ <target>Datum:</target> <source>Files have the same date but a different size.</source> -<target>Datoteke imajo isti datum a različno velikost.</target> +<target>Datoteke imajo isti datum toda različno velikost.</target> <source>Size:</source> <target>Velikost:</target> <source>Content comparison was skipped for excluded files.</source> -<target>Primerjava vsebine je bila preskočena za neizbrane datoteke.</target> +<target>Primerjava vsebine je bila preskočena zaradi izključenih datotek.</target> <source>Items differ in attributes only</source> -<target>Elementi se razlikujejo samo v atributih</target> +<target>Postavke se razlikujejo samo po atributih</target> <source>Resolving symbolic link %x</source> <target>Razrešujem simbolično povezavo %x</target> @@ -143,7 +146,7 @@ <target>Ustvarjam seznam datotek...</target> <source>Fail-safe file copy</source> -<target>Kopiranje datotek varno pred odpovedjo</target> +<target>Pred napako varno kopiranje datotek</target> <source>Enabled</source> <target>Omogočeno</target> @@ -155,7 +158,7 @@ <target>Kopiraj zaklenjene datoteke</target> <source>Copy file access permissions</source> -<target>Kopiraj dovoljenja dostopov datoteke</target> +<target>Kopiraj dovoljenja za dostop do datotek</target> <source>File time tolerance</source> <target>Časovna toleranca datoteke</target> @@ -167,7 +170,7 @@ <target>Zaženi s prioriteto v ozadju</target> <source>Lock directories during sync</source> -<target>Zakleni direktorije med sinhroniziranjem</target> +<target>Zakleni imenike med sinhronizacijo</target> <source>Verify copied files</source> <target>Preveri kopirane datoteke</target> @@ -179,7 +182,7 @@ <target>Začenjam primerjavo</target> <source>A folder input field is empty.</source> -<target>Vnosno polje za mapo je prazno.</target> +<target>Polje za vnos mape je prazno.</target> <source>The corresponding folder will be considered as empty.</source> <target>Ustrezna mapa bo smatrana kot prazna.</target> @@ -194,16 +197,16 @@ <target>Mapa mora biti izvzeta iz sinhronizacije z uporabo filtra.</target> <source>Calculating sync directions...</source> -<target>Preračunavam sinhronizacijske smeri...</target> +<target>Izračunavam smeri sinhronizacije...</target> <source>Out of memory.</source> <target>Ni dovolj pomnilnika.</target> <source>Item exists on left side only</source> -<target>Element obstaja samo na levi strani</target> +<target>Postavka obstaja samo na levi strani</target> <source>Item exists on right side only</source> -<target>Element obstaja samo na desni strani</target> +<target>Postavka obstaja samo na desni strani</target> <source>Left side is newer</source> <target>Leva stran je novejša</target> @@ -212,25 +215,25 @@ <target>Desna stran je novejša</target> <source>Items have different content</source> -<target>Elementi imajo različno vsebino</target> +<target>Postavke imajo različno vsebino</target> <source>Both sides are equal</source> <target>Obe strani sta enaki</target> <source>Conflict/item cannot be categorized</source> -<target>Spora/elementa ni mogoče kategorizirati</target> +<target>Konflikt/postavka ni mogoče kategorizirati</target> <source>Copy new item to left</source> -<target>Kopiraj nov element na levo</target> +<target>Kopiraj novo postavko na levo</target> <source>Copy new item to right</source> -<target>Kopiraj nov element na desno</target> +<target>Kopiraj novo postavko na desno</target> <source>Delete left item</source> -<target>Izbriši levi element</target> +<target>Izbriši postavko na levi strani</target> <source>Delete right item</source> -<target>Izbriši desni element</target> +<target>Izbriši postavko na desni strani</target> <source>Move file on left</source> <target>Premakni datoteko na levo stran</target> @@ -239,10 +242,10 @@ <target>Premakni datoteko na desno stran</target> <source>Update left item</source> -<target>Posodobi levi predmet</target> +<target>Posodobi postavko na levi strani</target> <source>Update right item</source> -<target>Posodobi desni predmet</target> +<target>Posodobi postavko na desni strani</target> <source>Do nothing</source> <target>Ne naredi ničesar</target> @@ -268,10 +271,10 @@ Dejansko: %y bajtov </target> <source>Cannot write permissions of %x.</source> -<target>Ne morem zapisati dovoljenj od %x.</target> +<target>Ne morem zapisati dovoljenj za %x.</target> <source>Operation not supported for different base folder types.</source> -<target>Operacija ni podprta za različne osnovne tipe map.</target> +<target>Operacija ni podprta za različne vrste osnovnih map.</target> <source>Cannot write file %x.</source> <target>Ne morem zapisati datoteke %x.</target> @@ -286,7 +289,7 @@ Dejansko: %y bajtov <target>Ne morem povezati na %x.</target> <source>Failed to get information about server %x.</source> -<target>Ne morem pridobiti informacije o serverju %x.</target> +<target>Ne morem pridobiti informacije o strežniku %x.</target> <source>Cannot open directory %x.</source> <target>Ne morem odpreti imenika %x.</target> @@ -328,7 +331,7 @@ Dejansko: %y bajtov <target>Ne najdem %x.</target> <source>Type of item %x is not supported:</source> -<target>Element tipa %x ni podprt:</target> +<target>Vrsta postavke %x ni podprta:</target> <source>Cannot delete symbolic link %x.</source> <target>Ne morem izbrisati simbolične povezave %x.</target> @@ -343,7 +346,7 @@ Dejansko: %y bajtov <target>Napačna koda %x.</target> <source>The server does not support authentication via %x.</source> -<target>Server ne podpira preverjanja pristnosti preko %x.</target> +<target>Strežnik ne podpira preverjanja pristnosti preko %x.</target> <source>Required:</source> <target>Zahtevano:</target> @@ -453,10 +456,10 @@ Dejansko: %y bajtov <target>Zaznavanje opuščenega zaklepa...</target> <source>Items processed:</source> -<target>Obdelanih elementov:</target> +<target>Obdelanih postavk:</target> <source>Items remaining:</source> -<target>Preostalih elementov:</target> +<target>Preostalih postavk:</target> <source>Total time:</source> <target>Celoten čas:</target> @@ -464,8 +467,8 @@ Dejansko: %y bajtov <source>Error parsing file %x, row %y, column %z.</source> <target>Napaka pri razčlenjevanju datoteke %x, vrstica %y, stolpec %z.</target> -<source>Cannot set directory lock for %x.</source> -<target>Ne morem nastaviti zaklepanja imenikov za %x.</target> +<source>Cannot set directory locks for the following folders:</source> +<target>Ne morem nastaviti zaklepanje imenika za naslednje mape:</target> <source> <pluralform>1 thread</pluralform> @@ -479,13 +482,13 @@ Dejansko: %y bajtov </target> <source>Scanning:</source> -<target>Pregledujem:</target> +<target>Skeniranje:</target> <source>/sec</source> <target>/sek</target> <source>%x items/sec</source> -<target>%x elementov/s</target> +<target>%x postavk/sek</target> <source>Show in Explorer</source> <target>Prikaži v Raziskovalcu</target> @@ -497,10 +500,10 @@ Dejansko: %y bajtov <target>Brskaj po imeniku</target> <source>Cannot access the Volume Shadow Copy Service.</source> -<target>Ne morem dostopati do Volume Shadov Copy servisa.</target> +<target>Ne morem dostopati do Volume Shadov Copy storitve.</target> <source>Please run the 64-bit version of FreeFileSync to create shadow copies on this system.</source> -<target>Prosim zaženite 64 bitno različico FreeFileSync, da ustvarite samodejne kopije v ozadju na tem sistemu.</target> +<target>Prosimo, zaženite 64-bitno različico FreeFileSync za ustvarjanje senčnih kopij v tem sistemu.</target> <source>Cannot determine volume name for %x.</source> <target>Ne morem določiti ime nosilca za %x.</target> @@ -512,7 +515,7 @@ Dejansko: %y bajtov <target>Zahteva za ustavitev: čakam da se trenutni proces zaključi...</target> <source>Unable to create time stamp for versioning:</source> -<target>Časovnega žiga za verzioniranje ni bilo mogoče ustvariti:</target> +<target>Časovnega žiga za oznčitev ni bilo mogoče ustvariti:</target> <source>Drag && drop</source> <target>Povleci && spusti</target> @@ -539,7 +542,7 @@ Dejansko: %y bajtov <target>&Prikaži pomoč</target> <source>&About</source> -<target>&O programu</target> +<target>&Vizitka</target> <source>&Help</source> <target>&Pomoč</target> @@ -557,7 +560,7 @@ Dejansko: %y bajtov <target>3. Pritisnite 'Začni'.</target> <source>To get started just import a .ffs_batch file.</source> -<target>Da začnete uvozite datoteko .ffs_batch.</target> +<target>Če želite začeti, uvozite datoteko .ffs_batch.</target> <source>Folders to watch:</source> <target>Mape za pregled:</target> @@ -572,10 +575,10 @@ Dejansko: %y bajtov <target>Brskaj</target> <source>Idle time (in seconds):</source> -<target>Nedejavni čas (v sekundah):</target> +<target>Čas mirovanja (v sekundah):</target> <source>Idle time between last detected change and execution of command</source> -<target>Čas nedejavnosti med zadnjo zaznano spremembo in izvršitvijo ukaza</target> +<target>Čas mirovanja med zadnjo zaznano spremembo in izvedbo ukaza</target> <source>Command line:</source> <target>Ukazna vrstica:</target> @@ -588,32 +591,32 @@ The command is triggered if: <target> Ukaz se sproži če: - se spremenijo datoteke ali podmape -- pridejo nove mape (npr. ob vstavitvi USB ključka) +- pojavijo nove mape (npr. ob vstavitvi USB ključka) </target> <source>Start</source> <target>Začni</target> <source>About</source> -<target>O programu(1)</target> +<target>Vizitka</target> <source>Build: %x</source> -<target>Verzija: %x</target> +<target>Grsdnja: %x</target> <source>All files</source> <target>Vse datoteke</target> <source>Automated Synchronization</source> -<target>Avtomatska sinhnorizacija</target> +<target>Samodejna sinhronizacija</target> <source>The %x protocol does not support directory monitoring:</source> -<target></target> +<target>Protokol %x ne podpira nadzora imenikov:</target> <source>Directory monitoring active</source> <target>Nadzor imenikov je aktiven</target> <source>Waiting until all directories are available...</source> -<target>Čakam, da so vsi imeniki dostopni...</target> +<target>Čakam, dokler ne bodo na voljo vsi imeniki...</target> <source>&Restore</source> <target>&Obnovi</target> @@ -637,7 +640,7 @@ Ukaz se sproži če: <target>Velikost datoteke</target> <source>Two way</source> -<target>Obojesmerno</target> +<target>Dvosmerno</target> <source>Mirror</source> <target>Zrcalno</target> @@ -691,19 +694,19 @@ Ukaz se sproži če: <target>Ciljna mapa %x že obstaja.</target> <source>Target folder input field must not be empty.</source> -<target>Vnosno polje za ciljno mapo ne sme biti prazno.</target> +<target>Vnosno polje ciljnë mape ne sme biti prazno.</target> <source>Source folder %x not found.</source> <target>Izvorne mape %x ni moč najti.</target> <source>Please enter a target folder for versioning.</source> -<target>Prosim vnesite ciljno mapo za verzioniranje.</target> +<target>Prosim vnesite ciljno mapo za označitev.</target> <source>The following items have unresolved conflicts and will not be synchronized:</source> -<target>Naslednji elementi imajo nerešene konflikte in ne bodo sinhronizirani:</target> +<target>Naslednje postavke imajo nerešene konflikte in ne bodo sinhronizirane:</target> <source>The following folders are significantly different. Please check that the correct folders are selected for synchronization.</source> -<target>Naslednje mape so bistveno različne. Prosimo preverite, da so izbrane pravilne mape za sinhroniziranje.</target> +<target>Naslednje mape so bistveno različne. Prosimo preverite, ali so izbrane pravilne mape za sinhroniziranje.</target> <source>Not enough free disk space available in:</source> <target>Na voljo ni dovolj prostega prostora na disku v:</target> @@ -718,16 +721,16 @@ Ukaz se sproži če: <target>Da bi se izognili sporom, nastavite izključno filtre tako, da je vsaka posodobljena datoteka upoštevana samo v eni osnovni mapi.</target> <source>Versioning folder:</source> -<target>Verzioniranje mape:</target> +<target>Označitev mape:</target> <source>Base folder:</source> <target>Osnovna mapa:</target> <source>The versioning folder is contained in a base folder.</source> -<target></target> +<target>Mapa različic je vsebovana v osnovni mapi.</target> <source>Synchronizing folder pair:</source> -<target>Sinhroniziram par map:</target> +<target>Sinhroniziram parne mape:</target> <source>Generating database...</source> <target>Ustvarjam podatkovno bazo...</target> @@ -739,19 +742,19 @@ Ukaz se sproži če: <target>naziv opravila</target> <source>Show summary</source> -<target></target> +<target>Pokaži povzetek</target> <source>Sleep</source> -<target></target> +<target>Spanje</target> <source>Shut down</source> -<target>Ugasni</target> +<target>Izključi računalnik</target> <source>Synchronization stopped</source> <target>Sinhnorizacija zaustavljena</target> <source>Stopped</source> -<target>Ustavljen</target> +<target>Ustavljeno</target> <source>Synchronization completed with errors</source> <target>Sinhronizacija dokončana z napakami</target> @@ -760,48 +763,48 @@ Ukaz se sproži če: <target>Sinhronizacija dokončana z opozorili</target> <source>Warning</source> -<target>Pozor</target> +<target>Opozorilo</target> <source>Nothing to synchronize</source> -<target>Ni ničesar za sinhroniziranje</target> +<target>Nič za sinhroniziranje</target> <source>Synchronization completed successfully</source> -<target>Sinhronizacija se je uspešno končala</target> +<target>Sinhronizacija je uspešno končana</target> <source>Executing command %x</source> -<target></target> +<target>Izvedba ukaza %x</target> <source>Cleaning up old log files...</source> -<target>Čistim stare datoteke beleženja...</target> +<target>Čiščenje starih datotek dnevnika...</target> <source>You can switch to FreeFileSync's main window to resolve this issue.</source> -<target>Preklopite na FreeFileSync glavno okno za odpravo težave.</target> +<target>Če želite odpraviti to težavo, lahko preklopite na glavno okno FreeFileSync.</target> <source>&Don't show this warning again</source> -<target>&Ne pokaži več tega opozorila</target> +<target>&Ne prikazuj več tega opozorila</target> <source>&Ignore</source> -<target>&Ignoriraj</target> +<target>&Prezri</target> <source>&Switch</source> <target>&Preklopi</target> <source>Switching to FreeFileSync's main window</source> -<target>Preklopi na FreeFileSync glavno okno</target> +<target>Preklop na glavno okno FreeFileSync</target> <source> <pluralform>Automatic retry in 1 second...</pluralform> <pluralform>Automatic retry in %x seconds...</pluralform> </source> <target> -<pluralform>Ponovni poskus čez %x sekundo...</pluralform> -<pluralform>Ponovni poskus čez %x sekundi...</pluralform> -<pluralform>Ponovni poskus čez %x sekunde...</pluralform> -<pluralform>Ponovni poskus čez %x sekund...</pluralform> +<pluralform>Samodejni poskus znova čez %x sekundo...</pluralform> +<pluralform>Samodejni poskus znova čez %x sekundi...</pluralform> +<pluralform>Samodejni poskus znova čez %x sekunde...</pluralform> +<pluralform>Samodejni poskus znova čez %x sekund...</pluralform> </target> <source>Ignore &all</source> -<target></target> +<target>Prezri &vse</target> <source>Retrying operation...</source> <target>Ponovni poizkus operacije...</target> @@ -809,6 +812,29 @@ Ukaz se sproži če: <source>Serious Error</source> <target>Resna napaka</target> +<source>Last session</source> +<target>Zadnja seja</target> + +<source>Today</source> +<target>Danes</target> + +<source> +<pluralform>1 day</pluralform> +<pluralform>%x days</pluralform> +</source> +<target> +<pluralform>%x dan</pluralform> +<pluralform>%x dneva</pluralform> +<pluralform>%x dnevi</pluralform> +<pluralform>%x dni</pluralform> +</target> + +<source>Name</source> +<target>Ime</target> + +<source>Last sync</source> +<target>Zadnja sinhronizacija</target> + <source>Folder</source> <target>Mapa</target> @@ -816,13 +842,13 @@ Ukaz se sproži če: <target>Simbolična povezava</target> <source>Full path</source> -<target>Polna pot</target> +<target>Celotna pot</target> <source>Relative path</source> <target>Relativna pot</target> <source>Item name</source> -<target>Ime objekta</target> +<target>Ime postavke</target> <source>Size</source> <target>Velikost</target> @@ -831,7 +857,7 @@ Ukaz se sproži če: <target>Datum</target> <source>Extension</source> -<target>Razširitev</target> +<target>Pripona</target> <source>Category</source> <target>Kategorija</target> @@ -840,10 +866,10 @@ Ukaz se sproži če: <target>Ukrep</target> <source>Local comparison settings</source> -<target>Lokalne primerjalne nastavitve</target> +<target>Lokalne nastavitve primerjave</target> <source>Local synchronization settings</source> -<target>Lokalne sinhnorizacijske nastavitve</target> +<target>Lokalne nastavitve sinhnorizacije</target> <source>Local filter</source> <target>Lokalni filter</target> @@ -870,7 +896,7 @@ Ukaz se sproži če: <target>Izbrana mapa %x ne more biti uprabljena s FreeFileSync.</target> <source>Please select a folder on a local file system, network or an MTP device.</source> -<target>Prosim izberite mapo na lokalnem sistemu datotek, mreži ali na MTP napravi.</target> +<target>Prosim izberite mapo na lokalnem datotečnem sistemu, mreži ali na MTP napravi.</target> <source>&New</source> <target>&Nova</target> @@ -879,13 +905,13 @@ Ukaz se sproži če: <target>&Shrani</target> <source>Save as &batch job...</source> -<target>Shrani kot serijsko op&ravilo...</target> +<target>Shrani kot paketno op&ravilo...</target> <source>Start &comparison</source> <target>Začni &primerjavo</target> <source>C&omparison settings</source> -<target>P&rimerjalne nastavitve</target> +<target>N&astavitve primerjave</target> <source>&Filter settings</source> <target>Nastavitve &filtra</target> @@ -912,13 +938,13 @@ Ukaz se sproži če: <target>&Izvozi seznam datotek...</target> <source>&Reset layout</source> -<target>&Ponastavi razporeditev</target> +<target>&Ponastavi postavitev</target> <source>&Tools</source> <target>&Orodja</target> <source>&Check for updates now</source> -<target>&Preveri posodobitve sedaj</target> +<target>&Preveri, ali so zdaj na voljo posodobitve</target> <source>Check &automatically once a week</source> <target>S&amodejno preveri enkrat tedensko</target> @@ -933,10 +959,10 @@ Ukaz se sproži če: <target>Sinhroniziraj</target> <source>Add folder pair</source> -<target>Dodaj par imenikov</target> +<target>Dodaj pare imenikov</target> <source>Remove folder pair</source> -<target>Odstrani par imenikov</target> +<target>Odstrani pare imenikov</target> <source>Access online storage</source> <target>Dostop do spletnega prostora za shranjevanje</target> @@ -951,7 +977,7 @@ Ukaz se sproži če: <target>Išči:</target> <source>Match case</source> -<target>Ujemanje s primerom</target> +<target>Ujemanje primera</target> <source>New</source> <target>Nova</target> @@ -966,10 +992,10 @@ Ukaz se sproži če: <target>Shrani kot...</target> <source>View type:</source> -<target>Tip pogleda:</target> +<target>Vrsta prikaza:</target> <source>Select view:</source> -<target>Izberi pogled:</target> +<target>Izberi prikaz:</target> <source>Statistics:</source> <target>Statistika:</target> @@ -996,10 +1022,10 @@ Ukaz se sproži če: <target>Uporabi lokalne nastavitve:</target> <source>Select a variant:</source> -<target>Izberi možnost:</target> +<target>Izberi varianto:</target> <source>Include &symbolic links:</source> -<target>Vključuje &simbolične povezave:</target> +<target>Vključi &simbolične povezave:</target> <source>&Follow</source> <target>&Sledi</target> @@ -1014,7 +1040,7 @@ Ukaz se sproži če: <target>&Prezri časovni zamik [hh:mm]</target> <source>List of file time offsets to ignore</source> -<target>Seznam časovnih zamikov datotek, ki bodo prezrte</target> +<target>Seznam časovnih zamikov datotek, ki jih je treba prezreti</target> <source>Example:</source> <target>Primer:</target> @@ -1058,8 +1084,8 @@ Ukaz se sproži če: - Detection not available for first sync </source> <target> -- Ni podprto z vsemi sistemi datotek -- Zahteva in ustvari datoteke v podatkovni bazi +- Ni podprto z vsemi datotečnimi sistemi +- Zahteva in ustvari datoteke v bazi podatkov - Zaznavanje ni na voljo pri prvem sinhroniziranju </target> @@ -1070,37 +1096,37 @@ Ukaz se sproži če: <target>&Koš</target> <source>&Permanent</source> -<target>&Dokončno</target> +<target>&Trajno</target> <source>&Versioning</source> -<target>&Verzioniranje</target> +<target>&Označitev</target> <source>Naming convention:</source> -<target>Konvencija poimenovanja:</target> +<target>Imenovanje konvencije:</target> <source>&Ignore errors</source> -<target></target> +<target>&Prezri napake</target> <source>Show pop-up on errors or warnings</source> -<target>Prikaži pojavne napaka ali opozorila</target> +<target>Pokaži pojavna okna napak ali opozoril</target> <source>Run a command after synchronization:</source> -<target></target> +<target>Zaženi ukaz po sinhronizaciji:</target> <source>OK</source> <target>V redu</target> <source>Arrange folder pair</source> -<target>Uredi par map</target> +<target>Uredi pare map</target> <source>Enter your login details:</source> -<target>Vnestite vaše podatke za prijavo:</target> +<target>Vnestite podatke za prijavo:</target> <source>Connection type:</source> -<target>Tip povezave:</target> +<target>Vrsta povezave:</target> <source>Server name or IP address:</source> -<target>Ime serverja ali IP naslova:</target> +<target>Ime strežnika ali naslov IP:</target> <source>Port:</source> <target>Vrata:</target> @@ -1121,37 +1147,37 @@ Ukaz se sproži če: <target>&Geslo</target> <source>&Key file</source> -<target>&Ključna datoteka</target> +<target>&Datoteka ključa</target> <source>&SSH agent</source> -<target></target> +<target>&SSH agent</target> <source>User name:</source> <target>Uporabniško ime:</target> <source>Private key file:</source> -<target>Zasebni ključ datoteke:</target> +<target>Zasebna datoteka ključa:</target> <source>&Show password</source> <target>&Prikaži geslo</target> <source>Directory on server:</source> -<target>Imenik na serverju:</target> +<target>Imenik na strežniku:</target> <source>Performance improvements:</source> -<target>Izboljšanje izvedbe:</target> +<target>Izboljšave zmogljivosti:</target> <source>How to get best performance?</source> -<target>Kako dobiti najboljšo izvedbo?</target> +<target>Kako doseči najboljšo učinkovitost?</target> <source>Connections for directory reading:</source> -<target></target> +<target>Povezave za branje imenika:</target> <source>SFTP channels per connection:</source> <target>SFTP kanali za povezavo:</target> <source>Detect server limit</source> -<target>Zaznaj omejitve serverja</target> +<target>Zaznaj omejitve strežnika</target> <source>Select a directory on the server:</source> <target>Izberite imenik na strežniku:</target> @@ -1163,13 +1189,13 @@ Ukaz se sproži če: <target>Zaženem sinhronizacijo zdaj?</target> <source>Variant:</source> -<target>Možnost:</target> +<target>Varianta:</target> <source>&Don't show this dialog again</source> -<target>&Ne pokaži več tega sporočila</target> +<target>&Tega pogovornega okna ne prikazuj znova</target> <source>Items found:</source> -<target>Najdenih elementov:</target> +<target>Najdenih postavk:</target> <source>Time remaining:</source> <target>Preostali čas:</target> @@ -1181,7 +1207,7 @@ Ukaz se sproži če: <target>Bitov</target> <source>Items</source> -<target>Objektov</target> +<target>Postavk</target> <source>Synchronizing...</source> <target>Sinhroniziram...</target> @@ -1193,7 +1219,7 @@ Ukaz se sproži če: <target>Prepisanih bajtov:</target> <source>When finished:</source> -<target></target> +<target>Po zaključku:</target> <source>Close</source> <target>Zapri</target> @@ -1205,16 +1231,16 @@ Ukaz se sproži če: <target>Ustavi</target> <source>Create a batch file for unattended synchronization. To start, double-click this file or schedule in a task planner: %x</source> -<target>Ustvari skriptno datoteko za nenadzorovano sinhnorizacijo. Za zagon dvojno kliknite to datoteko ali pa jo umestite v razporejevalnik opravil: %x</target> +<target>Ustvari paketno datoteko za nenadzorovano sinhronizacijo. Za začetek dvokliknite to datoteko ali določite v načrtovalniku nalog: %x</target> <source>Run minimized</source> -<target>Začeni minimizirano</target> +<target>Zaženi minimirano</target> <source>&Show error dialog</source> -<target></target> +<target>&Prikaži pogovorno okno napak</target> <source>&Cancel</source> -<target></target> +<target>&Prekliči</target> <source>Stop synchronization at first error</source> <target>Ustavi sinhronizacijo ob prvi napaki</target> @@ -1226,10 +1252,10 @@ Ukaz se sproži če: <target>Omejitev:</target> <source>Limit maximum number of log files</source> -<target>Omeji maksimalno število datotek beleženja</target> +<target>Omeji največje število dnevnikov</target> <source>How can I schedule a batch job?</source> -<target>Kako nastavim urnik za serijsko opravilo?</target> +<target>Kako lahko načrtujem opravilo v paketu?</target> <source>&Keep relative paths</source> <target>&Ohrani relativne poti</target> @@ -1246,23 +1272,23 @@ This guarantees a consistent state even in case of a serious error. </source> <target> Kopiraj v začasno datoteko (*.ffs_tmp) preden prepišeš cilj. -To zagotavlja konsistenčnost podatkov v primeru napake. +To zagotavlja dosledno stanje tudi v primeru resne napake. </target> <source>recommended</source> <target>priporočeno</target> <source>Copy shared or locked files using the Volume Shadow Copy Service.</source> -<target>Kopiraj zaklenjene in datoteke v skupni rabi s pomočjo Shadow Copy Service.</target> +<target>Kopiraj skupne ali zaklenjene datoteke s storitvijo Volume Shadow Copy Service.</target> <source>requires administrator rights</source> -<target></target> +<target>zahteva skrbniške pravice</target> <source>Transfer file and folder permissions.</source> -<target>Prenesi pravice datotek in map.</target> +<target>Prenesi dovoljenja za datoteke in mape.</target> <source>Automatic retry on error:</source> -<target>Ob napaki avtomatsko poskusi znova:</target> +<target>Ob napaki samodejo poskusi znova:</target> <source>Retry count:</source> <target>Število poiskusov:</target> @@ -1271,16 +1297,16 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Zakasnitev (v sekundah):</target> <source>Customize context menu:</source> -<target>Prilagodi vsebinski meni:</target> +<target>Prilagodi kontekstni meni:</target> <source>Description</source> <target>Opis</target> <source>Show hidden dialogs again</source> -<target>Zopet prikaži skrite dilaoge</target> +<target>Ponovno prikaži skrita pogovorna okna</target> <source>Show all permanently hidden dialogs and warning messages again</source> -<target>Znova prikaži vse dokončno skrite dialoge in opozorila</target> +<target>Prikaži vsa trajno skrita pogovorna okna in opozorilna sporočila</target> <source>&Default</source> <target>&Privzeto</target> @@ -1292,13 +1318,13 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Če vam je FreeFileSync všeč:</target> <source>Support with a donation</source> -<target></target> +<target>Podpora z donacijo</target> <source>Donation details</source> <target>Podrobnosti o donaciji</target> <source>The auto updater was disabled by the administrator.</source> -<target></target> +<target>Skrbnik je onemogočil samodejno posodabljanje.</target> <source>Feedback and suggestions are welcome</source> <target>Povratne informacije in predlogi so dobrodošli</target> @@ -1316,13 +1342,13 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Zahvala prevajalcem za lokalizacijo:</target> <source>Activate the FreeFileSync Donation Edition by one of the following methods:</source> -<target>Aktivirajte FreeFileSync Donacijsko Verzijo z eno izmed naslednjih metod:</target> +<target>Aktivirajte FreeFileSync Donation Edition na en od naslednjih načinov:</target> <source>1. Activate via internet now:</source> -<target>1. Aktivirajte z uporabo interneta sedaj:</target> +<target>1. Aktivirajte preko interneta zdaj:</target> <source>Activate online</source> -<target>Aktivirajte na spletu</target> +<target>Aktivirajte po spletu</target> <source>2. Retrieve an offline activation key from the following URL:</source> <target>2. Pridobite aktivacijski ključ brez povezave na naslednjem URL:</target> @@ -1336,14 +1362,17 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <source>Activate offline</source> <target>Aktivirajte brez povezave</target> +<source>Highlight configurations that have not been run for more than the following number of days:</source> +<target>Označite konfiguracije, ki se ne izvajajo več kot naslednje število dni:</target> + <source>Save as a Batch Job</source> -<target></target> +<target>Shrani kot paketno opravilo</target> <source>Delete Items</source> -<target>Izbriši elemente</target> +<target>Izbriši postavke</target> <source>Copy items</source> -<target>Kopiraj elemente</target> +<target>Kopiraj postavke</target> <source>Options</source> <target>Možnosti</target> @@ -1352,7 +1381,10 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Izberi časovno obdobje</target> <source>FreeFileSync Donation Edition</source> -<target>FreeFileSync Donacijska Verzija</target> +<target>FreeFileSync Donation Edition</target> + +<source>Highlight Configurations</source> +<target>Označite konfiguracije</target> <source>&Options</source> <target>&Možnosti</target> @@ -1373,7 +1405,7 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Konfiguracija</target> <source>Overview</source> -<target>Pregled</target> +<target>Predogled</target> <source>Show "%x"</source> <target>Prikaži "%x"</target> @@ -1382,7 +1414,7 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>&Pokaži podrobnosti</target> <source>FreeFileSync %x is available!</source> -<target></target> +<target>FreeFileSync %x je na voljo</target> <source>Installation files are corrupted. Please reinstall FreeFileSync.</source> <target>Namestitvena datoteka je poškodovana. Prosim ponovno naložite FreeFileSync.</target> @@ -1398,10 +1430,10 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <pluralform>Do you really want to execute the command %y for %x items?</pluralform> </source> <target> -<pluralform>Ali res želite izvesti ukaz %y za %x element?</pluralform> -<pluralform>Ali res želite izvesti ukaz %y za %x elementa?</pluralform> -<pluralform>Ali res želite izvesti ukaz %y za %x elemente?</pluralform> -<pluralform>Ali res želite izvesti ukaz %y za %x elementov?</pluralform> +<pluralform>Ali res želite izvesti ukaz %y za %x postavko?</pluralform> +<pluralform>Ali res želite izvesti ukaz %y za %x postavki?</pluralform> +<pluralform>Ali res želite izvesti ukaz %y za %x postavke?</pluralform> +<pluralform>Ali res želite izvesti ukaz %y za %x postavk?</pluralform> </target> <source>&Execute</source> @@ -1453,10 +1485,10 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Izključi preko filtra:</target> <source>Include temporarily</source> -<target>Trenutno vključi</target> +<target>Vključi začasno</target> <source>Exclude temporarily</source> -<target>Začasno izključi</target> +<target>Izključi začasno</target> <source>&Copy to...</source> <target>&Kopiraj v...</target> @@ -1483,10 +1515,7 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Velika</target> <source>Select time span...</source> -<target>Izberite časovni okvir...</target> - -<source>Last session</source> -<target>Zadnja seja</target> +<target>Izberite časovni razpon...</target> <source>Folder Comparison and Synchronization</source> <target>Primerjava in sinhronizacija mape</target> @@ -1506,8 +1535,11 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <source>Do&n't save</source> <target>Ne shra&ni</target> -<source>Remove entry from list</source> -<target>Odstrani vnos iz seznama</target> +<source>Hide configuration</source> +<target>Skrij konfiguracijo</target> + +<source>Highlight...</source> +<target>Označi...</target> <source>Clear filter</source> <target>Počisti filter</target> @@ -1525,13 +1557,13 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Prikaži novejše datoteke, ki so na desni strani</target> <source>Show files that are equal</source> -<target>Prikaši datoteke, ki so identične</target> +<target>Prikaži datoteke, ki so identične</target> <source>Show files that are different</source> <target>Prikaži datoteke, ki so različne</target> <source>Show conflicts</source> -<target>Prikaži spore</target> +<target>Prikaži konflikte</target> <source>Show files that will be created on the left side</source> <target>Prikaži datoteke, ki bodo ustvarjene na levi strani</target> @@ -1570,10 +1602,10 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Ne najdem %x</target> <source>Move up</source> -<target>Premakni gor</target> +<target>Premakni navzgor</target> <source>Move down</source> -<target>Premakni dol</target> +<target>Premakni navzdol</target> <source>Comma-separated values</source> <target>Vrednosti ločene z vejico</target> @@ -1585,13 +1617,13 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Iščem posodobitve programa...</target> <source>Paused</source> -<target>Začasna ustavitev</target> +<target>Začasno ustavljeno</target> <source>Initializing...</source> <target>Inicializiram...</target> <source>Scanning...</source> -<target>Pregledujem...</target> +<target>Skeniram...</target> <source>Comparing content...</source> <target>Primerjam vsebino...</target> @@ -1618,40 +1650,40 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Najlepša hvala, %x, za vašo donacijo in podporo!</target> <source>Recommended range:</source> -<target></target> +<target>Priporočeni obseg:</target> <source>Password:</source> <target>Geslo:</target> <source>Key password:</source> -<target>Ključno geslo:</target> +<target>Aktivacijski ključ:</target> <source>Please enter a file path.</source> -<target></target> +<target>Vnesite pot do datoteke.</target> <source> <pluralform>Copy the following item to another folder?</pluralform> <pluralform>Copy the following %x items to another folder?</pluralform> </source> <target> -<pluralform>Kopiraj sledeč %x element v drugo mapo?</pluralform> -<pluralform>Kopiraj sledeča %x elementa v drugo mapo?</pluralform> -<pluralform>Kopiraj sledeče %x elemente v drugo mapo?</pluralform> -<pluralform>Kopiraj sledečih %x elementov v drugo mapo?</pluralform> +<pluralform>Kopiraj sledečo %x postavko v drugo mapo?</pluralform> +<pluralform>Kopiraj sledeči %x postavki v drugo mapo?</pluralform> +<pluralform>Kopiraj sledeče %x postavke v drugo mapo?</pluralform> +<pluralform>Kopiraj sledečih %x postavk v drugo mapo?</pluralform> </target> <source>Please enter a target folder.</source> -<target>Prosim, navedite ciljno mapo.</target> +<target>Vnesite ciljno mapo.</target> <source> <pluralform>Do you really want to move the following item to the recycle bin?</pluralform> <pluralform>Do you really want to move the following %x items to the recycle bin?</pluralform> </source> <target> -<pluralform>Ali res želite premakniti sledeč %x element v koš?</pluralform> -<pluralform>Ali res želite premakniti sledeča %x elementa v koš?</pluralform> -<pluralform>Ali res želite premakniti sledeče %x elemente v koš?</pluralform> -<pluralform>Ali res želite premakniti sledečih %x elementov v koš?</pluralform> +<pluralform>Ali res želite premakniti sledečo %x postavko v koš?</pluralform> +<pluralform>Ali res želite premakniti sledeči %x postavki v koš?</pluralform> +<pluralform>Ali res želite premakniti sledeče %x postavke v koš?</pluralform> +<pluralform>Ali res želite premakniti sledečih %x postavk v koš?</pluralform> </target> <source>Move</source> @@ -1662,20 +1694,20 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <pluralform>Do you really want to delete the following %x items?</pluralform> </source> <target> -<pluralform>Ali resnično želite izbrisati sledeči %x element?</pluralform> -<pluralform>Ali resnično želite izbrisati naslednja %x elementa?</pluralform> -<pluralform>Ali resnično želite izbrisati naslednje %x elemente?</pluralform> -<pluralform>Ali resnično želite izbrisati naslednjih %x elementov?</pluralform> +<pluralform>Ali res želite izbrisati sledečo %x postavko?</pluralform> +<pluralform>Ali res želite izbrisati sledeči %x postavki?</pluralform> +<pluralform>Ali res želite izbrisati sledeče %x postavke?</pluralform> +<pluralform>Ali res želite izbrisati sledečih %x postavk?</pluralform> </target> <source>Copy DACL, SACL, Owner, Group</source> <target>Kopiraj DACL, SACL, lastnik, skupina</target> <source>Integrate external applications into context menu. The following macros are available:</source> -<target>Integriraj zunanje aplikacije v kontekstni menu. Na voljo so naslednji makri:</target> +<target>Integriraj zunanje aplikacije v kontekstni meni. Na voljo so naslednji makroji:</target> <source>Full file or folder path</source> -<target>Celotna datoteka ali pot do mape</target> +<target>Celotna pot do datoteke ali mape</target> <source>Parent folder path</source> <target>Pot do nadrejene mape</target> @@ -1687,7 +1719,7 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Parametri za nasprotno stran</target> <source>Show hidden dialogs and warning messages again?</source> -<target>Ponovno prikaži skrite dialoge in obvestila?</target> +<target>Ponovno prikažem skrita pogovorna okna in opozorilna sporočila?</target> <source>&Show</source> <target>&Prikaži</target> @@ -1696,19 +1728,19 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Prenašam posodobitve...</target> <source>Identify equal files by comparing modification time and size.</source> -<target>Določi enake datoteke s primerjavo datuma spremembe in velikosti.</target> +<target>Ugotovi enake datoteke s primerjanjem časa spremembe in velikosti.</target> <source>Identify equal files by comparing the file content.</source> -<target>Določi enake datoteke s primerjavo vsebine.</target> +<target>Ugotovi enake datoteke s primerjanjem vsebine.</target> <source>Identify equal files by comparing their file size.</source> -<target>Identificiraj enake datoteke s primerjavo velikoti teh datotek.</target> +<target>Ugotovi enake datoteke s primerjanjem njihove velikosti.</target> <source>Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database.</source> -<target>Identificiraj in razširjaj spremembe na obeh straneh. Izbrisi, premiki in spori so samodejno zaznani z uporabo podatkovne baze.</target> +<target>Ugotovi in izvrši spremembe na obeh straneh. Izbrisi, premiki in spori so samodejno zaznani z uporabo podatkovne baze.</target> <source>Create a mirror backup of the left folder by adapting the right folder to match.</source> -<target>Ustvari zrcalno kopijo levega imenika s prilagoditvijo desnega imenika, tako da se ujemata.</target> +<target>Ustvari zrcalno varnostno kopijo leve mape tako, da prilagodite desno mapo, ki se bo ujemala.</target> <source>Copy new and updated files to the right folder.</source> <target>Kopiraj nove in posodobljene datoteke v desni imenik.</target> @@ -1725,9 +1757,6 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <source>Synchronization</source> <target>Sinhnorizacija</target> -<source>Today</source> -<target>Danes</target> - <source>This week</source> <target>Ta teden</target> @@ -1750,19 +1779,19 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>MB</target> <source>Retain deleted and overwritten files in the recycle bin</source> -<target></target> +<target>Zadrži izbrisane in prepisane datoteke v košu</target> <source>Delete and overwrite files permanently</source> -<target></target> +<target>Trajni izbris in prepis datotek</target> <source>Move files to a user-defined folder</source> -<target>Premakni datoteke v izbran imenik</target> +<target>Premakni datoteke v uporabniško določeno mapo</target> <source>Replace</source> <target>Zamenjaj</target> <source>Move files and replace if existing</source> -<target>Premakne datoteke in jih zamenja, že obstajajo</target> +<target>Premakni datoteke in jih zamenjaj, če že obstajajo</target> <source>Time stamp</source> <target>Časovna oznaka</target> @@ -1774,10 +1803,10 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Ob zaključku:</target> <source>On errors:</source> -<target></target> +<target>Ob napakah:</target> <source>On success:</source> -<target></target> +<target>Ob uspehu:</target> <source>Main config</source> <target>Glavna konfiguracija</target> @@ -1786,7 +1815,7 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>prazno</target> <source>Leave as unresolved conflict</source> -<target>Pusti kot nerešeni spor</target> +<target>Pusti kot nerešeni konflikt</target> <source>File</source> <target>Datoteka</target> @@ -1797,9 +1826,6 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <source>Files</source> <target>Datoteke</target> -<source>Name</source> -<target>Ime</target> - <source>Percentage</source> <target>Odstotek</target> @@ -1810,22 +1836,22 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Samodejne posodobitve:</target> <source>Requires FreeFileSync Donation Edition</source> -<target>Zahteva FreeFileSync Donacijsko Verzijo</target> +<target>Zahteva FreeFileSync Donation Edition</target> <source>Check for Program Updates</source> -<target>Prevri obstoj nadgradnje programa</target> +<target>Preveri obstoj posodobitve programa</target> <source>Auto-update now or download manually from the FreeFileSync home page?</source> -<target>Avtomatska posodobitev ali ročni prenos iz FreeFileSync domače strani?</target> +<target>Samodejna posodobitev zdaj, ali prenos z domače strani FreeFileSync?</target> <source>&Auto-update</source> -<target>&Avtomatska posodobitev</target> +<target>&Samodejna posodobitev</target> <source>&Home page</source> <target>&Domača stran</target> <source>Download now?</source> -<target>Prenesem sedaj?</target> +<target>Prenesem zdaj?</target> <source>&Download</source> <target>&Prenesi</target> @@ -1834,49 +1860,49 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>FreeFileSync je posodobljen.</target> <source>Cannot find current FreeFileSync version number online. A newer version is likely available. Check manually now?</source> -<target>Ne morem najti trenutne številke verzije FreeFileSync na spletu. Novejša verzija je najbrž na voljo. Želite preveriti ročno?</target> +<target>Na spletu ni mogoče najti trenutne številke različice FreeFileSync. Verjetno je na voljo nova različica. Želite preveriti zdaj?</target> <source>&Check</source> <target>&Preveri</target> <source>Consistency check failed for %x.</source> -<target>Preverjanje usklajenosti ni uspelo za %x.</target> +<target>Preverjanje doslednosti ni uspelo za %x.</target> <source>Installation was registered on a different operating system.</source> -<target>Namestitev je bila registrirana na drugem operacisjkem sistemu.</target> +<target>Namestitev je bila registrirana v drugem operacijskem sistemu.</target> <source>Failed to activate FreeFileSync Donation Edition.</source> -<target>Ne morem aktivirati FreeFileSync Donacijske verzije.</target> +<target>Programa FreeFileSync Donation Edition ni bilo mogoče aktivirati.</target> <source>Incorrect activation key.</source> -<target>Nepravilen aktivacisjki ključ.</target> +<target>Nepravilni ključ za aktiviranje.</target> <source>Unable to register to receive system messages.</source> -<target>Ne morem se registrirati za prejem sistemskih sporočil.</target> +<target>Sistemskih sporočil ni mogoče registrirati.</target> <source>Cannot find system function %x.</source> -<target>Ne morem najti sistemske funkcije %x.</target> +<target>Sistemske funkcije ni mogoče najti %x.</target> <source>Unable to register device notifications for %x.</source> -<target>Omogoči registracijo obvestil naprave za %x.</target> +<target>Ne morem registrirati obvestil naprave za %x.</target> <source>Cannot monitor directory %x.</source> -<target>Ne morem nadzorovati imenika %x.</target> +<target>Ne morem nadzirati imenika %x.</target> <source>The file is locked by another process:</source> -<target>Datoteka je zaklenjena s strani drugega procesa:</target> +<target>Datoteko je zaklenil drug proces:</target> <source>Cannot read security context of %x.</source> -<target>Ne morem prebrati varnostnega konteksta od %x.</target> +<target>Ne morem prebrati varnostni kontekst od %x.</target> <source>Cannot write security context of %x.</source> -<target>Ne morem zapisati varnostnega konteksta od %x.</target> +<target>Ne morem zapisati varnostni kontekst od %x.</target> <source>Cannot read permissions of %x.</source> <target>Ne morem prebrati dovoljenja od %x.</target> <source>Cannot copy permissions from %x to %y.</source> -<target>Ne morem kopirati uporabniških pravic iz %x v %y.</target> +<target>Ne morem kopirati dovoljenj iz %x v %y.</target> <source>%x is not a regular directory name.</source> <target>%x ni pravilno ime imenika.</target> @@ -1915,46 +1941,35 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <pluralform>%x ur</pluralform> </target> -<source> -<pluralform>1 day</pluralform> -<pluralform>%x days</pluralform> -</source> -<target> -<pluralform>%x dan</pluralform> -<pluralform>%x dneva</pluralform> -<pluralform>%x dnevi</pluralform> -<pluralform>%x dni</pluralform> -</target> - <source>Cannot set privilege %x.</source> <target>Ne morem nastaviti privilegija %x.</target> <source>Unable to suspend system sleep mode.</source> -<target>Ne morem preprečiti mirovanja sistema.</target> +<target>Ne morem preprečiti način mirovanja sistema.</target> <source>Cannot change process I/O priorities.</source> -<target>Ne morem spremeniti V/I prioritet procesa.</target> +<target>Ne morem spremeniti prioritet procesa I/O.</target> <source>Unable to shut down the system.</source> -<target></target> +<target>Sistema ni mogoče zapustiti.</target> <source>Checking recycle bin failed for folder %x.</source> <target>Preverjanje koša za mapo %x ni uspelo.</target> <source>The following XML elements could not be read:</source> -<target>Neaslednji XML elementi niso berljivi:</target> +<target>Neaslednje XML postavke niso berljive:</target> <source>Configuration file %x is incomplete. The missing elements will be set to their default values.</source> -<target>Nastavitvena datoteka %x je nepopolna. Manjkajoči element bo nastavljen na privzete vrednosti.</target> +<target>Nastavitvena datoteka %x je nepopolna. Manjkajoči elementi bodo nastavljeni na privzete vrednosti.</target> <source>Prepare installation</source> <target>Pripravljam namestitev</target> <source>Choose which components you want to install.</source> -<target>Izberite komponente za namestitev.</target> +<target>Izberite komponente, ki jih želite namestiti.</target> <source>Select installation type:</source> -<target>Izberi tip namestitve:</target> +<target>Izberite vrsto namestitve:</target> <source>Local</source> <target>Lokalna</target> @@ -1966,7 +1981,7 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Shrani nastavitve v "%APPDATA%\FreeFileSync"</target> <source>Register FreeFileSync file extensions</source> -<target>Registracija FreeFileSync končnic datotek</target> +<target>Registriraj FreeFileSync datoteče pripone</target> <source>Create Explorer context menu entries</source> <target>Ustvari vnose v Explorer-jev kontekstni meni</target> @@ -1990,13 +2005,16 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Namizje</target> <source>Start menu</source> -<target>Začetni meni</target> +<target>Meni Start</target> + +<source>Send To</source> +<target>Pošlji</target> <source>Registering FreeFileSync file extensions</source> -<target>Registracija FreeFileSync končnic datotek</target> +<target>Registracija FreeFileSync datotečnih pripon</target> <source>Unregistering FreeFileSync file extensions</source> -<target>Odstranjevanje registracije FreeFileSync končnic datotek</target> +<target>Odstranjevanje registracije FreeFileSync datotečnih pripon</target> <source>FreeFileSync Configuration</source> <target>Nastavitve FreeFileSync</target> @@ -2005,7 +2023,7 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>FreeFileSync paketna datoteka</target> <source>FreeFileSync Synchronization Database</source> -<target>FreeFileSync sinhronizacijska baza</target> +<target>FreeFileSync sinhronizacijska baza podatkov</target> <source>RealTimeSync Configuration</source> <target>RealTimeSync nastavitve</target> @@ -2014,11 +2032,11 @@ To zagotavlja konsistenčnost podatkov v primeru napake. <target>Uredi z FreeFileSync</target> <source>The FreeFileSync portable version cannot install into a subfolder of %x.</source> -<target></target> +<target>Prenosne različice FreeFileSync ni mogoče namestiti v podmapo %x.</target> <source>Please choose the local installation type or select a different folder for installation.</source> -<target>Prosimo izberite tip lokalne namestitve ali pa izberite drugo mapo za namestitev.</target> +<target>Prosimo izberite vrsto lokalne namestitve ali pa izberite drugo mapo za namestitev.</target> -<source>The silent installation mode is only available in the FreeFileSync Donation Edition.</source> -<target>Tihi namestitveni način je na voljo samo v FreeFileSync Donacijski Verziji.</target> +<source>The %x installation option is only available in the FreeFileSync Donation Edition.</source> +<target>Možnost namestitve %x je na voljo samo v FreeFileSync Donation Edition.</target> diff --git a/FreeFileSync/Build/Languages/turkish.lng b/FreeFileSync/Build/Languages/turkish.lng index 1e4843c2..5afdd52e 100755 --- a/FreeFileSync/Build/Languages/turkish.lng +++ b/FreeFileSync/Build/Languages/turkish.lng @@ -53,7 +53,7 @@ <target>%x klasörü için Geri Dönüşüm Kutusu kullanılabilir mi diye bakılıyor...</target> <source>The recycle bin is not supported by the following folders. Deleted or overwritten files will not be able to be restored:</source> -<target></target> +<target>Geri Dönüşüm Kutusu şu klasörler tarafından desteklenmiyor. Silinmiş ya da üzerine yazılmış klasörler geri yüklenemez:</target> <source>An exception occurred</source> <target>Olağan dışı bir durumla karşılaşıldı</target> @@ -71,7 +71,7 @@ <target>Hata</target> <source>File %x does not contain a valid configuration.</source> -<target>%x dosyası geçerli ayar bilgilerini içermiyor.</target> +<target>%x dosyası geçerli yapılandırma bilgilerini içermiyor.</target> <source>Unequal number of left and right directories specified.</source> <target>Sağdan ve soldan seçilen klasör sayısı aynı değil.</target> @@ -80,7 +80,7 @@ <target>Klasörler komut satırından seçildiği zaman, ayar dosyasında klasör çifti düzeyinde ayarlar bulunmamalıdır.</target> <source>Directories cannot be set for more than one configuration file.</source> -<target>Klasörler birden fazla ayar dosyasında kullanılamaz.</target> +<target>Klasörler birden fazla yapılandırma dosyasında kullanılamaz.</target> <source>Command line</source> <target>Komut Satırı</target> @@ -98,13 +98,13 @@ <target>genel ayar dosyası:</target> <source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> -<target>FreeFileSync .ffs_gui ya da .ffs_batch ayar dosyalarının sayısı.</target> +<target>FreeFileSync .ffs_gui ya da .ffs_batch yapılandırma dosyalarının sayısı.</target> <source>Any number of alternative directory pairs for at most one config file.</source> <target>En fazla bir ayar dosyası için herhangi bir sayıda alternatif klasör çifti.</target> <source>Open the selected configuration for editing only without executing it.</source> -<target></target> +<target>Seçilmiş yapılandırmayı yürütmeden yalnız düzenlemek için açar.</target> <source>Path to an alternate GlobalSettings.xml file.</source> <target>Alternatif GlobalSettings.xml dosyasının yolu.</target> @@ -316,7 +316,7 @@ Gerçekleşen: %y bayt <target>%x sembolik bağlantısı çözümlenemedi.</target> <source>Unable to move %x to the recycle bin.</source> -<target>%x çöp kutusuna atılamadı.</target> +<target>%x geri dönüşüm kutusuna atılamadı.</target> <source>Cannot open file %x.</source> <target>%x dosyası açılamadı.</target> @@ -597,7 +597,7 @@ Komut şu durumlarda yürütülür: <target>Otomatik Eşitleme</target> <source>The %x protocol does not support directory monitoring:</source> -<target></target> +<target>%x iletişim kuralı klasör izlemesini desteklemiyor:</target> <source>Directory monitoring active</source> <target>Klasör izlemesi yapılıyor</target> @@ -714,7 +714,7 @@ Komut şu durumlarda yürütülür: <target>Temel klasör:</target> <source>The versioning folder is contained in a base folder.</source> -<target></target> +<target>Sürümlendirme klasörü bir temel klasör içinde bulunuyor.</target> <source>Synchronizing folder pair:</source> <target>Eşitlenen klasör çifti:</target> @@ -729,10 +729,10 @@ Komut şu durumlarda yürütülür: <target>iş adı</target> <source>Show summary</source> -<target></target> +<target>Özet Görüntülensin</target> <source>Sleep</source> -<target></target> +<target>Bilgisayar Uykuya Dalsın</target> <source>Shut down</source> <target>Bilgisayar Kapatılsın</target> @@ -759,7 +759,7 @@ Komut şu durumlarda yürütülür: <target>Eşitleme tamamlandı</target> <source>Executing command %x</source> -<target></target> +<target>%x komutu yürütülüyor</target> <source>Cleaning up old log files...</source> <target>Eski günlük dosyaları temizleniyor...</target> @@ -789,7 +789,7 @@ Komut şu durumlarda yürütülür: </target> <source>Ignore &all</source> -<target></target> +<target>Tümünü Yok S&ay</target> <source>Retrying operation...</source> <target>İşlem yeniden deneniyor...</target> @@ -1067,13 +1067,13 @@ Komut şu durumlarda yürütülür: <target>Adlandırma Kuralı:</target> <source>&Ignore errors</source> -<target></target> +<target>&Hatalar yok sayılsın</target> <source>Show pop-up on errors or warnings</source> <target>Hata ya da uyarılar açılır pencerede görüntülenir</target> <source>Run a command after synchronization:</source> -<target></target> +<target>Eşitleme sonrası yürütülecek komut:</target> <source>OK</source> <target>Tamam</target> @@ -1112,7 +1112,7 @@ Komut şu durumlarda yürütülür: <target>Anahtar &Dosyası</target> <source>&SSH agent</source> -<target></target> +<target>&SSH İstemcisi</target> <source>User name:</source> <target>Kullanıcı Adı:</target> @@ -1133,7 +1133,7 @@ Komut şu durumlarda yürütülür: <target>En iyi başarım nasıl elde edilir?</target> <source>Connections for directory reading:</source> -<target></target> +<target>Klasör okuma bağlantıları:</target> <source>SFTP channels per connection:</source> <target>Bir Bağlantı için SFTP Kanalı Sayısı:</target> @@ -1181,7 +1181,7 @@ Komut şu durumlarda yürütülür: <target>Kopyalanan bayt:</target> <source>When finished:</source> -<target></target> +<target>Tamamlandığında:</target> <source>Close</source> <target>Kapat</target> @@ -1196,19 +1196,19 @@ Komut şu durumlarda yürütülür: <target>Eşitleme işleminin hiç bir soru sorulmadan yapılması için bir toplu iş dosyası oluşturun. İşlemi başlatmak için bu dosyaya çift tıklayın ya da bir görev zamanlayıcıya şu şekilde ekleyin: %x</target> <source>Run minimized</source> -<target>Küçülterek Çalıştır</target> +<target>Küçültülmüş Çalıştırılsın</target> <source>&Show error dialog</source> -<target></target> +<target>Hata &penceresi görüntülensin</target> <source>&Cancel</source> -<target></target> +<target>İ&ptal edilsin</target> <source>Stop synchronization at first error</source> <target>Oluşacak ilk hatada eşitleme durdurulsun</target> <source>Save log:</source> -<target>İşlem Günlüğünü Kaydet:</target> +<target>İşlem Günlüğü Kaydedilsin:</target> <source>Limit:</source> <target>Sınır:</target> @@ -1244,7 +1244,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Paylaşılan ya da kilitlenmiş dosyalar Birim Gölge Hizmetini kullanılarak kopyalanır.</target> <source>requires administrator rights</source> -<target></target> +<target>yönetici izinleri gerekir</target> <source>Transfer file and folder permissions.</source> <target>Dosya ve klasör izinleri de aktarılır.</target> @@ -1280,13 +1280,13 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>FreeFileSync hoşunuza gittiyse:</target> <source>Support with a donation</source> -<target></target> +<target>Bağış yaparak destek olun</target> <source>Donation details</source> <target>Bağış Bilgileri</target> <source>The auto updater was disabled by the administrator.</source> -<target></target> +<target>Otomatik güncelleme yönetici tarafından devre dışı bırakılmış.</target> <source>Feedback and suggestions are welcome</source> <target>Öneri ve geri bildirimlerinizi bekleriz</target> @@ -1325,7 +1325,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Çevrimdışı Etkinleştir</target> <source>Save as a Batch Job</source> -<target></target> +<target>Toplu İş Olarak Kaydet</target> <source>Delete Items</source> <target>Ögeleri Sil</target> @@ -1370,7 +1370,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>&Ayrıntılara Bakın</target> <source>FreeFileSync %x is available!</source> -<target></target> +<target>FreeFileSync %x sürümü yayınlanmış!</target> <source>Installation files are corrupted. Please reinstall FreeFileSync.</source> <target>Kurulum dosyaları bozulmuş. Lütfen FreeFileSync uygulamasını yeniden kurun.</target> @@ -1472,7 +1472,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Klasör Karşılaştırma ve Eşitleme</target> <source>Configuration saved</source> -<target>Ayarlar kaydedildi</target> +<target>Yapılandırma kaydedildi</target> <source>FreeFileSync batch</source> <target>FreeFileSync toplu işi</target> @@ -1598,7 +1598,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Sevgili %x, bağışın ve desteğin için teşekkürler!</target> <source>Recommended range:</source> -<target></target> +<target>Önerilen Aralık:</target> <source>Password:</source> <target>Parola:</target> @@ -1607,7 +1607,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Anahtar Parolası:</target> <source>Please enter a file path.</source> -<target></target> +<target>Lütfen bir dosya yolu yazın.</target> <source> <pluralform>Copy the following item to another folder?</pluralform> @@ -1691,7 +1691,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Eşitleme kuralları kullanıcının isteğine göre belirlenir.</target> <source>Synchronization Settings</source> -<target>Eşitleme ayarları</target> +<target>Eşitleme Ayarları</target> <source>Comparison</source> <target>Karşılaştırma</target> @@ -1724,10 +1724,10 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>MB</target> <source>Retain deleted and overwritten files in the recycle bin</source> -<target></target> +<target>Silinmiş ve üzerine yazılmış dosyalar geri dönüşüm kutusunda korunsun</target> <source>Delete and overwrite files permanently</source> -<target></target> +<target>Dosyalar silinsin ve kalıcı olarak üzerine yazılsın</target> <source>Move files to a user-defined folder</source> <target>Dosyalar kullanıcı tarafından belirtilen bir klasöre taşınır</target> @@ -1745,16 +1745,16 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Dosya adlarına zaman damgası eklensin</target> <source>On completion:</source> -<target>İşlem Tamamlandığında:</target> +<target>Tamamlandığında:</target> <source>On errors:</source> -<target></target> +<target>Sorun Çıktığında:</target> <source>On success:</source> -<target></target> +<target>Başarılı Olduğunda:</target> <source>Main config</source> -<target>Temel ayarlar</target> +<target>Temel Yapılandırma</target> <source>empty</source> <target>boş</target> @@ -1904,7 +1904,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Giriş/Çıkış işlemi öncelikleri değiştirilemedi.</target> <source>Unable to shut down the system.</source> -<target></target> +<target>Bilgisayar kapatılamıyor.</target> <source>Checking recycle bin failed for folder %x.</source> <target>%x klasörü için Geri Dönüşüm Kutusu denetlenemedi.</target> @@ -1913,7 +1913,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>Şu XML bileşenleri okunamadı:</target> <source>Configuration file %x is incomplete. The missing elements will be set to their default values.</source> -<target>%x ayar dosyası tam değil. Eksik bileşenler için varsayılan değerler kullanılacak.</target> +<target>%x yapılandırma dosyası tam değil. Eksik bileşenler için varsayılan değerler kullanılacak.</target> <source>Prepare installation</source> <target>Kuruluma hazırlanıyor</target> @@ -1967,7 +1967,7 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>FreeFileSync dosya uzantıları kayıt defterinden siliniyor</target> <source>FreeFileSync Configuration</source> -<target>FreeFileSync Ayarları</target> +<target>FreeFileSync Yapılandırması</target> <source>FreeFileSync Batch File</source> <target>FreeFileSync Toplu İşlem Dosyası</target> @@ -1976,13 +1976,13 @@ Bu yöntem, ciddi bir hata oluşması durumunda bile işlemin tutarlı olarak ya <target>FreeFileSync Eşitleme Veritabanı</target> <source>RealTimeSync Configuration</source> -<target>RealTimeSync Ayarları</target> +<target>RealTimeSync Yapılandırması</target> <source>Edit with FreeFileSync</source> <target>FreeFileSync ile Düzenlensin</target> <source>The FreeFileSync portable version cannot install into a subfolder of %x.</source> -<target></target> +<target>FreeFileSync taşınabilir sürümü bir %x alt klasörüne yüklenemez.</target> <source>Please choose the local installation type or select a different folder for installation.</source> <target>Kurulum için farklı bir klasör ya da yerel kurulum türünü seçin.</target> diff --git a/FreeFileSync/Build/Resources.zip b/FreeFileSync/Build/Resources.zip Binary files differindex 5c7cc44b..c0c2c2ed 100755 --- a/FreeFileSync/Build/Resources.zip +++ b/FreeFileSync/Build/Resources.zip diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile index 46e18617..33197267 100755 --- a/FreeFileSync/Source/Makefile +++ b/FreeFileSync/Source/Makefile @@ -37,15 +37,16 @@ CPP_LIST+=fs/abstract.cpp CPP_LIST+=fs/concrete.cpp CPP_LIST+=fs/native.cpp CPP_LIST+=file_hierarchy.cpp -CPP_LIST+=ui/custom_grid.cpp +CPP_LIST+=ui/cfg_grid.cpp +CPP_LIST+=ui/file_grid.cpp CPP_LIST+=ui/folder_history_box.cpp CPP_LIST+=ui/command_box.cpp CPP_LIST+=ui/folder_selector.cpp CPP_LIST+=ui/batch_config.cpp CPP_LIST+=ui/batch_status_handler.cpp CPP_LIST+=ui/version_check.cpp -CPP_LIST+=ui/grid_view.cpp -CPP_LIST+=ui/tree_view.cpp +CPP_LIST+=ui/file_view.cpp +CPP_LIST+=ui/tree_grid.cpp CPP_LIST+=ui/gui_generated.cpp CPP_LIST+=ui/gui_status_handler.cpp CPP_LIST+=ui/main_dlg.cpp diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index 4acd8c25..f665c783 100755 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -214,7 +214,7 @@ void MainDialog::OnConfigSave(wxCommandEvent& event) { Zstring defaultFileName = currentConfigFileName_.empty() ? Zstr("Realtime.ffs_real") : currentConfigFileName_; //attention: currentConfigFileName may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config! - if (endsWith(defaultFileName, Zstr(".ffs_batch"))) + if (endsWith(defaultFileName, Zstr(".ffs_batch"), CmpFilePath())) defaultFileName = beforeLast(defaultFileName, Zstr("."), IF_MISSING_RETURN_NONE) + Zstr(".ffs_real"); wxFileDialog filePicker(this, diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index 005f5a64..d38573ef 100755 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -133,8 +133,8 @@ void Application::onQueryEndSession(wxEvent& event) void runGuiMode (const Zstring& globalConfigFile); -void runGuiMode (const Zstring& globalConfigFile, const XmlGuiConfig& guiCfg, const std::vector<Zstring>& referenceFiles, bool startComparison); -void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCfg, const Zstring& referenceFile, FfsReturnCode& returnCode); +void runGuiMode (const Zstring& globalConfigFile, const XmlGuiConfig& guiCfg, const std::vector<Zstring>& cfgFilePaths, bool startComparison); +void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCfg, const Zstring& cfgFilePath, FfsReturnCode& returnCode); void showSyntaxHelp(); @@ -156,15 +156,19 @@ void Application::launch(const std::vector<Zstring>& commandArgs) }; //parse command line arguments - std::vector<Zstring> dirPathPhrasesLeft; - std::vector<Zstring> dirPathPhrasesRight; + std::vector<std::pair<Zstring, Zstring>> dirPathPhrasePairs; std::vector<std::pair<Zstring, XmlType>> configFiles; //XmlType: batch or GUI files only Zstring globalConfigFile; bool openForEdit = false; { + std::vector<Zstring> dirPathPhrasesLeft; //TODO: remove migration code at some time! 2017-12-14 + std::vector<Zstring> dirPathPhrasesRight; // + const Zchar optionEdit [] = Zstr("-edit"); - const Zchar optionLeftDir [] = Zstr("-leftdir"); - const Zchar optionRightDir[] = Zstr("-rightdir"); + const Zchar optionLeftDir [] = Zstr("-leftdir"); //TODO: remove migration code at some time! 2017-12-14 + const Zchar optionRightDir[] = Zstr("-rightdir"); // + const Zchar optionDirPair [] = Zstr("-dirpair"); + const Zchar optionSendTo [] = Zstr("-sendto"); //remaining arguments are unspecified number of folder paths; wonky syntax; let's keep it undocumented auto syntaxHelpRequested = [&](const Zstring& arg) { @@ -177,6 +181,16 @@ void Application::launch(const std::vector<Zstring>& commandArgs) argTmp == Zstr("?"); }; + auto isCommandLineOption = [&](const Zstring& arg) + { + return strEqual(arg, optionEdit, CmpAsciiNoCase()) || + strEqual(arg, optionLeftDir, CmpAsciiNoCase()) || + strEqual(arg, optionRightDir, CmpAsciiNoCase()) || + strEqual(arg, optionDirPair, CmpAsciiNoCase()) || + strEqual(arg, optionSendTo, CmpAsciiNoCase()) || + syntaxHelpRequested(arg); + }; + for (auto it = commandArgs.begin(); it != commandArgs.end(); ++it) if (syntaxHelpRequested(*it)) return showSyntaxHelp(); @@ -184,7 +198,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) openForEdit = true; else if (strEqual(*it, optionLeftDir, CmpAsciiNoCase())) { - if (++it == commandArgs.end()) + if (++it == commandArgs.end() || isCommandLineOption(*it)) { notifyFatalError(replaceCpy(_("A directory path is expected after %x."), L"%x", utfTo<std::wstring>(optionLeftDir)), _("Syntax error")); return; @@ -193,13 +207,66 @@ void Application::launch(const std::vector<Zstring>& commandArgs) } else if (strEqual(*it, optionRightDir, CmpAsciiNoCase())) { - if (++it == commandArgs.end()) + if (++it == commandArgs.end() || isCommandLineOption(*it)) { notifyFatalError(replaceCpy(_("A directory path is expected after %x."), L"%x", utfTo<std::wstring>(optionRightDir)), _("Syntax error")); return; } dirPathPhrasesRight.push_back(*it); } + else if (strEqual(*it, optionDirPair, CmpAsciiNoCase())) + { + if (++it == commandArgs.end() || isCommandLineOption(*it)) + { + notifyFatalError(replaceCpy(_("A left and a right directory path are expected after %x."), L"%x", utfTo<std::wstring>(optionDirPair)), _("Syntax error")); + return; + } + dirPathPhrasePairs.emplace_back(*it, Zstring()); + + if (++it == commandArgs.end() || isCommandLineOption(*it)) + { + notifyFatalError(replaceCpy(_("A left and a right directory path are expected after %x."), L"%x", utfTo<std::wstring>(optionDirPair)), _("Syntax error")); + return; + } + dirPathPhrasePairs.back().second = *it; + } + else if (strEqual(*it, optionSendTo, CmpAsciiNoCase())) + { + for (size_t i = 0; ; ++i) + { + if (++it == commandArgs.end() || isCommandLineOption(*it)) + { + --it; + break; + } + + if (i < 2) //-SendTo with more than 2 paths? Doesn't make any sense, does it!? + { + //for -SendTo we expect a list of full native paths, not "phrases" that need to be resolved! + auto getFolderPath = [](Zstring itemPath) + { + try + { + if (getItemType(itemPath) == ItemType::FILE) //throw FileError + if (Opt<Zstring> parentPath = getParentFolderPath(itemPath)) + return *parentPath; + } + catch (FileError&) {} + + return itemPath; + }; + + if (i % 2 == 0) + dirPathPhrasePairs.emplace_back(getFolderPath(*it), Zstring()); + else + { + const Zstring folderPath = getFolderPath(*it); + if (!equalFilePath(dirPathPhrasePairs.back().first, folderPath)) //user accidentally sending to two files, which each time yield the same parent folder + dirPathPhrasePairs.back().second = folderPath; + } + } + } + } else { Zstring filePath = getResolvedFilePath(*it); @@ -243,13 +310,17 @@ void Application::launch(const std::vector<Zstring>& commandArgs) return; } } - } - if (dirPathPhrasesLeft.size() != dirPathPhrasesRight.size()) - { - notifyFatalError(_("Unequal number of left and right directories specified."), _("Syntax error")); - return; + if (dirPathPhrasesLeft.size() != dirPathPhrasesRight.size()) + { + notifyFatalError(_("Unequal number of left and right directories specified."), _("Syntax error")); + return; + } + + for (size_t i = 0; i < dirPathPhrasesLeft.size(); ++i) + dirPathPhrasePairs.emplace_back(dirPathPhrasesLeft[i], dirPathPhrasesRight[i]); } + //---------------------------------------------------------------------------------------------------- auto hasNonDefaultConfig = [](const FolderPairEnh& fp) { @@ -260,7 +331,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) auto replaceDirectories = [&](MainConfiguration& mainCfg) { - if (!dirPathPhrasesLeft.empty()) + if (!dirPathPhrasePairs.empty()) { //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)) @@ -270,14 +341,14 @@ void Application::launch(const std::vector<Zstring>& commandArgs) } mainCfg.additionalPairs.clear(); - for (size_t i = 0; i < dirPathPhrasesLeft.size(); ++i) + for (size_t i = 0; i < dirPathPhrasePairs.size(); ++i) if (i == 0) { - mainCfg.firstPair.folderPathPhraseLeft_ = dirPathPhrasesLeft [0]; - mainCfg.firstPair.folderPathPhraseRight_ = dirPathPhrasesRight[0]; + mainCfg.firstPair.folderPathPhraseLeft_ = dirPathPhrasePairs[0].first; + mainCfg.firstPair.folderPathPhraseRight_ = dirPathPhrasePairs[0].second; } else - mainCfg.additionalPairs.emplace_back(dirPathPhrasesLeft[i], dirPathPhrasesRight[i], + mainCfg.additionalPairs.emplace_back(dirPathPhrasePairs[i].first, dirPathPhrasePairs[i].second, nullptr, nullptr, FilterConfig()); } return true; @@ -290,7 +361,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) if (configFiles.empty()) { //gui mode: default startup - if (dirPathPhrasesLeft.empty()) + if (dirPathPhrasePairs.empty()) runGuiMode(globalConfigFilePath); //gui mode: default config with given directories else @@ -357,7 +428,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs) //gui mode: merged configs else { - if (!dirPathPhrasesLeft.empty()) + if (!dirPathPhrasePairs.empty()) { notifyFatalError(_("Directories cannot be set for more than one configuration file."), _("Syntax error")); return; @@ -387,15 +458,15 @@ void Application::launch(const std::vector<Zstring>& commandArgs) } -void runGuiMode(const Zstring& globalConfigFile) { MainDialog::create(globalConfigFile); } +void runGuiMode(const Zstring& globalConfigFilePath) { MainDialog::create(globalConfigFilePath); } -void runGuiMode(const Zstring& globalConfigFile, +void runGuiMode(const Zstring& globalConfigFilePath, const xmlAccess::XmlGuiConfig& guiCfg, - const std::vector<Zstring>& referenceFiles, + const std::vector<Zstring>& cfgFilePaths, bool startComparison) { - MainDialog::create(globalConfigFile, nullptr, guiCfg, referenceFiles, startComparison); + MainDialog::create(globalConfigFilePath, nullptr, guiCfg, cfgFilePaths, startComparison); } @@ -406,7 +477,7 @@ void showSyntaxHelp() setDetailInstructions(_("Syntax:") + L"\n\n" + L"./FreeFileSync " + L"\n" + L" [" + _("config files:") + L" *.ffs_gui/*.ffs_batch]" + L"\n" + - L" [-LeftDir " + _("directory") + L"] [-RightDir " + _("directory") + L"]" + L"\n" + + L" [-DirPair " + _("directory") + L" " + _("directory") + L"]" + L"\n" + L" [-Edit]" + L"\n" + L" [" + _("global config file:") + L" GlobalSettings.xml]" + L"\n" + L"\n" + @@ -414,7 +485,7 @@ void showSyntaxHelp() _("config files:") + L"\n" + _("Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.") + L"\n\n" + - L"-LeftDir " + _("directory") + L" -RightDir " + _("directory") + L"\n" + + L"-DirPair " + _("directory") + L" " + _("directory") + L"\n" + _("Any number of alternative directory pairs for at most one config file.") + L"\n\n" + L"-Edit" + L"\n" + @@ -425,7 +496,7 @@ void showSyntaxHelp() } -void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& batchCfg, const Zstring& referenceFile, FfsReturnCode& returnCode) +void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& batchCfg, const Zstring& cfgFilePath, FfsReturnCode& returnCode) { const bool showPopupAllowed = !batchCfg.mainCfg.ignoreErrors && batchCfg.batchExCfg.batchErrorDialog == BatchErrorDialog::SHOW; @@ -476,7 +547,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat //class handling status updates and error messages BatchStatusHandler statusHandler(!batchCfg.batchExCfg.runMinimized, //throw AbortProcess, BatchRequestSwitchToMainDialog - extractJobName(referenceFile), + extractJobName(cfgFilePath), globalCfg.soundFileSyncFinished, batchStartTime, batchCfg.batchExCfg.logFolderPathPhrase, @@ -525,12 +596,20 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat cmpResult, globalCfg.optDialogs, statusHandler); //throw ? + + //not cancelled? => update last sync date for the selected cfg file + for (xmlAccess::ConfigFileItem& cfi : globalCfg.gui.mainDlg.cfgFileHistory) + if (equalFilePath(cfi.filePath, cfgFilePath)) + { + cfi.lastSyncTime = std::time(nullptr); + break; + } } catch (AbortProcess&) {} //exit used by statusHandler catch (BatchRequestSwitchToMainDialog&) { //open new toplevel window *after* progress dialog is gone => run on main event loop - return MainDialog::create(globalConfigFilePath, &globalCfg, xmlAccess::convertBatchToGui(batchCfg), { referenceFile }, true /*startComparison*/); + return MainDialog::create(globalConfigFilePath, &globalCfg, xmlAccess::convertBatchToGui(batchCfg), { cfgFilePath }, true /*startComparison*/); } try //save global settings to XML: e.g. ignored warnings diff --git a/FreeFileSync/Source/comparison.cpp b/FreeFileSync/Source/comparison.cpp index b75dc62a..ac89cb6c 100755 --- a/FreeFileSync/Source/comparison.cpp +++ b/FreeFileSync/Source/comparison.cpp @@ -86,25 +86,25 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector<FolderPairCfg>& cfgL if (!status.notExisting.empty() || !status.failedChecks.empty()) { - std::wstring errorMsg = _("Cannot find the following folders:") + L"\n"; + std::wstring msg = _("Cannot find the following folders:") + L"\n"; for (const AbstractPath& folderPath : status.notExisting) - errorMsg += L"\n" + AFS::getDisplayPath(folderPath); + msg += L"\n" + AFS::getDisplayPath(folderPath); for (const auto& fc : status.failedChecks) - errorMsg += L"\n" + AFS::getDisplayPath(fc.first); + msg += L"\n" + AFS::getDisplayPath(fc.first); - errorMsg += L"\n\n"; - errorMsg += _("If this error is ignored the folders will be considered empty. Missing folders are created automatically when needed."); + msg += L"\n\n"; + msg += _("If this error is ignored the folders will be considered empty. Missing folders are created automatically when needed."); if (!status.failedChecks.empty()) { - errorMsg += L"\n___________________________________________"; + msg += L"\n___________________________________________"; for (const auto& fc : status.failedChecks) - errorMsg += L"\n\n" + replaceCpy(fc.second.toString(), L"\n\n", L"\n"); + msg += L"\n\n" + replaceCpy(fc.second.toString(), L"\n\n", L"\n"); } - throw FileError(errorMsg); + throw FileError(msg); } }, callback); //throw X? diff --git a/FreeFileSync/Source/file_hierarchy.h b/FreeFileSync/Source/file_hierarchy.h index ca9e6248..5b0ae6c7 100755 --- a/FreeFileSync/Source/file_hierarchy.h +++ b/FreeFileSync/Source/file_hierarchy.h @@ -347,7 +347,7 @@ public: V& operator* () const { return **it_; } V* operator->() const { return &** it_; } private: - IterImpl it_; + IterImpl it_{}; }; /* @@ -505,7 +505,7 @@ private: std::unique_ptr<std::wstring> syncDirectionConflict_; //non-empty if we have a conflict setting sync-direction //get rid of std::wstring small string optimization (consumes 32/48 byte on VS2010 x86/x64!) - Zstring itemNameL_; //slightly redundant under linux, but on windows the "same" file paths can differ in case + Zstring itemNameL_; //slightly redundant under Linux, but on Windows the "same" file paths can differ in case Zstring itemNameR_; //use as indicator: an empty name means: not existing on this side! ContainerObject& parent_; @@ -794,7 +794,7 @@ bool FileSystemObject::isPairEmpty() const template <SelectedSide side> inline Zstring FileSystemObject::getItemName() const { - assert(!itemNameL_.empty() || !itemNameR_.empty()); + //assert(!itemNameL_.empty() || !itemNameR_.empty()); -> file pair might be empty (until removed after sync) const Zstring& itemName = SelectParam<side>::ref(itemNameL_, itemNameR_); //empty if not existing if (!itemName.empty()) //avoid ternary-WTF! (implicit copy-constructor call!!!!!!) diff --git a/FreeFileSync/Source/lib/lock_holder.h b/FreeFileSync/Source/lib/lock_holder.h index dc0dccfc..fc3f7a5c 100755 --- a/FreeFileSync/Source/lib/lock_holder.h +++ b/FreeFileSync/Source/lib/lock_holder.h @@ -11,7 +11,7 @@ namespace zen { //intermediate locks created by DirLock use this extension, too: -const Zchar LOCK_FILE_ENDING[] = Zstr(".ffs_lock"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace! +const Zchar LOCK_FILE_ENDING[] = Zstr(".ffs_lock"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace! //hold locks for a number of directories without blocking during lock creation //call after having checked directory existence! @@ -20,35 +20,44 @@ class LockHolder public: LockHolder(const std::set<Zstring, LessFilePath>& dirpathsExisting, //resolved paths bool& warnDirectoryLockFailed, - ProcessCallback& procCallback) + ProcessCallback& pcb) { - for (const Zstring& dirpath : dirpathsExisting) + class WaitOnLockHandler : public DirLockCallback { - class WaitOnLockHandler : public DirLockCallback - { - public: - WaitOnLockHandler(ProcessCallback& pc) : pc_(pc) {} - void requestUiRefresh() override { pc_.requestUiRefresh(); } //allowed to throw exceptions - void reportStatus(const std::wstring& text) override { pc_.reportStatus(text); } - private: - ProcessCallback& pc_; - } callback(procCallback); + public: + WaitOnLockHandler(ProcessCallback& pc) : pc_(pc) {} + void requestUiRefresh() override { pc_.requestUiRefresh(); } //allowed to throw exceptions + void reportStatus(const std::wstring& text) override { pc_.reportStatus(text); } + private: + ProcessCallback& pc_; + } lcb(pcb); + std::map<Zstring, FileError, LessFilePath> failedLocks; + + for (const Zstring& dirpath : dirpathsExisting) try { //lock file creation is synchronous and may block noticeably for very slow devices (usb sticks, mapped cloud storages) - lockHolder.emplace_back(appendSeparator(dirpath) + Zstr("sync") + LOCK_FILE_ENDING, &callback); //throw FileError + lockHolder_.emplace_back(appendSeparator(dirpath) + Zstr("sync") + LOCK_FILE_ENDING, &lcb); //throw FileError } - catch (const FileError& e) + catch (const FileError& e) { failedLocks.emplace(dirpath, e); } + + if (!failedLocks.empty()) + { + std::wstring msg = _("Cannot set directory locks for the following folders:"); + + for (const auto& fl : failedLocks) { - const std::wstring msg = replaceCpy(_("Cannot set directory lock for %x."), L"%x", fmtPath(dirpath)) + L"\n\n" + e.toString(); - procCallback.reportWarning(msg, warnDirectoryLockFailed); //may throw! + msg += L"\n\n" + fmtPath(fl.first); + msg += L"\n" + replaceCpy(fl.second.toString(), L"\n\n", L"\n"); } + + pcb.reportWarning(msg, warnDirectoryLockFailed); //may throw! } } private: - std::vector<DirLock> lockHolder; + std::vector<DirLock> lockHolder_; }; } diff --git a/FreeFileSync/Source/lib/process_xml.cpp b/FreeFileSync/Source/lib/process_xml.cpp index f60bf16d..8c9eb0b2 100755 --- a/FreeFileSync/Source/lib/process_xml.cpp +++ b/FreeFileSync/Source/lib/process_xml.cpp @@ -23,7 +23,7 @@ using namespace std::rel_ops; namespace { //------------------------------------------------------------------------------------------------------------------------------- -const int XML_FORMAT_VER_GLOBAL = 5; // +const int XML_FORMAT_VER_GLOBAL = 6; //2018-01-08 const int XML_FORMAT_VER_FFS_GUI = 8; //2017-10-24 const int XML_FORMAT_VER_FFS_BATCH = 8; // //------------------------------------------------------------------------------------------------------------------------------- @@ -486,34 +486,61 @@ bool readText(const std::string& input, ItemPathFormat& value) return true; } +template <> inline +void writeText(const ColumnTypeCfg& value, std::string& output) +{ + switch (value) + { + case ColumnTypeCfg::NAME: + output = "Name"; + break; + case ColumnTypeCfg::LAST_SYNC: + output = "Last"; + break; + } +} + +template <> inline +bool readText(const std::string& input, ColumnTypeCfg& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Name") + value = ColumnTypeCfg::NAME; + else if (tmp == "Last") + value = ColumnTypeCfg::LAST_SYNC; + else + return false; + return true; +} + template <> inline -void writeText(const ColumnTypeNavi& value, std::string& output) +void writeText(const ColumnTypeTree& value, std::string& output) { switch (value) { - case ColumnTypeNavi::FOLDER_NAME: + case ColumnTypeTree::FOLDER_NAME: output = "Tree"; break; - case ColumnTypeNavi::ITEM_COUNT: + case ColumnTypeTree::ITEM_COUNT: output = "Count"; break; - case ColumnTypeNavi::BYTES: + case ColumnTypeTree::BYTES: output = "Bytes"; break; } } template <> inline -bool readText(const std::string& input, ColumnTypeNavi& value) +bool readText(const std::string& input, ColumnTypeTree& value) { const std::string tmp = trimCpy(input); if (tmp == "Tree") - value = ColumnTypeNavi::FOLDER_NAME; + value = ColumnTypeTree::FOLDER_NAME; else if (tmp == "Count") - value = ColumnTypeNavi::ITEM_COUNT; + value = ColumnTypeTree::ITEM_COUNT; else if (tmp == "Bytes") - value = ColumnTypeNavi::BYTES; + value = ColumnTypeTree::BYTES; else return false; return true; @@ -666,46 +693,68 @@ bool readText(const std::string& input, DirectionConfig::Variant& value) template <> inline -bool readStruc(const XmlElement& input, ColumnAttributeRim& value) +bool readStruc(const XmlElement& input, ColAttributesRim& value) { XmlIn in(input); - bool rv1 = in.attribute("Type", value.type_); - bool rv2 = in.attribute("Visible", value.visible_); - bool rv3 = in.attribute("Width", value.offset_); //offset == width if stretch is 0 - bool rv4 = in.attribute("Stretch", value.stretch_); + bool rv1 = in.attribute("Type", value.type); + bool rv2 = in.attribute("Visible", value.visible); + bool rv3 = in.attribute("Width", value.offset); //offset == width if stretch is 0 + bool rv4 = in.attribute("Stretch", value.stretch); return rv1 && rv2 && rv3 && rv4; } template <> inline -void writeStruc(const ColumnAttributeRim& value, XmlElement& output) +void writeStruc(const ColAttributesRim& value, XmlElement& output) { XmlOut out(output); - out.attribute("Type", value.type_); - out.attribute("Visible", value.visible_); - out.attribute("Width", value.offset_); - out.attribute("Stretch", value.stretch_); + out.attribute("Type", value.type); + out.attribute("Visible", value.visible); + out.attribute("Width", value.offset); + out.attribute("Stretch", value.stretch); } template <> inline -bool readStruc(const XmlElement& input, ColumnAttributeNavi& value) +bool readStruc(const XmlElement& input, ColAttributesCfg& value) { XmlIn in(input); - bool rv1 = in.attribute("Type", value.type_); - bool rv2 = in.attribute("Visible", value.visible_); - bool rv3 = in.attribute("Width", value.offset_); //offset == width if stretch is 0 - bool rv4 = in.attribute("Stretch", value.stretch_); + bool rv1 = in.attribute("Type", value.type); + bool rv2 = in.attribute("Visible", value.visible); + bool rv3 = in.attribute("Width", value.offset); //offset == width if stretch is 0 + bool rv4 = in.attribute("Stretch", value.stretch); return rv1 && rv2 && rv3 && rv4; } template <> inline -void writeStruc(const ColumnAttributeNavi& value, XmlElement& output) +void writeStruc(const ColAttributesCfg& value, XmlElement& output) { XmlOut out(output); - out.attribute("Type", value.type_); - out.attribute("Visible", value.visible_); - out.attribute("Width", value.offset_); - out.attribute("Stretch", value.stretch_); + out.attribute("Type", value.type); + out.attribute("Visible", value.visible); + out.attribute("Width", value.offset); + out.attribute("Stretch", value.stretch); +} + + +template <> inline +bool readStruc(const XmlElement& input, ColAttributesTree& value) +{ + XmlIn in(input); + bool rv1 = in.attribute("Type", value.type); + bool rv2 = in.attribute("Visible", value.visible); + bool rv3 = in.attribute("Width", value.offset); //offset == width if stretch is 0 + bool rv4 = in.attribute("Stretch", value.stretch); + return rv1 && rv2 && rv3 && rv4; +} + +template <> inline +void writeStruc(const ColAttributesTree& value, XmlElement& output) +{ + XmlOut out(output); + out.attribute("Type", value.type); + out.attribute("Visible", value.visible); + out.attribute("Width", value.offset); + out.attribute("Stretch", value.stretch); } @@ -795,17 +844,26 @@ namespace zen { //FFS portable: use special syntax for config file paths: e.g. "ffs_drive:\SyncJob.ffs_gui" template <> inline -bool readText(const std::string& input, ConfigFileItem& value) +bool readStruc(const XmlElement& input, ConfigFileItem& value) { - value.filePath_ = resolveFreeFileSyncDriveMacro(utfTo<Zstring>(input)); - return true; -} + XmlIn in(input); + + Zstring rawPath; + const bool rv1 = in(rawPath); + if (rv1) + value.filePath = resolveFreeFileSyncDriveMacro(rawPath); + const bool rv2 = in.attribute("LastSync", value.lastSyncTime); + + return rv1 && rv2; +} template <> inline -void writeText(const ConfigFileItem& value, std::string& output) +void writeStruc(const ConfigFileItem& value, XmlElement& output) { - output = utfTo<std::string>(substituteFreeFileSyncDriveLetter(value.filePath_)); + XmlOut out(output); + out(substituteFreeFileSyncDriveLetter(value.filePath)); + out.attribute("LastSync", value.lastSyncTime); } } @@ -1152,27 +1210,86 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config, int formatVer) //########################################################### + XmlIn inConfig = inWnd["ConfigPanel"]; + inConfig.attribute("ScrollPos", config.gui.mainDlg.cfgGridTopRowPos); + inConfig.attribute("SyncOverdue", config.gui.mainDlg.cfgGridSyncOverdueDays); + inConfig.attribute("SortByColumn", config.gui.mainDlg.cfgGridLastSortColumn); + inConfig.attribute("SortAscending", config.gui.mainDlg.cfgGridLastSortAscending); + + inConfig["Columns"](config.gui.mainDlg.cfgGridColumnAttribs); + + //TODO: remove parameter migration after some time! 2018-01-08 + if (formatVer < 6) + { + inGui["ConfigHistory"].attribute("MaxSize", config.gui.mainDlg.cfgHistItemsMax); + + std::vector<Zstring> cfgHist; + inGui["ConfigHistory"](cfgHist); + + for (const Zstring& cfgPath : cfgHist) + config.gui.mainDlg.cfgFileHistory.emplace_back(cfgPath, 0); + } + else + { + inConfig["Configurations"].attribute("MaxSize", config.gui.mainDlg.cfgHistItemsMax); + inConfig["Configurations"](config.gui.mainDlg.cfgFileHistory); + } + + //TODO: remove parameter migration after some time! 2018-01-08 + if (formatVer < 6) + { + inGui["LastUsedConfig"](config.gui.mainDlg.lastUsedConfigFiles); + } + else + { + std::vector<Zstring> cfgPaths; + if (inConfig["LastUsed"](cfgPaths)) + { + for (Zstring& filePath : cfgPaths) + filePath = resolveFreeFileSyncDriveMacro(filePath); + + config.gui.mainDlg.lastUsedConfigFiles = cfgPaths; + } + } + + //########################################################### + XmlIn inOverview = inWnd["OverviewPanel"]; - inOverview.attribute("ShowPercentage", config.gui.mainDlg.naviGridShowPercentBar); - inOverview.attribute("SortByColumn", config.gui.mainDlg.naviGridLastSortColumn); - inOverview.attribute("SortAscending", config.gui.mainDlg.naviGridLastSortAscending); + inOverview.attribute("ShowPercentage", config.gui.mainDlg.treeGridShowPercentBar); + inOverview.attribute("SortByColumn", config.gui.mainDlg.treeGridLastSortColumn); + inOverview.attribute("SortAscending", config.gui.mainDlg.treeGridLastSortAscending); //read column attributes - XmlIn inColNavi = inOverview["Columns"]; - inColNavi(config.gui.mainDlg.columnAttribNavi); + XmlIn inColTree = inOverview["Columns"]; + inColTree(config.gui.mainDlg.treeGridColumnAttribs); + + XmlIn inFileGrid = inWnd["FilePanel"]; + //TODO: remove parameter migration after some time! 2018-01-08 + if (formatVer < 6) + inFileGrid = inWnd["CenterPanel"]; + + inFileGrid.attribute("ShowIcons", config.gui.mainDlg.showIcons); + inFileGrid.attribute("IconSize", config.gui.mainDlg.iconSize); + inFileGrid.attribute("SashOffset", config.gui.mainDlg.sashOffset); + inFileGrid.attribute("HistoryMaxSize", config.gui.mainDlg.folderHistItemsMax); - XmlIn inMainGrid = inWnd["CenterPanel"]; - inMainGrid.attribute("ShowIcons", config.gui.mainDlg.showIcons); - inMainGrid.attribute("IconSize", config.gui.mainDlg.iconSize); - inMainGrid.attribute("SashOffset", config.gui.mainDlg.sashOffset); + inFileGrid["ColumnsLeft"].attribute("PathFormat", config.gui.mainDlg.itemPathFormatLeftGrid); + inFileGrid["ColumnsLeft"](config.gui.mainDlg.columnAttribLeft); - XmlIn inColLeft = inMainGrid["ColumnsLeft"]; - inColLeft.attribute("PathFormat", config.gui.mainDlg.itemPathFormatLeftGrid); - inColLeft(config.gui.mainDlg.columnAttribLeft); + inFileGrid["FolderHistoryLeft" ](config.gui.mainDlg.folderHistoryLeft); - XmlIn inColRight = inMainGrid["ColumnsRight"]; - inColRight.attribute("PathFormat", config.gui.mainDlg.itemPathFormatRightGrid); - inColRight(config.gui.mainDlg.columnAttribRight); + inFileGrid["ColumnsRight"].attribute("PathFormat", config.gui.mainDlg.itemPathFormatRightGrid); + inFileGrid["ColumnsRight"](config.gui.mainDlg.columnAttribRight); + + inFileGrid["FolderHistoryRight"](config.gui.mainDlg.folderHistoryRight); + + //TODO: remove parameter migration after some time! 2018-01-08 + if (formatVer < 6) + { + inGui["FolderHistoryLeft" ](config.gui.mainDlg.folderHistoryLeft); + inGui["FolderHistoryRight"](config.gui.mainDlg.folderHistoryRight); + inGui["FolderHistoryLeft"].attribute("MaxSize", config.gui.mainDlg.folderHistItemsMax); + } //########################################################### @@ -1183,31 +1300,20 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config, int formatVer) inGui["DefaultExclusionFilter"](tmp); config.gui.defaultExclusionFilter = mergeFilterLines(tmp); - //load config file history - inGui["LastUsedConfig"](config.gui.lastUsedConfigFiles); - - inGui["ConfigHistory"](config.gui.cfgFileHistory); - inGui["ConfigHistory"].attribute("MaxSize", config.gui.cfgFileHistMax); - inGui["ConfigHistory"].attribute("ScrollPos", config.gui.cfgFileHistFirstItemPos); - //TODO: remove parameter migration after some time! 2016-09-23 if (formatVer < 4) - config.gui.cfgFileHistMax = std::max<size_t>(config.gui.cfgFileHistMax, 100); - - inGui["FolderHistoryLeft" ](config.gui.folderHistoryLeft); - inGui["FolderHistoryRight"](config.gui.folderHistoryRight); - inGui["FolderHistoryLeft"].attribute("MaxSize", config.gui.folderHistMax); + config.gui.mainDlg.cfgHistItemsMax = std::max<size_t>(config.gui.mainDlg.cfgHistItemsMax, 100); //TODO: remove if clause after migration! 2017-10-24 if (formatVer < 5) { inGui["OnCompletionHistory"](config.gui.commandHistory); - inGui["OnCompletionHistory"].attribute("MaxSize", config.gui.commandHistoryMax); + inGui["OnCompletionHistory"].attribute("MaxSize", config.gui.commandHistItemsMax); } else { inGui["CommandHistory"](config.gui.commandHistory); - inGui["CommandHistory"].attribute("MaxSize", config.gui.commandHistoryMax); + inGui["CommandHistory"].attribute("MaxSize", config.gui.commandHistItemsMax); } //external applications @@ -1345,16 +1451,16 @@ XmlCfg parseConfig(const XmlDoc& doc, const Zstring& filepath, int currentXmlFor } -void xmlAccess::readAnyConfig(const std::vector<Zstring>& filepaths, XmlGuiConfig& config, std::wstring& warningMsg) //throw FileError +void xmlAccess::readAnyConfig(const std::vector<Zstring>& filePaths, XmlGuiConfig& config, std::wstring& warningMsg) //throw FileError { - assert(!filepaths.empty()); + assert(!filePaths.empty()); std::vector<zen::MainConfiguration> mainCfgs; - for (auto it = filepaths.begin(); it != filepaths.end(); ++it) + for (auto it = filePaths.begin(); it != filePaths.end(); ++it) { const Zstring& filepath = *it; - const bool firstItem = it == filepaths.begin(); //init all non-"mainCfg" settings with first config file + const bool firstItem = it == filePaths.begin(); //init all non-"mainCfg" settings with first config file XmlDoc doc = loadXmlDocument(filepath); //throw FileError @@ -1604,27 +1710,49 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) //########################################################### + XmlOut outConfig = outWnd["ConfigPanel"]; + outConfig.attribute("ScrollPos", config.gui.mainDlg.cfgGridTopRowPos); + outConfig.attribute("SyncOverdue", config.gui.mainDlg.cfgGridSyncOverdueDays); + outConfig.attribute("SortByColumn", config.gui.mainDlg.cfgGridLastSortColumn); + outConfig.attribute("SortAscending", config.gui.mainDlg.cfgGridLastSortAscending); + + outConfig["Columns"](config.gui.mainDlg.cfgGridColumnAttribs); + outConfig["Configurations"].attribute("MaxSize", config.gui.mainDlg.cfgHistItemsMax); + outConfig["Configurations"](config.gui.mainDlg.cfgFileHistory); + { + std::vector<Zstring> cfgPaths = config.gui.mainDlg.lastUsedConfigFiles; + for (Zstring& filePath : cfgPaths) + filePath = substituteFreeFileSyncDriveLetter(filePath); + + outConfig["LastUsed"](cfgPaths); + } + + //########################################################### + XmlOut outOverview = outWnd["OverviewPanel"]; - outOverview.attribute("ShowPercentage", config.gui.mainDlg.naviGridShowPercentBar); - outOverview.attribute("SortByColumn", config.gui.mainDlg.naviGridLastSortColumn); - outOverview.attribute("SortAscending", config.gui.mainDlg.naviGridLastSortAscending); + outOverview.attribute("ShowPercentage", config.gui.mainDlg.treeGridShowPercentBar); + outOverview.attribute("SortByColumn", config.gui.mainDlg.treeGridLastSortColumn); + outOverview.attribute("SortAscending", config.gui.mainDlg.treeGridLastSortAscending); //write column attributes - XmlOut outColNavi = outOverview["Columns"]; - outColNavi(config.gui.mainDlg.columnAttribNavi); + XmlOut outColTree = outOverview["Columns"]; + outColTree(config.gui.mainDlg.treeGridColumnAttribs); + + XmlOut outFileGrid = outWnd["FilePanel"]; + outFileGrid.attribute("ShowIcons", config.gui.mainDlg.showIcons); + outFileGrid.attribute("IconSize", config.gui.mainDlg.iconSize); + outFileGrid.attribute("SashOffset", config.gui.mainDlg.sashOffset); + outFileGrid.attribute("HistoryMaxSize", config.gui.mainDlg.folderHistItemsMax); - XmlOut outMainGrid = outWnd["CenterPanel"]; - outMainGrid.attribute("ShowIcons", config.gui.mainDlg.showIcons); - outMainGrid.attribute("IconSize", config.gui.mainDlg.iconSize); - outMainGrid.attribute("SashOffset", config.gui.mainDlg.sashOffset); + outFileGrid["ColumnsLeft"].attribute("PathFormat", config.gui.mainDlg.itemPathFormatLeftGrid); + outFileGrid["ColumnsLeft"](config.gui.mainDlg.columnAttribLeft); - XmlOut outColLeft = outMainGrid["ColumnsLeft"]; - outColLeft.attribute("PathFormat", config.gui.mainDlg.itemPathFormatLeftGrid); - outColLeft(config.gui.mainDlg.columnAttribLeft); + outFileGrid["FolderHistoryLeft" ](config.gui.mainDlg.folderHistoryLeft); - XmlOut outColRight = outMainGrid["ColumnsRight"]; - outColRight.attribute("PathFormat", config.gui.mainDlg.itemPathFormatRightGrid); - outColRight(config.gui.mainDlg.columnAttribRight); + outFileGrid["ColumnsRight"].attribute("PathFormat", config.gui.mainDlg.itemPathFormatRightGrid); + outFileGrid["ColumnsRight"](config.gui.mainDlg.columnAttribRight); + + outFileGrid["FolderHistoryRight"](config.gui.mainDlg.folderHistoryRight); //########################################################### @@ -1633,19 +1761,8 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) outGui["DefaultExclusionFilter"](splitFilterByLines(config.gui.defaultExclusionFilter)); - //load config file history - outGui["LastUsedConfig"](config.gui.lastUsedConfigFiles); - - outGui["ConfigHistory" ](config.gui.cfgFileHistory); - outGui["ConfigHistory"].attribute("MaxSize", config.gui.cfgFileHistMax); - outGui["ConfigHistory"].attribute("ScrollPos", config.gui.cfgFileHistFirstItemPos); - - outGui["FolderHistoryLeft" ](config.gui.folderHistoryLeft); - outGui["FolderHistoryRight"](config.gui.folderHistoryRight); - outGui["FolderHistoryLeft" ].attribute("MaxSize", config.gui.folderHistMax); - outGui["CommandHistory"](config.gui.commandHistory); - outGui["CommandHistory"].attribute("MaxSize", config.gui.commandHistoryMax); + outGui["CommandHistory"].attribute("MaxSize", config.gui.commandHistItemsMax); //external applications outGui["ExternalApps"](config.gui.externelApplications); diff --git a/FreeFileSync/Source/lib/process_xml.h b/FreeFileSync/Source/lib/process_xml.h index 1ff7584e..1328ceb0 100755 --- a/FreeFileSync/Source/lib/process_xml.h +++ b/FreeFileSync/Source/lib/process_xml.h @@ -11,7 +11,9 @@ #include <wx/gdicmn.h> #include "localization.h" #include "../structures.h" -#include "../ui/column_attr.h" +#include "../ui/file_grid_attr.h" +#include "../ui/tree_grid_attr.h" //RTS: avoid tree grid's "file_hierarchy.h" dependency! +#include "../ui/cfg_grid.h" namespace xmlAccess @@ -132,9 +134,11 @@ struct ViewFilterDefault struct ConfigFileItem { ConfigFileItem() {} - explicit ConfigFileItem(const Zstring& filePath) : filePath_(filePath) {} - Zstring filePath_; - //add support? -> time_t lastSyncTime + ConfigFileItem(const Zstring& fp, time_t lst) : filePath(fp), lastSyncTime(lst) {} + + Zstring filePath; + time_t lastSyncTime = 0; + //Zstring logFilePath; }; @@ -154,13 +158,13 @@ struct XmlGlobalSettings size_t automaticRetryDelay = 5; //unit: [sec] int fileTimeTolerance = 2; //max. allowed file time deviation; < 0 means unlimited tolerance; default 2s: FAT vs NTFS - int folderAccessTimeout = 20; //unit: [s]; consider CD-ROM insert or hard disk spin up time from sleep + int folderAccessTimeout = 20; //unit: [s]; consider CD-ROM insert or hard disk spin up time from sleep bool runWithBackgroundPriority = false; bool createLockFile = true; bool verifyFileCopy = false; size_t lastSyncsLogFileSizeMax = 100000; //maximum size for LastSyncs.log: use a human-readable number Zstring soundFileCompareFinished; - Zstring soundFileSyncFinished= Zstr("gong.wav"); + Zstring soundFileSyncFinished = Zstr("gong.wav"); OptionalDialogs optDialogs; @@ -186,12 +190,23 @@ struct XmlGlobalSettings bool textSearchRespectCase = false; //good default for Linux, too! int maxFolderPairsVisible = 6; - bool naviGridShowPercentBar = zen::naviGridShowPercentageDefault; //in navigation panel - zen::ColumnTypeNavi naviGridLastSortColumn = zen::naviGridLastSortColumnDefault; //remember sort on navigation panel - bool naviGridLastSortAscending = zen::naviGridLastSortAscendingDefault; // - - std::vector<zen::ColumnAttributeNavi> columnAttribNavi = zen::getDefaultColumnAttributesNavi(); //compressed view/navigation - + size_t cfgGridTopRowPos = 0; + int cfgGridSyncOverdueDays = 7; + zen::ColumnTypeCfg cfgGridLastSortColumn = zen::cfgGridLastSortColumnDefault; + bool cfgGridLastSortAscending = zen::getDefaultSortDirection(zen::cfgGridLastSortColumnDefault); + std::vector<zen::ColAttributesCfg> cfgGridColumnAttribs = zen::getCfgGridDefaultColAttribs(); + size_t cfgHistItemsMax = 100; + std::vector<ConfigFileItem> cfgFileHistory; + std::vector<Zstring> lastUsedConfigFiles; + + bool treeGridShowPercentBar = zen::treeGridShowPercentageDefault; + zen::ColumnTypeTree treeGridLastSortColumn = zen::treeGridLastSortColumnDefault; //remember sort on overview panel + bool treeGridLastSortAscending = zen::getDefaultSortDirection(zen::treeGridLastSortColumnDefault); // + std::vector<zen::ColAttributesTree> treeGridColumnAttribs = zen::getTreeGridDefaultColAttribs(); + + std::vector<Zstring> folderHistoryLeft; + std::vector<Zstring> folderHistoryRight; + size_t folderHistItemsMax = 15; bool showIcons = true; FileIconSize iconSize = ICON_SIZE_SMALL; int sashOffset = 0; @@ -199,8 +214,8 @@ struct XmlGlobalSettings zen::ItemPathFormat itemPathFormatLeftGrid = zen::defaultItemPathFormatLeftGrid; zen::ItemPathFormat itemPathFormatRightGrid = zen::defaultItemPathFormatRightGrid; - std::vector<zen::ColumnAttributeRim> columnAttribLeft = zen::getDefaultColumnAttributesLeft(); - std::vector<zen::ColumnAttributeRim> columnAttribRight = zen::getDefaultColumnAttributesRight(); + std::vector<zen::ColAttributesRim> columnAttribLeft = zen::getFileGridDefaultColAttribsLeft(); + std::vector<zen::ColAttributesRim> columnAttribRight = zen::getFileGridDefaultColAttribsRight(); ViewFilterDefault viewFilterDefault; wxString guiPerspectiveLast; //used by wxAuiManager @@ -209,18 +224,8 @@ struct XmlGlobalSettings Zstring defaultExclusionFilter = Zstr("/.Trash-*/") Zstr("\n") Zstr("/.recycle/"); - std::vector<ConfigFileItem> lastUsedConfigFiles; - - std::vector<ConfigFileItem> cfgFileHistory; - size_t cfgFileHistMax = 100; - int cfgFileHistFirstItemPos = 0; - - std::vector<Zstring> folderHistoryLeft; - std::vector<Zstring> folderHistoryRight; - size_t folderHistMax = 15; - std::vector<Zstring> commandHistory; - size_t commandHistoryMax = 8; + size_t commandHistItemsMax = 8; ExternalApps externelApplications { diff --git a/FreeFileSync/Source/lib/resolve_path.cpp b/FreeFileSync/Source/lib/resolve_path.cpp index 1c67bd78..62d56577 100755 --- a/FreeFileSync/Source/lib/resolve_path.cpp +++ b/FreeFileSync/Source/lib/resolve_path.cpp @@ -150,36 +150,36 @@ namespace //expand volume name if possible, return original input otherwise -Zstring expandVolumeName(const Zstring& text) // [volname]:\folder [volname]\folder [volname]folder -> C:\folder +Zstring expandVolumeName(Zstring pathPhrase) // [volname]:\folder [volname]\folder [volname]folder -> C:\folder { //this would be a nice job for a C++11 regex... //we only expect the [.*] pattern at the beginning => do not touch dir names like "C:\somedir\[stuff]" - const Zstring textTmp = trimCpy(text, true, false); - if (startsWith(textTmp, Zstr("["))) + trim(pathPhrase, true, false); + if (startsWith(pathPhrase, Zstr("["))) { - size_t posEnd = textTmp.find(Zstr("]")); + size_t posEnd = pathPhrase.find(Zstr("]")); if (posEnd != Zstring::npos) { - Zstring volname = Zstring(textTmp.c_str() + 1, posEnd - 1); - Zstring rest = Zstring(textTmp.c_str() + posEnd + 1); - - if (startsWith(rest, Zstr(':'))) - rest = afterFirst(rest, Zstr(':'), IF_MISSING_RETURN_NONE); - if (startsWith(rest, FILE_NAME_SEPARATOR)) - rest = afterFirst(rest, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); - return "/.../[" + volname + "]/" + rest; + Zstring volName = Zstring(pathPhrase.c_str() + 1, posEnd - 1); + Zstring relPath = Zstring(pathPhrase.c_str() + posEnd + 1); + + if (startsWith(relPath, Zstr(':'))) + relPath = afterFirst(relPath, Zstr(':'), IF_MISSING_RETURN_NONE); + if (startsWith(relPath, FILE_NAME_SEPARATOR)) + relPath = afterFirst(relPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + return "/.../[" + volName + "]/" + relPath; } } - return text; + return pathPhrase; } } -void getDirectoryAliasesRecursive(const Zstring& dirPath, std::set<Zstring, LessFilePath>& output) +void getDirectoryAliasesRecursive(const Zstring& pathPhrase, std::set<Zstring, LessFilePath>& output) { - //3. environment variables: C:\Users\<user> -> %UserProfile%, C:\Users\%UserName% + //3. environment variables: C:\Users\<user> -> %UserProfile% { std::vector<std::pair<Zstring, Zstring>> macroList; @@ -197,16 +197,16 @@ void getDirectoryAliasesRecursive(const Zstring& dirPath, std::set<Zstring, Less const Zstring& macroName = item.first; const Zstring& macroPath = item.second; - const Zstring pathSubst = ciReplaceCpy(dirPath, macroPath, MACRO_SEP + macroName + MACRO_SEP); //ci on Linux, too? okay - if (pathSubst != dirPath) + const Zstring pathSubst = ciReplaceCpy(pathPhrase, macroPath, MACRO_SEP + macroName + MACRO_SEP); //ci on Linux, too? okay + if (pathSubst != pathPhrase) output.insert(pathSubst); } } //4. replace (all) macros: %UserProfile% -> C:\Users\<user> { - const Zstring pathExp = expandMacros(dirPath); - if (pathExp != dirPath) + const Zstring pathExp = expandMacros(pathPhrase); + if (pathExp != pathPhrase) if (output.insert(pathExp).second) getDirectoryAliasesRecursive(pathExp, output); //recurse! } @@ -217,7 +217,7 @@ std::vector<Zstring> zen::getDirectoryAliases(const Zstring& folderPathPhrase) { const Zstring dirPath = trimCpy(folderPathPhrase, true, false); if (dirPath.empty()) - return std::vector<Zstring>(); + return {}; std::set<Zstring, LessFilePath> tmp; getDirectoryAliasesRecursive(dirPath, tmp); @@ -225,7 +225,7 @@ std::vector<Zstring> zen::getDirectoryAliases(const Zstring& folderPathPhrase) tmp.erase(dirPath); tmp.erase(Zstring()); - return std::vector<Zstring>(tmp.begin(), tmp.end()); + return { tmp.begin(), tmp.end() }; } @@ -261,12 +261,11 @@ Zstring zen::getResolvedFilePath(const Zstring& pathPhrase) //noexcept //remove trailing slash, unless volume root: if (Opt<PathComponents> pc = parsePathComponents(path)) { - //keep this brace for GCC: -Wparentheses if (pc->relPath.empty()) path = pc->rootPath; else path = appendSeparator(pc->rootPath) + pc->relPath; - } + } //keep this brace for GCC: -Wparentheses return path; } diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp index 80a03bfb..814d23c8 100755 --- a/FreeFileSync/Source/ui/batch_config.cpp +++ b/FreeFileSync/Source/ui/batch_config.cpp @@ -47,6 +47,8 @@ private: void OnToggleGenerateLogfile(wxCommandEvent& event) override { updateGui(); } void OnToggleLogfilesLimit (wxCommandEvent& event) override { updateGui(); } + void onLocalKeyEvent(wxKeyEvent& event); + void updateGui(); //re-evaluate gui after config changes void setConfig(const BatchDialogConfig& batchCfg); @@ -82,6 +84,9 @@ BatchDialog::BatchDialog(wxWindow* parent, BatchDialogConfig& dlgCfg) : setConfig(dlgCfg); + //enable dialog-specific key local events + Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(BatchDialog::onLocalKeyEvent), nullptr, this); + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! Center(); //needs to be re-applied after a dialog size change! @@ -161,6 +166,12 @@ BatchDialogConfig BatchDialog::getConfig() const } +void BatchDialog::onLocalKeyEvent(wxKeyEvent& event) +{ + event.Skip(); +} + + void BatchDialog::OnSaveBatchJob(wxCommandEvent& event) { dlgCfgOut_ = getConfig(); diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index 50945143..e3e59690 100755 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -308,6 +308,7 @@ BatchStatusHandler::~BatchStatusHandler() { //post sync action bool showSummary = true; + bool triggerSleep = false; if (!getAbortStatus() || *getAbortStatus() != AbortTrigger::USER) //user cancelled => don't run post sync action! switch (progressDlg_->getOptionPostSyncAction()) { @@ -317,11 +318,7 @@ BatchStatusHandler::~BatchStatusHandler() showSummary = false; break; case PostSyncAction::SLEEP: - try - { - tryReportingError([&] { suspendSystem(); /*throw FileError*/ }, *this); //throw X - } - catch (...) {} + triggerSleep = true; break; case PostSyncAction::SHUTDOWN: showSummary = false; @@ -343,6 +340,13 @@ BatchStatusHandler::~BatchStatusHandler() else progressDlg_->closeDirectly(true /*restoreParentFrame: n/a here*/); //progressDlg_ is main window => program will quit shortly after + if (triggerSleep) //sleep *after* showing results dialog (consider total time!) + try + { + tryReportingError([&] { suspendSystem(); /*throw FileError*/ }, *this); //throw X + } + catch (...) {} + //wait until progress dialog notified shutdown via onProgressDialogTerminate() //-> required since it has our "this" pointer captured in lambda "notifyWindowTerminate"! //-> nicely manages dialog lifetime diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp new file mode 100755 index 00000000..3d6cc451 --- /dev/null +++ b/FreeFileSync/Source/ui/cfg_grid.cpp @@ -0,0 +1,387 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "cfg_grid.h" +#include <zen/time.h> +#include <zen/basic_math.h> +#include <wx+/dc.h> +#include <wx+/rtl.h> +#include <wx+/image_resources.h> +#include <wx/settings.h> +#include "../lib/icon_buffer.h" +#include "../lib/ffs_paths.h" + +using namespace zen; + + +Zstring zen::getLastRunConfigPath() +{ + return zen::getConfigDirPathPf() + Zstr("LastRun.ffs_gui"); +} + + +void ConfigView::addCfgFiles(const std::vector<Zstring>& filePaths) +{ + //determine highest "last use" index number of m_listBoxHistory + int lastUseIndexMax = 0; + for (const auto& item : cfgList_) + lastUseIndexMax = std::max(lastUseIndexMax, item.second.lastUseIndex); + + for (const Zstring& filePath : filePaths) + { + auto it = cfgList_.find(filePath); + if (it == cfgList_.end()) + { + Details detail = {}; + detail.filePath = filePath; + detail.lastUseIndex = ++lastUseIndexMax; + + std::tie(detail.name, detail.cfgType, detail.isLastRunCfg) = [&] + { + if (equalFilePath(filePath, lastRunConfigPath_)) + return std::make_tuple(utfTo<Zstring>(L"<" + _("Last session") + L">"), Details::CFG_TYPE_GUI, true); + + const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); + + if (endsWith(fileName, Zstr(".ffs_gui"), CmpFilePath())) + return std::make_tuple(beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_NONE), Details::CFG_TYPE_GUI, false); + else if (endsWith(fileName, Zstr(".ffs_batch"), CmpFilePath())) + return std::make_tuple(beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_NONE), Details::CFG_TYPE_BATCH, false); + else + return std::make_tuple(fileName, Details::CFG_TYPE_NONE, false); + }(); + + auto itNew = cfgList_.emplace_hint(cfgList_.end(), filePath, std::move(detail)); + cfgListView_.push_back(itNew); + } + else + it->second.lastUseIndex = ++lastUseIndexMax; + } + + sortListView(); +} + + +void ConfigView::removeItems(const std::vector<Zstring>& filePaths) +{ + const std::set<Zstring, LessFilePath> pathsSorted(filePaths.begin(), filePaths.end()); + + erase_if(cfgListView_, [&](auto it) { return pathsSorted.find(it->first) != pathsSorted.end(); }); + + for (const Zstring& filePath : filePaths) + cfgList_.erase(filePath); + + assert(cfgList_.size() == cfgListView_.size()); +} + + +void ConfigView::setLastSyncTime(const std::vector<std::pair<Zstring, time_t>>& syncTimes) +{ + for (const auto& st : syncTimes) + { + auto it = cfgList_.find(st.first); + if (it != cfgList_.end()) + it->second.lastSyncTime = st.second; + } + sortListView(); //needed if sorted by last sync time +} + + +const ConfigView::Details* ConfigView::getItem(size_t row) const +{ + if (row < cfgListView_.size()) + return &cfgListView_[row]->second; + return nullptr; +} + + +void ConfigView::setSortDirection(ColumnTypeCfg colType, bool ascending) +{ + sortColumn_ = colType; + sortAscending_ = ascending; + + sortListView(); +} + + +template <bool ascending> +void ConfigView::sortListViewImpl() +{ + const auto lessCfgName = [](CfgFileList::iterator lhs, CfgFileList::iterator rhs) + { + if (lhs->second.isLastRunCfg != rhs->second.isLastRunCfg) + return lhs->second.isLastRunCfg > rhs->second.isLastRunCfg; //"last session" label should be at top position! + + return LessNaturalSort()(lhs->second.name, rhs->second.name); + }; + + const auto lessLastSync = [](CfgFileList::iterator lhs, CfgFileList::iterator rhs) + { + if (lhs->second.isLastRunCfg != rhs->second.isLastRunCfg) + return lhs->second.isLastRunCfg < rhs->second.isLastRunCfg; //"last session" label should be (always) last + + return makeSortDirection(std::greater<>(), Int2Type<ascending>())(lhs->second.lastSyncTime, rhs->second.lastSyncTime); + //[!] ascending LAST_SYNC shows lowest "days past" first <=> highest lastSyncTime first + }; + + switch (sortColumn_) + { + case ColumnTypeCfg::NAME: + std::sort(cfgListView_.begin(), cfgListView_.end(), makeSortDirection(lessCfgName, Int2Type<ascending>())); + break; + case ColumnTypeCfg::LAST_SYNC: + std::sort(cfgListView_.begin(), cfgListView_.end(), lessLastSync); + break; + } +} + + +void ConfigView::sortListView() +{ + if (sortAscending_) + sortListViewImpl<true>(); + else + sortListViewImpl<false>(); +} + +//------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------- + +namespace +{ +class GridDataCfg : public GridData +{ +public: + GridDataCfg(int fileIconSize) : fileIconSize_(fileIconSize) {} + + ConfigView& getDataView() { return cfgView_; } + + static int getRowDefaultHeight(const Grid& grid) + { + return grid.getMainWin().GetCharHeight(); + } + + int getSyncOverdueDays() const { return syncOverdueDays_; } + void setSyncOverdueDays(int syncOverdueDays) { syncOverdueDays_ = syncOverdueDays; } + +private: + size_t getRowCount() const override { return cfgView_.getRowCount(); } + + std::wstring getValue(size_t row, ColumnType colType) const override + { + if (const ConfigView::Details* item = cfgView_.getItem(row)) + switch (static_cast<ColumnTypeCfg>(colType)) + { + case ColumnTypeCfg::NAME: + return utfTo<std::wstring>(item->name); + + case ColumnTypeCfg::LAST_SYNC: + { + if (item->isLastRunCfg) + return std::wstring(); + + if (item->lastSyncTime == 0) + return std::wstring(1, EN_DASH); + + const int daysPast = numeric::round((std::time(nullptr) - item->lastSyncTime) / (24.0 * 3600)); + if (daysPast == 0) + return _("Today"); + + return _P("1 day", "%x days", daysPast); + } + //return formatTime<std::wstring>(FORMAT_DATE_TIME, getLocalTime(item->lastSyncTime)); + } + return std::wstring(); + } + + void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override + { + wxRect rectTmp = rect; + + wxDCTextColourChanger dummy(dc); //accessibility: always set both foreground AND background colors! + if (selected) + dummy.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT)); + else + { + if (enabled) + dummy.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + else + dummy.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); + } + + if (const ConfigView::Details* item = cfgView_.getItem(row)) + switch (static_cast<ColumnTypeCfg>(colType)) + { + case ColumnTypeCfg::NAME: + rectTmp.x += COLUMN_GAP_LEFT; + rectTmp.width -= COLUMN_GAP_LEFT; + + switch (item->cfgType) + { + case ConfigView::Details::CFG_TYPE_NONE: + break; + case ConfigView::Details::CFG_TYPE_GUI: + drawBitmapRtlNoMirror(dc, enabled ? syncIconSmall_ : syncIconSmall_.ConvertToDisabled(), rectTmp, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + break; + case ConfigView::Details::CFG_TYPE_BATCH: + drawBitmapRtlNoMirror(dc, enabled ? batchIconSmall_ : batchIconSmall_.ConvertToDisabled(), rectTmp, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + break; + } + rectTmp.x += fileIconSize_ + COLUMN_GAP_LEFT; + rectTmp.width -= fileIconSize_ + COLUMN_GAP_LEFT; + + drawCellText(dc, rectTmp, getValue(row, colType)); + break; + + case ColumnTypeCfg::LAST_SYNC: + { + wxDCTextColourChanger dummy2(dc); + if (syncOverdueDays_ > 0) + { + const int daysPast = numeric::round((std::time(nullptr) - item->lastSyncTime) / (24.0 * 3600)); + if (daysPast >= syncOverdueDays_) + dummy2.Set(*wxRED); + } + drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_CENTER); + } + break; + } + } + + int getBestSize(wxDC& dc, size_t row, ColumnType colType) override + { + // -> synchronize renderCell() <-> getBestSize() + + switch (static_cast<ColumnTypeCfg>(colType)) + { + case ColumnTypeCfg::NAME: + return COLUMN_GAP_LEFT + fileIconSize_ + COLUMN_GAP_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth() + COLUMN_GAP_LEFT; + + case ColumnTypeCfg::LAST_SYNC: + return COLUMN_GAP_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth() + COLUMN_GAP_LEFT; + } + return 0; + } + + void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override + { + if (selected) + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)); + else + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + } + + void renderColumnLabel(Grid& tree, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted) override + { + wxRect rectInside = drawColumnLabelBorder(dc, rect); + drawColumnLabelBackground(dc, rectInside, highlighted); + + rectInside.x += COLUMN_GAP_LEFT; + rectInside.width -= COLUMN_GAP_LEFT; + drawColumnLabelText(dc, rectInside, getColumnLabel(colType)); + + auto sortInfo = cfgView_.getSortDirection(); + if (colType == static_cast<ColumnType>(sortInfo.first)) + { + const wxBitmap& marker = getResourceImage(sortInfo.second ? L"sortAscending" : L"sortDescending"); + drawBitmapRtlNoMirror(dc, marker, rectInside, wxALIGN_CENTER_HORIZONTAL); + } + } + + std::wstring getColumnLabel(ColumnType colType) const override + { + switch (static_cast<ColumnTypeCfg>(colType)) + { + case ColumnTypeCfg::NAME: + return _("Name"); + case ColumnTypeCfg::LAST_SYNC: + return _("Last sync"); + } + return std::wstring(); + } + +private: + ConfigView cfgView_; + int syncOverdueDays_ = 0; + const int fileIconSize_; + const wxBitmap syncIconSmall_ = getResourceImage(L"sync" ).ConvertToImage().Scale(fileIconSize_, fileIconSize_, wxIMAGE_QUALITY_BILINEAR); //looks sharper than wxIMAGE_QUALITY_HIGH! + const wxBitmap batchIconSmall_ = getResourceImage(L"batch").ConvertToImage().Scale(fileIconSize_, fileIconSize_, wxIMAGE_QUALITY_BILINEAR); +}; +} + + +void cfggrid::init(Grid& grid) +{ + const int rowHeight = GridDataCfg::getRowDefaultHeight(grid); + + auto prov = std::make_shared<GridDataCfg>(rowHeight /*fileIconSize*/); + + grid.setDataProvider(prov); + grid.showRowLabel(false); + grid.setRowHeight(rowHeight); + + grid.setColumnLabelHeight(rowHeight + 2); +} + + +ConfigView& cfggrid::getDataView(Grid& grid) +{ + if (auto* prov = dynamic_cast<GridDataCfg*>(grid.getDataProvider())) + return prov->getDataView(); + throw std::runtime_error("cfggrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); +} + + +void cfggrid::addAndSelect(Grid& grid, const std::vector<Zstring>& filePaths, bool scrollToSelection) +{ + auto* prov = dynamic_cast<GridDataCfg*>(grid.getDataProvider()); + if (!prov) + throw std::runtime_error("cfggrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + + prov->getDataView().addCfgFiles(filePaths); + grid.Refresh(); //[!] let Grid know about changed row count *before* fiddling with selection!!! + + grid.clearSelection(GridEventPolicy::DENY_GRID_EVENT); + + const std::set<Zstring, LessFilePath> pathsSorted(filePaths.begin(), filePaths.end()); + ptrdiff_t selectionTopRow = -1; + + for (size_t i = 0; i < grid.getRowCount(); ++i) + if (const ConfigView::Details* cfg = prov->getDataView().getItem(i)) + { + if (pathsSorted.find(cfg->filePath) != pathsSorted.end()) + { + if (selectionTopRow < 0) + selectionTopRow = i; + + grid.selectRow(i, GridEventPolicy::DENY_GRID_EVENT); + } + } + else + assert(false); + + if (scrollToSelection && selectionTopRow >= 0) + grid.makeRowVisible(selectionTopRow); +} + + +int cfggrid::getSyncOverdueDays(Grid& grid) +{ + if (auto* prov = dynamic_cast<GridDataCfg*>(grid.getDataProvider())) + return prov->getSyncOverdueDays(); + throw std::runtime_error("cfggrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); +} + + +void cfggrid::setSyncOverdueDays(Grid& grid, int syncOverdueDays) +{ + auto* prov = dynamic_cast<GridDataCfg*>(grid.getDataProvider()); + if (!prov) + throw std::runtime_error("cfggrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + + prov->setSyncOverdueDays(syncOverdueDays); + grid.Refresh(); +} diff --git a/FreeFileSync/Source/ui/cfg_grid.h b/FreeFileSync/Source/ui/cfg_grid.h new file mode 100755 index 00000000..8a898aa7 --- /dev/null +++ b/FreeFileSync/Source/ui/cfg_grid.h @@ -0,0 +1,122 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef CONFIG_HISTORY_3248789479826359832 +#define CONFIG_HISTORY_3248789479826359832 + +#include <wx+/grid.h> +#include <zen/zstring.h> + +namespace zen +{ +enum class ColumnTypeCfg +{ + NAME, + LAST_SYNC, +}; + + +struct ColAttributesCfg +{ + ColumnTypeCfg type = ColumnTypeCfg::NAME; + int offset = 0; + int stretch = 0; + bool visible = false; +}; + +inline +std::vector<ColAttributesCfg> getCfgGridDefaultColAttribs() +{ + return + { + { ColumnTypeCfg::NAME, -75, 1, true }, + { ColumnTypeCfg::LAST_SYNC, 75, 0, true }, + }; +} + +const ColumnTypeCfg cfgGridLastSortColumnDefault = ColumnTypeCfg::NAME; + +inline +bool getDefaultSortDirection(ColumnTypeCfg colType) +{ + switch (colType) + { + case ColumnTypeCfg::NAME: + return true; + case ColumnTypeCfg::LAST_SYNC: //actual sort order is "time since last sync" + return false; + } + assert(false); + return true; +} +//--------------------------------------------------------------------------------------------------------------------- +Zstring getLastRunConfigPath(); + + +class ConfigView +{ +public: + ConfigView() {} + + void addCfgFiles(const std::vector<Zstring>& filePaths); + void removeItems(const std::vector<Zstring>& filePaths); + + void setLastSyncTime(const std::vector<std::pair<Zstring /*filePath*/, time_t /*lastSyncTime*/>>& syncTimes); + + struct Details + { + Zstring filePath; + Zstring name; + time_t lastSyncTime = 0; + int lastUseIndex = 0; //support truncating the config list size via last usage, the higher the index the more recent the usage + bool isLastRunCfg = false; //LastRun.ffs_gui + + enum ConfigType + { + CFG_TYPE_NONE, + CFG_TYPE_GUI, + CFG_TYPE_BATCH, + } cfgType = CFG_TYPE_NONE; + }; + + const Details* getItem(size_t row) const; + size_t getRowCount() const { assert(cfgList_.size() == cfgListView_.size()); return cfgListView_.size(); } + + void setSortDirection(ColumnTypeCfg colType, bool ascending); + std::pair<ColumnTypeCfg, bool> getSortDirection() { return std::make_pair(sortColumn_, sortAscending_); } + +private: + ConfigView (const ConfigView&) = delete; + ConfigView& operator=(const ConfigView&) = delete; + + void sortListView(); + template <bool ascending> void sortListViewImpl(); + + const Zstring lastRunConfigPath_ = getLastRunConfigPath(); //let's not use another static... + + using CfgFileList = std::map<Zstring /*file path*/, Details, LessFilePath>; + + CfgFileList cfgList_; + std::vector<CfgFileList::iterator> cfgListView_; //sorted view on cfgList_ + + ColumnTypeCfg sortColumn_ = cfgGridLastSortColumnDefault; + bool sortAscending_ = getDefaultSortDirection(cfgGridLastSortColumnDefault); +}; + + +namespace cfggrid +{ +void init(Grid& grid); +ConfigView& getDataView(Grid& grid); //grid.Refresh() after making changes! + +void addAndSelect(Grid& grid, const std::vector<Zstring>& filePaths, bool scrollToSelection); + +int getSyncOverdueDays(Grid& grid); +void setSyncOverdueDays(Grid& grid, int syncOverdueDays); +} +} + +#endif //CONFIG_HISTORY_3248789479826359832 diff --git a/FreeFileSync/Source/ui/column_attr.h b/FreeFileSync/Source/ui/column_attr.h deleted file mode 100755 index 25a96736..00000000 --- a/FreeFileSync/Source/ui/column_attr.h +++ /dev/null @@ -1,108 +0,0 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#ifndef COLUMN_ATTR_H_189467891346732143213 -#define COLUMN_ATTR_H_189467891346732143213 - -#include <vector> - - -namespace zen -{ -enum class ColumnTypeRim -{ - ITEM_PATH, - SIZE, - DATE, - EXTENSION, -}; - -struct ColumnAttributeRim -{ - ColumnAttributeRim() {} - ColumnAttributeRim(ColumnTypeRim type, int offset, int stretch, bool visible) : type_(type), offset_(offset), stretch_(stretch), visible_(visible) {} - - ColumnTypeRim type_ = ColumnTypeRim::ITEM_PATH; - int offset_ = 0; - int stretch_ = 0;; - bool visible_ = false; -}; - -inline -std::vector<ColumnAttributeRim> getDefaultColumnAttributesLeft() -{ - return //harmonize with main_dlg.cpp::onGridLabelContextRim() => expects stretched ITEM_PATH and non-stretched other columns! - { - { ColumnTypeRim::ITEM_PATH, -100, 1, true }, - { ColumnTypeRim::EXTENSION, 60, 0, false }, - { ColumnTypeRim::DATE, 140, 0, false }, - { ColumnTypeRim::SIZE, 100, 0, true }, - }; -} - -inline -std::vector<ColumnAttributeRim> getDefaultColumnAttributesRight() -{ - return getDefaultColumnAttributesLeft(); //*currently* same default -} - -enum class ItemPathFormat -{ - FULL_PATH, - RELATIVE_PATH, - ITEM_NAME, -}; - -const ItemPathFormat defaultItemPathFormatLeftGrid = ItemPathFormat::RELATIVE_PATH; -const ItemPathFormat defaultItemPathFormatRightGrid = ItemPathFormat::RELATIVE_PATH; - -//------------------------------------------------------------------ - -enum class ColumnTypeCenter -{ - CHECKBOX, - CMP_CATEGORY, - SYNC_ACTION, -}; - -//------------------------------------------------------------------ - -enum class ColumnTypeNavi -{ - FOLDER_NAME, - ITEM_COUNT, - BYTES, -}; - -struct ColumnAttributeNavi -{ - ColumnAttributeNavi() {} - ColumnAttributeNavi(ColumnTypeNavi type, int offset, int stretch, bool visible) : type_(type), offset_(offset), stretch_(stretch), visible_(visible) {} - - ColumnTypeNavi type_ = ColumnTypeNavi::FOLDER_NAME; - int offset_ = 0; - int stretch_ = 0;; - bool visible_ = false; -}; - - -inline -std::vector<ColumnAttributeNavi> getDefaultColumnAttributesNavi() -{ - return //harmonize with tree_view.cpp::onGridLabelContext() => expects stretched FOLDER_NAME and non-stretched other columns! - { - { ColumnTypeNavi::FOLDER_NAME, -120, 1, true }, //stretch to full width and substract sum of fixed size widths - { ColumnTypeNavi::ITEM_COUNT, 60, 0, true }, - { ColumnTypeNavi::BYTES, 60, 0, true }, //GTK needs a few pixels more width - }; -} - -const bool naviGridShowPercentageDefault = true; -const ColumnTypeNavi naviGridLastSortColumnDefault = ColumnTypeNavi::BYTES; //remember sort on navigation panel -const bool naviGridLastSortAscendingDefault = false; // -} - -#endif //COLUMN_ATTR_H_189467891346732143213 diff --git a/FreeFileSync/Source/ui/custom_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp index bcc97859..2a440adc 100755 --- a/FreeFileSync/Source/ui/custom_grid.cpp +++ b/FreeFileSync/Source/ui/file_grid.cpp @@ -4,7 +4,7 @@ // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** -#include "custom_grid.h" +#include "file_grid.h" #include <set> #include <wx/dc.h> #include <wx/settings.h> @@ -21,7 +21,7 @@ #include "../file_hierarchy.h" using namespace zen; -using namespace gridview; +using namespace filegrid; const wxEventType zen::EVENT_GRID_CHECK_ROWS = wxNewEventType(); @@ -151,16 +151,18 @@ private: class GridDataBase : public GridData { public: - GridDataBase(Grid& grid, const std::shared_ptr<const zen::GridView>& gridDataView) : grid_(grid), gridDataView_(gridDataView) {} + GridDataBase(Grid& grid, const std::shared_ptr<FileView>& gridDataView) : grid_(grid), gridDataView_(gridDataView) {} void holdOwnership(const std::shared_ptr<GridEventManager>& evtMgr) { evtMgr_ = evtMgr; } GridEventManager* getEventManager() { return evtMgr_.get(); } + FileView& getDataView() { return *gridDataView_; } + protected: Grid& refGrid() { return grid_; } const Grid& refGrid() const { return grid_; } - const GridView* getGridDataView() const { return gridDataView_.get(); } + const FileView* getGridDataView() const { return gridDataView_.get(); } const FileSystemObject* getRawData(size_t row) const { @@ -181,7 +183,7 @@ private: std::shared_ptr<GridEventManager> evtMgr_; Grid& grid_; - std::shared_ptr<const GridView> gridDataView_; + const std::shared_ptr<FileView> gridDataView_; }; //######################################################################################################## @@ -190,7 +192,7 @@ template <SelectedSide side> class GridDataRim : public GridDataBase { public: - GridDataRim(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataBase(grid, gridDataView) {} + GridDataRim(const std::shared_ptr<FileView>& gridDataView, Grid& grid) : GridDataBase(grid, gridDataView) {} void setIconManager(const std::shared_ptr<IconManager>& iconMgr) { iconMgr_ = iconMgr; } @@ -682,9 +684,9 @@ private: auto sortInfo = getGridDataView()->getSortInfo(); if (sortInfo) { - if (colType == static_cast<ColumnType>(sortInfo->type_) && (side == LEFT_SIDE) == sortInfo->onLeft_) + if (colType == static_cast<ColumnType>(sortInfo->type) && (side == LEFT_SIDE) == sortInfo->onLeft) { - const wxBitmap& marker = getResourceImage(sortInfo->ascending_ ? L"sortAscending" : L"sortDescending"); + const wxBitmap& marker = getResourceImage(sortInfo->ascending ? L"sortAscending" : L"sortDescending"); drawBitmapRtlNoMirror(dc, marker, rectInside, wxALIGN_CENTER_HORIZONTAL); } } @@ -766,14 +768,14 @@ private: ItemPathFormat itemPathFormat = ItemPathFormat::FULL_PATH; std::vector<char> failedLoads; //effectively a vector<bool> of size "number of rows" - Opt<wxBitmap> renderBuf; //avoid costs of recreating this temporal variable + Opt<wxBitmap> renderBuf; //avoid costs of recreating this temporary variable }; class GridDataLeft : public GridDataRim<LEFT_SIDE> { public: - GridDataLeft(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataRim<LEFT_SIDE>(gridDataView, grid) {} + GridDataLeft(const std::shared_ptr<FileView>& gridDataView, Grid& grid) : GridDataRim<LEFT_SIDE>(gridDataView, grid) {} void setNavigationMarker(std::unordered_set<const FileSystemObject*>&& markedFilesAndLinks, std::unordered_set<const ContainerObject*>&& markedContainer) @@ -787,7 +789,7 @@ private: { GridDataRim<LEFT_SIDE>::renderRowBackgound(dc, rect, row, enabled, selected); - //mark rows selected on navigation grid: + //mark rows selected on overview panel: if (enabled && !selected) { const bool markRow = [&] @@ -837,7 +839,7 @@ private: class GridDataRight : public GridDataRim<RIGHT_SIDE> { public: - GridDataRight(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataRim<RIGHT_SIDE>(gridDataView, grid) {} + GridDataRight(const std::shared_ptr<FileView>& gridDataView, Grid& grid) : GridDataRim<RIGHT_SIDE>(gridDataView, grid) {} }; //######################################################################################################## @@ -845,15 +847,15 @@ public: class GridDataCenter : public GridDataBase { public: - GridDataCenter(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : + GridDataCenter(const std::shared_ptr<FileView>& gridDataView, Grid& grid) : GridDataBase(grid, gridDataView), - toolTip(grid) {} //tool tip must not live longer than grid! + toolTip_(grid) {} //tool tip must not live longer than grid! void onSelectBegin() { - selectionInProgress = true; + selectionInProgress_ = true; refGrid().clearSelection(DENY_GRID_EVENT); //don't emit event, prevent recursion! - toolTip.hide(); //handle custom tooltip + toolTip_.hide(); //handle custom tooltip } void onSelectEnd(size_t rowFirst, size_t rowLast, HoverArea rowHover, ptrdiff_t clickInitRow) @@ -861,7 +863,7 @@ public: refGrid().clearSelection(DENY_GRID_EVENT); //don't emit event, prevent recursion! //issue custom event - if (selectionInProgress) //don't process selections initiated by right-click + 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 (static_cast<HoverAreaCenter>(rowHover)) @@ -893,7 +895,7 @@ public: } break; } - selectionInProgress = false; + 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()); @@ -903,7 +905,7 @@ public: void onMouseMovement(const wxPoint& clientPos) { //manage block highlighting and custom tooltip - if (!selectionInProgress) + if (!selectionInProgress_) { 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 @@ -913,13 +915,13 @@ public: 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 - toolTip.hide(); + toolTip_.hide(); } } void onMouseLeave() //wxEVT_LEAVE_WINDOW does not respect mouse capture! { - toolTip.hide(); //handle custom tooltip + toolTip_.hide(); //handle custom tooltip } void highlightSyncAction(bool value) { highlightSyncAction_ = value; } @@ -987,9 +989,9 @@ private: const bool drawMouseHover = static_cast<HoverAreaCenter>(rowHover) == HoverAreaCenter::CHECK_BOX; if (fsObj->isActive()) - drawBitmapRtlMirror(dc, getResourceImage(drawMouseHover ? L"checkbox_true_hover" : L"checkbox_true"), rect, wxALIGN_CENTER, renderBuf); + drawBitmapRtlMirror(dc, getResourceImage(drawMouseHover ? L"checkbox_true_hover" : L"checkbox_true"), rect, wxALIGN_CENTER, renderBuf_); else //default - drawBitmapRtlMirror(dc, getResourceImage(drawMouseHover ? L"checkbox_false_hover" : L"checkbox_false"), rect, wxALIGN_CENTER, renderBuf); + drawBitmapRtlMirror(dc, getResourceImage(drawMouseHover ? L"checkbox_false_hover" : L"checkbox_false"), rect, wxALIGN_CENTER, renderBuf_); } break; @@ -1002,19 +1004,19 @@ private: wxRect rectTmp = rect; { //draw notch on left side - if (notch.GetHeight() != rectTmp.GetHeight()) - notch.Rescale(notch.GetWidth(), rectTmp.GetHeight()); + if (notch_.GetHeight() != rectTmp.GetHeight()) + notch_.Rescale(notch_.GetWidth(), rectTmp.GetHeight()); //wxWidgets screws up again and has wxALIGN_RIGHT off by one pixel! -> use wxALIGN_LEFT instead - const wxRect rectNotch(rectTmp.x + rectTmp.width - notch.GetWidth(), rectTmp.y, notch.GetWidth(), rectTmp.height); - drawBitmapRtlMirror(dc, notch, rectNotch, wxALIGN_LEFT, renderBuf); - rectTmp.width -= notch.GetWidth(); + const wxRect rectNotch(rectTmp.x + rectTmp.width - notch_.GetWidth(), rectTmp.y, notch_.GetWidth(), rectTmp.height); + drawBitmapRtlMirror(dc, notch_, rectNotch, wxALIGN_LEFT, renderBuf_); + rectTmp.width -= notch_.GetWidth(); } if (!highlightSyncAction_) - drawBitmapRtlMirror(dc, getCmpResultImage(fsObj->getCategory()), rectTmp, wxALIGN_CENTER, renderBuf); + drawBitmapRtlMirror(dc, getCmpResultImage(fsObj->getCategory()), rectTmp, wxALIGN_CENTER, renderBuf_); else if (fsObj->getCategory() != FILE_EQUAL) //don't show = in both middle columns - drawBitmapRtlMirror(dc, greyScale(getCmpResultImage(fsObj->getCategory())), rectTmp, wxALIGN_CENTER, renderBuf); + drawBitmapRtlMirror(dc, greyScale(getCmpResultImage(fsObj->getCategory())), rectTmp, wxALIGN_CENTER, renderBuf_); } break; @@ -1029,19 +1031,19 @@ private: switch (rowHoverCenter) { case HoverAreaCenter::DIR_LEFT: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::LEFT)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, renderBuf); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::LEFT)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, renderBuf_); break; case HoverAreaCenter::DIR_NONE: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::NONE)), rect, wxALIGN_CENTER, renderBuf); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::NONE)), rect, wxALIGN_CENTER, renderBuf_); break; case HoverAreaCenter::DIR_RIGHT: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::RIGHT)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, renderBuf); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::RIGHT)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, renderBuf_); break; case HoverAreaCenter::CHECK_BOX: if (highlightSyncAction_) - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->getSyncOperation()), rect, wxALIGN_CENTER, renderBuf); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->getSyncOperation()), rect, wxALIGN_CENTER, renderBuf_); else if (fsObj->getSyncOperation() != SO_EQUAL) //don't show = in both middle columns - drawBitmapRtlMirror(dc, greyScale(getSyncOpImage(fsObj->getSyncOperation())), rect, wxALIGN_CENTER, renderBuf); + drawBitmapRtlMirror(dc, greyScale(getSyncOpImage(fsObj->getSyncOperation())), rect, wxALIGN_CENTER, renderBuf_); break; } } @@ -1226,7 +1228,7 @@ private: return L""; }(); const auto& img = mirrorIfRtl(getResourceImage(imageName)); - toolTip.show(getCategoryDescription(*fsObj), posScreen, &img); + toolTip_.show(getCategoryDescription(*fsObj), posScreen, &img); } break; @@ -1272,21 +1274,21 @@ private: return L""; }(); const auto& img = mirrorIfRtl(getResourceImage(imageName)); - toolTip.show(getSyncOpDescription(*fsObj), posScreen, &img); + toolTip_.show(getSyncOpDescription(*fsObj), posScreen, &img); } break; } } else - toolTip.hide(); //if invalid row... + toolTip_.hide(); //if invalid row... } bool highlightSyncAction_ = false; - bool selectionInProgress = false; + bool selectionInProgress_ = false; - Opt<wxBitmap> renderBuf; //avoid costs of recreating this temporal variable - Tooltip toolTip; - wxImage notch { getResourceImage(L"notch").ConvertToImage() }; + Opt<wxBitmap> renderBuf_; //avoid costs of recreating this temporary variable + Tooltip toolTip_; + wxImage notch_ = getResourceImage(L"notch").ConvertToImage(); }; //######################################################################################################## @@ -1306,19 +1308,19 @@ public: gridL_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnL), nullptr, this); gridR_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnR), nullptr, this); - gridL_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (GridEventManager::onKeyDownL), nullptr, this); - gridC_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (GridEventManager::onKeyDownC), nullptr, this); - gridR_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (GridEventManager::onKeyDownR), nullptr, this); + gridL_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GridEventManager::onKeyDownL), nullptr, this); + gridC_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GridEventManager::onKeyDownC), nullptr, this); + gridR_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GridEventManager::onKeyDownR), nullptr, this); gridC_.getMainWin().Connect(wxEVT_MOTION, wxMouseEventHandler(GridEventManager::onCenterMouseMovement), nullptr, this); gridC_.getMainWin().Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(GridEventManager::onCenterMouseLeave ), nullptr, this); - gridC_.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler (GridEventManager::onCenterSelectBegin), nullptr, this); - gridC_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onCenterSelectEnd ), nullptr, this); + gridC_.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler (GridEventManager::onCenterSelectBegin), nullptr, this); + gridC_.Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(GridEventManager::onCenterSelectEnd ), nullptr, this); //clear selection of other grid when selecting on - gridL_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionL), nullptr, this); - gridR_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionR), nullptr, this); + gridL_.Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(GridEventManager::onGridSelectionL), nullptr, this); + gridR_.Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(GridEventManager::onGridSelectionR), nullptr, this); //parallel grid scrolling: do NOT use DoPrepareDC() to align grids! GDI resource leak! Use regular paint event instead: gridL_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridL), nullptr, this); @@ -1355,9 +1357,9 @@ public: Connect(EVENT_ALIGN_SCROLLBARS, wxEventHandler(GridEventManager::onAlignScrollBars), NULL, this); } - ~GridEventManager() { assert(!scrollbarUpdatePending); } + ~GridEventManager() { assert(!scrollbarUpdatePending_); } - void setScrollMaster(const Grid& grid) { scrollMaster = &grid; } + void setScrollMaster(const Grid& grid) { scrollMaster_ = &grid; } private: void onCenterSelectBegin(GridClickEvent& event) @@ -1366,12 +1368,12 @@ private: event.Skip(); } - void onCenterSelectEnd(GridRangeSelectEvent& event) + void onCenterSelectEnd(GridSelectEvent& event) { if (event.positive_) { - if (event.mouseInitiated_) - provCenter_.onSelectEnd(event.rowFirst_, event.rowLast_, event.mouseInitiated_->hoverArea_, event.mouseInitiated_->row_); + if (event.mouseSelect_) + provCenter_.onSelectEnd(event.rowFirst_, event.rowLast_, event.mouseSelect_->click.hoverArea_, event.mouseSelect_->click.row_); else provCenter_.onSelectEnd(event.rowFirst_, event.rowLast_, HoverArea::NONE, -1); } @@ -1390,8 +1392,8 @@ private: event.Skip(); } - void onGridSelectionL(GridRangeSelectEvent& event) { onGridSelection(gridL_, gridR_); event.Skip(); } - void onGridSelectionR(GridRangeSelectEvent& event) { onGridSelection(gridR_, gridL_); event.Skip(); } + void onGridSelectionL(GridSelectEvent& event) { onGridSelection(gridL_, gridR_); event.Skip(); } + void onGridSelectionR(GridSelectEvent& event) { onGridSelection(gridR_, gridL_); event.Skip(); } void onGridSelection(const Grid& grid, Grid& other) { @@ -1429,14 +1431,14 @@ private: gridL_.setGridCursor(row); gridL_.SetFocus(); //since key event is likely originating from right grid, we need to set scrollMaster manually! - scrollMaster = &gridL_; //onKeyDown is called *after* onGridAccessL()! + scrollMaster_ = &gridL_; //onKeyDown is called *after* onGridAccessL()! return; //swallow event case WXK_RIGHT: case WXK_NUMPAD_RIGHT: gridR_.setGridCursor(row); gridR_.SetFocus(); - scrollMaster = &gridR_; + scrollMaster_ = &gridR_; return; //swallow event } @@ -1449,27 +1451,27 @@ private: void resizeOtherSide(const Grid& src, Grid& trg, ColumnType type, int offset) { //find stretch factor of resized column: type is unique due to makeConsistent()! - std::vector<Grid::ColumnAttribute> cfgSrc = src.getColumnConfig(); - auto it = std::find_if(cfgSrc.begin(), cfgSrc.end(), [&](Grid::ColumnAttribute& ca) { return ca.type_ == type; }); + std::vector<Grid::ColAttributes> cfgSrc = src.getColumnConfig(); + auto it = std::find_if(cfgSrc.begin(), cfgSrc.end(), [&](Grid::ColAttributes& ca) { return ca.type == type; }); if (it == cfgSrc.end()) return; - const int stretchSrc = it->stretch_; + const int stretchSrc = it->stretch; //we do not propagate resizings on stretched columns to the other side: awkward user experience if (stretchSrc > 0) return; //apply resized offset to other side, but only if stretch factors match! - std::vector<Grid::ColumnAttribute> cfgTrg = trg.getColumnConfig(); - for (Grid::ColumnAttribute& ca : cfgTrg) - if (ca.type_ == type && ca.stretch_ == stretchSrc) - ca.offset_ = offset; + std::vector<Grid::ColAttributes> cfgTrg = trg.getColumnConfig(); + for (Grid::ColAttributes& ca : cfgTrg) + if (ca.type == type && ca.stretch == stretchSrc) + ca.offset = offset; trg.setColumnConfig(cfgTrg); } - void onGridAccessL(wxEvent& event) { scrollMaster = &gridL_; event.Skip(); } - void onGridAccessC(wxEvent& event) { scrollMaster = &gridC_; event.Skip(); } - void onGridAccessR(wxEvent& event) { scrollMaster = &gridR_; event.Skip(); } + void onGridAccessL(wxEvent& event) { scrollMaster_ = &gridL_; event.Skip(); } + void onGridAccessC(wxEvent& event) { scrollMaster_ = &gridC_; event.Skip(); } + void onGridAccessR(wxEvent& event) { scrollMaster_ = &gridR_; event.Skip(); } void onPaintGridL(wxEvent& event) { onPaintGrid(gridL_); event.Skip(); } void onPaintGridC(wxEvent& event) { onPaintGrid(gridC_); event.Skip(); } @@ -1485,9 +1487,9 @@ private: Grid* follow2 = nullptr; auto setGrids = [&](const Grid& l, Grid& f1, Grid& f2) { lead = &l; follow1 = &f1; follow2 = &f2; }; - if (&gridC_ == scrollMaster) + if (&gridC_ == scrollMaster_) setGrids(gridC_, gridL_, gridR_); - else if (&gridR_ == scrollMaster) + else if (&gridR_ == scrollMaster_) setGrids(gridR_, gridL_, gridC_); else //default: left panel setGrids(gridL_, gridC_, gridR_); @@ -1514,9 +1516,9 @@ private: //avoids at least this problem: remaining graphics artifact when changing from Grid::SB_SHOW_ALWAYS to Grid::SB_SHOW_NEVER at location of old scrollbar (Windows only) //perf note: send one async event at most, else they may accumulate and create perf issues, see grid.cpp - if (!scrollbarUpdatePending) + if (!scrollbarUpdatePending_) { - scrollbarUpdatePending = true; + scrollbarUpdatePending_ = true; wxCommandEvent alignEvent(EVENT_ALIGN_SCROLLBARS); AddPendingEvent(alignEvent); //waits until next idle event - may take up to a second if the app is busy on wxGTK! } @@ -1524,8 +1526,8 @@ private: void onAlignScrollBars(wxEvent& event) { - ZEN_ON_SCOPE_EXIT(scrollbarUpdatePending = false); - assert(scrollbarUpdatePending); + ZEN_ON_SCOPE_EXIT(scrollbarUpdatePending_ = false); + assert(scrollbarUpdatePending_); auto needsHorizontalScrollbars = [](const Grid& grid) -> bool { @@ -1551,18 +1553,20 @@ private: Grid& gridC_; Grid& gridR_; - const Grid* scrollMaster = nullptr; //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 GridDataCenter& provCenter_; - bool scrollbarUpdatePending = false; + bool scrollbarUpdatePending_ = false; }; } //######################################################################################################## -void gridview::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std::shared_ptr<const zen::GridView>& gridDataView) +void filegrid::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) { + const auto gridDataView = std::make_shared<FileView>(); + auto provLeft_ = std::make_shared<GridDataLeft >(gridDataView, gridLeft); auto provCenter_ = std::make_shared<GridDataCenter>(gridDataView, gridCenter); auto provRight_ = std::make_shared<GridDataRight >(gridDataView, gridRight); @@ -1599,41 +1603,12 @@ void gridview::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std } -namespace -{ -std::vector<ColumnAttributeRim> makeConsistent(const std::vector<ColumnAttributeRim>& attribs) -{ - std::set<ColumnTypeRim> usedTypes; - - std::vector<ColumnAttributeRim> output; - //remove duplicates: required by GridEventManager::resizeOtherSide() to find corresponding column on other side - 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 - const auto& defAttr = getDefaultColumnAttributesLeft(); - std::copy_if(defAttr.begin(), defAttr.end(), std::back_inserter(output), - [&](const ColumnAttributeRim& a) { return usedTypes.insert(a.type_).second; }); - - return output; -} -} - -std::vector<Grid::ColumnAttribute> gridview::convertConfig(const std::vector<ColumnAttributeRim>& attribs) +FileView& filegrid::getDataView(Grid& grid) { - std::vector<Grid::ColumnAttribute> output; - for (const ColumnAttributeRim& ca : makeConsistent(attribs)) - output.emplace_back(static_cast<ColumnType>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); - return output; -} - + if (auto* prov = dynamic_cast<GridDataBase*>(grid.getDataProvider())) + return prov->getDataView(); -std::vector<ColumnAttributeRim> gridview::convertConfig(const std::vector<Grid::ColumnAttribute>& attribs) -{ - std::vector<ColumnAttributeRim> output; - for (const Grid::ColumnAttribute& ca : attribs) - output.emplace_back(static_cast<ColumnTypeRim>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); - return makeConsistent(output); + throw std::runtime_error("filegrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); } @@ -1677,7 +1652,7 @@ private: stop(); } - GridDataLeft& provLeft_; + GridDataLeft& provLeft_; GridDataRight& provRight_; IconBuffer& iconBuffer_; wxTimer timer_; @@ -1690,7 +1665,7 @@ void IconManager::startIconUpdater() { if (iconUpdater) iconUpdater->start(); } } -void gridview::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, bool show, IconBuffer::IconSize sz) +void filegrid::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, bool show, IconBuffer::IconSize sz) { auto* provLeft = dynamic_cast<GridDataLeft*>(gridLeft .getDataProvider()); auto* provRight = dynamic_cast<GridDataRight*>(gridRight.getDataProvider()); @@ -1723,7 +1698,7 @@ void gridview::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, boo } -void gridview::setItemPathForm(Grid& grid, ItemPathFormat fmt) +void filegrid::setItemPathForm(Grid& grid, ItemPathFormat fmt) { if (auto* provLeft = dynamic_cast<GridDataLeft*>(grid.getDataProvider())) provLeft->setItemPathForm(fmt); @@ -1735,7 +1710,7 @@ void gridview::setItemPathForm(Grid& grid, ItemPathFormat fmt) } -void gridview::refresh(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) +void filegrid::refresh(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) { gridLeft .Refresh(); gridCenter.Refresh(); @@ -1743,7 +1718,7 @@ void gridview::refresh(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) } -void gridview::setScrollMaster(Grid& grid) +void filegrid::setScrollMaster(Grid& grid) { if (auto prov = dynamic_cast<GridDataBase*>(grid.getDataProvider())) if (auto evtMgr = prov->getEventManager()) @@ -1755,7 +1730,7 @@ void gridview::setScrollMaster(Grid& grid) } -void gridview::setNavigationMarker(Grid& gridLeft, +void filegrid::setNavigationMarker(Grid& gridLeft, std::unordered_set<const FileSystemObject*>&& markedFilesAndLinks, std::unordered_set<const ContainerObject*>&& markedContainer) { @@ -1767,7 +1742,7 @@ void gridview::setNavigationMarker(Grid& gridLeft, } -void gridview::highlightSyncAction(Grid& gridCenter, bool value) +void filegrid::highlightSyncAction(Grid& gridCenter, bool value) { if (auto provCenter = dynamic_cast<GridDataCenter*>(gridCenter.getDataProvider())) provCenter->highlightSyncAction(value); diff --git a/FreeFileSync/Source/ui/custom_grid.h b/FreeFileSync/Source/ui/file_grid.h index c5353ba9..40853ceb 100755 --- a/FreeFileSync/Source/ui/custom_grid.h +++ b/FreeFileSync/Source/ui/file_grid.h @@ -8,20 +8,19 @@ #define CUSTOM_GRID_H_8405817408327894 #include <wx+/grid.h> -#include "grid_view.h" -#include "column_attr.h" +#include "file_view.h" +#include "file_grid_attr.h" #include "../lib/icon_buffer.h" namespace zen { //setup grid to show grid view within three components: -namespace gridview +namespace filegrid { -void init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std::shared_ptr<const GridView>& gridDataView); +void init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight); +FileView& getDataView(Grid& grid); -std::vector<Grid::ColumnAttribute> convertConfig(const std::vector< ColumnAttributeRim>& attribs); //+ make consistent -std::vector<ColumnAttributeRim> convertConfig(const std::vector<Grid::ColumnAttribute>& attribs); // void highlightSyncAction(Grid& gridCenter, bool value); @@ -33,7 +32,7 @@ void refresh(Grid& gridLeft, Grid& gridCenter, Grid& gridRight); void setScrollMaster(Grid& grid); -//mark rows selected in navigation/compressed tree and navigate to leading object +//mark rows selected in overview panel and navigate to leading object void setNavigationMarker(Grid& gridLeft, std::unordered_set<const FileSystemObject*>&& markedFilesAndLinks,//mark files/symlinks directly within a container std::unordered_set<const ContainerObject*>&& markedContainer); //mark full container including child-objects diff --git a/FreeFileSync/Source/ui/file_grid_attr.h b/FreeFileSync/Source/ui/file_grid_attr.h new file mode 100755 index 00000000..0257f268 --- /dev/null +++ b/FreeFileSync/Source/ui/file_grid_attr.h @@ -0,0 +1,91 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef COLUMN_ATTR_H_189467891346732143213 +#define COLUMN_ATTR_H_189467891346732143213 + +#include <vector> +#include <cassert> + + +namespace zen +{ +enum class ColumnTypeRim +{ + ITEM_PATH, + SIZE, + DATE, + EXTENSION, +}; + +struct ColAttributesRim +{ + ColumnTypeRim type = ColumnTypeRim::ITEM_PATH; + int offset = 0; + int stretch = 0; + bool visible = false; +}; + +inline +std::vector<ColAttributesRim> getFileGridDefaultColAttribsLeft() +{ + return //harmonize with main_dlg.cpp::onGridLabelContextRim() => expects stretched ITEM_PATH and non-stretched other columns! + { + { ColumnTypeRim::ITEM_PATH, -100, 1, true }, + { ColumnTypeRim::EXTENSION, 60, 0, false }, + { ColumnTypeRim::DATE, 140, 0, false }, + { ColumnTypeRim::SIZE, 100, 0, true }, + }; +} + +inline +std::vector<ColAttributesRim> getFileGridDefaultColAttribsRight() +{ + return getFileGridDefaultColAttribsLeft(); //*currently* same default +} + + +inline +bool getDefaultSortDirection(ColumnTypeRim type) //true: ascending; false: descending +{ + switch (type) + { + case ColumnTypeRim::SIZE: + case ColumnTypeRim::DATE: + return false; + + case ColumnTypeRim::ITEM_PATH: + case ColumnTypeRim::EXTENSION: + return true; + } + assert(false); + return true; +} + + +enum class ItemPathFormat +{ + FULL_PATH, + RELATIVE_PATH, + ITEM_NAME, +}; + +const ItemPathFormat defaultItemPathFormatLeftGrid = ItemPathFormat::RELATIVE_PATH; +const ItemPathFormat defaultItemPathFormatRightGrid = ItemPathFormat::RELATIVE_PATH; + +//------------------------------------------------------------------ + +enum class ColumnTypeCenter +{ + CHECKBOX, + CMP_CATEGORY, + SYNC_ACTION, +}; + +//------------------------------------------------------------------ +} + +#endif //COLUMN_ATTR_H_189467891346732143213 diff --git a/FreeFileSync/Source/ui/grid_view.cpp b/FreeFileSync/Source/ui/file_view.cpp index 0de75f14..f2527520 100755 --- a/FreeFileSync/Source/ui/grid_view.cpp +++ b/FreeFileSync/Source/ui/file_view.cpp @@ -4,7 +4,7 @@ // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** -#include "grid_view.h" +#include "file_view.h" #include "sorting.h" #include "../synchronization.h" #include <zen/stl_tools.h> @@ -51,7 +51,7 @@ void addNumbers(const FileSystemObject& fsObj, StatusResult& result) template <class Predicate> -void GridView::updateView(Predicate pred) +void FileView::updateView(Predicate pred) { viewRef_.clear(); rowPositions_.clear(); @@ -87,20 +87,21 @@ void GridView::updateView(Predicate pred) } -ptrdiff_t GridView::findRowDirect(FileSystemObject::ObjectIdConst objId) const +ptrdiff_t FileView::findRowDirect(FileSystemObject::ObjectIdConst objId) const { auto it = rowPositions_.find(objId); return it != rowPositions_.end() ? it->second : -1; } -ptrdiff_t GridView::findRowFirstChild(const ContainerObject* hierObj) const + +ptrdiff_t FileView::findRowFirstChild(const ContainerObject* hierObj) const { auto it = rowPositionsFirstChild_.find(hierObj); return it != rowPositionsFirstChild_.end() ? it->second : -1; } -GridView::StatusCmpResult GridView::updateCmpResult(bool showExcluded, //maps sortedRef to viewRef +FileView::StatusCmpResult FileView::updateCmpResult(bool showExcluded, //maps sortedRef to viewRef bool leftOnlyFilesActive, bool rightOnlyFilesActive, bool leftNewerFilesActive, @@ -161,7 +162,7 @@ GridView::StatusCmpResult GridView::updateCmpResult(bool showExcluded, //maps so } -GridView::StatusSyncPreview GridView::updateSyncPreview(bool showExcluded, //maps sortedRef to viewRef +FileView::StatusSyncPreview FileView::updateSyncPreview(bool showExcluded, //maps sortedRef to viewRef bool syncCreateLeftActive, bool syncCreateRightActive, bool syncDeleteLeftActive, @@ -238,7 +239,7 @@ GridView::StatusSyncPreview GridView::updateSyncPreview(bool showExcluded, //map } -std::vector<FileSystemObject*> GridView::getAllFileRef(const std::vector<size_t>& rows) +std::vector<FileSystemObject*> FileView::getAllFileRef(const std::vector<size_t>& rows) { const size_t viewSize = viewRef_.size(); @@ -253,7 +254,7 @@ std::vector<FileSystemObject*> GridView::getAllFileRef(const std::vector<size_t> } -void GridView::removeInvalidRows() +void FileView::removeInvalidRows() { viewRef_.clear(); rowPositions_.clear(); @@ -264,15 +265,16 @@ void GridView::removeInvalidRows() } -class GridView::SerializeHierarchy +class FileView::SerializeHierarchy { public: - static void execute(ContainerObject& hierObj, std::vector<GridView::RefIndex>& sortedRef, size_t index) { SerializeHierarchy(sortedRef, index).recurse(hierObj); } + static void execute(ContainerObject& hierObj, std::vector<FileView::RefIndex>& sortedRef, size_t index) { SerializeHierarchy(sortedRef, index).recurse(hierObj); } private: - SerializeHierarchy(std::vector<GridView::RefIndex>& sortedRef, size_t index) : + SerializeHierarchy(std::vector<FileView::RefIndex>& sortedRef, size_t index) : index_(index), output_(sortedRef) {} +#if 0 /* Spend additional CPU cycles to sort the standard file list? @@ -283,7 +285,6 @@ private: CmpAsciiNoCase: 189 ms No sorting: 30 ms */ -#if 0 template <class ItemPair> static std::vector<ItemPair*> getItemsSorted(FixedList<ItemPair>& itemList) { @@ -298,24 +299,24 @@ private: void recurse(ContainerObject& hierObj) { for (FilePair& file : hierObj.refSubFiles()) - output_.emplace_back(index_, file.getId()); + output_.push_back({ index_, file.getId() }); for (SymlinkPair& symlink : hierObj.refSubLinks()) - output_.emplace_back(index_, symlink.getId()); + output_.push_back({ index_, symlink.getId() }); for (FolderPair& folder : hierObj.refSubFolders()) { - output_.emplace_back(index_, folder.getId()); + output_.push_back({ index_, folder.getId() }); recurse(folder); //add recursion here to list sub-objects directly below parent! } } const size_t index_; - std::vector<GridView::RefIndex>& output_; + std::vector<FileView::RefIndex>& output_; }; -void GridView::setData(FolderComparison& folderCmp) +void FileView::setData(FolderComparison& folderCmp) { //clear everything std::vector<FileSystemObject::ObjectId>().swap(viewRef_); //free mem @@ -336,7 +337,7 @@ void GridView::setData(FolderComparison& folderCmp) //------------------------------------ SORTING TEMPLATES ------------------------------------------------ template <bool ascending, SelectedSide side> -struct GridView::LessFullPath +struct FileView::LessFullPath { bool operator()(const RefIndex a, const RefIndex b) const { @@ -353,7 +354,7 @@ struct GridView::LessFullPath template <bool ascending> -struct GridView::LessRelativeFolder +struct FileView::LessRelativeFolder { bool operator()(const RefIndex a, const RefIndex b) const { @@ -376,7 +377,7 @@ struct GridView::LessRelativeFolder template <bool ascending, SelectedSide side> -struct GridView::LessShortFileName +struct FileView::LessShortFileName { bool operator()(const RefIndex a, const RefIndex b) const { @@ -393,7 +394,7 @@ struct GridView::LessShortFileName template <bool ascending, SelectedSide side> -struct GridView::LessFilesize +struct FileView::LessFilesize { bool operator()(const RefIndex a, const RefIndex b) const { @@ -410,7 +411,7 @@ struct GridView::LessFilesize template <bool ascending, SelectedSide side> -struct GridView::LessFiletime +struct FileView::LessFiletime { bool operator()(const RefIndex a, const RefIndex b) const { @@ -427,7 +428,7 @@ struct GridView::LessFiletime template <bool ascending, SelectedSide side> -struct GridView::LessExtension +struct FileView::LessExtension { bool operator()(const RefIndex a, const RefIndex b) const { @@ -444,7 +445,7 @@ struct GridView::LessExtension template <bool ascending> -struct GridView::LessCmpResult +struct FileView::LessCmpResult { bool operator()(const RefIndex a, const RefIndex b) const { @@ -461,7 +462,7 @@ struct GridView::LessCmpResult template <bool ascending> -struct GridView::LessSyncDirection +struct FileView::LessSyncDirection { bool operator()(const RefIndex a, const RefIndex b) const { @@ -477,29 +478,13 @@ struct GridView::LessSyncDirection }; //------------------------------------------------------------------------------------------------------- -bool GridView::getDefaultSortDirection(ColumnTypeRim type) //true: ascending; false: descending -{ - switch (type) - { - case ColumnTypeRim::SIZE: - case ColumnTypeRim::DATE: - return false; - - case ColumnTypeRim::ITEM_PATH: - case ColumnTypeRim::EXTENSION: - return true; - } - assert(false); - return true; -} - -void GridView::sortView(ColumnTypeRim type, ItemPathFormat pathFmt, bool onLeft, bool ascending) +void FileView::sortView(ColumnTypeRim type, ItemPathFormat pathFmt, bool onLeft, bool ascending) { viewRef_.clear(); rowPositions_.clear(); rowPositionsFirstChild_.clear(); - currentSort_ = SortInfo(type, onLeft, ascending); + currentSort_ = SortInfo({ type, onLeft, ascending }); switch (type) { diff --git a/FreeFileSync/Source/ui/grid_view.h b/FreeFileSync/Source/ui/file_view.h index 70838122..ad399401 100755 --- a/FreeFileSync/Source/ui/grid_view.h +++ b/FreeFileSync/Source/ui/file_view.h @@ -9,17 +9,17 @@ #include <vector> #include <unordered_map> -#include "column_attr.h" +#include "file_grid_attr.h" #include "../file_hierarchy.h" namespace zen { //grid view of FolderComparison -class GridView +class FileView { public: - GridView() {} + FileView() {} //direct data access via row number const FileSystemObject* getObject(size_t row) const; //returns nullptr if object is not found; complexity: constant! @@ -101,16 +101,13 @@ public: void removeInvalidRows(); //remove references to rows that have been deleted meanwhile: call after manual deletion and synchronization! //sorting... - bool static getDefaultSortDirection(zen::ColumnTypeRim type); //true: ascending; false: descending - void sortView(zen::ColumnTypeRim type, zen::ItemPathFormat pathFmt, bool onLeft, bool ascending); //always call this method for sorting, never sort externally! struct SortInfo { - SortInfo(zen::ColumnTypeRim type, bool onLeft, bool ascending) : type_(type), onLeft_(onLeft), ascending_(ascending) {} - zen::ColumnTypeRim type_; - bool onLeft_; - bool ascending_; + zen::ColumnTypeRim type = zen::ColumnTypeRim::ITEM_PATH; + bool onLeft = false; + bool ascending = false; }; const SortInfo* getSortInfo() const { return currentSort_.get(); } //return nullptr if currently not sorted @@ -121,16 +118,13 @@ public: size_t getFolderPairCount() const { return folderPairCount_; } //count non-empty pairs to distinguish single/multiple folder pair cases private: - GridView (const GridView&) = delete; - GridView& operator=(const GridView&) = delete; + FileView (const FileView&) = delete; + FileView& operator=(const FileView&) = delete; struct RefIndex { - RefIndex(size_t folderInd, FileSystemObject::ObjectId id) : - folderIndex(folderInd), - objId(id) {} - size_t folderIndex; //because of alignment there's no benefit in using "unsigned int" in 64-bit code here! - FileSystemObject::ObjectId objId; + size_t folderIndex = 0; //because of alignment there's no benefit in using "unsigned int" in 64-bit code here! + FileSystemObject::ObjectId objId = nullptr; }; template <class Predicate> void updateView(Predicate pred); @@ -148,7 +142,7 @@ private: /* /|\ | (setData...) | */ - //std::shared_ptr<FolderComparison> folderCmp; //actual comparison data: owned by GridView! + //std::shared_ptr<FolderComparison> folderCmp; //actual comparison data: owned by FileView! size_t folderPairCount_ = 0; //number of non-empty folder pairs @@ -191,17 +185,17 @@ private: //##################### implementation ######################################### inline -const FileSystemObject* GridView::getObject(size_t row) const +const FileSystemObject* FileView::getObject(size_t row) const { return row < viewRef_.size() ? FileSystemObject::retrieve(viewRef_[row]) : nullptr; } inline -FileSystemObject* GridView::getObject(size_t row) +FileSystemObject* FileView::getObject(size_t row) { //code re-use of const method: see Meyers Effective C++ - return const_cast<FileSystemObject*>(static_cast<const GridView&>(*this).getObject(row)); + return const_cast<FileSystemObject*>(static_cast<const FileView&>(*this).getObject(row)); } } diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index 40033094..73d4d461 100755 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -69,7 +69,7 @@ FolderSelector::FolderSelector(wxWindow& dropWindow, auto setupDragDrop = [&](wxWindow& dropWin) { setupFileDrop(dropWin); - dropWin.Connect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector::onKeyFileDropped), nullptr, this); + dropWin.Connect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector::onItemPathDropped), nullptr, this); }; setupDragDrop(dropWindow_); @@ -88,10 +88,10 @@ FolderSelector::FolderSelector(wxWindow& dropWindow, FolderSelector::~FolderSelector() { - dropWindow_.Disconnect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector::onKeyFileDropped), nullptr, this); + dropWindow_.Disconnect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector::onItemPathDropped), nullptr, this); if (dropWindow2_) - dropWindow2_->Disconnect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector::onKeyFileDropped), nullptr, this); + dropWindow2_->Disconnect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector::onItemPathDropped), nullptr, this); folderComboBox_ .Disconnect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector::onMouseWheel ), nullptr, this); folderComboBox_ .Disconnect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector::onEditFolderPath ), nullptr, this); @@ -119,7 +119,7 @@ void FolderSelector::onMouseWheel(wxMouseEvent& event) } -void FolderSelector::onKeyFileDropped(FileDropEvent& event) +void FolderSelector::onItemPathDropped(FileDropEvent& event) { const auto& itemPaths = event.getPaths(); if (itemPaths.empty()) diff --git a/FreeFileSync/Source/ui/folder_selector.h b/FreeFileSync/Source/ui/folder_selector.h index df3293e3..a0271f6f 100755 --- a/FreeFileSync/Source/ui/folder_selector.h +++ b/FreeFileSync/Source/ui/folder_selector.h @@ -50,7 +50,7 @@ private: virtual bool shouldSetDroppedPaths(const std::vector<Zstring>& shellItemPaths) { return true; } //return true if drop should be processed void onMouseWheel (wxMouseEvent& event); - void onKeyFileDropped (FileDropEvent& event); + void onItemPathDropped(FileDropEvent& event); void onEditFolderPath (wxCommandEvent& event); void onSelectFolder (wxCommandEvent& event); void onSelectAltFolder(wxCommandEvent& event); diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index 6fa0d35b..cbe00bd7 100755 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -610,8 +610,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_panelConfig = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); bSizerConfig = new wxBoxSizer( wxHORIZONTAL ); - wxBoxSizer* bSizer151; - bSizer151 = new wxBoxSizer( wxHORIZONTAL ); + bSizerCfgHistoryButtons = new wxBoxSizer( wxHORIZONTAL ); wxBoxSizer* bSizer17611; bSizer17611 = new wxBoxSizer( wxVERTICAL ); @@ -626,7 +625,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer17611->Add( m_staticText951, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT, 2 ); - bSizer151->Add( bSizer17611, 0, 0, 5 ); + bSizerCfgHistoryButtons->Add( bSizer17611, 0, 0, 5 ); wxBoxSizer* bSizer1761; bSizer1761 = new wxBoxSizer( wxVERTICAL ); @@ -641,7 +640,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer1761->Add( m_staticText95, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT, 2 ); - bSizer151->Add( bSizer1761, 0, 0, 5 ); + bSizerCfgHistoryButtons->Add( bSizer1761, 0, 0, 5 ); wxBoxSizer* bSizer175; bSizer175 = new wxBoxSizer( wxVERTICAL ); @@ -656,7 +655,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer175->Add( m_staticText961, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT, 2 ); - bSizer151->Add( bSizer175, 0, 0, 5 ); + bSizerCfgHistoryButtons->Add( bSizer175, 0, 0, 5 ); wxBoxSizer* bSizer174; bSizer174 = new wxBoxSizer( wxVERTICAL ); @@ -682,15 +681,14 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer174->Add( m_staticText97, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT, 2 ); - bSizer151->Add( bSizer174, 0, 0, 5 ); + bSizerCfgHistoryButtons->Add( bSizer174, 0, 0, 5 ); - bSizerConfig->Add( bSizer151, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + bSizerConfig->Add( bSizerCfgHistoryButtons, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - m_listBoxHistory = new wxListBox( m_panelConfig, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_EXTENDED|wxLB_NEEDED_SB ); - m_listBoxHistory->SetMinSize( wxSize( -1, 40 ) ); - - bSizerConfig->Add( m_listBoxHistory, 1, wxEXPAND, 5 ); + m_gridCfgHistory = new zen::Grid( m_panelConfig, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_gridCfgHistory->SetScrollRate( 5, 5 ); + bSizerConfig->Add( m_gridCfgHistory, 1, wxEXPAND, 5 ); m_panelConfig->SetSizer( bSizerConfig ); @@ -1019,10 +1017,6 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_bpButtonSave->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), NULL, this ); m_bpButtonSaveAs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSaveAs ), NULL, this ); m_bpButtonSaveAsBatch->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSaveAsBatchJob ), NULL, this ); - m_listBoxHistory->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( MainDialogGenerated::OnCfgHistoryKeyEvent ), NULL, this ); - m_listBoxHistory->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistory ), NULL, this ); - m_listBoxHistory->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistoryDoubleClick ), NULL, this ); - m_listBoxHistory->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnCfgHistoryRightClick ), NULL, this ); m_bpButtonViewTypeSyncAction->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewType ), NULL, this ); m_bpButtonShowExcluded->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); m_bpButtonShowExcluded->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this ); @@ -1655,8 +1649,23 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer233->Add( bSizerKeepVerticalHeight, 0, 0, 5 ); + bSizerDatabase = new wxWrapSizer( wxVERTICAL ); + m_bitmapDatabase = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); - bSizer233->Add( m_bitmapDatabase, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxLEFT, 10 ); + bSizerDatabase->Add( m_bitmapDatabase, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + + + bSizerDatabase->Add( 0, 3, 0, 0, 5 ); + + m_staticText145 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("sync.ffs_db"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText145->Wrap( -1 ); + m_staticText145->SetFont( wxFont( 9, wxFONTFAMILY_SWISS, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_NORMAL, false, wxT("Arial") ) ); + m_staticText145->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); + + bSizerDatabase->Add( m_staticText145, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + + + bSizer233->Add( bSizerDatabase, 0, wxLEFT|wxALIGN_CENTER_VERTICAL, 5 ); m_staticTextSyncVarDescription = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); m_staticTextSyncVarDescription->Wrap( -1 ); @@ -1831,7 +1840,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer242 = new wxBoxSizer( wxHORIZONTAL ); m_bitmapIgnoreErrors = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer242->Add( m_bitmapIgnoreErrors, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + bSizer242->Add( m_bitmapIgnoreErrors, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); m_checkBoxIgnoreErrors = new wxCheckBox( m_panelSyncSettings, wxID_ANY, _("&Ignore errors"), wxDefaultPosition, wxDefaultSize, 0 ); m_checkBoxIgnoreErrors->SetToolTip( _("Show pop-up on errors or warnings") ); @@ -2108,7 +2117,7 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, m_staticline371 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ), wxLI_HORIZONTAL ); bSizer134->Add( m_staticline371, 0, wxEXPAND, 5 ); - m_panel41 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_panel41 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel41->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); wxBoxSizer* bSizer185; @@ -2312,7 +2321,7 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, m_staticline57 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizerSftpTweaks->Add( m_staticline57, 0, wxEXPAND, 5 ); - m_panel411 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_panel411 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel411->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); wxBoxSizer* bSizer1851; @@ -2386,7 +2395,7 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, m_staticline573 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizerFtpTweaks->Add( m_staticline573, 0, wxEXPAND, 5 ); - m_panel4111 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_panel4111 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel4111->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); wxBoxSizer* bSizer18511; @@ -2492,7 +2501,7 @@ AbstractFolderPickerGenerated::AbstractFolderPickerGenerated( wxWindow* parent, m_staticline371 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ), wxLI_HORIZONTAL ); bSizer134->Add( m_staticline371, 0, wxEXPAND, 5 ); - m_panel41 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_panel41 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel41->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); wxBoxSizer* bSizer185; @@ -2861,7 +2870,7 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi bSizerProgressFooter = new wxBoxSizer( wxHORIZONTAL ); m_bitmapIgnoreErrors = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizerProgressFooter->Add( m_bitmapIgnoreErrors, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizerProgressFooter->Add( m_bitmapIgnoreErrors, 0, wxALIGN_CENTER_VERTICAL, 5 ); m_checkBoxIgnoreErrors = new wxCheckBox( this, wxID_ANY, _("&Ignore errors"), wxDefaultPosition, wxDefaultSize, 0 ); bSizerProgressFooter->Add( m_checkBoxIgnoreErrors, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); @@ -3310,7 +3319,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer236 = new wxBoxSizer( wxHORIZONTAL ); m_bitmapMinimizeToTray = new wxStaticBitmap( m_panel35, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer236->Add( m_bitmapMinimizeToTray, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + bSizer236->Add( m_bitmapMinimizeToTray, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); m_checkBoxRunMinimized = new wxCheckBox( m_panel35, wxID_ANY, _("Run minimized"), wxDefaultPosition, wxDefaultSize, 0 ); bSizer236->Add( m_checkBoxRunMinimized, 1, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); @@ -4631,7 +4640,7 @@ ActivationDlgGenerated::ActivationDlgGenerated( wxWindow* parent, wxWindowID id, bSizer237->Add( bSizer236, 0, wxEXPAND, 5 ); - m_textCtrlManualActivationUrl = new wxTextCtrl( m_panel351, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 220, -1 ), wxTE_MULTILINE|wxTE_READONLY|wxWANTS_CHARS ); + m_textCtrlManualActivationUrl = new wxTextCtrl( m_panel351, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 220, 55 ), wxTE_MULTILINE|wxTE_READONLY|wxWANTS_CHARS ); bSizer237->Add( m_textCtrlManualActivationUrl, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); wxBoxSizer* bSizer235; @@ -4691,3 +4700,70 @@ ActivationDlgGenerated::ActivationDlgGenerated( wxWindow* parent, wxWindowID id, ActivationDlgGenerated::~ActivationDlgGenerated() { } + +CfgHighlightDlgGenerated::CfgHighlightDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + + wxBoxSizer* bSizer96; + bSizer96 = new wxBoxSizer( wxVERTICAL ); + + m_panel35 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panel35->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + wxBoxSizer* bSizer98; + bSizer98 = new wxBoxSizer( wxHORIZONTAL ); + + wxBoxSizer* bSizer238; + bSizer238 = new wxBoxSizer( wxVERTICAL ); + + m_staticText145 = new wxStaticText( m_panel35, wxID_ANY, _("Highlight configurations that have not been run for more than the following number of days:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText145->Wrap( 300 ); + bSizer238->Add( m_staticText145, 0, wxTOP|wxRIGHT|wxLEFT, 5 ); + + m_spinCtrlSyncOverdueDays = new wxSpinCtrl( m_panel35, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 70, -1 ), wxSP_ARROW_KEYS, 0, 2000000000, 0 ); + bSizer238->Add( m_spinCtrlSyncOverdueDays, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + + bSizer98->Add( bSizer238, 1, wxALL|wxEXPAND, 5 ); + + + m_panel35->SetSizer( bSizer98 ); + m_panel35->Layout(); + bSizer98->Fit( m_panel35 ); + bSizer96->Add( m_panel35, 0, 0, 5 ); + + m_staticline21 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer96->Add( m_staticline21, 0, wxEXPAND, 5 ); + + bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); + + m_buttonOkay = new wxButton( this, wxID_OK, _("OK"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + m_buttonOkay->SetDefault(); + m_buttonOkay->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); + + bSizerStdButtons->Add( m_buttonOkay, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bSizer96->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 ); + + + this->SetSizer( bSizer96 ); + this->Layout(); + bSizer96->Fit( this ); + + this->Centre( wxBOTH ); + + // Connect Events + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( CfgHighlightDlgGenerated::OnClose ) ); + m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CfgHighlightDlgGenerated::OnOkay ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CfgHighlightDlgGenerated::OnCancel ), NULL, this ); +} + +CfgHighlightDlgGenerated::~CfgHighlightDlgGenerated() +{ +} diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index c0b02b82..2272410e 100755 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -39,13 +39,14 @@ namespace zen { class TripleSplitter; } #include <wx/statline.h> #include <wx/textctrl.h> #include <wx/checkbox.h> -#include <wx/listbox.h> #include <wx/frame.h> +#include <wx/listbox.h> #include <wx/tglbtn.h> #include <wx/radiobut.h> #include <wx/hyperlink.h> #include <wx/spinctrl.h> #include <wx/choice.h> +#include <wx/wrapsizer.h> #include <wx/notebook.h> #include <wx/dialog.h> #include <wx/treectrl.h> @@ -151,6 +152,7 @@ protected: wxCheckBox* m_checkBoxMatchCase; wxPanel* m_panelConfig; wxBoxSizer* bSizerConfig; + wxBoxSizer* bSizerCfgHistoryButtons; wxBitmapButton* m_bpButtonNew; wxStaticText* m_staticText951; wxBitmapButton* m_bpButtonOpen; @@ -160,7 +162,7 @@ protected: wxBitmapButton* m_bpButtonSaveAs; wxBitmapButton* m_bpButtonSaveAsBatch; wxStaticText* m_staticText97; - wxListBox* m_listBoxHistory; + zen::Grid* m_gridCfgHistory; wxPanel* m_panelViewFilter; wxBoxSizer* bSizerViewFilter; wxStaticText* m_staticTextViewType; @@ -234,10 +236,6 @@ protected: virtual void OnTopLocalSyncCfg( wxCommandEvent& event ) { event.Skip(); } virtual void OnHideSearchPanel( wxCommandEvent& event ) { event.Skip(); } virtual void OnSearchGridEnter( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCfgHistoryKeyEvent( wxKeyEvent& event ) { event.Skip(); } - virtual void OnLoadFromHistory( wxCommandEvent& event ) { event.Skip(); } - virtual void OnLoadFromHistoryDoubleClick( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCfgHistoryRightClick( wxMouseEvent& event ) { event.Skip(); } virtual void OnToggleViewType( wxCommandEvent& event ) { event.Skip(); } virtual void OnToggleViewButton( wxCommandEvent& event ) { event.Skip(); } virtual void OnViewButtonRightClick( wxMouseEvent& event ) { event.Skip(); } @@ -363,7 +361,9 @@ protected: wxStaticText* m_staticText120; wxStaticText* m_staticText140; wxStaticText* m_staticText1401; + wxWrapSizer* bSizerDatabase; wxStaticBitmap* m_bitmapDatabase; + wxStaticText* m_staticText145; wxStaticText* m_staticTextSyncVarDescription; wxStaticLine* m_staticline431; wxCheckBox* m_checkBoxDetectMove; @@ -1157,4 +1157,33 @@ public: }; +/////////////////////////////////////////////////////////////////////////////// +/// Class CfgHighlightDlgGenerated +/////////////////////////////////////////////////////////////////////////////// +class CfgHighlightDlgGenerated : public wxDialog +{ +private: + +protected: + wxPanel* m_panel35; + wxStaticText* m_staticText145; + wxSpinCtrl* m_spinCtrlSyncOverdueDays; + wxStaticLine* m_staticline21; + wxBoxSizer* bSizerStdButtons; + wxButton* m_buttonOkay; + wxButton* m_buttonCancel; + + // Virtual event handlers, overide them in your derived class + virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } + virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } + virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + + +public: + + CfgHighlightDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Highlight Configurations"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE ); + ~CfgHighlightDlgGenerated(); + +}; + #endif //__GUI_GENERATED_H__ diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index 54b4ee93..ac9f2479 100755 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -346,6 +346,7 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() { //post sync action bool showSummary = true; + bool triggerSleep = false; if (!getAbortStatus() || *getAbortStatus() != AbortTrigger::USER) //user cancelled => don't run post sync action! switch (progressDlg_->getOptionPostSyncAction()) { @@ -356,11 +357,7 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() exitAfterSync_ = true; //program shutdown must be handled by calling context! break; case PostSyncAction::SLEEP: - try - { - tryReportingError([&] { suspendSystem(); /*throw FileError*/ }, *this); //throw X - } - catch (...) {} + triggerSleep = true; break; case PostSyncAction::SHUTDOWN: showSummary = false; @@ -379,6 +376,13 @@ StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() else progressDlg_->closeDirectly(false /*restoreParentFrame*/); + if (triggerSleep) //sleep *after* showing results dialog (consider total time!) + try + { + tryReportingError([&] { suspendSystem(); /*throw FileError*/ }, *this); //throw X + } + catch (...) {} + //wait until progress dialog notified shutdown via onProgressDialogTerminate() //-> required since it has our "this" pointer captured in lambda "notifyWindowTerminate"! //-> nicely manages dialog lifetime diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index ddd864fd..41ccf802 100755 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -23,8 +23,10 @@ #include <wx+/no_flicker.h> #include <wx+/rtl.h> #include <wx+/font_size.h> +#include <wx+/focus.h> #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> +#include "cfg_grid.h" #include "version_check.h" #include "gui_status_handler.h" #include "small_dlgs.h" @@ -55,14 +57,6 @@ namespace const size_t EXT_APP_MASS_INVOKE_THRESHOLD = 10; //more than this is likely a user mistake (Explorer uses limit of 15) -struct wxClientHistoryData : public wxClientData //we need a wxClientData derived class to tell wxWidgets to take object ownership! -{ - wxClientHistoryData(const Zstring& cfgFile, int lastUseIndex) : cfgFile_(cfgFile), lastUseIndex_(lastUseIndex) {} - - Zstring cfgFile_; - int lastUseIndex_; //support sorting history by last usage, the higher the index the more recent the usage -}; - IconBuffer::IconSize convert(xmlAccess::FileIconSize isize) { using namespace xmlAccess; @@ -78,56 +72,6 @@ IconBuffer::IconSize convert(xmlAccess::FileIconSize isize) return IconBuffer::SIZE_SMALL; } -//pretty much the same like "bool wxWindowBase::IsDescendant(wxWindowBase* child) const" but without the obvious misnomer -inline -bool isComponentOf(const wxWindow* child, const wxWindow* top) -{ - for (const wxWindow* wnd = child; wnd != nullptr; wnd = wnd->GetParent()) - if (wnd == top) - return true; - return false; -} - - -inline -wxTopLevelWindow* getTopLevelWindow(wxWindow* child) -{ - for (wxWindow* wnd = child; wnd != nullptr; wnd = wnd->GetParent()) - if (auto tlw = dynamic_cast<wxTopLevelWindow*>(wnd)) //why does wxWidgets use wxWindows::IsTopLevel() ?? - return tlw; - return nullptr; -} - - -/* -Preserving input focus has to be more clever than: - wxWindow* oldFocus = wxWindow::FindFocus(); - ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus()); - -=> wxWindow::SetFocus() internally calls Win32 ::SetFocus, which calls ::SetActiveWindow, which - lord knows why - changes the foreground window to the focus window - even if the user is currently busy using a different app! More curiosity: this foreground focus stealing happens only during the *first* SetFocus() after app start! - It also can be avoided by changing focus back and forth with some other app after start => wxWidgets bug or Win32 feature??? -*/ -struct FocusPreserver -{ - ~FocusPreserver() - { - //wxTopLevelWindow::IsActive() does NOT call Win32 ::GetActiveWindow()! - //Instead it checks if ::GetFocus() is set somewhere inside the top level - //Note: Both Win32 active and focus windows are *thread-local* values, while foreground window is global! https://blogs.msdn.microsoft.com/oldnewthing/20131016-00/?p=2913 - if (oldFocus_) - if (wxTopLevelWindow* topWin = getTopLevelWindow(oldFocus_)) - if (topWin->IsActive()) //Linux/macOS: already behaves just like ::GetForegroundWindow() on Windows! - oldFocus_->SetFocus(); - } - - wxWindow* getFocus() const { return oldFocus_; } - void setFocus(wxWindow* win) { oldFocus_ = win; } - -private: - wxWindow* oldFocus_ = wxWindow::FindFocus(); -}; - bool acceptDialogFileDrop(const std::vector<Zstring>& shellItemPaths) { @@ -158,6 +102,7 @@ public: { if (acceptDialogFileDrop(shellItemPaths)) { + assert(!shellItemPaths.empty()); mainDlg_.loadConfiguration(shellItemPaths); return false; } @@ -351,21 +296,13 @@ xmlAccess::XmlGlobalSettings tryLoadGlobalConfig(const Zstring& globalConfigFile } -Zstring MainDialog::getLastRunConfigPath() -{ - return zen::getConfigDirPathPf() + Zstr("LastRun.ffs_gui"); -} - - void MainDialog::create(const Zstring& globalConfigFilePath) { using namespace xmlAccess; const XmlGlobalSettings globalSettings = tryLoadGlobalConfig(globalConfigFilePath); - std::vector<Zstring> cfgFilePaths; - for (const ConfigFileItem& item : globalSettings.gui.lastUsedConfigFiles) - cfgFilePaths.push_back(item.filePath_); + std::vector<Zstring> cfgFilePaths = globalSettings.gui.mainDlg.lastUsedConfigFiles; //------------------------------------------------------------------------------------------ //check existence of all files in parallel: @@ -451,14 +388,15 @@ void MainDialog::create(const Zstring& globalConfigFilePath, } -MainDialog::MainDialog(const Zstring& globalConfigFile, +MainDialog::MainDialog(const Zstring& globalConfigFilePath, const xmlAccess::XmlGuiConfig& guiCfg, const std::vector<Zstring>& referenceFiles, const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison) : MainDialogGenerated(nullptr), - globalConfigFile_(globalConfigFile), + globalConfigFilePath_(globalConfigFilePath), lastRunConfigPath_(getLastRunConfigPath()) + { m_folderPathLeft ->init(folderHistoryLeft_); m_folderPathRight->init(folderHistoryRight_); @@ -539,7 +477,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, wxAuiPaneInfo().Name(L"ViewFilterPanel").Layer(2).Bottom().Row(1).Caption(_("View Settings")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(m_bpButtonViewTypeSyncAction->GetSize().GetWidth(), m_panelViewFilter->GetSize().GetHeight())); auiMgr_.AddPane(m_panelConfig, - wxAuiPaneInfo().Name(L"ConfigPanel").Layer(3).Left().Position(1).Caption(_("Configuration")).MinSize(m_listBoxHistory->GetSize().GetWidth(), m_panelConfig->GetSize().GetHeight())); + wxAuiPaneInfo().Name(L"ConfigPanel").Layer(3).Left().Position(1).Caption(_("Configuration")).MinSize(bSizerCfgHistoryButtons->GetSize())); auiMgr_.AddPane(m_gridOverview, wxAuiPaneInfo().Name(L"OverviewPanel").Layer(3).Left().Position(2).Caption(_("Overview")).MinSize(300, m_gridOverview->GetSize().GetHeight())); //MinSize(): just default size, see comment below @@ -573,25 +511,34 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, m_panelStatusBar ->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this); //---------------------------------------------------------------------------------- - //sort grids + //file grid: sorting 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, 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_DOWN, GridClickEventHandler(MainDialog::onMainGridContextC), nullptr, this); - m_gridMainR ->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextR), nullptr, this); - m_gridOverview->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 ); - - m_gridOverview->Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(MainDialog::onNaviSelection), 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); + + //file grid: context menu + 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_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); + + //tree grid: + m_gridOverview->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onTreeGridContext), nullptr, this); + m_gridOverview->Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(MainDialog::onTreeGridSelection), nullptr, this); + + //cfg grid: + m_gridCfgHistory->Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(MainDialog::onCfgGridSelection), nullptr, this); + m_gridCfgHistory->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onCfgGridDoubleClick), nullptr, this); + m_gridCfgHistory->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onCfgGridKeyEvent), nullptr, this); + m_gridCfgHistory->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onCfgGridContext), nullptr, this); + m_gridCfgHistory->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(MainDialog::onCfgGridLabelContext ), nullptr, this); + m_gridCfgHistory->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(MainDialog::onCfgGridLabelLeftClick), nullptr, this); //---------------------------------------------------------------------------------- m_panelSearch->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::OnSearchPanelKeyPressed), nullptr, this); @@ -611,9 +558,6 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, m_bpButtonCmpContext ->SetToolTip(m_bpButtonCmpConfig ->GetToolTipText()); m_bpButtonSyncContext->SetToolTip(m_bpButtonSyncConfig->GetToolTipText()); - gridDataView_ = std::make_shared<GridView>(); - treeDataView_ = std::make_shared<TreeView>(); - { const wxBitmap& bmpFile = IconBuffer::genericFileIcon(IconBuffer::SIZE_SMALL); @@ -701,8 +645,9 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, initViewFilterButtons(); //init grid settings - gridview::init(*m_gridMainL, *m_gridMainC, *m_gridMainR, gridDataView_); - treeview::init(*m_gridOverview, treeDataView_); + filegrid::init(*m_gridMainL, *m_gridMainC, *m_gridMainR); + treegrid::init(*m_gridOverview); + cfggrid ::init(*m_gridCfgHistory); //initialize and load configuration setGlobalCfgOnInit(globalSettings); @@ -753,19 +698,16 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, OnResizeLeftFolderWidth(evtDummy); // //scroll cfg history to last used position. We cannot do this earlier e.g. in setGlobalCfgOnInit() - //1. setConfig() indirectly calls addFileToCfgHistory() which changes cfg history scroll position - //2. EnsureVisible() requires final window height! => do this after window resizing is complete - if (!m_listBoxHistory->IsEmpty()) - m_listBoxHistory->SetFirstItem(numeric::clampCpy(globalSettings.gui.cfgFileHistFirstItemPos, //must be set *after* wxAuiManager::LoadPerspective() to have any effect - 0, static_cast<int>(m_listBoxHistory->GetCount()) - 1)); - - //first selected item must be visible: - for (int i = 0; i < static_cast<int>(m_listBoxHistory->GetCount()); ++i) - if (m_listBoxHistory->IsSelected(i)) - { - m_listBoxHistory->EnsureVisible(i); - break; - } + //1. setConfig() indirectly calls cfggrid::addAndSelect() which changes cfg history scroll position + //2. Grid::makeRowVisible() requires final window height! => do this after window resizing is complete + if (m_gridCfgHistory->getRowCount() > 0) + m_gridCfgHistory->scrollTo(numeric::clampCpy<size_t>(globalSettings.gui.mainDlg.cfgGridTopRowPos, //must be set *after* wxAuiManager::LoadPerspective() to have any effect + 0, m_gridCfgHistory->getRowCount() - 1)); + + //first selected item should always be visible: + const std::vector<size_t> selectedRows = m_gridCfgHistory->getSelectedRows(); + if (!selectedRows.empty()) + m_gridCfgHistory->makeRowVisible(selectedRows.front()); m_buttonCompare->SetFocus(); @@ -824,11 +766,12 @@ MainDialog::MainDialog(const Zstring& globalConfigFile, !firstMissingDir.get(); //= all directories exist if (startComparisonNow) + { + wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); + //better!? => m_buttonCompare->Command(dummy2); //simulate click if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler()) - { - wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); evtHandler->AddPendingEvent(dummy2); //simulate button click on "compare" - } + } } } } @@ -841,7 +784,7 @@ MainDialog::~MainDialog() Opt<FileError> firstError; try //save "GlobalSettings.xml" { - writeConfig(getGlobalCfgBeforeExit(), globalConfigFile_); //throw FileError + writeConfig(getGlobalCfgBeforeExit(), globalConfigFilePath_); //throw FileError } catch (const FileError& e) { firstError = e; } @@ -849,13 +792,16 @@ MainDialog::~MainDialog() { writeConfig(getConfig(), lastRunConfigPath_); //throw FileError } - catch (const FileError& e) { firstError = e; } + catch (const FileError& e) + { + if (!firstError) + firstError = e; + } //don't annoy users on read-only drives: it's enough to show a single error message when saving global config if (firstError) showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(firstError->toString())); - auiMgr_.UnInit(); for (wxMenuItem* item : detachedMenuItems_) @@ -871,7 +817,7 @@ void MainDialog::onQueryEndSession() using namespace xmlAccess; //we try our best to do something useful in this extreme situation - no reason to notify or even log errors here! - try { writeConfig(getGlobalCfgBeforeExit(), globalConfigFile_); } + try { writeConfig(getGlobalCfgBeforeExit(), globalConfigFilePath_); } catch (const FileError&) {} try { writeConfig(getConfig(), lastRunConfigPath_); } @@ -955,40 +901,52 @@ void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSe } //set column attributes - m_gridMainL ->setColumnConfig(gridview::convertConfig(globalSettings.gui.mainDlg.columnAttribLeft)); - m_gridMainR ->setColumnConfig(gridview::convertConfig(globalSettings.gui.mainDlg.columnAttribRight)); + m_gridMainL ->setColumnConfig(convertColAttributes(globalSettings.gui.mainDlg.columnAttribLeft, getFileGridDefaultColAttribsLeft())); + m_gridMainR ->setColumnConfig(convertColAttributes(globalSettings.gui.mainDlg.columnAttribRight, getFileGridDefaultColAttribsLeft())); m_splitterMain->setSashOffset(globalSettings.gui.mainDlg.sashOffset); - m_gridOverview->setColumnConfig(treeview::convertConfig(globalSettings.gui.mainDlg.columnAttribNavi)); - treeview::setShowPercentage(*m_gridOverview, globalSettings.gui.mainDlg.naviGridShowPercentBar); + m_gridOverview->setColumnConfig(convertColAttributes(globalSettings.gui.mainDlg.treeGridColumnAttribs, getTreeGridDefaultColAttribs())); + treegrid::setShowPercentage(*m_gridOverview, globalSettings.gui.mainDlg.treeGridShowPercentBar); - treeDataView_->setSortDirection(globalSettings.gui.mainDlg.naviGridLastSortColumn, globalSettings.gui.mainDlg.naviGridLastSortAscending); + treegrid::getDataView(*m_gridOverview).setSortDirection(globalSettings.gui.mainDlg.treeGridLastSortColumn, globalSettings.gui.mainDlg.treeGridLastSortAscending); //-------------------------------------------------------------------------------- - //load list of last used configuration files + //load list of configuration files 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!!! - + std::vector<std::pair<Zstring, time_t>> lastSyncTimes; + //list is stored with last used files first in XML, however m_gridCfgHistory needs them last!!! + std::for_each(globalSettings.gui.mainDlg.cfgFileHistory.crbegin(), + globalSettings.gui.mainDlg.cfgFileHistory.crend(), + [&](const xmlAccess::ConfigFileItem& item) + { + cfgFilePaths.push_back(item.filePath); + lastSyncTimes.emplace_back(item.filePath, item.lastSyncTime); + }); + warn_static("finish") cfgFilePaths.push_back(lastRunConfigPath_); //make sure <Last session> is always part of history list (if existing) - addFileToCfgHistory(cfgFilePaths); + cfggrid::getDataView(*m_gridCfgHistory).addCfgFiles(cfgFilePaths); + cfggrid::getDataView(*m_gridCfgHistory).setLastSyncTime(lastSyncTimes); + m_gridCfgHistory->Refresh(); + + m_gridCfgHistory->setColumnConfig(convertColAttributes(globalSettings.gui.mainDlg.cfgGridColumnAttribs, getCfgGridDefaultColAttribs())); + cfggrid::getDataView(*m_gridCfgHistory).setSortDirection(globalSettings.gui.mainDlg.cfgGridLastSortColumn, globalSettings.gui.mainDlg.cfgGridLastSortAscending); + cfggrid::setSyncOverdueDays(*m_gridCfgHistory, globalSettings.gui.mainDlg.cfgGridSyncOverdueDays); + //m_gridCfgHistory->Refresh(); <- implicit in last call - removeObsoleteCfgHistoryItems(cfgFilePaths); //remove non-existent items (we need this only on startup) + cfgHistoryRemoveObsolete(cfgFilePaths); //remove non-existent items (we need this only on startup) //globalSettings.gui.cfgFileHistFirstItemPos => defer evaluation until later within MainDialog constructor //-------------------------------------------------------------------------------- //load list of last used folders - *folderHistoryLeft_ = FolderHistory(globalSettings.gui.folderHistoryLeft, globalSettings.gui.folderHistMax); - *folderHistoryRight_ = FolderHistory(globalSettings.gui.folderHistoryRight, globalSettings.gui.folderHistMax); + *folderHistoryLeft_ = FolderHistory(globalSettings.gui.mainDlg.folderHistoryLeft, globalSettings.gui.mainDlg.folderHistItemsMax); + *folderHistoryRight_ = FolderHistory(globalSettings.gui.mainDlg.folderHistoryRight, globalSettings.gui.mainDlg.folderHistItemsMax); //show/hide file icons - gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalSettings.gui.mainDlg.showIcons, convert(globalSettings.gui.mainDlg.iconSize)); + filegrid::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalSettings.gui.mainDlg.showIcons, convert(globalSettings.gui.mainDlg.iconSize)); - gridview::setItemPathForm(*m_gridMainL, globalSettings.gui.mainDlg.itemPathFormatLeftGrid); - gridview::setItemPathForm(*m_gridMainR, globalSettings.gui.mainDlg.itemPathFormatRightGrid); + filegrid::setItemPathForm(*m_gridMainL, globalSettings.gui.mainDlg.itemPathFormatLeftGrid); + filegrid::setItemPathForm(*m_gridMainR, globalSettings.gui.mainDlg.itemPathFormatRightGrid); //------------------------------------------------------------------------------------------------ m_checkBoxMatchCase->SetValue(globalCfg_.gui.mainDlg.textSearchRespectCase); @@ -1026,45 +984,48 @@ xmlAccess::XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit() globalSettings.programLanguage = getLanguage(); //retrieve column attributes - globalSettings.gui.mainDlg.columnAttribLeft = gridview::convertConfig(m_gridMainL->getColumnConfig()); - globalSettings.gui.mainDlg.columnAttribRight = gridview::convertConfig(m_gridMainR->getColumnConfig()); + globalSettings.gui.mainDlg.columnAttribLeft = convertColAttributes<ColAttributesRim>(m_gridMainL->getColumnConfig()); + globalSettings.gui.mainDlg.columnAttribRight = convertColAttributes<ColAttributesRim>(m_gridMainR->getColumnConfig()); globalSettings.gui.mainDlg.sashOffset = m_splitterMain->getSashOffset(); - globalSettings.gui.mainDlg.columnAttribNavi = treeview::convertConfig(m_gridOverview->getColumnConfig()); - globalSettings.gui.mainDlg.naviGridShowPercentBar = treeview::getShowPercentage(*m_gridOverview); + globalSettings.gui.mainDlg.treeGridColumnAttribs = convertColAttributes<ColAttributesTree>(m_gridOverview->getColumnConfig()); + globalSettings.gui.mainDlg.treeGridShowPercentBar = treegrid::getShowPercentage(*m_gridOverview); - const std::pair<ColumnTypeNavi, bool> sortInfo = treeDataView_->getSortDirection(); - globalSettings.gui.mainDlg.naviGridLastSortColumn = sortInfo.first; - globalSettings.gui.mainDlg.naviGridLastSortAscending = sortInfo.second; + std::tie(globalSettings.gui.mainDlg.treeGridLastSortColumn, + globalSettings.gui.mainDlg.treeGridLastSortAscending) = treegrid::getDataView(*m_gridOverview).getSortDirection(); //-------------------------------------------------------------------------------- - //write list of last used configuration files - std::map<int, Zstring> historyDetail; //(cfg-file/last use index) - for (unsigned int i = 0; i < m_listBoxHistory->GetCount(); ++i) - if (auto clientString = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i))) - historyDetail.emplace(clientString->lastUseIndex_, clientString->cfgFile_); + //write list of configuration files + std::map<int, xmlAccess::ConfigFileItem> cfgItemsSorted; //(last use index/cfg file path) + for (size_t i = 0; i < m_gridCfgHistory->getRowCount(); ++i) + if (const ConfigView::Details* cfg = cfggrid::getDataView(*m_gridCfgHistory).getItem(i)) + cfgItemsSorted.emplace(cfg->lastUseIndex, xmlAccess::ConfigFileItem{ cfg->filePath, cfg->lastSyncTime }); else assert(false); - //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()); + //sort by last use; put most recent items *first* (looks better in XML than reverted) + std::vector<xmlAccess::ConfigFileItem> cfgHistory; + std::for_each(cfgItemsSorted.crbegin(), + cfgItemsSorted.crend(), + [&](const auto& item) { cfgHistory.emplace_back(item.second); }); - if (history.size() > globalSettings.gui.cfgFileHistMax) //erase oldest elements - history.resize(globalSettings.gui.cfgFileHistMax); + if (cfgHistory.size() > globalSettings.gui.mainDlg.cfgHistItemsMax) //erase oldest elements + cfgHistory.resize(globalSettings.gui.mainDlg.cfgHistItemsMax); - globalSettings.gui.cfgFileHistory = history; - globalSettings.gui.cfgFileHistFirstItemPos = m_listBoxHistory->GetTopItem(); + globalSettings.gui.mainDlg.cfgFileHistory = cfgHistory; + + globalSettings.gui.mainDlg.cfgGridTopRowPos = m_gridCfgHistory->getTopRow(); + globalSettings.gui.mainDlg.cfgGridColumnAttribs = convertColAttributes<ColAttributesCfg>(m_gridCfgHistory->getColumnConfig()); + globalSettings.gui.mainDlg.cfgGridSyncOverdueDays = cfggrid::getSyncOverdueDays(*m_gridCfgHistory); + + std::tie(globalSettings.gui.mainDlg.cfgGridLastSortColumn, + globalSettings.gui.mainDlg.cfgGridLastSortAscending) = cfggrid::getDataView(*m_gridCfgHistory).getSortDirection(); //-------------------------------------------------------------------------------- - globalSettings.gui.lastUsedConfigFiles.clear(); - for (const Zstring& cfgFilePath : activeConfigFiles_) - globalSettings.gui.lastUsedConfigFiles.emplace_back(cfgFilePath); + globalSettings.gui.mainDlg.lastUsedConfigFiles = activeConfigFiles_; //write list of last used folders - globalSettings.gui.folderHistoryLeft = folderHistoryLeft_ ->getList(); - globalSettings.gui.folderHistoryRight = folderHistoryRight_->getList(); + globalSettings.gui.mainDlg.folderHistoryLeft = folderHistoryLeft_ ->getList(); + globalSettings.gui.mainDlg.folderHistoryRight = folderHistoryRight_->getList(); globalSettings.gui.mainDlg.textSearchRespectCase = m_checkBoxMatchCase->GetValue(); @@ -1143,18 +1104,18 @@ void MainDialog::copySelectionToClipboard(const std::vector<const Grid*>& gridRe { if (auto prov = grid.getDataProvider()) { - std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig(); - erase_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); + std::vector<Grid::ColAttributes> colAttr = grid.getColumnConfig(); + erase_if(colAttr, [](const Grid::ColAttributes& ca) { return !ca.visible; }); if (!colAttr.empty()) for (size_t row : grid.getSelectedRows()) { std::for_each(colAttr.begin(), colAttr.end() - 1, - [&](const Grid::ColumnAttribute& ca) + [&](const Grid::ColAttributes& ca) { - clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_)); + clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type)); clipboardString += L'\t'; }); - clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_)); + clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type)); clipboardString += L'\n'; } } @@ -1190,7 +1151,7 @@ std::vector<FileSystemObject*> MainDialog::getGridSelection(bool fromLeft, bool removeDuplicates(selectedRows); assert(std::is_sorted(selectedRows.begin(), selectedRows.end())); - return gridDataView_->getAllFileRef(selectedRows); + return filegrid::getDataView(*m_gridMainC).getAllFileRef(selectedRows); } @@ -1199,7 +1160,7 @@ std::vector<FileSystemObject*> MainDialog::getTreeSelection() const std::vector<FileSystemObject*> output; for (size_t row : m_gridOverview->getSelectedRows()) - if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) + if (std::unique_ptr<TreeView::Node> node = treegrid::getDataView(*m_gridOverview).getLine(row)) { if (auto root = dynamic_cast<const TreeView::RootNode*>(node.get())) { @@ -1313,7 +1274,7 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec catch (AbortProcess&) {} //do not clear grids, if aborted! //remove rows that are empty: just a beautification, invalid rows shouldn't cause issues - gridDataView_->removeInvalidRows(); + filegrid::getDataView(*m_gridMainC).removeInvalidRows(); updateGui(); } @@ -1554,10 +1515,10 @@ void MainDialog::setStatusBarFileStatistics(size_t filesOnLeftView, setText(*m_staticTextStatusRightBytes, L"(" + formatFilesizeShort(filesizeRightView) + L")"); //------------------------------------------------------------------------------ wxString statusCenterNew; - if (gridDataView_->rowsTotal() > 0) + if (filegrid::getDataView(*m_gridMainC).rowsTotal() > 0) { - statusCenterNew = _P("Showing %y of 1 row", "Showing %y of %x rows", gridDataView_->rowsTotal()); - replace(statusCenterNew, L"%y", formatNumber(gridDataView_->rowsOnView())); //%x is already used as plural form placeholder! + statusCenterNew = _P("Showing %y of 1 row", "Showing %y of %x rows", filegrid::getDataView(*m_gridMainC).rowsTotal()); + replace(statusCenterNew, L"%y", formatNumber(filegrid::getDataView(*m_gridMainC).rowsOnView())); //%x is already used as plural form placeholder! } //fill middle text (considering flashStatusInformation()) @@ -1655,6 +1616,7 @@ void MainDialog::disableAllElements(bool enableAbort) m_panelViewFilter ->Disable(); m_panelConfig ->Disable(); m_gridOverview ->Disable(); + m_gridCfgHistory ->Disable(); m_panelSearch ->Disable(); m_bpButtonCmpContext ->Disable(); m_bpButtonSyncContext->Disable(); @@ -1700,6 +1662,7 @@ void MainDialog::enableAllElements() m_panelViewFilter ->Enable(); m_panelConfig ->Enable(); m_gridOverview ->Enable(); + m_gridCfgHistory ->Enable(); m_panelSearch ->Enable(); m_bpButtonCmpContext ->Enable(); m_bpButtonSyncContext->Enable(); @@ -1969,24 +1932,21 @@ void MainDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without //case WXK_F6: //{ // wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); //simulate button click - // if (wxEvtHandler* evtHandler = m_bpButtonCmpConfig->GetEventHandler()) - // evtHandler->ProcessEvent(dummy2); //synchronous call + // m_bpButtonCmpConfig->Command(dummy2); //simulate click //} //return; //-> swallow event! //case WXK_F7: //{ // wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); //simulate button click - // if (wxEvtHandler* evtHandler = m_bpButtonFilter->GetEventHandler()) - // evtHandler->ProcessEvent(dummy2); //synchronous call + // m_bpButtonFilter->Command(dummy2); //simulate click //} //return; //-> swallow event! //case WXK_F8: //{ // wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); //simulate button click - // if (wxEvtHandler* evtHandler = m_bpButtonSyncConfig->GetEventHandler()) - // evtHandler->ProcessEvent(dummy2); //synchronous call + // m_bpButtonSyncConfig->Command(dummy2); //simulate click //} //return; //-> swallow event! @@ -2018,9 +1978,9 @@ void MainDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without !isComponentOf(focus, m_gridMainC ) && //don't propagate keyboard commands if grid is already in focus !isComponentOf(focus, m_gridMainR ) && // !isComponentOf(focus, m_gridOverview ) && - !isComponentOf(focus, m_listBoxHistory) && //don't propagate if selecting config + !isComponentOf(focus, m_gridCfgHistory) && //don't propagate if selecting config !isComponentOf(focus, m_panelSearch ) && - !isComponentOf(focus, m_panelTopLeft ) && //don't propagate if changing directory fields + !isComponentOf(focus, m_panelTopLeft ) && //don't propagate if changing directory fields !isComponentOf(focus, m_panelTopCenter) && !isComponentOf(focus, m_panelTopRight ) && !isComponentOf(focus, m_scrolledWindowFolderPairs) && @@ -2042,26 +2002,26 @@ void MainDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without } -void MainDialog::onNaviSelection(GridRangeSelectEvent& event) +void MainDialog::onTreeGridSelection(GridSelectEvent& event) { //scroll m_gridMain to user's new selection on m_gridOverview ptrdiff_t leadRow = -1; if (event.positive_ && event.rowFirst_ != event.rowLast_) - if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(event.rowFirst_)) + if (std::unique_ptr<TreeView::Node> node = treegrid::getDataView(*m_gridOverview).getLine(event.rowFirst_)) { if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) - leadRow = gridDataView_->findRowFirstChild(&(root->baseFolder_)); + leadRow = filegrid::getDataView(*m_gridMainC).findRowFirstChild(&(root->baseFolder_)); else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get())) { - leadRow = gridDataView_->findRowDirect(&(dir->folder_)); + leadRow = filegrid::getDataView(*m_gridMainC).findRowDirect(&(dir->folder_)); if (leadRow < 0) //directory was filtered out! still on tree view (but NOT on grid view) - leadRow = gridDataView_->findRowFirstChild(&(dir->folder_)); + leadRow = filegrid::getDataView(*m_gridMainC).findRowFirstChild(&(dir->folder_)); } else if (const TreeView::FilesNode* files = dynamic_cast<const TreeView::FilesNode*>(node.get())) { assert(!files->filesAndLinks_.empty()); if (!files->filesAndLinks_.empty()) - leadRow = gridDataView_->findRowDirect(files->filesAndLinks_[0]->getId()); + leadRow = filegrid::getDataView(*m_gridMainC).findRowDirect(files->filesAndLinks_[0]->getId()); } } @@ -2076,12 +2036,12 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) m_gridOverview->getMainWin().Update(); //draw cursor immediately rather than on next idle event (required for slow CPUs, netbook) } - //get selection on navigation tree and set corresponding markers on main grid + //get selection on overview panel and set corresponding markers on main grid std::unordered_set<const FileSystemObject*> markedFilesAndLinks; //mark files/symlinks directly std::unordered_set<const ContainerObject*> markedContainer; //mark full container including child-objects for (size_t row : m_gridOverview->getSelectedRows()) - if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) + if (std::unique_ptr<TreeView::Node> node = treegrid::getDataView(*m_gridOverview).getLine(row)) { if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) markedContainer.insert(&(root->baseFolder_)); @@ -2091,13 +2051,13 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) markedFilesAndLinks.insert(files->filesAndLinks_.begin(), files->filesAndLinks_.end()); } - gridview::setNavigationMarker(*m_gridMainL, std::move(markedFilesAndLinks), std::move(markedContainer)); + filegrid::setNavigationMarker(*m_gridMainL, std::move(markedFilesAndLinks), std::move(markedContainer)); event.Skip(); } -void MainDialog::onNaviGridContext(GridClickEvent& event) +void MainDialog::onTreeGridContext(GridClickEvent& event) { const auto& selection = getTreeSelection(); //referenced by lambdas! ContextMenu menu; @@ -2198,13 +2158,13 @@ void MainDialog::onMainGridContextC(GridClickEvent& event) { zen::setActiveStatus(true, folderCmp_); updateGui(); - }, nullptr, gridDataView_->rowsTotal() > 0); + }, nullptr, filegrid::getDataView(*m_gridMainC).rowsTotal() > 0); menu.addItem(_("Exclude all"), [&] { zen::setActiveStatus(false, folderCmp_); updateGuiDelayedIf(!m_bpButtonShowExcluded->isActive()); //show update GUI before removing rows - }, nullptr, gridDataView_->rowsTotal() > 0); + }, nullptr, filegrid::getDataView(*m_gridMainC).rowsTotal() > 0); menu.popup(*this); } @@ -2476,38 +2436,38 @@ void MainDialog::onGridLabelContextR(GridLabelClickEvent& event) void MainDialog::onGridLabelContextRim(Grid& grid, ColumnTypeRim type, bool left) { ContextMenu menu; - + //-------------------------------------------------------------------------------------------------------- auto toggleColumn = [&](ColumnType ct) { auto colAttr = grid.getColumnConfig(); - Grid::ColumnAttribute* caItemPath = nullptr; - Grid::ColumnAttribute* caToggle = nullptr; + Grid::ColAttributes* caItemPath = nullptr; + Grid::ColAttributes* caToggle = nullptr; - for (Grid::ColumnAttribute& ca : colAttr) - if (ca.type_ == static_cast<ColumnType>(ColumnTypeRim::ITEM_PATH)) + for (Grid::ColAttributes& ca : colAttr) + if (ca.type == static_cast<ColumnType>(ColumnTypeRim::ITEM_PATH)) caItemPath = &ca; - else if (ca.type_ == ct) + else if (ca.type == ct) caToggle = &ca; - assert(caItemPath && caItemPath->stretch_ > 0 && caItemPath->visible_); - assert(caToggle && caToggle->stretch_ == 0); + assert(caItemPath && caItemPath->stretch > 0 && caItemPath->visible); + assert(caToggle && caToggle ->stretch == 0); if (caItemPath && caToggle) { - caToggle->visible_ = !caToggle->visible_; + caToggle->visible = !caToggle->visible; //take width of newly visible column from stretched item path column - caItemPath->offset_ -= caToggle->visible_ ? caToggle->offset_ : -caToggle->offset_; + caItemPath->offset -= caToggle->visible ? caToggle->offset : -caToggle->offset; grid.setColumnConfig(colAttr); } }; if (const GridData* prov = grid.getDataProvider()) - for (const Grid::ColumnAttribute& ca : grid.getColumnConfig()) - menu.addCheckBox(prov->getColumnLabel(ca.type_), [ct = ca.type_, toggleColumn] { toggleColumn(ct); }, - ca.visible_, ca.type_ != static_cast<ColumnType>(ColumnTypeRim::ITEM_PATH)); //do not allow user to hide this column! + for (const Grid::ColAttributes& ca : grid.getColumnConfig()) + menu.addCheckBox(prov->getColumnLabel(ca.type), [ct = ca.type, toggleColumn] { toggleColumn(ct); }, + ca.visible, ca.type != static_cast<ColumnType>(ColumnTypeRim::ITEM_PATH)); //do not allow user to hide this column! //---------------------------------------------------------------------------------------------- menu.addSeparator(); @@ -2516,7 +2476,7 @@ void MainDialog::onGridLabelContextRim(Grid& grid, ColumnTypeRim type, bool left auto setItemPathFormat = [&](ItemPathFormat fmt) { itemPathFormat = fmt; - gridview::setItemPathForm(grid, fmt); + filegrid::setItemPathForm(grid, fmt); }; auto addFormatEntry = [&](const wxString& label, ItemPathFormat fmt) { @@ -2527,37 +2487,39 @@ void MainDialog::onGridLabelContextRim(Grid& grid, ColumnTypeRim type, bool left addFormatEntry(_("Item name" ), ItemPathFormat::ITEM_NAME); //---------------------------------------------------------------------------------------------- - menu.addSeparator(); + auto setIconSize = [&](xmlAccess::FileIconSize sz, bool showIcons) + { + globalCfg_.gui.mainDlg.iconSize = sz; + globalCfg_.gui.mainDlg.showIcons = showIcons; + filegrid::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg_.gui.mainDlg.showIcons, convert(globalCfg_.gui.mainDlg.iconSize)); + }; + auto setDefault = [&] { - grid.setColumnConfig(gridview::convertConfig(left ? getDefaultColumnAttributesLeft() : getDefaultColumnAttributesRight())); + const xmlAccess::XmlGlobalSettings defaultCfg; + + grid.setColumnConfig(convertColAttributes(left ? defaultCfg.gui.mainDlg.columnAttribLeft : defaultCfg.gui.mainDlg.columnAttribRight, defaultCfg.gui.mainDlg.columnAttribLeft)); + + setItemPathFormat(left ? defaultCfg.gui.mainDlg.itemPathFormatLeftGrid : defaultCfg.gui.mainDlg.itemPathFormatRightGrid); + + setIconSize(defaultCfg.gui.mainDlg.iconSize, defaultCfg.gui.mainDlg.showIcons); }; menu.addItem(_("&Default"), setDefault); //'&' -> reuse text from "default" buttons elsewhere //---------------------------------------------------------------------------------------------- menu.addSeparator(); - menu.addCheckBox(_("Show icons:"), [&] - { - globalCfg_.gui.mainDlg.showIcons = !globalCfg_.gui.mainDlg.showIcons; - gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg_.gui.mainDlg.showIcons, convert(globalCfg_.gui.mainDlg.iconSize)); - - }, globalCfg_.gui.mainDlg.showIcons); + menu.addCheckBox(_("Show icons:"), [&] { setIconSize(globalCfg_.gui.mainDlg.iconSize, !globalCfg_.gui.mainDlg.showIcons); }, globalCfg_.gui.mainDlg.showIcons); - auto setIconSize = [&](xmlAccess::FileIconSize sz) - { - globalCfg_.gui.mainDlg.iconSize = sz; - gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg_.gui.mainDlg.showIcons, convert(sz)); - }; auto addSizeEntry = [&](const wxString& label, xmlAccess::FileIconSize sz) { - menu.addRadio(label, [sz, &setIconSize] { setIconSize(sz); }, globalCfg_.gui.mainDlg.iconSize == sz, globalCfg_.gui.mainDlg.showIcons); + menu.addRadio(label, [sz, &setIconSize] { setIconSize(sz, true /*showIcons*/); }, globalCfg_.gui.mainDlg.iconSize == sz, globalCfg_.gui.mainDlg.showIcons); }; addSizeEntry(L" " + _("Small" ), xmlAccess::ICON_SIZE_SMALL ); addSizeEntry(L" " + _("Medium"), xmlAccess::ICON_SIZE_MEDIUM); addSizeEntry(L" " + _("Large" ), xmlAccess::ICON_SIZE_LARGE ); //---------------------------------------------------------------------------------------------- - if (type == ColumnTypeRim::DATE) + // if (type == ColumnTypeRim::DATE) { menu.addSeparator(); @@ -2572,8 +2534,9 @@ void MainDialog::onGridLabelContextRim(Grid& grid, ColumnTypeRim type, bool left }; menu.addItem(_("Select time span..."), selectTimeSpan); } - + //-------------------------------------------------------------------------------------------------------- menu.popup(*this); + //event.Skip(); } @@ -2702,6 +2665,7 @@ void MainDialog::OnSyncSettingsContext(wxEvent& event) void MainDialog::onDialogFilesDropped(FileDropEvent& event) { + assert(!event.getPaths().empty()); loadConfiguration(event.getPaths()); //event.Skip(); } @@ -2722,95 +2686,7 @@ void MainDialog::onDirManualCorrection(wxCommandEvent& event) } -Zstring getFormattedHistoryElement(const Zstring& filepath) -{ - Zstring output = afterLast(filepath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); - if (endsWith(output, Zstr(".ffs_gui"))) - output = beforeLast(output, Zstr('.'), IF_MISSING_RETURN_NONE); - return output; -} - - -void MainDialog::addFileToCfgHistory(const std::vector<Zstring>& filePaths) -{ - //determine highest "last use" index number of m_listBoxHistory - int lastUseIndexMax = 0; - for (unsigned int i = 0; i < m_listBoxHistory->GetCount(); ++i) - if (auto histData = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i))) - lastUseIndexMax = std::max(lastUseIndexMax, histData->lastUseIndex_); - else - assert(false); - - std::deque<bool> selections(m_listBoxHistory->GetCount()); //items to select after update of history list - - for (const Zstring& filePath : filePaths) - { - //Do we need to additionally check for aliases of the same physical files here? (and aliases for lastRunConfigName?) - - const auto itemPos = [&]() -> std::pair<wxClientHistoryData*, unsigned int> - { - for (unsigned int i = 0; i < m_listBoxHistory->GetCount(); ++i) - if (auto histData = dynamic_cast<wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i))) - { - if (equalFilePath(filePath, histData->cfgFile_)) - return std::make_pair(histData, i); - } - else - assert(false); - return std::make_pair(nullptr, 0); - }(); - - if (itemPos.first) //update - { - itemPos.first->lastUseIndex_ = ++lastUseIndexMax; - selections[itemPos.second] = true; - } - else //insert - { - const wxString lastSessionLabel = L"<" + _("Last session") + L">"; - - wxString newLabel; - unsigned int newPos = 0; - - if (equalFilePath(filePath, lastRunConfigPath_)) - newLabel = lastSessionLabel; - else - { - //workaround wxWidgets 2.9 bug on GTK screwing up the client data if the list box is sorted: - const Zstring labelFmt = getFormattedHistoryElement(filePath); - newLabel = utfTo<wxString>(labelFmt); - - //"linear-time insertion sort": - for (; newPos < m_listBoxHistory->GetCount(); ++newPos) - { - if (auto histData = dynamic_cast<wxClientHistoryData*>(m_listBoxHistory->GetClientObject(newPos))) - if (equalFilePath(histData->cfgFile_, lastRunConfigPath_)) - continue; //last session label should always be at top position! - - if (LessNaturalSort()(labelFmt, utfTo<Zstring>(m_listBoxHistory->GetString(newPos)))) - break; - } - } - - assert(!m_listBoxHistory->IsSorted()); - m_listBoxHistory->Insert(newLabel, newPos, new wxClientHistoryData(filePath, ++lastUseIndexMax)); - - selections.insert(selections.begin() + newPos, true); - } - } - - assert(selections.size() == m_listBoxHistory->GetCount()); - - //do not apply selections immediately but only when needed! - //this prevents problems with m_listBoxHistory losing keyboard selection focus if identical selection is redundantly reapplied - for (int pos = 0; pos < static_cast<int>(selections.size()); ++pos) - if (m_listBoxHistory->IsSelected(pos) != selections[pos]) - m_listBoxHistory->SetSelection(pos, selections[pos]); - //note: a *positive* SetSelection() will include a EnsureVisible()! -} - - -void MainDialog::removeObsoleteCfgHistoryItems(const std::vector<Zstring>& filePaths) +void MainDialog::cfgHistoryRemoveObsolete(const std::vector<Zstring>& filePaths) { auto getUnavailableCfgFilesAsync = [filePaths] //don't use wxString: NOT thread-safe! (e.g. non-atomic ref-count) { @@ -2822,33 +2698,21 @@ void MainDialog::removeObsoleteCfgHistoryItems(const std::vector<Zstring>& fileP //potentially slow network access => limit maximum wait time! wait_for_all_timed(availableFiles.begin(), availableFiles.end(), std::chrono::milliseconds(1000)); - std::vector<Zstring> filePathsForRemoval; + std::vector<Zstring> pathsToRemove; auto itFut = availableFiles.begin(); for (auto it = filePaths.begin(); it != filePaths.end(); ++it, ++itFut) if (isReady(*itFut) && !itFut->get()) //remove only files that are confirmed to be non-existent - filePathsForRemoval.push_back(*it); //file access error? probably not accessible network share or usb stick => remove cfg + pathsToRemove.push_back(*it); //file access error? probably not accessible network share or usb stick => remove cfg - return filePathsForRemoval; + return pathsToRemove; }; - guiQueue_.processAsync(getUnavailableCfgFilesAsync, [this](const std::vector<Zstring>& files) { removeCfgHistoryItems(files); }); -} - - -void MainDialog::removeCfgHistoryItems(const std::vector<Zstring>& filePaths) -{ - for (const Zstring& filepath : filePaths) + guiQueue_.processAsync(getUnavailableCfgFilesAsync, [this](const std::vector<Zstring>& filePaths2) { - const int histSize = m_listBoxHistory->GetCount(); - for (int i = 0; i < histSize; ++i) - if (auto histData = dynamic_cast<wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i))) - if (equalFilePath(filepath, histData->cfgFile_)) - { - m_listBoxHistory->Delete(i); - break; - } - } + cfggrid::getDataView(*m_gridCfgHistory).removeItems(filePaths2); + m_gridCfgHistory->Refresh(); + }); } @@ -2856,7 +2720,7 @@ void MainDialog::updateUnsavedCfgStatus() { const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); - const bool haveUnsavedCfg = lastConfigurationSaved_ != getConfig(); + const bool haveUnsavedCfg = lastSavedCfg_ != getConfig(); //update save config button const bool allowSave = haveUnsavedCfg || @@ -2952,7 +2816,7 @@ bool MainDialog::trySaveConfig(const Zstring* guiFilename) //return true if save { Zstring defaultFileName = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstr("SyncSettings.ffs_gui"); //attention: activeConfigFiles may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config! - if (endsWith(defaultFileName, Zstr(".ffs_batch"))) + if (endsWith(defaultFileName, Zstr(".ffs_batch"), CmpFilePath())) defaultFileName = beforeLast(defaultFileName, Zstr("."), IF_MISSING_RETURN_NONE) + Zstr(".ffs_gui"); wxFileDialog filePicker(this, //put modal dialog on stack: creating this on freestore leads to memleak! @@ -3037,7 +2901,7 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchFileToUpdate) Zstring defaultFileName = !activeCfgFilePath.empty() ? activeCfgFilePath : Zstr("BatchRun.ffs_batch"); //attention: activeConfigFiles may be a *.ffs_gui file! We don't want to overwrite it with a BATCH config! - if (endsWith(defaultFileName, Zstr(".ffs_gui"))) + if (endsWith(defaultFileName, Zstr(".ffs_gui"), CmpFilePath())) defaultFileName = beforeLast(defaultFileName, Zstr("."), IF_MISSING_RETURN_NONE) + Zstr(".ffs_batch"); wxFileDialog filePicker(this, //put modal dialog on stack: creating this on freestore leads to memleak! @@ -3073,7 +2937,7 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchFileToUpdate) bool MainDialog::saveOldConfig() //return false on user abort { - if (lastConfigurationSaved_ != getConfig()) + if (lastSavedCfg_ != getConfig()) { const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalFilePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); @@ -3125,7 +2989,7 @@ bool MainDialog::saveOldConfig() //return false on user abort } //discard current reference file(s), this ensures next app start will load <last session> instead of the original non-modified config selection - setLastUsedConfig(std::vector<Zstring>(), lastConfigurationSaved_); + setLastUsedConfig(std::vector<Zstring>(), lastSavedCfg_); //this seems to make theoretical sense also: the job of this function is to make sure current (volatile) config and reference file name are in sync // => if user does not save cfg, it is not attached to a physical file names anymore! } @@ -3152,6 +3016,7 @@ void MainDialog::OnConfigLoad(wxCommandEvent& event) for (const wxString& path : tmp) filePaths.push_back(utfTo<Zstring>(path)); + assert(!filePaths.empty()); loadConfiguration(filePaths); } } @@ -3159,6 +3024,9 @@ void MainDialog::OnConfigLoad(wxCommandEvent& event) void MainDialog::OnConfigNew(wxCommandEvent& event) { + warn_static("replace by loadConfiguration({});") + warn_static("replace excludeFilter handling below with: cfgGrid context menu /new default configuration/") + if (!saveOldConfig()) //notify user about changed settings return; @@ -3175,86 +3043,70 @@ void MainDialog::OnConfigNew(wxCommandEvent& event) } -void MainDialog::OnLoadFromHistory(wxCommandEvent& event) +void MainDialog::onCfgGridSelection(GridSelectEvent& event) { - wxArrayInt selections; - m_listBoxHistory->GetSelections(selections); + if (event.mouseSelect_ && !event.mouseSelect_->complete) + return; //skip the preliminary "clear range" event for mouse-down! + //the mouse is still captured, so we don't want to show a modal dialog (e.g. save changes?) before mouse-up! + //what if mouse capture is lost? minor glitch: grid selection is empty, but parameter owner is "activeConfigFiles_" in any case - std::vector<Zstring> filepaths; - for (int pos : selections) - if (auto histData = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(pos))) - filepaths.push_back(histData->cfgFile_); + std::vector<Zstring> filePaths; + for (size_t row : m_gridCfgHistory->getSelectedRows()) + if (const ConfigView::Details* cfg = cfggrid::getDataView(*m_gridCfgHistory).getItem(row)) + filePaths.push_back(cfg->filePath); else assert(false); - - if (!filepaths.empty()) - loadConfiguration(filepaths); - - //user changed m_listBoxHistory selection so it's this method's responsibility to synchronize with activeConfigFiles: - //- if user cancelled saving old config - //- there's an error loading new config - //- filepaths is empty and user tried to unselect the current config - addFileToCfgHistory(activeConfigFiles_); +#if 1 + if (!loadConfiguration(filePaths)) + //user changed m_gridCfgHistory selection so it's this method's responsibility to synchronize with activeConfigFiles: + //- if user cancelled saving old config + //- there's an error loading new config + //- filePaths is empty and user tried to unselect the current config + cfggrid::addAndSelect(*m_gridCfgHistory, activeConfigFiles_, false /*scrollToSelection*/); +#endif + warn_static("support the last one??? does NOT support newConfig.mainCfg.globalFilter.excludeFilter!!!") } -void MainDialog::OnLoadFromHistoryDoubleClick(wxCommandEvent& event) +void MainDialog::onCfgGridDoubleClick(GridClickEvent& event) { - wxArrayInt selections; - m_listBoxHistory->GetSelections(selections); - - std::vector<Zstring> filepaths; - for (int pos : selections) - if (auto histData = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(pos))) - filepaths.push_back(histData->cfgFile_); - else - assert(false); - - if (!filepaths.empty()) - if (loadConfiguration(filepaths)) - { - //simulate button click on "compare" - wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); - if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler()) - evtHandler->ProcessEvent(dummy2); //synchronous call - } - - //synchronize m_listBoxHistory and activeConfigFiles, see OnLoadFromHistory() - addFileToCfgHistory(activeConfigFiles_); + if (!activeConfigFiles_.empty()) + { + wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED); + m_buttonCompare->Command(dummy); //simulate click + } } -bool MainDialog::loadConfiguration(const std::vector<Zstring>& filepaths) +bool MainDialog::loadConfiguration(const std::vector<Zstring>& filePaths) { - if (filepaths.empty()) - return true; - if (!saveOldConfig()) return false; //cancelled by user - //load XML - xmlAccess::XmlGuiConfig newGuiCfg; //structure to receive gui settings, already defaulted!! - try - { - //allow reading batch configurations also - std::wstring warningMsg; - xmlAccess::readAnyConfig(filepaths, newGuiCfg, warningMsg); //throw FileError + xmlAccess::XmlGuiConfig newGuiCfg; //contains default values - if (!warningMsg.empty()) + if (!filePaths.empty()) //empty cfg file list means "use default" + try { - showNotificationDialog(this, DialogInfoType::WARNING, PopupDialogCfg().setDetailInstructions(warningMsg)); - setConfig(newGuiCfg, filepaths); - setLastUsedConfig(filepaths, xmlAccess::XmlGuiConfig()); //simulate changed config due to parsing errors + //allow reading batch configurations also + std::wstring warningMsg; + xmlAccess::readAnyConfig(filePaths, newGuiCfg, warningMsg); //throw FileError + + if (!warningMsg.empty()) + { + showNotificationDialog(this, DialogInfoType::WARNING, PopupDialogCfg().setDetailInstructions(warningMsg)); + setConfig(newGuiCfg, filePaths); + setLastUsedConfig(filePaths, xmlAccess::XmlGuiConfig()); //simulate changed config due to parsing errors + return true; + } + } + catch (const FileError& e) + { + showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); return false; } - } - catch (const FileError& e) - { - showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); - return false; - } - setConfig(newGuiCfg, filepaths); + setConfig(newGuiCfg, filePaths); //flashStatusInformation("Configuration loaded"); -> irrelevant!? return true; } @@ -3262,33 +3114,33 @@ bool MainDialog::loadConfiguration(const std::vector<Zstring>& filepaths) void MainDialog::deleteSelectedCfgHistoryItems() { - wxArrayInt tmp; - m_listBoxHistory->GetSelections(tmp); - - std::set<int> selections(tmp.begin(), tmp.end()); //sort ascending! - //delete starting with high positions: - std::for_each(selections.rbegin(), selections.rend(), [&](int pos) { m_listBoxHistory->Delete(pos); }); - - //set active selection on next element to allow "batch-deletion" by holding down DEL key - if (!selections.empty() && !m_listBoxHistory->IsEmpty()) + const std::vector<size_t> selectedRows = m_gridCfgHistory->getSelectedRows(); + if (!selectedRows.empty()) { - int newSelection = *selections.begin(); - if (newSelection >= static_cast<int>(m_listBoxHistory->GetCount())) - newSelection = m_listBoxHistory->GetCount() - 1; - m_listBoxHistory->SetSelection(newSelection); - } -} + std::vector<Zstring> filePaths; + for (size_t row : selectedRows) + if (const ConfigView::Details* cfg = cfggrid::getDataView(*m_gridCfgHistory).getItem(row)) + filePaths.push_back(cfg->filePath); + else + assert(false); + cfggrid::getDataView(*m_gridCfgHistory).removeItems(filePaths); + m_gridCfgHistory->Refresh(); //grid size changed => clears selection! -void MainDialog::OnCfgHistoryRightClick(wxMouseEvent& event) -{ - ContextMenu menu; - menu.addItem(_("Remove entry from list") + L"\tDel", [this] { deleteSelectedCfgHistoryItems(); }); - menu.popup(*this); + //set active selection on next element to allow "batch-deletion" by holding down DEL key + if (m_gridCfgHistory->getRowCount() > 0) + { + size_t nextRow = selectedRows.front(); + if (nextRow >= m_gridCfgHistory->getRowCount()) + nextRow = m_gridCfgHistory->getRowCount() - 1; + + m_gridCfgHistory->selectRow(nextRow, GridEventPolicy::DENY_GRID_EVENT); + } + } } -void MainDialog::OnCfgHistoryKeyEvent(wxKeyEvent& event) +void MainDialog::onCfgGridKeyEvent(wxKeyEvent& event) { const int keyCode = event.GetKeyCode(); if (keyCode == WXK_DELETE || @@ -3301,17 +3153,110 @@ void MainDialog::OnCfgHistoryKeyEvent(wxKeyEvent& event) } +void MainDialog::onCfgGridContext(GridClickEvent& event) +{ + ContextMenu menu; + //-------------------------------------------------------------------------------------------------------- + const std::vector<size_t> selectedRows = m_gridCfgHistory->getSelectedRows(); + + menu.addItem(_("Hide configuration") + L"\tDel", [this] { deleteSelectedCfgHistoryItems(); }, nullptr, !selectedRows.empty()); + //-------------------------------------------------------------------------------------------------------- + menu.popup(*this); + //event.Skip(); +} + + +void MainDialog::onCfgGridLabelContext(GridLabelClickEvent& event) +{ + ContextMenu menu; + //-------------------------------------------------------------------------------------------------------- + auto toggleColumn = [&](ColumnType ct) + { + auto colAttr = m_gridCfgHistory->getColumnConfig(); + + Grid::ColAttributes* caName = nullptr; + Grid::ColAttributes* caToggle = nullptr; + + for (Grid::ColAttributes& ca : colAttr) + if (ca.type == static_cast<ColumnType>(ColumnTypeCfg::NAME)) + caName = &ca; + else if (ca.type == ct) + caToggle = &ca; + + assert(caName && caName->stretch > 0 && caName->visible); + assert(caToggle && caToggle->stretch == 0); + + if (caName && caToggle) + { + caToggle->visible = !caToggle->visible; + + //take width of newly visible column from stretched folder name column + caName->offset -= caToggle->visible ? caToggle->offset : -caToggle->offset; + + m_gridCfgHistory->setColumnConfig(colAttr); + } + }; + + if (auto prov = m_gridCfgHistory->getDataProvider()) + for (const Grid::ColAttributes& ca : m_gridCfgHistory->getColumnConfig()) + menu.addCheckBox(prov->getColumnLabel(ca.type), [ct = ca.type, toggleColumn] { toggleColumn(ct); }, + ca.visible, ca.type != static_cast<ColumnType>(ColumnTypeCfg::NAME)); //do not allow user to hide name column! + else assert(false); + //-------------------------------------------------------------------------------------------------------- + menu.addSeparator(); + + auto setDefault = [&] + { + const xmlAccess::XmlGlobalSettings defaultCfg; + m_gridCfgHistory->setColumnConfig(convertColAttributes(defaultCfg.gui.mainDlg.cfgGridColumnAttribs, getCfgGridDefaultColAttribs())); + }; + menu.addItem(_("&Default"), setDefault); //'&' -> reuse text from "default" buttons elsewhere + //-------------------------------------------------------------------------------------------------------- + menu.addSeparator(); + + auto setCfgHighlight = [&] + { + int cfgGridSyncOverdueDays = cfggrid::getSyncOverdueDays(*m_gridCfgHistory); + + if (showCfgHighlightDlg(this, cfgGridSyncOverdueDays) == ReturnSmallDlg::BUTTON_OKAY) + cfggrid::setSyncOverdueDays(*m_gridCfgHistory, cfgGridSyncOverdueDays); + }; + menu.addItem(_("Highlight..."), setCfgHighlight); + //-------------------------------------------------------------------------------------------------------- + + menu.popup(*m_gridCfgHistory); + //event.Skip(); +} + + +void MainDialog::onCfgGridLabelLeftClick(GridLabelClickEvent& event) +{ + const auto colType = static_cast<ColumnTypeCfg>(event.colType_); + bool sortAscending = getDefaultSortDirection(colType); + + const auto sortInfo = cfggrid::getDataView(*m_gridCfgHistory).getSortDirection(); + if (sortInfo.first == colType) + sortAscending = !sortInfo.second; + + cfggrid::getDataView(*m_gridCfgHistory).setSortDirection(colType, sortAscending); + m_gridCfgHistory->Refresh(); + + //re-apply selection: + cfggrid::addAndSelect(*m_gridCfgHistory, activeConfigFiles_, false /*scrollToSelection*/); +} + + void MainDialog::onCheckRows(CheckRowsEvent& event) { std::vector<size_t> selectedRows; - const size_t rowLast = std::min(event.rowLast_, gridDataView_->rowsOnView()); //consider dummy rows + const size_t rowLast = std::min(event.rowLast_, filegrid::getDataView(*m_gridMainC).rowsOnView()); //consider dummy rows for (size_t i = event.rowFirst_; i < rowLast; ++i) selectedRows.push_back(i); if (!selectedRows.empty()) { - std::vector<FileSystemObject*> objects = gridDataView_->getAllFileRef(selectedRows); + std::vector<FileSystemObject*> objects = filegrid::getDataView(*m_gridMainC).getAllFileRef(selectedRows); setFilterManually(objects, event.setIncluded_); } } @@ -3321,13 +3266,13 @@ void MainDialog::onSetSyncDirection(SyncDirectionEvent& event) { std::vector<size_t> selectedRows; - const size_t rowLast = std::min(event.rowLast_, gridDataView_->rowsOnView()); //consider dummy rows + const size_t rowLast = std::min(event.rowLast_, filegrid::getDataView(*m_gridMainC).rowsOnView()); //consider dummy rows for (size_t i = event.rowFirst_; i < rowLast; ++i) selectedRows.push_back(i); if (!selectedRows.empty()) { - std::vector<FileSystemObject*> objects = gridDataView_->getAllFileRef(selectedRows); + std::vector<FileSystemObject*> objects = filegrid::getDataView(*m_gridMainC).getAllFileRef(selectedRows); setSyncDirManually(objects, event.direction_); } } @@ -3337,9 +3282,9 @@ void MainDialog::setLastUsedConfig(const std::vector<Zstring>& cfgFilePaths, const xmlAccess::XmlGuiConfig& guiConfig) { activeConfigFiles_ = cfgFilePaths; - lastConfigurationSaved_ = guiConfig; + lastSavedCfg_ = guiConfig; - addFileToCfgHistory(activeConfigFiles_); //put filepath on list of last used config files + cfggrid::addAndSelect(*m_gridCfgHistory, activeConfigFiles_, true /*scrollToSelection*/); //put filepath on list of last used config files updateUnsavedCfgStatus(); } @@ -3400,7 +3345,7 @@ void MainDialog::updateGuiDelayedIf(bool condition) if (condition) { - gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); + filegrid::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); m_gridMainL->Update(); m_gridMainC->Update(); m_gridMainR->Update(); @@ -3467,7 +3412,7 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde currentCfg_.mainCfg.postSyncCommand, currentCfg_.mainCfg.postSyncCondition, globalCfg_.gui.commandHistory, - globalCfg_.gui.commandHistoryMax) == ReturnSyncConfig::BUTTON_OKAY) + globalCfg_.gui.commandHistItemsMax) == ReturnSyncConfig::BUTTON_OKAY) { assert(folderPairConfig.size() == folderPairConfigOld.size()); @@ -3780,8 +3725,8 @@ void MainDialog::OnCompare(wxCommandEvent& event) return; } - gridDataView_->setData(folderCmp_); //update view on data - treeDataView_->setData(folderCmp_); // + filegrid::getDataView(*m_gridMainC).setData(folderCmp_); //update view on data + treegrid::getDataView(*m_gridOverview).setData(folderCmp_); // updateGui(); m_gridMainL->clearSelection(ALLOW_GRID_EVENT); @@ -3844,8 +3789,8 @@ void MainDialog::clearGrid(ptrdiff_t pos) folderCmp_.erase(folderCmp_.begin() + pos); } - gridDataView_->setData(folderCmp_); - treeDataView_->setData(folderCmp_); + filegrid::getDataView(*m_gridMainC).setData(folderCmp_); + treegrid::getDataView(*m_gridOverview).setData(folderCmp_); updateGui(); } @@ -3912,9 +3857,8 @@ void MainDialog::OnStartSync(wxCommandEvent& event) if (folderCmp_.empty()) { //quick sync: simulate button click on "compare" - wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); - if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler()) - evtHandler->ProcessEvent(dummy2); //synchronous call + wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED); + m_buttonCompare->Command(dummy); //simulate click if (folderCmp_.empty()) //check if user aborted or error occurred, ect... return; @@ -3999,11 +3943,21 @@ void MainDialog::OnStartSync(wxCommandEvent& event) folderCmp_, globalCfg_.optDialogs, statusHandler); + + //not cancelled? => update last sync date for selected cfg files + std::vector<std::pair<Zstring, time_t>> lastSyncTimes; + for (const Zstring& cfgPath : activeConfigFiles_) + lastSyncTimes.emplace_back(cfgPath, std::time(nullptr)); + + cfggrid::getDataView(*m_gridCfgHistory).setLastSyncTime(lastSyncTimes); + //re-apply selection: sort order changed if sorted by last sync time + cfggrid::addAndSelect(*m_gridCfgHistory, activeConfigFiles_, false /*scrollToSelection*/); + //m_gridCfgHistory->Refresh(); <- implicit in last call } catch (AbortProcess&) {} //remove empty rows: just a beautification, invalid rows shouldn't cause issues - gridDataView_->removeInvalidRows(); + filegrid::getDataView(*m_gridMainC).removeInvalidRows(); updateGui(); @@ -4030,7 +3984,7 @@ void MainDialog::onGridDoubleClickRim(size_t row, bool leftSide) { std::vector<FileSystemObject*> selectionLeft; std::vector<FileSystemObject*> selectionRight; - if (FileSystemObject* fsObj = gridDataView_->getObject(row)) //selection must be a list of BOUND pointers! + if (FileSystemObject* fsObj = filegrid::getDataView(*m_gridMainC).getObject(row)) //selection must be a list of BOUND pointers! (leftSide ? selectionLeft : selectionRight) = { fsObj }; openExternalApplication(globalCfg_.gui.externelApplications[0].second, leftSide, selectionLeft, selectionRight); @@ -4040,15 +3994,15 @@ void MainDialog::onGridDoubleClickRim(size_t row, bool leftSide) void MainDialog::onGridLabelLeftClick(bool onLeft, ColumnTypeRim type) { - auto sortInfo = gridDataView_->getSortInfo(); + auto sortInfo = filegrid::getDataView(*m_gridMainC).getSortInfo(); - bool sortAscending = GridView::getDefaultSortDirection(type); - if (sortInfo && sortInfo->onLeft_ == onLeft && sortInfo->type_ == type) - sortAscending = !sortInfo->ascending_; + bool sortAscending = getDefaultSortDirection(type); + if (sortInfo && sortInfo->onLeft == onLeft && sortInfo->type == type) + sortAscending = !sortInfo->ascending; const ItemPathFormat itemPathFormat = onLeft ? globalCfg_.gui.mainDlg.itemPathFormatLeftGrid : globalCfg_.gui.mainDlg.itemPathFormatRightGrid; - gridDataView_->sortView(type, itemPathFormat, onLeft, sortAscending); + filegrid::getDataView(*m_gridMainC).sortView(type, itemPathFormat, onLeft, sortAscending); m_gridMainL->clearSelection(ALLOW_GRID_EVENT); m_gridMainC->clearSelection(ALLOW_GRID_EVENT); @@ -4143,16 +4097,16 @@ void MainDialog::updateGridViewData() if (m_bpButtonViewTypeSyncAction->isActive()) { - const GridView::StatusSyncPreview result = gridDataView_->updateSyncPreview(m_bpButtonShowExcluded ->isActive(), - m_bpButtonShowCreateLeft ->isActive(), - m_bpButtonShowCreateRight->isActive(), - m_bpButtonShowDeleteLeft ->isActive(), - m_bpButtonShowDeleteRight->isActive(), - m_bpButtonShowUpdateLeft ->isActive(), - m_bpButtonShowUpdateRight->isActive(), - m_bpButtonShowDoNothing ->isActive(), - m_bpButtonShowEqual ->isActive(), - m_bpButtonShowConflict ->isActive()); + const FileView::StatusSyncPreview result = filegrid::getDataView(*m_gridMainC).updateSyncPreview(m_bpButtonShowExcluded ->isActive(), + m_bpButtonShowCreateLeft ->isActive(), + m_bpButtonShowCreateRight->isActive(), + m_bpButtonShowDeleteLeft ->isActive(), + m_bpButtonShowDeleteRight->isActive(), + m_bpButtonShowUpdateLeft ->isActive(), + m_bpButtonShowUpdateRight->isActive(), + m_bpButtonShowDoNothing ->isActive(), + m_bpButtonShowEqual ->isActive(), + m_bpButtonShowConflict ->isActive()); filesOnLeftView = result.filesOnLeftView; foldersOnLeftView = result.foldersOnLeftView; filesOnRightView = result.filesOnRightView; @@ -4181,14 +4135,14 @@ void MainDialog::updateGridViewData() } else { - const GridView::StatusCmpResult result = gridDataView_->updateCmpResult(m_bpButtonShowExcluded ->isActive(), - m_bpButtonShowLeftOnly ->isActive(), - m_bpButtonShowRightOnly ->isActive(), - m_bpButtonShowLeftNewer ->isActive(), - m_bpButtonShowRightNewer->isActive(), - m_bpButtonShowDifferent ->isActive(), - m_bpButtonShowEqual ->isActive(), - m_bpButtonShowConflict ->isActive()); + const FileView::StatusCmpResult result = filegrid::getDataView(*m_gridMainC).updateCmpResult(m_bpButtonShowExcluded ->isActive(), + m_bpButtonShowLeftOnly ->isActive(), + m_bpButtonShowRightOnly ->isActive(), + m_bpButtonShowLeftNewer ->isActive(), + m_bpButtonShowRightNewer->isActive(), + m_bpButtonShowDifferent ->isActive(), + m_bpButtonShowEqual ->isActive(), + m_bpButtonShowConflict ->isActive()); filesOnLeftView = result.filesOnLeftView; foldersOnLeftView = result.foldersOnLeftView; filesOnRightView = result.filesOnRightView; @@ -4242,29 +4196,29 @@ void MainDialog::updateGridViewData() m_panelViewFilter->Layout(); //all three grids retrieve their data directly via gridDataView - gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); + filegrid::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); - //navigation tree + //overview panel if (m_bpButtonViewTypeSyncAction->isActive()) - treeDataView_->updateSyncPreview(m_bpButtonShowExcluded ->isActive(), - m_bpButtonShowCreateLeft ->isActive(), - m_bpButtonShowCreateRight->isActive(), - m_bpButtonShowDeleteLeft ->isActive(), - m_bpButtonShowDeleteRight->isActive(), - m_bpButtonShowUpdateLeft ->isActive(), - m_bpButtonShowUpdateRight->isActive(), - m_bpButtonShowDoNothing ->isActive(), - m_bpButtonShowEqual ->isActive(), - m_bpButtonShowConflict ->isActive()); + treegrid::getDataView(*m_gridOverview).updateSyncPreview(m_bpButtonShowExcluded ->isActive(), + m_bpButtonShowCreateLeft ->isActive(), + m_bpButtonShowCreateRight->isActive(), + m_bpButtonShowDeleteLeft ->isActive(), + m_bpButtonShowDeleteRight->isActive(), + m_bpButtonShowUpdateLeft ->isActive(), + m_bpButtonShowUpdateRight->isActive(), + m_bpButtonShowDoNothing ->isActive(), + m_bpButtonShowEqual ->isActive(), + m_bpButtonShowConflict ->isActive()); else - treeDataView_->updateCmpResult(m_bpButtonShowExcluded ->isActive(), - m_bpButtonShowLeftOnly ->isActive(), - m_bpButtonShowRightOnly ->isActive(), - m_bpButtonShowLeftNewer ->isActive(), - m_bpButtonShowRightNewer->isActive(), - m_bpButtonShowDifferent ->isActive(), - m_bpButtonShowEqual ->isActive(), - m_bpButtonShowConflict ->isActive()); + treegrid::getDataView(*m_gridOverview).updateCmpResult(m_bpButtonShowExcluded ->isActive(), + m_bpButtonShowLeftOnly ->isActive(), + m_bpButtonShowRightOnly ->isActive(), + m_bpButtonShowLeftNewer ->isActive(), + m_bpButtonShowRightNewer->isActive(), + m_bpButtonShowDifferent ->isActive(), + m_bpButtonShowEqual ->isActive(), + m_bpButtonShowConflict ->isActive()); m_gridOverview->Refresh(); //update status bar information @@ -4387,7 +4341,7 @@ void MainDialog::startFindNext(bool searchAscending) //F3 or ENTER in m_textCtrl { assert(result.second >= 0); - gridview::setScrollMaster(*grid); + filegrid::setScrollMaster(*grid); grid->setGridCursor(result.second); focusWindowAfterSearch_ = &grid->getMainWin(); @@ -4682,8 +4636,8 @@ void MainDialog::moveAddFolderPairUp(size_t pos) if (!folderCmp_.empty()) std::swap(folderCmp_[pos], folderCmp_[pos + 1]); //invariant: folderCmp is empty or matches number of all folder pairs - gridDataView_->setData(folderCmp_); - treeDataView_->setData(folderCmp_); + filegrid::getDataView(*m_gridMainC ).setData(folderCmp_); + treegrid::getDataView(*m_gridOverview).setData(folderCmp_); updateGui(); } } @@ -4784,31 +4738,31 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) auto colAttrCenter = m_gridMainC->getColumnConfig(); auto colAttrRight = m_gridMainR->getColumnConfig(); - erase_if(colAttrLeft, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); - 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_; }); + erase_if(colAttrLeft, [](const Grid::ColAttributes& ca) { return !ca.visible; }); + erase_if(colAttrCenter, [](const Grid::ColAttributes& ca) { return !ca.visible || static_cast<ColumnTypeCenter>(ca.type) == ColumnTypeCenter::CHECKBOX; }); + erase_if(colAttrRight, [](const Grid::ColAttributes& ca) { return !ca.visible; }); if (provLeft && provCenter && provRight) { - for (const Grid::ColumnAttribute& ca : colAttrLeft) + for (const Grid::ColAttributes& ca : colAttrLeft) { - header += fmtValue(provLeft->getColumnLabel(ca.type_)); + header += fmtValue(provLeft->getColumnLabel(ca.type)); header += CSV_SEP; } - for (const Grid::ColumnAttribute& ca : colAttrCenter) + for (const Grid::ColAttributes& ca : colAttrCenter) { - header += fmtValue(provCenter->getColumnLabel(ca.type_)); + header += fmtValue(provCenter->getColumnLabel(ca.type)); header += CSV_SEP; } if (!colAttrRight.empty()) { std::for_each(colAttrRight.begin(), colAttrRight.end() - 1, - [&](const Grid::ColumnAttribute& ca) + [&](const Grid::ColAttributes& ca) { - header += fmtValue(provRight->getColumnLabel(ca.type_)); + header += fmtValue(provRight->getColumnLabel(ca.type)); header += CSV_SEP; }); - header += fmtValue(provRight->getColumnLabel(colAttrRight.back().type_)); + header += fmtValue(provRight->getColumnLabel(colAttrRight.back().type)); } header += LINE_BREAK; @@ -4828,21 +4782,21 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) const size_t rowCount = m_gridMainL->getRowCount(); for (size_t row = 0; row < rowCount; ++row) { - for (const Grid::ColumnAttribute& ca : colAttrLeft) + for (const Grid::ColAttributes& ca : colAttrLeft) { - buffer += fmtValue(provLeft->getValue(row, ca.type_)); + buffer += fmtValue(provLeft->getValue(row, ca.type)); buffer += CSV_SEP; } - for (const Grid::ColumnAttribute& ca : colAttrCenter) + for (const Grid::ColAttributes& ca : colAttrCenter) { - buffer += fmtValue(provCenter->getValue(row, ca.type_)); + buffer += fmtValue(provCenter->getValue(row, ca.type)); buffer += CSV_SEP; } - for (const Grid::ColumnAttribute& ca : colAttrRight) + for (const Grid::ColAttributes& ca : colAttrRight) { - buffer += fmtValue(provRight->getValue(row, ca.type_)); + buffer += fmtValue(provRight->getValue(row, ca.type)); buffer += CSV_SEP; } buffer += LINE_BREAK; @@ -4951,7 +4905,7 @@ void MainDialog::switchProgramLanguage(wxLanguage langId) newGlobalCfg.programLanguage = langId; //show new dialog, then delete old one - MainDialog::create(globalConfigFile_, &newGlobalCfg, getConfig(), activeConfigFiles_, false); + MainDialog::create(globalConfigFilePath_, &newGlobalCfg, getConfig(), activeConfigFiles_, false); //we don't use Close(): //1. we don't want to show the prompt to save current config in OnClose() @@ -4970,7 +4924,7 @@ void MainDialog::setViewTypeSyncAction(bool value) m_bpButtonViewTypeSyncAction->SetToolTip((value ? _("Action") : _("Category")) + L" (F10)"); //toggle display of sync preview in middle grid - gridview::highlightSyncAction(*m_gridMainC, value); + filegrid::highlightSyncAction(*m_gridMainC, value); updateGui(); } diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index 045e94f1..ffc0ec52 100755 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -11,16 +11,14 @@ #include <list> #include <stack> #include <memory> -//#include <zen/error_log.h> #include <wx+/async_task.h> #include <wx+/file_drop.h> #include <wx/aui/aui.h> #include "gui_generated.h" -#include "custom_grid.h" +#include "file_grid.h" +#include "tree_grid.h" #include "sync_cfg.h" -#include "tree_view.h" #include "folder_history_box.h" -//#include "../lib/process_xml.h" #include "../algorithm.h" class FolderPairFirst; @@ -32,12 +30,12 @@ class MainDialog : public MainDialogGenerated { public: //default behavior, application start, restores last used config - static void create(const Zstring& globalConfigFile); + static void create(const Zstring& globalConfigFilePath); //when loading dynamically assembled config, //when switching language, //or switching from batch run to GUI on warnings - static void create(const Zstring& globalConfigFile, + static void create(const Zstring& globalConfigFilePath, const xmlAccess::XmlGlobalSettings* globalSettings, //optional: take over ownership => save on exit const xmlAccess::XmlGuiConfig& guiCfg, const std::vector<Zstring>& referenceFiles, @@ -49,7 +47,7 @@ public: void onQueryEndSession(); //last chance to do something useful before killing the application! private: - MainDialog(const Zstring& globalConfigFile, + MainDialog(const Zstring& globalConfigFilePath, const xmlAccess::XmlGuiConfig& guiCfg, const std::vector<Zstring>& referenceFiles, const xmlAccess::XmlGlobalSettings& globalSettings, //take over ownership => save on exit @@ -87,9 +85,7 @@ private: void initViewFilterButtons(); void setViewFilterDefault(); - void addFileToCfgHistory(const std::vector<Zstring>& filepaths); //= update/insert + apply selection - void removeObsoleteCfgHistoryItems(const std::vector<Zstring>& filepaths); - void removeCfgHistoryItems(const std::vector<Zstring>& filepaths); + void cfgHistoryRemoveObsolete(const std::vector<Zstring>& filepaths); void insertAddFolderPair(const std::vector<zen::FolderPairEnh>& newPairs, size_t pos); void moveAddFolderPairUp(size_t pos); @@ -162,9 +158,9 @@ private: void onMainGridContextR(zen::GridClickEvent& event); void onMainGridContextRim(bool leftSide); - void onNaviGridContext(zen::GridClickEvent& event); + void onTreeGridContext(zen::GridClickEvent& event); - void onNaviSelection(zen::GridRangeSelectEvent& event); + void onTreeGridSelection(zen::GridSelectEvent& event); void onDialogFilesDropped(zen::FileDropEvent& event); @@ -196,13 +192,16 @@ private: void OnConfigSaveAs (wxCommandEvent& event) override; void OnSaveAsBatchJob (wxCommandEvent& event) override; void OnConfigLoad (wxCommandEvent& event) override; - void OnLoadFromHistory(wxCommandEvent& event) override; - void OnLoadFromHistoryDoubleClick(wxCommandEvent& event) override; + + void onCfgGridSelection (zen::GridSelectEvent& event); + void onCfgGridDoubleClick(zen::GridClickEvent& event); + void onCfgGridKeyEvent (wxKeyEvent& event); + void onCfgGridContext (zen::GridClickEvent& event); + void onCfgGridLabelContext (zen::GridLabelClickEvent& event); + void onCfgGridLabelLeftClick(zen::GridLabelClickEvent& event); void deleteSelectedCfgHistoryItems(); - void OnCfgHistoryRightClick(wxMouseEvent& event) override; - void OnCfgHistoryKeyEvent (wxKeyEvent& event) override; void OnRegularUpdateCheck (wxIdleEvent& event); void OnLayoutWindowAsync (wxIdleEvent& event); @@ -281,25 +280,20 @@ private: //global settings shared by GUI and batch mode xmlAccess::XmlGlobalSettings globalCfg_; - const Zstring globalConfigFile_; + const Zstring globalConfigFilePath_; //------------------------------------- //program configuration xmlAccess::XmlGuiConfig currentCfg_; //used when saving configuration - std::vector<Zstring> activeConfigFiles_; //name of currently loaded config file (may be more than 1) + std::vector<Zstring> activeConfigFiles_; //name of currently loaded config files: NOT owned by m_gridCfgHistory, see onCfgGridSelection() - xmlAccess::XmlGuiConfig lastConfigurationSaved_; //support for: "Save changed configuration?" dialog + xmlAccess::XmlGuiConfig lastSavedCfg_; //support for: "Save changed configuration?" dialog - static Zstring getLastRunConfigPath(); const Zstring lastRunConfigPath_; //let's not use another static... //------------------------------------- - //UI view of FolderComparison structure (partially owns folderCmp) - std::shared_ptr<zen::GridView> gridDataView_; //always bound! - std::shared_ptr<zen::TreeView> treeDataView_; // - //the prime data structure of this tool *bling*: zen::FolderComparison folderCmp_; //optional!: sync button not available if empty diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index 3738b3ce..bd97f8d5 100755 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -595,50 +595,51 @@ private: //----------------------------------------------------------------------------- -enum ColumnTypeMsg +enum class ColumnTypeMsg { - COL_TYPE_MSG_TIME, - COL_TYPE_MSG_CATEGORY, - COL_TYPE_MSG_TEXT, + TIME, + CATEGORY, + TEXT, }; //Grid data implementation referencing MessageView class GridDataMessages : public GridData { public: - GridDataMessages(const std::shared_ptr<MessageView>& msgView) : msgView_(msgView) {} + GridDataMessages(const ErrorLog& log) : msgView_(log) {} - size_t getRowCount() const override { return msgView_ ? msgView_->rowsOnView() : 0; } + MessageView& getDataView() { return msgView_; } + + size_t getRowCount() const override { return msgView_.rowsOnView(); } std::wstring getValue(size_t row, ColumnType colType) const override { - if (msgView_) - if (Opt<MessageView::LogEntryView> entry = msgView_->getEntry(row)) - switch (static_cast<ColumnTypeMsg>(colType)) - { - case COL_TYPE_MSG_TIME: - if (entry->firstLine) - return formatTime<std::wstring>(FORMAT_TIME, getLocalTime(entry->time)); - break; + if (Opt<MessageView::LogEntryView> entry = msgView_.getEntry(row)) + switch (static_cast<ColumnTypeMsg>(colType)) + { + case ColumnTypeMsg::TIME: + if (entry->firstLine) + return formatTime<std::wstring>(FORMAT_TIME, getLocalTime(entry->time)); + break; - case COL_TYPE_MSG_CATEGORY: - if (entry->firstLine) - switch (entry->type) - { - case TYPE_INFO: - return _("Info"); - case TYPE_WARNING: - return _("Warning"); - case TYPE_ERROR: - return _("Error"); - case TYPE_FATAL_ERROR: - return _("Serious Error"); - } - break; + case ColumnTypeMsg::CATEGORY: + if (entry->firstLine) + switch (entry->type) + { + case TYPE_INFO: + return _("Info"); + case TYPE_WARNING: + return _("Warning"); + case TYPE_ERROR: + return _("Error"); + case TYPE_FATAL_ERROR: + return _("Serious Error"); + } + break; - case COL_TYPE_MSG_TEXT: - return copyStringTo<std::wstring>(entry->messageLine); - } + case ColumnTypeMsg::TEXT: + return copyStringTo<std::wstring>(entry->messageLine); + } return std::wstring(); } @@ -647,73 +648,71 @@ public: wxRect rectTmp = rect; //-------------- draw item separation line ----------------- - wxDCPenChanger dummy2(dc, getColorGridLine()); - const bool drawBottomLine = [&] //don't separate multi-line messages { - if (msgView_) - if (Opt<MessageView::LogEntryView> nextEntry = msgView_->getEntry(row + 1)) + wxDCPenChanger dummy2(dc, getColorGridLine()); + const bool drawBottomLine = [&] //don't separate multi-line messages + { + if (Opt<MessageView::LogEntryView> nextEntry = msgView_.getEntry(row + 1)) return nextEntry->firstLine; - return true; - }(); + return true; + }(); - if (drawBottomLine) - { - dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); - --rectTmp.height; + if (drawBottomLine) + { + dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); + --rectTmp.height; + } } - //-------------------------------------------------------- - if (msgView_) - if (Opt<MessageView::LogEntryView> entry = msgView_->getEntry(row)) - switch (static_cast<ColumnTypeMsg>(colType)) - { - case COL_TYPE_MSG_TIME: - drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_CENTER); - break; + if (Opt<MessageView::LogEntryView> entry = msgView_.getEntry(row)) + switch (static_cast<ColumnTypeMsg>(colType)) + { + case ColumnTypeMsg::TIME: + drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_CENTER); + break; - case COL_TYPE_MSG_CATEGORY: - if (entry->firstLine) - switch (entry->type) - { - case TYPE_INFO: - drawBitmapRtlNoMirror(dc, getResourceImage(L"msg_info_small"), rectTmp, wxALIGN_CENTER); - break; - case TYPE_WARNING: - drawBitmapRtlNoMirror(dc, getResourceImage(L"msg_warning_small"), rectTmp, wxALIGN_CENTER); - break; - case TYPE_ERROR: - case TYPE_FATAL_ERROR: - drawBitmapRtlNoMirror(dc, getResourceImage(L"msg_error_small"), rectTmp, wxALIGN_CENTER); - break; - } - break; + case ColumnTypeMsg::CATEGORY: + if (entry->firstLine) + switch (entry->type) + { + case TYPE_INFO: + drawBitmapRtlNoMirror(dc, getResourceImage(L"msg_info_small"), rectTmp, wxALIGN_CENTER); + break; + case TYPE_WARNING: + drawBitmapRtlNoMirror(dc, getResourceImage(L"msg_warning_small"), rectTmp, wxALIGN_CENTER); + break; + case TYPE_ERROR: + case TYPE_FATAL_ERROR: + drawBitmapRtlNoMirror(dc, getResourceImage(L"msg_error_small"), rectTmp, wxALIGN_CENTER); + break; + } + break; - case COL_TYPE_MSG_TEXT: - rectTmp.x += COLUMN_GAP_LEFT; - rectTmp.width -= COLUMN_GAP_LEFT; - drawCellText(dc, rectTmp, getValue(row, colType)); - break; - } + case ColumnTypeMsg::TEXT: + rectTmp.x += COLUMN_GAP_LEFT; + rectTmp.width -= COLUMN_GAP_LEFT; + drawCellText(dc, rectTmp, getValue(row, colType)); + break; + } } int getBestSize(wxDC& dc, size_t row, ColumnType colType) override { // -> synchronize renderCell() <-> getBestSize() - if (msgView_) - if (msgView_->getEntry(row)) - switch (static_cast<ColumnTypeMsg>(colType)) - { - case COL_TYPE_MSG_TIME: - return 2 * COLUMN_GAP_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth(); + if (msgView_.getEntry(row)) + switch (static_cast<ColumnTypeMsg>(colType)) + { + case ColumnTypeMsg::TIME: + return 2 * COLUMN_GAP_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth(); - case COL_TYPE_MSG_CATEGORY: - return getResourceImage(L"msg_info_small").GetWidth(); + case ColumnTypeMsg::CATEGORY: + return getResourceImage(L"msg_info_small").GetWidth(); - case COL_TYPE_MSG_TEXT: - return COLUMN_GAP_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth(); - } + case ColumnTypeMsg::TEXT: + return COLUMN_GAP_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth(); + } return 0; } @@ -738,11 +737,11 @@ public: { switch (static_cast<ColumnTypeMsg>(colType)) { - case COL_TYPE_MSG_TIME: - case COL_TYPE_MSG_TEXT: + case ColumnTypeMsg::TIME: + case ColumnTypeMsg::TEXT: break; - case COL_TYPE_MSG_CATEGORY: + case ColumnTypeMsg::CATEGORY: return getValue(row, colType); } return std::wstring(); @@ -751,7 +750,7 @@ public: std::wstring getColumnLabel(ColumnType colType) const override { return std::wstring(); } private: - const std::shared_ptr<MessageView> msgView_; + MessageView msgView_; }; } @@ -759,7 +758,7 @@ private: class LogPanel : public LogPanelGenerated { public: - LogPanel(wxWindow* parent, const ErrorLog& log) : LogPanelGenerated(parent), msgView_(std::make_shared<MessageView>(log)) + LogPanel(wxWindow* parent, const ErrorLog& log) : LogPanelGenerated(parent) { const int errorCount = log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); const int warningCount = log.getItemCount(TYPE_WARNING); @@ -788,15 +787,15 @@ public: const int colMsgTimeWidth = GridDataMessages::getColumnTimeDefaultWidth(*m_gridMessages); const int colMsgCategoryWidth = GridDataMessages::getColumnCategoryDefaultWidth(); - m_gridMessages->setDataProvider(std::make_shared<GridDataMessages>(msgView_)); + m_gridMessages->setDataProvider(std::make_shared<GridDataMessages>(log)); m_gridMessages->setColumnLabelHeight(0); m_gridMessages->showRowLabel(false); m_gridMessages->setRowHeight(rowHeight); m_gridMessages->setColumnConfig( { - { static_cast<ColumnType>(COL_TYPE_MSG_TIME ), colMsgTimeWidth, 0 }, - { static_cast<ColumnType>(COL_TYPE_MSG_CATEGORY), colMsgCategoryWidth, 0 }, - { static_cast<ColumnType>(COL_TYPE_MSG_TEXT ), -colMsgTimeWidth - colMsgCategoryWidth, 1 }, + { static_cast<ColumnType>(ColumnTypeMsg::TIME ), colMsgTimeWidth, 0, true }, + { static_cast<ColumnType>(ColumnTypeMsg::CATEGORY), colMsgCategoryWidth, 0, true }, + { static_cast<ColumnType>(ColumnTypeMsg::TEXT ), -colMsgTimeWidth - colMsgCategoryWidth, 1, true }, }); //support for CTRL + C @@ -811,6 +810,14 @@ public: } private: + MessageView& getDataView() + { + if (auto* prov = dynamic_cast<GridDataMessages*>(m_gridMessages->getDataProvider())) + return prov->getDataView(); + + throw std::runtime_error("m_gridMessages was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); + } + void OnErrors(wxCommandEvent& event) override { m_bpButtonErrors->toggle(); @@ -841,7 +848,7 @@ private: if (m_bpButtonInfo->isActive()) includedTypes |= TYPE_INFO; - msgView_->updateView(includedTypes); //update MVC "model" + getDataView().updateView(includedTypes); //update MVC "model" m_gridMessages->Refresh(); //update MVC "view" } @@ -960,18 +967,18 @@ private: if (auto prov = m_gridMessages->getDataProvider()) { - std::vector<Grid::ColumnAttribute> colAttr = m_gridMessages->getColumnConfig(); - erase_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); + std::vector<Grid::ColAttributes> colAttr = m_gridMessages->getColumnConfig(); + erase_if(colAttr, [](const Grid::ColAttributes& ca) { return !ca.visible; }); if (!colAttr.empty()) for (size_t row : m_gridMessages->getSelectedRows()) { std::for_each(colAttr.begin(), --colAttr.end(), - [&](const Grid::ColumnAttribute& ca) + [&](const Grid::ColAttributes& ca) { - clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_)); + clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type)); clipboardString += L'\t'; }); - clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_)); + clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type)); clipboardString += L'\n'; } } @@ -990,7 +997,6 @@ private: } } - std::shared_ptr<MessageView> msgView_; //bound! bool processingKeyEventHandler_ = false; }; @@ -1274,8 +1280,8 @@ public: } private: - void OnKeyPressed (wxKeyEvent& event); - void OnKeyPressedParent(wxKeyEvent& event); + void onLocalKeyEvent (wxKeyEvent& event); + void onParentKeyEvent(wxKeyEvent& event); void OnOkay (wxCommandEvent& event); void OnPause (wxCommandEvent& event); void OnCancel (wxCommandEvent& event); @@ -1366,7 +1372,7 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF //lifetime of event sources is subset of this instance's lifetime => no wxEvtHandler::Disconnect() needed this->Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler (SyncProgressDialogImpl<TopLevelDialog>::OnClose)); this->Connect(wxEVT_ICONIZE, wxIconizeEventHandler(SyncProgressDialogImpl<TopLevelDialog>::OnIconize)); - this->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::OnKeyPressed), nullptr, this); + this->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::onLocalKeyEvent), nullptr, this); pnl_.m_buttonClose->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnOkay ), NULL, this); pnl_.m_buttonPause->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnPause ), NULL, this); pnl_.m_buttonStop ->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnCancel), NULL, this); @@ -1374,7 +1380,7 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF pnl_.m_checkBoxIgnoreErrors->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnToggleIgnoreErrors), NULL, this); if (parentFrame_) - parentFrame_->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::OnKeyPressedParent), nullptr, this); + parentFrame_->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::onParentKeyEvent), nullptr, this); assert(pnl_.m_buttonClose->GetId() == wxID_OK); //we cannot use wxID_CLOSE else Esc key won't work: yet another wxWidgets bug?? @@ -1481,7 +1487,7 @@ SyncProgressDialogImpl<TopLevelDialog>::~SyncProgressDialogImpl() { if (parentFrame_) { - parentFrame_->Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::OnKeyPressedParent), nullptr, this); + parentFrame_->Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::onParentKeyEvent), nullptr, this); parentFrame_->SetTitle(parentTitleBackup_); //restore title text @@ -1496,26 +1502,19 @@ SyncProgressDialogImpl<TopLevelDialog>::~SyncProgressDialogImpl() template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnKeyPressed(wxKeyEvent& event) +void SyncProgressDialogImpl<TopLevelDialog>::onLocalKeyEvent(wxKeyEvent& event) { - const int keyCode = event.GetKeyCode(); - if (keyCode == WXK_ESCAPE) + switch (event.GetKeyCode()) { - wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED); - - //simulate click on abort button - if (pnl_.m_buttonStop->IsShown()) //delegate to "cancel" button if available - { - if (wxEvtHandler* handler = pnl_.m_buttonStop->GetEventHandler()) - handler->ProcessEvent(dummy); - return; - } - else if (pnl_.m_buttonClose->IsShown()) + case WXK_ESCAPE: { - if (wxEvtHandler* handler = pnl_.m_buttonClose->GetEventHandler()) - handler->ProcessEvent(dummy); + wxButton& activeButton = pnl_.m_buttonStop->IsShown() ? *pnl_.m_buttonStop : *pnl_.m_buttonClose; + + wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED); + activeButton.Command(dummy); //simulate click return; } + break; } event.Skip(); @@ -1523,15 +1522,15 @@ void SyncProgressDialogImpl<TopLevelDialog>::OnKeyPressed(wxKeyEvent& event) template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnKeyPressedParent(wxKeyEvent& event) +void SyncProgressDialogImpl<TopLevelDialog>::onParentKeyEvent(wxKeyEvent& event) { //redirect keys from main dialog to progress dialog - const int keyCode = event.GetKeyCode(); - if (keyCode == WXK_ESCAPE) + switch (event.GetKeyCode()) { - this->SetFocus(); - this->OnKeyPressed(event); - return; + case WXK_ESCAPE: + this->SetFocus(); + this->onLocalKeyEvent(event); //event will be handled => no event recursion to parent dialog! + return; } event.Skip(); @@ -2014,14 +2013,18 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult resultId, co //hide current operation status pnl_.bSizerStatusText->Show(false); - //show and prepare final statistics - pnl_.m_notebookResult->Show(); - pnl_.m_staticlineFooter->Hide(); //win: m_notebookResult already has a window frame //hide remaining time pnl_.m_panelTimeRemaining->Hide(); + //------------------------------------------------------------- + + pnl_.m_notebookResult->SetPadding(wxSize(2, 0)); //height cannot be changed + + const size_t pagePosProgress = 0; + const size_t pagePosLog = 1; + //1. re-arrange graph into results listbook const bool wasDetached = pnl_.bSizerRoot->Detach(pnl_.m_panelProgress); assert(wasDetached); @@ -2030,7 +2033,6 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult resultId, co pnl_.m_notebookResult->AddPage(pnl_.m_panelProgress, _("Progress"), true /*bSelect*/); //2. log file - const size_t posLog = 1; assert(pnl_.m_notebookResult->GetPageCount() == 1); LogPanel* logPanel = new LogPanel(pnl_.m_notebookResult, log); //owned by m_notebookResult pnl_.m_notebookResult->AddPage(logPanel, _("Log"), false /*bSelect*/); @@ -2038,8 +2040,29 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult resultId, co //show log instead of graph if errors occurred! (not required for ignored warnings) if (log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR) > 0) - pnl_.m_notebookResult->ChangeSelection(posLog); - warn_static("/|\ not working on linux") + pnl_.m_notebookResult->ChangeSelection(pagePosLog); + + //fill image list to cope with wxNotebook image setting design desaster... + const int imgListSize = getResourceImage(L"log_file_small").GetHeight(); + assert(imgListSize == 16); //Windows default size for panel caption + auto imgList = std::make_unique<wxImageList>(imgListSize, imgListSize); + + auto addToImageList = [&](const wxBitmap& bmp) + { + assert(bmp.GetWidth () <= imgListSize); + assert(bmp.GetHeight() <= imgListSize); + imgList->Add(bmp); + }; + addToImageList(getResourceImage(L"progress_small")); + addToImageList(getResourceImage(L"log_file_small")); + + pnl_.m_notebookResult->AssignImageList(imgList.release()); //pass ownership + + pnl_.m_notebookResult->SetPageImage(pagePosProgress, pagePosProgress); + pnl_.m_notebookResult->SetPageImage(pagePosLog, pagePosLog); + + //Caveat: we need "Show()" *after" the above wxNotebook::ChangeSelection() to get the correct selection on Linux + pnl_.m_notebookResult->Show(); //GetSizer()->SetSizeHints(this); //~=Fit() //not a good idea: will shrink even if window is maximized or was enlarged by the user pnl_.Layout(); diff --git a/FreeFileSync/Source/ui/search.cpp b/FreeFileSync/Source/ui/search.cpp index e22146d4..6bcfed34 100755 --- a/FreeFileSync/Source/ui/search.cpp +++ b/FreeFileSync/Source/ui/search.cpp @@ -47,8 +47,8 @@ ptrdiff_t findRow(const Grid& grid, //return -1 if no matching row found { if (auto prov = grid.getDataProvider()) { - std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig(); - erase_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); + std::vector<Grid::ColAttributes> colAttr = grid.getColumnConfig(); + erase_if(colAttr, [](const Grid::ColAttributes& ca) { return !ca.visible; }); if (!colAttr.empty()) { const MatchFound<respectCase> matchFound(searchString); @@ -56,14 +56,14 @@ ptrdiff_t findRow(const Grid& grid, //return -1 if no matching row found if (searchAscending) { for (size_t row = rowFirst; row < rowLast; ++row) - for (const Grid::ColumnAttribute& ca : colAttr) - if (matchFound(prov->getValue(row, ca.type_))) + for (const Grid::ColAttributes& ca : colAttr) + if (matchFound(prov->getValue(row, ca.type))) return row; } else for (size_t row = rowLast; row-- > rowFirst;) - for (const Grid::ColumnAttribute& ca : colAttr) - if (matchFound(prov->getValue(row, ca.type_))) + for (const Grid::ColAttributes& ca : colAttr) + if (matchFound(prov->getValue(row, ca.type))) return row; } } diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index 5c5c3851..9c4b9196 100755 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -23,7 +23,6 @@ #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> #include "gui_generated.h" -#include "custom_grid.h" #include "folder_selector.h" #include "version_check.h" #include "../algorithm.h" @@ -47,6 +46,7 @@ private: void OnOK (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_OKAY); } void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } void OnDonate(wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://www.freefilesync.org/donate.php"); } + void onLocalKeyEvent(wxKeyEvent& event); }; @@ -136,6 +136,9 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) } m_bitmapLogo->SetBitmap(headerBmp); + //enable dialog-specific key local events + Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(AboutDlg::onLocalKeyEvent), nullptr, this); + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! Center(); //needs to be re-applied after a dialog size change! @@ -144,6 +147,12 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) } +void AboutDlg::onLocalKeyEvent(wxKeyEvent& event) //process key events without explicit menu entry :) +{ + event.Skip(); +} + + void zen::showAboutDialog(wxWindow* parent) { AboutDlg aboutDlg(parent); @@ -171,6 +180,8 @@ private: void OnCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onLocalKeyEvent(wxKeyEvent& event); + std::unique_ptr<FolderSelector> targetFolder; //always bound std::shared_ptr<FolderHistory> folderHistory_; @@ -228,6 +239,9 @@ CopyToDialog::CopyToDialog(wxWindow* parent, m_checkBoxOverwriteIfExists->SetValue(overwriteIfExists); //----------------- /set config -------------------------------- + //enable dialog-specific key local events + Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(CopyToDialog::onLocalKeyEvent), nullptr, this); + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! Center(); //needs to be re-applied after a dialog size change! @@ -236,6 +250,12 @@ CopyToDialog::CopyToDialog(wxWindow* parent, } +void CopyToDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without explicit menu entry :) +{ + event.Skip(); +} + + void CopyToDialog::OnOK(wxCommandEvent& event) { //------- parameter validation (BEFORE writing output!) ------- @@ -288,10 +308,12 @@ public: bool& useRecycleBin); private: - void OnOK (wxCommandEvent& event) override; - void OnCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnUseRecycler (wxCommandEvent& event) override; + void OnUseRecycler(wxCommandEvent& event) override { updateGui(); } + void OnOK (wxCommandEvent& event) override; + void OnCancel (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + + void onLocalKeyEvent(wxKeyEvent& event); void updateGui(); @@ -322,6 +344,9 @@ DeleteDialog::DeleteDialog(wxWindow* parent, updateGui(); + //enable dialog-specific key local events + Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(DeleteDialog::onLocalKeyEvent), nullptr, this); + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! Layout(); @@ -368,6 +393,12 @@ void DeleteDialog::updateGui() } +void DeleteDialog::onLocalKeyEvent(wxKeyEvent& event) +{ + event.Skip(); +} + + void DeleteDialog::OnOK(wxCommandEvent& event) { //additional safety net, similar to Windows Explorer: time delta between DEL and ENTER must be at least 50ms to avoid accidental deletion! @@ -380,12 +411,6 @@ void DeleteDialog::OnOK(wxCommandEvent& event) } -void DeleteDialog::OnUseRecycler(wxCommandEvent& event) -{ - updateGui(); -} - - ReturnSmallDlg::ButtonPressed zen::showDeleteDialog(wxWindow* parent, const std::vector<const FileSystemObject*>& rowsOnLeft, const std::vector<const FileSystemObject*>& rowsOnRight, @@ -409,6 +434,8 @@ private: void OnCancel (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onLocalKeyEvent(wxKeyEvent& event); + //output-only parameters: bool& dontShowAgainOut_; }; @@ -429,6 +456,8 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent, m_staticTextVariant->SetLabel(variantName); m_checkBoxDontShowAgain->SetValue(dontShowAgain); + Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncConfirmationDlg::onLocalKeyEvent), nullptr, this); + //update preview of item count and bytes to be transferred: auto setValue = [](wxStaticText& txtControl, bool isZeroValue, const wxString& valueAsString, wxStaticBitmap& bmpControl, const wchar_t* bmpName) { @@ -467,6 +496,12 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent, } +void SyncConfirmationDlg::onLocalKeyEvent(wxKeyEvent& event) +{ + event.Skip(); +} + + void SyncConfirmationDlg::OnStartSync(wxCommandEvent& event) { dontShowAgainOut_ = m_checkBoxDontShowAgain->GetValue(); @@ -505,6 +540,9 @@ private: void onResize(wxSizeEvent& event); void updateGui(); + //work around defunct keyboard focus on macOS (or is it wxMac?) => not needed for this dialog! + //void onLocalKeyEvent(wxKeyEvent& event); + void OnToggleAutoRetryCount(wxCommandEvent& event) override { updateGui(); } void setExtApp(const xmlAccess::ExternalApps& extApp); @@ -745,6 +783,8 @@ private: m_calendarFrom->SetDate(m_calendarTo->GetDate()); } + void onLocalKeyEvent(wxKeyEvent& event); + //output-only parameters: time_t& timeFromOut_; time_t& timeToOut_; @@ -778,6 +818,9 @@ SelectTimespanDlg::SelectTimespanDlg(wxWindow* parent, time_t& timeFrom, time_t& m_calendarFrom->SetDate(timeFromTmp); m_calendarTo ->SetDate(timeToTmp ); + //enable dialog-specific key local events + Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SelectTimespanDlg::onLocalKeyEvent), nullptr, this); + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! Center(); //needs to be re-applied after a dialog size change! @@ -786,6 +829,12 @@ SelectTimespanDlg::SelectTimespanDlg(wxWindow* parent, time_t& timeFrom, time_t& } +void SelectTimespanDlg::onLocalKeyEvent(wxKeyEvent& event) //process key events without explicit menu entry :) +{ + event.Skip(); +} + + void SelectTimespanDlg::OnOkay(wxCommandEvent& event) { wxDateTime from = m_calendarFrom->GetDate(); @@ -825,6 +874,55 @@ ReturnSmallDlg::ButtonPressed zen::showSelectTimespanDlg(wxWindow* parent, time_ //######################################################################################## +class CfgHighlightDlg : public CfgHighlightDlgGenerated +{ +public: + CfgHighlightDlg(wxWindow* parent, int& cfgHistSyncOverdueDays); + +private: + void OnOkay (wxCommandEvent& event) override; + void OnCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + + //work around defunct keyboard focus on macOS (or is it wxMac?) => not needed for this dialog! + //void onLocalKeyEvent(wxKeyEvent& event); + + //output-only parameters: + int& cfgHistSyncOverdueDaysOut_; +}; + + +CfgHighlightDlg::CfgHighlightDlg(wxWindow* parent, int& cfgHistSyncOverdueDays) : + CfgHighlightDlgGenerated(parent), + cfgHistSyncOverdueDaysOut_(cfgHistSyncOverdueDays) +{ + setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOkay).setCancel(m_buttonCancel)); + + m_spinCtrlSyncOverdueDays->SetValue(cfgHistSyncOverdueDays); + + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() + //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! + Center(); //needs to be re-applied after a dialog size change! + + m_spinCtrlSyncOverdueDays->SetFocus(); +} + + +void CfgHighlightDlg::OnOkay(wxCommandEvent& event) +{ + cfgHistSyncOverdueDaysOut_ = m_spinCtrlSyncOverdueDays->GetValue(); + EndModal(ReturnSmallDlg::BUTTON_OKAY); +} + + +ReturnSmallDlg::ButtonPressed zen::showCfgHighlightDlg(wxWindow* parent, int& cfgHistSyncOverdueDays) +{ + CfgHighlightDlg cfgHighDlg(parent, cfgHistSyncOverdueDays); + return static_cast<ReturnSmallDlg::ButtonPressed>(cfgHighDlg.ShowModal()); +} + +//######################################################################################## + class ActivationDlg : public ActivationDlgGenerated { public: diff --git a/FreeFileSync/Source/ui/small_dlgs.h b/FreeFileSync/Source/ui/small_dlgs.h index ceff867b..e6af0872 100755 --- a/FreeFileSync/Source/ui/small_dlgs.h +++ b/FreeFileSync/Source/ui/small_dlgs.h @@ -50,6 +50,9 @@ ReturnSmallDlg::ButtonPressed showOptionsDlg(wxWindow* parent, xmlAccess::XmlGlo ReturnSmallDlg::ButtonPressed showSelectTimespanDlg(wxWindow* parent, time_t& timeFrom, time_t& timeTo); +ReturnSmallDlg::ButtonPressed showCfgHighlightDlg(wxWindow* parent, int& cfgHistSyncOverdueDays); + + enum class ReturnActivationDlg { diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index 7cbd3afe..887df0d6 100755 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -15,6 +15,7 @@ #include <wx+/std_button_layout.h> #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> +#include <wx+/focus.h> #include "gui_generated.h" #include "command_box.h" #include "folder_selector.h" @@ -55,7 +56,7 @@ public: int localPairIndexToShow, std::vector<LocalPairConfig>& folderPairConfig, GlobalSyncConfig& globalCfg, - size_t commandHistoryMax); + size_t commandHistItemsMax); private: void OnOkay (wxCommandEvent& event) override; @@ -180,7 +181,7 @@ private: int selectedPairIndexToShow_ = EMPTY_PAIR_INDEX_SELECTED; static const int EMPTY_PAIR_INDEX_SELECTED = -2; - const size_t commandHistoryMax_; + const size_t commandHistItemsMax_; }; //################################################################################################################# @@ -224,28 +225,30 @@ ConfigDialog::ConfigDialog(wxWindow* parent, int localPairIndexToShow, std::vector<LocalPairConfig>& folderPairConfig, GlobalSyncConfig& globalCfg, - size_t commandHistoryMax) : + size_t commandHistItemsMax) : ConfigDlgGenerated(parent), versioningFolder_(*m_panelVersioning, *m_buttonSelectVersioningFolder, *m_bpButtonSelectAltFolder, *m_versioningFolderPath, nullptr /*staticText*/, nullptr /*dropWindow2*/), globalCfgOut_(globalCfg), folderPairConfigOut_(folderPairConfig), globalCfg_(globalCfg), folderPairConfig_(folderPairConfig), - commandHistoryMax_(commandHistoryMax) + commandHistItemsMax_(commandHistItemsMax) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOkay).setCancel(m_buttonCancel)); SetTitle(_("Synchronization Settings")); + m_notebook->SetPadding(wxSize(2, 0)); //height cannot be changed + //fill image list to cope with wxNotebook image setting design desaster... - const int imageListSize = getResourceImage(L"cfg_compare_small").GetHeight(); - assert(imageListSize == 16); //Windows default size for panel caption - auto imgList = std::make_unique<wxImageList>(imageListSize, imageListSize); + const int imgListSize = getResourceImage(L"cfg_compare_small").GetHeight(); + assert(imgListSize == 16); //Windows default size for panel caption + auto imgList = std::make_unique<wxImageList>(imgListSize, imgListSize); auto addToImageList = [&](const wxBitmap& bmp) { - assert(bmp.GetWidth () <= imageListSize); - assert(bmp.GetHeight() <= imageListSize); + assert(bmp.GetWidth () <= imgListSize); + assert(bmp.GetHeight() <= imgListSize); imgList->Add(bmp); imgList->Add(greyScale(bmp)); }; @@ -372,24 +375,25 @@ ConfigDialog::ConfigDialog(wxWindow* parent, void ConfigDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without explicit menu entry :) { - const int keyCode = event.GetKeyCode(); + auto changeSelection = [&](SyncConfigPanel panel) + { + m_notebook->ChangeSelection(static_cast<size_t>(panel)); + (m_listBoxFolderPair->IsShown() ? static_cast<wxWindow*>(m_listBoxFolderPair) : m_notebook)->SetFocus(); //GTK ignores F-keys if focus is on hidden item! + }; - switch (keyCode) + switch (event.GetKeyCode()) { case WXK_F6: - m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::COMPARISON)); - break; //handled! + changeSelection(SyncConfigPanel::COMPARISON); + return; //handled! case WXK_F7: - m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::FILTER)); - break; + changeSelection(SyncConfigPanel::FILTER); + return; case WXK_F8: - m_notebook->ChangeSelection(static_cast<size_t>(SyncConfigPanel::SYNC)); - break; - default: - event.Skip(); + changeSelection(SyncConfigPanel::SYNC); return; } - (m_listBoxFolderPair->IsShown() ? static_cast<wxWindow*>(m_listBoxFolderPair) : m_notebook)->SetFocus(); //GTK ignores F-keys if focus is on hidden item! + event.Skip(); } @@ -927,7 +931,7 @@ void ConfigDialog::updateSyncGui() }; //display only relevant sync options - m_bitmapDatabase ->Show(directionCfg_.var == DirectionConfig::TWO_WAY); + bSizerDatabase ->Show(directionCfg_.var == DirectionConfig::TWO_WAY); bSizerSyncDirections->Show(directionCfg_.var != DirectionConfig::TWO_WAY); if (directionCfg_.var == DirectionConfig::TWO_WAY) @@ -1046,7 +1050,7 @@ MiscSyncConfig ConfigDialog::getMiscSyncOptions() const miscCfg.ignoreErrors = m_checkBoxIgnoreErrors->GetValue(); miscCfg.postSyncCommand = m_comboBoxPostSyncCommand->getValue(); miscCfg.postSyncCondition = getEnumVal(enumPostSyncCondition_, *m_choicePostSyncCondition), - miscCfg.commandHistory = m_comboBoxPostSyncCommand->getHistory(); + miscCfg.commandHistory = m_comboBoxPostSyncCommand->getHistory(); return miscCfg; } @@ -1056,7 +1060,7 @@ void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg) m_checkBoxIgnoreErrors->SetValue(miscCfg.ignoreErrors); m_comboBoxPostSyncCommand->setValue(miscCfg.postSyncCommand); setEnumVal(enumPostSyncCondition_, *m_choicePostSyncCondition, miscCfg.postSyncCondition), - m_comboBoxPostSyncCommand->setHistory(miscCfg.commandHistory, commandHistoryMax_); + m_comboBoxPostSyncCommand->setHistory(miscCfg.commandHistory, commandHistItemsMax_); updateMiscGui(); } @@ -1194,7 +1198,7 @@ ReturnSyncConfig::ButtonPressed zen::showSyncConfigDlg(wxWindow* parent, PostSyncCondition& postSyncCondition, std::vector<Zstring>& commandHistory, - size_t commandHistoryMax) + size_t commandHistItemsMax) { GlobalSyncConfig globalCfg; globalCfg.cmpConfig = globalCmpConfig; @@ -1211,7 +1215,7 @@ ReturnSyncConfig::ButtonPressed zen::showSyncConfigDlg(wxWindow* parent, localPairIndexToShow, folderPairConfig, globalCfg, - commandHistoryMax); + commandHistItemsMax); const auto rv = static_cast<ReturnSyncConfig::ButtonPressed>(syncDlg.ShowModal()); if (rv != ReturnSyncConfig::BUTTON_CANCEL) diff --git a/FreeFileSync/Source/ui/tree_view.cpp b/FreeFileSync/Source/ui/tree_grid.cpp index 19f80f4d..c64a52cf 100755 --- a/FreeFileSync/Source/ui/tree_view.cpp +++ b/FreeFileSync/Source/ui/tree_grid.cpp @@ -5,7 +5,7 @@ // ***************************************************************************** #include <set> -#include "tree_view.h" +#include "tree_grid.h" #include <wx/settings.h> #include <wx/menu.h> #include <zen/i18n.h> @@ -204,24 +204,24 @@ struct TreeView::LessShortName bool operator()(const TreeLine& lhs, const TreeLine& rhs) const { //files last (irrespective of sort direction) - if (lhs.type_ == TreeView::TYPE_FILES) + if (lhs.type == TreeView::TYPE_FILES) return false; - else if (rhs.type_ == TreeView::TYPE_FILES) + else if (rhs.type == TreeView::TYPE_FILES) return true; - if (lhs.type_ != rhs.type_) // - return lhs.type_ < rhs.type_; //shouldn't happen! root nodes not mixed with files or directories + if (lhs.type != rhs.type) // + return lhs.type < rhs.type; //shouldn't happen! root nodes not mixed with files or directories - switch (lhs.type_) + switch (lhs.type) { case TreeView::TYPE_ROOT: - return makeSortDirection(LessNaturalSort() /*even on Linux*/, Int2Type<ascending>())(static_cast<const RootNodeImpl*>(lhs.node_)->displayName, - static_cast<const RootNodeImpl*>(rhs.node_)->displayName); + return makeSortDirection(LessNaturalSort() /*even on Linux*/, Int2Type<ascending>())(static_cast<const RootNodeImpl*>(lhs.node)->displayName, + static_cast<const RootNodeImpl*>(rhs.node)->displayName); case TreeView::TYPE_DIRECTORY: { - const auto* folderL = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(lhs.node_)->objId)); - const auto* folderR = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(rhs.node_)->objId)); + const auto* folderL = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(lhs.node)->objId)); + const auto* folderR = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(rhs.node)->objId)); if (!folderL) //might be pathologic, but it's covered return false; @@ -241,17 +241,17 @@ struct TreeView::LessShortName template <bool ascending> -void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeNavi columnType) +void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeTree columnType) { auto getBytes = [](const TreeLine& line) -> uint64_t { - switch (line.type_) + switch (line.type) { case TreeView::TYPE_ROOT: case TreeView::TYPE_DIRECTORY: - return line.node_->bytesGross; + return line.node->bytesGross; case TreeView::TYPE_FILES: - return line.node_->bytesNet; + return line.node->bytesNet; } assert(false); return 0U; @@ -259,14 +259,14 @@ void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeNavi colu auto getCount = [](const TreeLine& line) -> int { - switch (line.type_) + switch (line.type) { case TreeView::TYPE_ROOT: case TreeView::TYPE_DIRECTORY: - return line.node_->itemCountGross; + return line.node->itemCountGross; case TreeView::TYPE_FILES: - return line.node_->itemCountNet; + return line.node->itemCountNet; } assert(false); return 0; @@ -277,13 +277,13 @@ void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeNavi colu switch (columnType) { - case ColumnTypeNavi::FOLDER_NAME: + case ColumnTypeTree::FOLDER_NAME: std::sort(items.begin(), items.end(), LessShortName<ascending>()); break; - case ColumnTypeNavi::ITEM_COUNT: + case ColumnTypeTree::ITEM_COUNT: std::sort(items.begin(), items.end(), makeSortDirection(lessCount, Int2Type<ascending>())); break; - case ColumnTypeNavi::BYTES: + case ColumnTypeTree::BYTES: std::sort(items.begin(), items.end(), makeSortDirection(lessBytes, Int2Type<ascending>())); break; } @@ -298,21 +298,21 @@ void TreeView::getChildren(const Container& cont, unsigned int level, std::vecto for (const DirNodeImpl& subDir : cont.subDirs) { - output.emplace_back(level, 0, &subDir, TreeView::TYPE_DIRECTORY); - workList.emplace_back(subDir.bytesGross, &output.back().percent_); + output.push_back({ level, 0, &subDir, TreeView::TYPE_DIRECTORY }); + workList.emplace_back(subDir.bytesGross, &output.back().percent); } if (cont.firstFileId) { - output.emplace_back(level, 0, &cont, TreeView::TYPE_FILES); - workList.emplace_back(cont.bytesNet, &output.back().percent_); + output.push_back({ level, 0, &cont, TreeView::TYPE_FILES }); + workList.emplace_back(cont.bytesNet, &output.back().percent); } calcPercentage(workList); - if (sortAscending) - sortSingleLevel<true>(output, sortColumn); + if (sortAscending_) + sortSingleLevel<true>(output, sortColumn_); else - sortSingleLevel<false>(output, sortColumn); + sortSingleLevel<false>(output, sortColumn_); } @@ -321,13 +321,13 @@ void TreeView::applySubView(std::vector<RootNodeImpl>&& newView) //preserve current node expansion status auto getHierAlias = [](const TreeView::TreeLine& tl) -> const ContainerObject* { - switch (tl.type_) + switch (tl.type) { case TreeView::TYPE_ROOT: - return static_cast<const RootNodeImpl*>(tl.node_)->baseFolder.get(); + return static_cast<const RootNodeImpl*>(tl.node)->baseFolder.get(); case TreeView::TYPE_DIRECTORY: - if (auto folder = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(tl.node_)->objId))) + if (auto folder = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(tl.node)->objId))) return folder; break; @@ -338,60 +338,60 @@ void TreeView::applySubView(std::vector<RootNodeImpl>&& newView) }; std::unordered_set<const ContainerObject*> expandedNodes; - if (!flatTree.empty()) + if (!flatTree_.empty()) { - auto it = flatTree.begin(); - for (auto iterNext = flatTree.begin() + 1; iterNext != flatTree.end(); ++iterNext, ++it) - if (it->level_ < iterNext->level_) + auto it = flatTree_.begin(); + for (auto iterNext = flatTree_.begin() + 1; iterNext != flatTree_.end(); ++iterNext, ++it) + if (it->level < iterNext->level) if (auto hierObj = getHierAlias(*it)) expandedNodes.insert(hierObj); } //update view on full data - folderCmpView.swap(newView); //newView may be an alias for folderCmpView! see sorting! + folderCmpView_.swap(newView); //newView may be an alias for folderCmpView! see sorting! //set default flat tree - flatTree.clear(); + flatTree_.clear(); - if (folderCmp.size() == 1) //single folder pair case (empty pairs were already removed!) do NOT use folderCmpView for this check! + if (folderCmp_.size() == 1) //single folder pair case (empty pairs were already removed!) do NOT use folderCmpView for this check! { - if (!folderCmpView.empty()) //possibly empty! - getChildren(folderCmpView[0], 0, flatTree); //do not show root + if (!folderCmpView_.empty()) //possibly empty! + getChildren(folderCmpView_[0], 0, flatTree_); //do not show root } else { //following is almost identical with TreeView::getChildren(): however we *cannot* reuse code here; //this were only possible if we replaced "std::vector<RootNodeImpl>" with "Container"! - flatTree.reserve(folderCmpView.size()); //keep pointers in "workList" valid + flatTree_.reserve(folderCmpView_.size()); //keep pointers in "workList" valid std::vector<std::pair<uint64_t, int*>> workList; - for (const RootNodeImpl& root : folderCmpView) + for (const RootNodeImpl& root : folderCmpView_) { - flatTree.emplace_back(0, 0, &root, TreeView::TYPE_ROOT); - workList.emplace_back(root.bytesGross, &flatTree.back().percent_); + flatTree_.push_back({ 0, 0, &root, TreeView::TYPE_ROOT }); + workList.emplace_back(root.bytesGross, &flatTree_.back().percent); } calcPercentage(workList); - if (sortAscending) - sortSingleLevel<true>(flatTree, sortColumn); + if (sortAscending_) + sortSingleLevel<true>(flatTree_, sortColumn_); else - sortSingleLevel<false>(flatTree, sortColumn); + sortSingleLevel<false>(flatTree_, sortColumn_); } //restore node expansion status - for (size_t row = 0; row < flatTree.size(); ++row) //flatTree size changes during loop! + for (size_t row = 0; row < flatTree_.size(); ++row) //flatTree size changes during loop! { - const TreeLine& line = flatTree[row]; + const TreeLine& line = flatTree_[row]; if (auto hierObj = getHierAlias(line)) if (expandedNodes.find(hierObj) != expandedNodes.end()) { std::vector<TreeLine> newLines; - getChildren(*line.node_, line.level_ + 1, newLines); + getChildren(*line.node, line.level + 1, newLines); - flatTree.insert(flatTree.begin() + row + 1, newLines.begin(), newLines.end()); + flatTree_.insert(flatTree_.begin() + row + 1, newLines.begin(), newLines.end()); } } } @@ -402,9 +402,9 @@ void TreeView::updateView(Predicate pred) { //update view on full data std::vector<RootNodeImpl> newView; - newView.reserve(folderCmp.size()); //avoid expensive reallocations! + newView.reserve(folderCmp_.size()); //avoid expensive reallocations! - for (const std::shared_ptr<BaseFolderPair>& baseObj : folderCmp) + for (const std::shared_ptr<BaseFolderPair>& baseObj : folderCmp_) { newView.emplace_back(); RootNodeImpl& root = newView.back(); @@ -424,50 +424,34 @@ void TreeView::updateView(Predicate pred) } } - lastViewFilterPred = pred; + lastViewFilterPred_ = pred; applySubView(std::move(newView)); } -void TreeView::setSortDirection(ColumnTypeNavi colType, bool ascending) //apply permanently! +void TreeView::setSortDirection(ColumnTypeTree colType, bool ascending) //apply permanently! { - sortColumn = colType; - sortAscending = ascending; + sortColumn_ = colType; + sortAscending_ = ascending; //reapply current view - applySubView(std::move(folderCmpView)); -} - - -bool TreeView::getDefaultSortDirection(ColumnTypeNavi colType) -{ - switch (colType) - { - case ColumnTypeNavi::FOLDER_NAME: - return true; - case ColumnTypeNavi::ITEM_COUNT: - return false; - case ColumnTypeNavi::BYTES: - return false; - } - assert(false); - return true; + applySubView(std::move(folderCmpView_)); } TreeView::NodeStatus TreeView::getStatus(size_t row) const { - if (row < flatTree.size()) + if (row < flatTree_.size()) { - if (row + 1 < flatTree.size() && flatTree[row + 1].level_ > flatTree[row].level_) + if (row + 1 < flatTree_.size() && flatTree_[row + 1].level > flatTree_[row].level) return TreeView::STATUS_EXPANDED; //it's either reduced or empty - switch (flatTree[row].type_) + switch (flatTree_[row].type) { case TreeView::TYPE_DIRECTORY: case TreeView::TYPE_ROOT: - return flatTree[row].node_->firstFileId || !flatTree[row].node_->subDirs.empty() ? TreeView::STATUS_REDUCED : TreeView::STATUS_EMPTY; + return flatTree_[row].node->firstFileId || !flatTree_[row].node->subDirs.empty() ? TreeView::STATUS_REDUCED : TreeView::STATUS_EMPTY; case TreeView::TYPE_FILES: return TreeView::STATUS_EMPTY; @@ -485,56 +469,56 @@ void TreeView::expandNode(size_t row) return; } - if (row < flatTree.size()) + if (row < flatTree_.size()) { std::vector<TreeLine> newLines; - switch (flatTree[row].type_) + switch (flatTree_[row].type) { case TreeView::TYPE_ROOT: case TreeView::TYPE_DIRECTORY: - getChildren(*flatTree[row].node_, flatTree[row].level_ + 1, newLines); + getChildren(*flatTree_[row].node, flatTree_[row].level + 1, newLines); break; case TreeView::TYPE_FILES: break; } - flatTree.insert(flatTree.begin() + row + 1, newLines.begin(), newLines.end()); + flatTree_.insert(flatTree_.begin() + row + 1, newLines.begin(), newLines.end()); } } void TreeView::reduceNode(size_t row) { - if (row < flatTree.size()) + if (row < flatTree_.size()) { - const unsigned int parentLevel = flatTree[row].level_; + const unsigned int parentLevel = flatTree_[row].level; bool done = false; - flatTree.erase(std::remove_if(flatTree.begin() + row + 1, flatTree.end(), - [&](const TreeLine& line) -> bool + flatTree_.erase(std::remove_if(flatTree_.begin() + row + 1, flatTree_.end(), + [&](const TreeLine& line) -> bool { if (done) return false; - if (line.level_ > parentLevel) + if (line.level > parentLevel) return true; else { done = true; return false; } - }), flatTree.end()); + }), flatTree_.end()); } } ptrdiff_t TreeView::getParent(size_t row) const { - if (row < flatTree.size()) + if (row < flatTree_.size()) { - const auto level = flatTree[row].level_; + const auto level = flatTree_[row].level; while (row-- > 0) - if (flatTree[row].level_ < level) + if (flatTree_[row].level < level) return row; } return -1; @@ -646,12 +630,12 @@ void TreeView::updateSyncPreview(bool showExcluded, void TreeView::setData(FolderComparison& newData) { - std::vector<TreeLine >().swap(flatTree); //free mem - std::vector<RootNodeImpl>().swap(folderCmpView); // - folderCmp = newData; + std::vector<TreeLine >().swap(flatTree_); //free mem + std::vector<RootNodeImpl>().swap(folderCmpView_); // + folderCmp_ = newData; //remove truly empty folder pairs as early as this: we want to distinguish single/multiple folder pair cases by looking at "folderCmp" - erase_if(folderCmp, [](const std::shared_ptr<BaseFolderPair>& baseObj) + erase_if(folderCmp_, [](const std::shared_ptr<BaseFolderPair>& baseObj) { return AFS::isNullPath(baseObj->getAbstractPath< LEFT_SIDE>()) && AFS::isNullPath(baseObj->getAbstractPath<RIGHT_SIDE>()); @@ -661,23 +645,23 @@ void TreeView::setData(FolderComparison& newData) std::unique_ptr<TreeView::Node> TreeView::getLine(size_t row) const { - if (row < flatTree.size()) + if (row < flatTree_.size()) { - const auto level = flatTree[row].level_; - const int percent = flatTree[row].percent_; + const auto level = flatTree_[row].level; + const int percent = flatTree_[row].percent; - switch (flatTree[row].type_) + switch (flatTree_[row].type) { case TreeView::TYPE_ROOT: { - const auto& root = *static_cast<const TreeView::RootNodeImpl*>(flatTree[row].node_); + const auto& root = *static_cast<const TreeView::RootNodeImpl*>(flatTree_[row].node); return std::make_unique<TreeView::RootNode>(percent, root.bytesGross, root.itemCountGross, getStatus(row), *root.baseFolder, root.displayName); } break; case TreeView::TYPE_DIRECTORY: { - const auto* dir = static_cast<const TreeView::DirNodeImpl*>(flatTree[row].node_); + const auto* dir = static_cast<const TreeView::DirNodeImpl*>(flatTree_[row].node); if (auto folder = dynamic_cast<FolderPair*>(FileSystemObject::retrieve(dir->objId))) return std::make_unique<TreeView::DirNode>(percent, dir->bytesGross, dir->itemCountGross, level, getStatus(row), *folder); } @@ -685,7 +669,7 @@ std::unique_ptr<TreeView::Node> TreeView::getLine(size_t row) const case TreeView::TYPE_FILES: { - const auto* parentDir = flatTree[row].node_; + const auto* parentDir = flatTree_[row].node; if (auto firstFile = FileSystemObject::retrieve(parentDir->firstFileId)) { std::vector<FileSystemObject*> filesAndLinks; @@ -693,11 +677,11 @@ std::unique_ptr<TreeView::Node> TreeView::getLine(size_t row) const //lazy evaluation: recheck "lastViewFilterPred" again rather than buffer and bloat "lastViewFilterPred" for (FileSystemObject& fsObj : parent.refSubFiles()) - if (lastViewFilterPred(fsObj)) + if (lastViewFilterPred_(fsObj)) filesAndLinks.push_back(&fsObj); for (FileSystemObject& fsObj : parent.refSubLinks()) - if (lastViewFilterPred(fsObj)) + if (lastViewFilterPred_(fsObj)) filesAndLinks.push_back(&fsObj); return std::make_unique<TreeView::FilesNode>(percent, parentDir->bytesNet, parentDir->itemCountNet, level, filesAndLinks); @@ -757,50 +741,51 @@ wxColor getColorForLevel(size_t level) } -class GridDataNavi : private wxEvtHandler, public GridData +class GridDataTree : private wxEvtHandler, public GridData { public: - GridDataNavi(Grid& grid, const std::shared_ptr<TreeView>& treeDataView) : treeDataView_(treeDataView), - rootBmp(getResourceImage(L"rootFolder").ConvertToImage().Scale(iconSizeSmall, iconSizeSmall, wxIMAGE_QUALITY_HIGH)), - widthNodeIcon(iconSizeSmall), - widthLevelStep(widthNodeIcon), - widthNodeStatus(getResourceImage(L"node_expanded").GetWidth()), + GridDataTree(Grid& grid) : + rootBmp_(getResourceImage(L"rootFolder").ConvertToImage().Scale(iconSizeSmall, iconSizeSmall, wxIMAGE_QUALITY_HIGH)), + widthNodeIcon_(iconSizeSmall), + widthLevelStep_(widthNodeIcon_), + 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, GridLabelClickEventHandler(GridDataNavi::onGridLabelContext ), nullptr, this); - grid.Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(GridDataNavi::onGridLabelLeftClick), nullptr, this); + grid.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GridDataTree::onKeyDown), nullptr, this); + grid.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler (GridDataTree::onMouseLeft ), nullptr, this); + grid.Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler (GridDataTree::onMouseLeftDouble ), nullptr, this); + grid.Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(GridDataTree::onGridLabelContext ), nullptr, this); + grid.Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(GridDataTree::onGridLabelLeftClick), nullptr, this); } - void setShowPercentage(bool value) { showPercentBar = value; grid_.Refresh(); } - bool getShowPercentage() const { return showPercentBar; } + void setShowPercentage(bool value) { showPercentBar_ = value; grid_.Refresh(); } + bool getShowPercentage() const { return showPercentBar_; } + + TreeView& getDataView() { return treeDataView_; } private: - size_t getRowCount() const override { return treeDataView_ ? treeDataView_->linesTotal() : 0; } + size_t getRowCount() const override { return treeDataView_.linesTotal(); } std::wstring getToolTip(size_t row, ColumnType colType) const override { - switch (static_cast<ColumnTypeNavi>(colType)) + switch (static_cast<ColumnTypeTree>(colType)) { - case ColumnTypeNavi::FOLDER_NAME: - if (treeDataView_) - if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) - if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) - { - const std::wstring& dirLeft = AFS::getDisplayPath(root->baseFolder_.getAbstractPath< LEFT_SIDE>()); - const std::wstring& dirRight = AFS::getDisplayPath(root->baseFolder_.getAbstractPath<RIGHT_SIDE>()); - if (dirLeft.empty()) - return dirRight; - else if (dirRight.empty()) - return dirLeft; - return dirLeft + L" \u2013"/*en dash*/ + L"\n" + dirRight; - } + case ColumnTypeTree::FOLDER_NAME: + if (std::unique_ptr<TreeView::Node> node = treeDataView_.getLine(row)) + if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) + { + const std::wstring& dirLeft = AFS::getDisplayPath(root->baseFolder_.getAbstractPath< LEFT_SIDE>()); + const std::wstring& dirRight = AFS::getDisplayPath(root->baseFolder_.getAbstractPath<RIGHT_SIDE>()); + if (dirLeft.empty()) + return dirRight; + else if (dirRight.empty()) + return dirLeft; + return dirLeft + L" \u2013"/*en dash*/ + L"\n" + dirRight; + } break; - case ColumnTypeNavi::ITEM_COUNT: - case ColumnTypeNavi::BYTES: + case ColumnTypeTree::ITEM_COUNT: + case ColumnTypeTree::BYTES: break; } return std::wstring(); @@ -808,27 +793,25 @@ private: std::wstring getValue(size_t row, ColumnType colType) const override { - if (treeDataView_) - { - if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) - switch (static_cast<ColumnTypeNavi>(colType)) - { - case ColumnTypeNavi::FOLDER_NAME: - if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) - return utfTo<std::wstring>(root->displayName_); - else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get())) - return utfTo<std::wstring>(dir->folder_.getPairItemName()); - else if (dynamic_cast<const TreeView::FilesNode*>(node.get())) - return _("Files"); - break; + if (std::unique_ptr<TreeView::Node> node = treeDataView_.getLine(row)) + switch (static_cast<ColumnTypeTree>(colType)) + { + case ColumnTypeTree::FOLDER_NAME: + if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) + return utfTo<std::wstring>(root->displayName_); + else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get())) + return utfTo<std::wstring>(dir->folder_.getPairItemName()); + else if (dynamic_cast<const TreeView::FilesNode*>(node.get())) + return _("Files"); + break; - case ColumnTypeNavi::ITEM_COUNT: - return formatNumber(node->itemCount_); + case ColumnTypeTree::ITEM_COUNT: + return formatNumber(node->itemCount_); + + case ColumnTypeTree::BYTES: + return formatFilesizeShort(node->bytes_); + } - case ColumnTypeNavi::BYTES: - return formatFilesizeShort(node->bytes_); - } - } return std::wstring(); } @@ -841,20 +824,17 @@ private: rectInside.width -= COLUMN_GAP_LEFT; drawColumnLabelText(dc, rectInside, getColumnLabel(colType)); - if (treeDataView_) //draw sort marker + auto sortInfo = treeDataView_.getSortDirection(); + if (colType == static_cast<ColumnType>(sortInfo.first)) { - auto sortInfo = treeDataView_->getSortDirection(); - if (colType == static_cast<ColumnType>(sortInfo.first)) - { - const wxBitmap& marker = getResourceImage(sortInfo.second ? L"sortAscending" : L"sortDescending"); - drawBitmapRtlNoMirror(dc, marker, rectInside, wxALIGN_CENTER_HORIZONTAL); - } + const wxBitmap& marker = getResourceImage(sortInfo.second ? L"sortAscending" : L"sortDescending"); + drawBitmapRtlNoMirror(dc, marker, rectInside, wxALIGN_CENTER_HORIZONTAL); } } static const int GAP_SIZE = 2; - enum class HoverAreaNavi + enum class HoverAreaTree { NODE, }; @@ -884,20 +864,20 @@ private: // -------------------------------------------------------------------------------- // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() - if (static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::FOLDER_NAME && treeDataView_) + if (static_cast<ColumnTypeTree>(colType) == ColumnTypeTree::FOLDER_NAME) { - if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row)) + if (std::unique_ptr<TreeView::Node> node = treeDataView_.getLine(row)) { ////clear first secion: //clearArea(dc, wxRect(rect.GetTopLeft(), wxSize( - // node->level_ * widthLevelStep + GAP_SIZE + //width + // node->level_ * widthLevelStep_ + GAP_SIZE + //width // (showPercentBar ? WIDTH_PERCENTAGE_BAR + 2 * GAP_SIZE : 0) + // - // widthNodeStatus + GAP_SIZE + widthNodeIcon + GAP_SIZE, // + // widthNodeStatus_ + GAP_SIZE + widthNodeIcon + GAP_SIZE, // // rect.height)), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); //consume space - rectTmp.x += static_cast<int>(node->level_) * widthLevelStep; - rectTmp.width -= static_cast<int>(node->level_) * widthLevelStep; + rectTmp.x += static_cast<int>(node->level_) * widthLevelStep_; + rectTmp.width -= static_cast<int>(node->level_) * widthLevelStep_; rectTmp.x += GAP_SIZE; rectTmp.width -= GAP_SIZE; @@ -905,7 +885,7 @@ private: if (rectTmp.width > 0) { //percentage bar - if (showPercentBar) + if (showPercentBar_) { const wxRect areaPerc(rectTmp.x, rectTmp.y + 2, WIDTH_PERCENTAGE_BAR, rectTmp.height - 4); @@ -944,10 +924,10 @@ private: //clearArea(dc, rectStat, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); clearArea(dc, rectStat, *wxWHITE); //accessibility: always set both foreground AND background colors! - drawBitmapRtlMirror(dc, bmp, rectStat, wxALIGN_CENTER, renderBuf); + drawBitmapRtlMirror(dc, bmp, rectStat, wxALIGN_CENTER, renderBuf_); }; - const bool drawMouseHover = static_cast<HoverAreaNavi>(rowHover) == HoverAreaNavi::NODE; + const bool drawMouseHover = static_cast<HoverAreaTree>(rowHover) == HoverAreaTree::NODE; switch (node->status_) { case TreeView::STATUS_EXPANDED: @@ -960,30 +940,30 @@ private: break; } - rectTmp.x += widthNodeStatus + GAP_SIZE; - rectTmp.width -= widthNodeStatus + GAP_SIZE; + rectTmp.x += widthNodeStatus_ + GAP_SIZE; + rectTmp.width -= widthNodeStatus_ + GAP_SIZE; if (rectTmp.width > 0) { wxBitmap nodeIcon; bool isActive = true; //icon if (dynamic_cast<const TreeView::RootNode*>(node.get())) - nodeIcon = rootBmp; + nodeIcon = rootBmp_; else if (auto dir = dynamic_cast<const TreeView::DirNode*>(node.get())) { - nodeIcon = dirIcon; + nodeIcon = dirIcon_; isActive = dir->folder_.isActive(); } else if (dynamic_cast<const TreeView::FilesNode*>(node.get())) - nodeIcon = fileIcon; + nodeIcon = fileIcon_; if (!isActive) nodeIcon = wxBitmap(nodeIcon.ConvertToImage().ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3)); //treat all channels equally! drawBitmapRtlNoMirror(dc, nodeIcon, rectTmp, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - rectTmp.x += widthNodeIcon + GAP_SIZE; - rectTmp.width -= widthNodeIcon + GAP_SIZE; + rectTmp.x += widthNodeIcon_ + GAP_SIZE; + rectTmp.width -= widthNodeIcon_ + GAP_SIZE; if (rectTmp.width > 0) { @@ -1003,8 +983,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) == ColumnTypeNavi::BYTES || - static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::ITEM_COUNT) && grid_.GetLayoutDirection() != wxLayout_RightToLeft) + if ((static_cast<ColumnTypeTree>(colType) == ColumnTypeTree::BYTES || + static_cast<ColumnTypeTree>(colType) == ColumnTypeTree::ITEM_COUNT) && grid_.GetLayoutDirection() != wxLayout_RightToLeft) { rectTmp.width -= 2 * GAP_SIZE; alignment = wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL; @@ -1023,11 +1003,11 @@ private: { // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() - if (static_cast<ColumnTypeNavi>(colType) == ColumnTypeNavi::FOLDER_NAME && treeDataView_) + if (static_cast<ColumnTypeTree>(colType) == ColumnTypeTree::FOLDER_NAME) { - 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 - + widthNodeIcon + GAP_SIZE + dc.GetTextExtent(getValue(row, colType)).GetWidth() + + 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 + + widthNodeIcon_ + GAP_SIZE + dc.GetTextExtent(getValue(row, colType)).GetWidth() + GAP_SIZE; //additional gap from right else return 0; @@ -1039,24 +1019,23 @@ private: HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override { - switch (static_cast<ColumnTypeNavi>(colType)) + switch (static_cast<ColumnTypeTree>(colType)) { - case ColumnTypeNavi::FOLDER_NAME: - 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() + case ColumnTypeTree::FOLDER_NAME: + 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); - } + if (nodeStatusXFirst <= cellRelativePosX && cellRelativePosX < nodeStatusXLast) + return static_cast<HoverArea>(HoverAreaTree::NODE); + } break; - case ColumnTypeNavi::ITEM_COUNT: - case ColumnTypeNavi::BYTES: + case ColumnTypeTree::ITEM_COUNT: + case ColumnTypeTree::BYTES: break; } return HoverArea::NONE; @@ -1064,13 +1043,13 @@ private: std::wstring getColumnLabel(ColumnType colType) const override { - switch (static_cast<ColumnTypeNavi>(colType)) + switch (static_cast<ColumnTypeTree>(colType)) { - case ColumnTypeNavi::FOLDER_NAME: + case ColumnTypeTree::FOLDER_NAME: return _("Name"); - case ColumnTypeNavi::ITEM_COUNT: + case ColumnTypeTree::ITEM_COUNT: return _("Items"); - case ColumnTypeNavi::BYTES: + case ColumnTypeTree::BYTES: return _("Size"); } return std::wstring(); @@ -1078,19 +1057,18 @@ private: void onMouseLeft(GridClickEvent& event) { - switch (static_cast<HoverAreaNavi>(event.hoverArea_)) + switch (static_cast<HoverAreaTree>(event.hoverArea_)) { - case HoverAreaNavi::NODE: - if (treeDataView_) - 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; - } + case HoverAreaTree::NODE: + 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(); @@ -1098,16 +1076,15 @@ private: void onMouseLeftDouble(GridClickEvent& event) { - if (treeDataView_) - 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; - } + 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; + } event.Skip(); } @@ -1136,35 +1113,33 @@ private: case WXK_LEFT: case WXK_NUMPAD_LEFT: case WXK_NUMPAD_SUBTRACT: //https://msdn.microsoft.com/en-us/library/ms971323#atg_keyboardshortcuts_windows_shortcut_keys - if (treeDataView_) - switch (treeDataView_->getStatus(row)) - { - case TreeView::STATUS_EXPANDED: - return reduceNode(row); - case TreeView::STATUS_REDUCED: - case TreeView::STATUS_EMPTY: + switch (treeDataView_.getStatus(row)) + { + case TreeView::STATUS_EXPANDED: + return reduceNode(row); + case TreeView::STATUS_REDUCED: + case TreeView::STATUS_EMPTY: - const int parentRow = treeDataView_->getParent(row); - if (parentRow >= 0) - grid_.setGridCursor(parentRow); - break; - } + const int parentRow = treeDataView_.getParent(row); + if (parentRow >= 0) + grid_.setGridCursor(parentRow); + break; + } return; //swallow event case WXK_RIGHT: case WXK_NUMPAD_RIGHT: case WXK_NUMPAD_ADD: - if (treeDataView_) - switch (treeDataView_->getStatus(row)) - { - case TreeView::STATUS_EXPANDED: - grid_.setGridCursor(std::min(rowCount - 1, row + 1)); - break; - case TreeView::STATUS_REDUCED: - return expandNode(row); - case TreeView::STATUS_EMPTY: - break; - } + switch (treeDataView_.getStatus(row)) + { + case TreeView::STATUS_EXPANDED: + grid_.setGridCursor(std::min(rowCount - 1, row + 1)); + break; + case TreeView::STATUS_REDUCED: + return expandNode(row); + case TreeView::STATUS_EMPTY: + break; + } return; //swallow event } @@ -1174,7 +1149,6 @@ private: void onGridLabelContext(GridLabelClickEvent& event) { ContextMenu menu; - //-------------------------------------------------------------------------------------------------------- menu.addCheckBox(_("Percentage"), [this] { setShowPercentage(!getShowPercentage()); }, getShowPercentage()); //-------------------------------------------------------------------------------------------------------- @@ -1182,69 +1156,66 @@ private: { auto colAttr = grid_.getColumnConfig(); - Grid::ColumnAttribute* caFolderName = nullptr; - Grid::ColumnAttribute* caToggle = nullptr; + Grid::ColAttributes* caFolderName = nullptr; + Grid::ColAttributes* caToggle = nullptr; - for (Grid::ColumnAttribute& ca : colAttr) - if (ca.type_ == static_cast<ColumnType>(ColumnTypeNavi::FOLDER_NAME)) + for (Grid::ColAttributes& ca : colAttr) + if (ca.type == static_cast<ColumnType>(ColumnTypeTree::FOLDER_NAME)) caFolderName = &ca; - else if (ca.type_ == ct) + else if (ca.type == ct) caToggle = &ca; - assert(caFolderName && caFolderName->stretch_ > 0 && caFolderName->visible_); - assert(caToggle && caToggle->stretch_ == 0); + assert(caFolderName && caFolderName->stretch > 0 && caFolderName->visible); + assert(caToggle && caToggle->stretch == 0); if (caFolderName && caToggle) { - caToggle->visible_ = !caToggle->visible_; + caToggle->visible = !caToggle->visible; //take width of newly visible column from stretched folder name column - caFolderName->offset_ -= caToggle->visible_ ? caToggle->offset_ : -caToggle->offset_; + caFolderName->offset -= caToggle->visible ? caToggle->offset : -caToggle->offset; grid_.setColumnConfig(colAttr); } }; - for (const Grid::ColumnAttribute& ca : grid_.getColumnConfig()) + for (const Grid::ColAttributes& ca : grid_.getColumnConfig()) { - menu.addCheckBox(getColumnLabel(ca.type_), [ct = ca.type_, toggleColumn] { toggleColumn(ct); }, - ca.visible_, ca.type_ != static_cast<ColumnType>(ColumnTypeNavi::FOLDER_NAME)); //do not allow user to hide file name column! + menu.addCheckBox(getColumnLabel(ca.type), [ct = ca.type, toggleColumn] { toggleColumn(ct); }, + ca.visible, ca.type != static_cast<ColumnType>(ColumnTypeTree::FOLDER_NAME)); //do not allow user to hide file name column! } //-------------------------------------------------------------------------------------------------------- menu.addSeparator(); auto setDefaultColumns = [&] { - setShowPercentage(naviGridShowPercentageDefault); - grid_.setColumnConfig(treeview::convertConfig(getDefaultColumnAttributesNavi())); + setShowPercentage(treeGridShowPercentageDefault); + grid_.setColumnConfig(convertColAttributes(getTreeGridDefaultColAttribs(), getTreeGridDefaultColAttribs())); }; menu.addItem(_("&Default"), setDefaultColumns); //'&' -> reuse text from "default" buttons elsewhere + //-------------------------------------------------------------------------------------------------------- menu.popup(grid_); - //event.Skip(); } void onGridLabelLeftClick(GridLabelClickEvent& event) { - if (treeDataView_) - { - const auto colTypeNavi = static_cast<ColumnTypeNavi>(event.colType_); - bool sortAscending = TreeView::getDefaultSortDirection(colTypeNavi); + const auto colTypeTree = static_cast<ColumnTypeTree>(event.colType_); + bool sortAscending = getDefaultSortDirection(colTypeTree); - const auto sortInfo = treeDataView_->getSortDirection(); - if (sortInfo.first == colTypeNavi) - sortAscending = !sortInfo.second; + const auto sortInfo = treeDataView_.getSortDirection(); + if (sortInfo.first == colTypeTree) + sortAscending = !sortInfo.second; - treeDataView_->setSortDirection(colTypeNavi, sortAscending); - grid_.clearSelection(ALLOW_GRID_EVENT); - grid_.Refresh(); - } + treeDataView_.setSortDirection(colTypeTree, sortAscending); + grid_.clearSelection(ALLOW_GRID_EVENT); + grid_.Refresh(); } void expandNode(size_t row) { - treeDataView_->expandNode(row); + treeDataView_.expandNode(row); grid_.Refresh(); //implicitly clears selection (changed row count after expand) grid_.setGridCursor(row); //grid_.autoSizeColumns(); -> doesn't look as good as expected @@ -1252,29 +1223,30 @@ private: void reduceNode(size_t row) { - treeDataView_->reduceNode(row); + treeDataView_.reduceNode(row); grid_.Refresh(); grid_.setGridCursor(row); } - std::shared_ptr<TreeView> treeDataView_; - const wxBitmap fileIcon = IconBuffer::genericFileIcon(IconBuffer::SIZE_SMALL); - const wxBitmap dirIcon = IconBuffer::genericDirIcon (IconBuffer::SIZE_SMALL); + TreeView treeDataView_; - const wxBitmap rootBmp; - Opt<wxBitmap> renderBuf; //avoid costs of recreating this temporal variable - const int widthNodeIcon; - const int widthLevelStep; - const int widthNodeStatus; + const wxBitmap fileIcon_ = IconBuffer::genericFileIcon(IconBuffer::SIZE_SMALL); + const wxBitmap dirIcon_ = IconBuffer::genericDirIcon (IconBuffer::SIZE_SMALL); + + const wxBitmap rootBmp_; + Opt<wxBitmap> renderBuf_; //avoid costs of recreating this temporary variable + const int widthNodeIcon_; + const int widthLevelStep_; + const int widthNodeStatus_; Grid& grid_; - bool showPercentBar = true; + bool showPercentBar_ = true; }; } -void treeview::init(Grid& grid, const std::shared_ptr<TreeView>& treeDataView) +void zen::treegrid::init(Grid& grid) { - grid.setDataProvider(std::make_shared<GridDataNavi>(grid, treeDataView)); + grid.setDataProvider(std::make_shared<GridDataTree>(grid)); grid.showRowLabel(false); const int rowHeight = std::max(IconBuffer::getSize(IconBuffer::SIZE_SMALL), grid.getMainWin().GetCharHeight()) + 2; //allow 1 pixel space on top and bottom; dearly needed on OS X! @@ -1282,57 +1254,28 @@ void treeview::init(Grid& grid, const std::shared_ptr<TreeView>& treeDataView) } -void treeview::setShowPercentage(Grid& grid, bool value) +TreeView& zen::treegrid::getDataView(Grid& grid) { - if (auto* prov = dynamic_cast<GridDataNavi*>(grid.getDataProvider())) + if (auto* prov = dynamic_cast<GridDataTree*>(grid.getDataProvider())) + return prov->getDataView(); + + throw std::runtime_error("treegrid was not initialized! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); +} + + +void zen::treegrid::setShowPercentage(Grid& grid, bool value) +{ + if (auto* prov = dynamic_cast<GridDataTree*>(grid.getDataProvider())) prov->setShowPercentage(value); else assert(false); } -bool treeview::getShowPercentage(const Grid& grid) +bool zen::treegrid::getShowPercentage(const Grid& grid) { - if (auto* prov = dynamic_cast<const GridDataNavi*>(grid.getDataProvider())) + if (auto* prov = dynamic_cast<const GridDataTree*>(grid.getDataProvider())) return prov->getShowPercentage(); assert(false); return true; } - - -namespace -{ -std::vector<ColumnAttributeNavi> makeConsistent(const std::vector<ColumnAttributeNavi>& attribs) -{ - std::set<ColumnTypeNavi> usedTypes; - - std::vector<ColumnAttributeNavi> output; - //remove duplicates - std::copy_if(attribs.begin(), attribs.end(), std::back_inserter(output), - [&](const ColumnAttributeNavi& a) { return usedTypes.insert(a.type_).second; }); - - //make sure each type is existing! - const auto& defAttr = getDefaultColumnAttributesNavi(); - std::copy_if(defAttr.begin(), defAttr.end(), std::back_inserter(output), - [&](const ColumnAttributeNavi& a) { return usedTypes.insert(a.type_).second; }); - - return output; -} -} - -std::vector<Grid::ColumnAttribute> treeview::convertConfig(const std::vector<ColumnAttributeNavi>& attribs) -{ - std::vector<Grid::ColumnAttribute> output; - for (const ColumnAttributeNavi& ca : makeConsistent(attribs)) - output.emplace_back(static_cast<ColumnType>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); - return output; -} - - -std::vector<ColumnAttributeNavi> treeview::convertConfig(const std::vector<Grid::ColumnAttribute>& attribs) -{ - std::vector<ColumnAttributeNavi> output; - 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_grid.h index 6ec1a2d8..0d69e820 100755 --- a/FreeFileSync/Source/ui/tree_view.h +++ b/FreeFileSync/Source/ui/tree_grid.h @@ -10,7 +10,7 @@ #include <functional> #include <zen/optional.h> #include <wx+/grid.h> -#include "column_attr.h" +#include "tree_grid_attr.h" #include "../file_hierarchy.h" @@ -57,14 +57,14 @@ public: struct Node { Node(int percent, uint64_t bytes, int itemCount, unsigned int level, NodeStatus status) : - percent_(percent), level_(level), status_(status), bytes_(bytes), itemCount_(itemCount) {} + percent_(percent), bytes_(bytes), itemCount_(itemCount), level_(level), status_(status) {} virtual ~Node() {} const int percent_; //[0, 100] - const unsigned int level_; - const NodeStatus status_; const uint64_t bytes_; const int itemCount_; + const unsigned int level_; + const NodeStatus status_; }; struct FilesNode : public Node @@ -91,16 +91,15 @@ public: }; std::unique_ptr<Node> getLine(size_t row) const; //return nullptr on error - size_t linesTotal() const { return flatTree.size(); } + size_t linesTotal() const { return flatTree_.size(); } void expandNode(size_t row); void reduceNode(size_t row); NodeStatus getStatus(size_t row) const; ptrdiff_t getParent(size_t row) const; //return < 0 if none - void setSortDirection(ColumnTypeNavi colType, bool ascending); //apply permanently! - std::pair<ColumnTypeNavi, bool> getSortDirection() { return std::make_pair(sortColumn, sortAscending); } - static bool getDefaultSortDirection(ColumnTypeNavi colType); //ascending? + void setSortDirection(ColumnTypeTree colType, bool ascending); //apply permanently! + std::pair<ColumnTypeTree, bool> getSortDirection() { return std::make_pair(sortColumn_, sortAscending_); } private: struct DirNodeImpl; @@ -139,12 +138,10 @@ private: struct TreeLine { - 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] - const Container* node_; // - NodeType type_; //we increase size of "flatTree" using C-style types rather than have a polymorphic "folderCmpView" + unsigned int level = 0; + int percent = 0; //[0, 100] + const Container* node = nullptr; // + NodeType type = NodeType::TYPE_ROOT; //we increase size of "flatTree" using C-style types rather than have a polymorphic "folderCmpView" }; static void compressNode(Container& cont); @@ -154,37 +151,34 @@ private: template <class Predicate> void updateView(Predicate pred); void applySubView(std::vector<RootNodeImpl>&& newView); - template <bool ascending> static void sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeNavi columnType); + template <bool ascending> static void sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeTree columnType); template <bool ascending> struct LessShortName; - std::vector<TreeLine> flatTree; //collapsable/expandable sub-tree of folderCmpView -> always sorted! + std::vector<TreeLine> flatTree_; //collapsable/expandable sub-tree of folderCmpView -> always sorted! /* /|\ | (update...) | */ - std::vector<RootNodeImpl> folderCmpView; //partial view on folderCmp -> unsorted (cannot be, because files are not a separate entity) - std::function<bool(const FileSystemObject& fsObj)> lastViewFilterPred; //buffer view filter predicate for lazy evaluation of files/symlinks corresponding to a TYPE_FILES node + std::vector<RootNodeImpl> folderCmpView_; //partial view on folderCmp -> unsorted (cannot be, because files are not a separate entity) + std::function<bool(const FileSystemObject& fsObj)> lastViewFilterPred_; //buffer view filter predicate for lazy evaluation of files/symlinks corresponding to a TYPE_FILES node /* /|\ | (update...) | */ - std::vector<std::shared_ptr<BaseFolderPair>> folderCmp; //full raw data + std::vector<std::shared_ptr<BaseFolderPair>> folderCmp_; //full raw data - ColumnTypeNavi sortColumn = naviGridLastSortColumnDefault; - bool sortAscending = naviGridLastSortAscendingDefault; + ColumnTypeTree sortColumn_ = treeGridLastSortColumnDefault; + bool sortAscending_ = getDefaultSortDirection(treeGridLastSortColumnDefault); }; Zstring getShortDisplayNameForFolderPair(const AbstractPath& itemPathL, const AbstractPath& itemPathR); - -namespace treeview +namespace treegrid { -void init(Grid& grid, const std::shared_ptr<TreeView>& treeDataView); +void init(Grid& grid); +TreeView& getDataView(Grid& grid); void setShowPercentage(Grid& grid, bool value); bool getShowPercentage(const Grid& grid); - -std::vector<Grid::ColumnAttribute> convertConfig(const std::vector<ColumnAttributeNavi >& attribs); //+ make consistent -std::vector<ColumnAttributeNavi> convertConfig(const std::vector<Grid::ColumnAttribute>& attribs); // } } diff --git a/FreeFileSync/Source/ui/tree_grid_attr.h b/FreeFileSync/Source/ui/tree_grid_attr.h new file mode 100755 index 00000000..0c5da30e --- /dev/null +++ b/FreeFileSync/Source/ui/tree_grid_attr.h @@ -0,0 +1,63 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef TREE_GRID_ATTR_H_83470918473021745 +#define TREE_GRID_ATTR_H_83470918473021745 + +#include <vector> +#include <cassert> + + +namespace zen +{ +enum class ColumnTypeTree +{ + FOLDER_NAME, + ITEM_COUNT, + BYTES, +}; + +struct ColAttributesTree +{ + ColumnTypeTree type = ColumnTypeTree::FOLDER_NAME; + int offset = 0; + int stretch = 0; + bool visible = false; +}; + + +inline +std::vector<ColAttributesTree> getTreeGridDefaultColAttribs() +{ + return //harmonize with tree_view.cpp::onGridLabelContext() => expects stretched FOLDER_NAME and non-stretched other columns! + { + { ColumnTypeTree::FOLDER_NAME, -120, 1, true }, //stretch to full width and substract sum of fixed size widths + { ColumnTypeTree::ITEM_COUNT, 60, 0, true }, + { ColumnTypeTree::BYTES, 60, 0, true }, //GTK needs a few pixels more width + }; +} + +const bool treeGridShowPercentageDefault = true; +const ColumnTypeTree treeGridLastSortColumnDefault = ColumnTypeTree::BYTES; + +inline +bool getDefaultSortDirection(ColumnTypeTree colType) +{ + switch (colType) + { + case ColumnTypeTree::FOLDER_NAME: + return true; + case ColumnTypeTree::ITEM_COUNT: + return false; + case ColumnTypeTree::BYTES: + return false; + } + assert(false); + return true; +} +} + +#endif //TREE_GRID_ATTR_H_83470918473021745 diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index 06eb503f..5fc0f704 100755 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace zen { -const char ffsVersion[] = "9.6"; //internal linkage! +const char ffsVersion[] = "9.7"; //internal linkage! const char FFS_VERSION_SEPARATOR = '.'; } diff --git a/wx+/file_drop.cpp b/wx+/file_drop.cpp index 2c0b471e..65d5d861 100755 --- a/wx+/file_drop.cpp +++ b/wx+/file_drop.cpp @@ -7,6 +7,7 @@ #include "file_drop.h" #include <wx/dnd.h> #include <zen/utf.h> +#include <zen/file_access.h> using namespace zen; diff --git a/wx+/file_drop.h b/wx+/file_drop.h index 9826bf27..ee5393b7 100755 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -60,6 +60,8 @@ using FileDropEventFunction = void (wxEvtHandler::*)(FileDropEvent&); void setupFileDrop(wxWindow& wnd); + + } #endif //FILE_DROP_H_09457802957842560325626 diff --git a/wx+/focus.h b/wx+/focus.h new file mode 100755 index 00000000..cd99d010 --- /dev/null +++ b/wx+/focus.h @@ -0,0 +1,66 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef FOCUS_1084731021985757843 +#define FOCUS_1084731021985757843 + +#include <wx/toplevel.h> + + +namespace zen +{ +//pretty much the same like "bool wxWindowBase::IsDescendant(wxWindowBase* child) const" but without the obvious misnomer +inline +bool isComponentOf(const wxWindow* child, const wxWindow* top) +{ + for (const wxWindow* wnd = child; wnd != nullptr; wnd = wnd->GetParent()) + if (wnd == top) + return true; + return false; +} + + +inline +wxTopLevelWindow* getTopLevelWindow(wxWindow* child) +{ + for (wxWindow* wnd = child; wnd != nullptr; wnd = wnd->GetParent()) + if (auto tlw = dynamic_cast<wxTopLevelWindow*>(wnd)) //why does wxWidgets use wxWindows::IsTopLevel() ?? + return tlw; + return nullptr; +} + + +/* +Preserving input focus has to be more clever than: + wxWindow* oldFocus = wxWindow::FindFocus(); + ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus()); + +=> wxWindow::SetFocus() internally calls Win32 ::SetFocus, which calls ::SetActiveWindow, which - lord knows why - changes the foreground window to the focus window + even if the user is currently busy using a different app! More curiosity: this foreground focus stealing happens only during the *first* SetFocus() after app start! + It also can be avoided by changing focus back and forth with some other app after start => wxWidgets bug or Win32 feature??? +*/ +struct FocusPreserver +{ + ~FocusPreserver() + { + //wxTopLevelWindow::IsActive() does NOT call Win32 ::GetActiveWindow()! + //Instead it checks if ::GetFocus() is set somewhere inside the top level + //Note: Both Win32 active and focus windows are *thread-local* values, while foreground window is global! https://blogs.msdn.microsoft.com/oldnewthing/20131016-00/?p=2913 + if (oldFocus_) + if (wxTopLevelWindow* topWin = getTopLevelWindow(oldFocus_)) + if (topWin->IsActive()) //Linux/macOS: already behaves just like ::GetForegroundWindow() on Windows! + oldFocus_->SetFocus(); + } + + wxWindow* getFocus() const { return oldFocus_; } + void setFocus(wxWindow* win) { oldFocus_ = win; } + +private: + wxWindow* oldFocus_ = wxWindow::FindFocus(); +}; +} + +#endif //FOCUS_1084731021985757843 diff --git a/wx+/grid.cpp b/wx+/grid.cpp index f048d059..b301bf6b 100755 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -631,12 +631,12 @@ private: for (auto it = absWidths.begin(); it != absWidths.end(); ++it) { const size_t col = it - absWidths.begin(); - const int width = it->width_; //don't use unsigned for calculations! + const int width = it->width; //don't use unsigned for calculations! if (labelAreaTL.x > rect.GetRight()) return; //done, rect is fully covered if (labelAreaTL.x + width > rect.x) - drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(width, colLabelHeight)), col, it->type_); + drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(width, colLabelHeight)), col, it->type); labelAreaTL.x += width; } if (labelAreaTL.x > rect.GetRight()) @@ -647,7 +647,7 @@ private: { int totalWidth = 0; for (const ColumnWidth& cw : absWidths) - totalWidth += cw.width_; + totalWidth += cw.width; const int clientWidth = GetClientSize().GetWidth(); //need reliable, stable width in contrast to rect.width if (totalWidth < clientWidth) @@ -894,7 +894,7 @@ private: { int totalRowWidth = 0; for (const ColumnWidth& cw : absWidths) - totalRowWidth += cw.width_; + totalRowWidth += cw.width; //fill gap after columns and cover full width if (fillGapAfterColumns) @@ -922,14 +922,14 @@ private: if (cellAreaTL.x > rect.GetRight()) return; //done - if (cellAreaTL.x + cw.width_ > rect.x) + if (cellAreaTL.x + cw.width > rect.x) for (auto row = rowRange.first; row < rowRange.second; ++row) { - const wxRect cellRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, cw.width_, rowHeight); + 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), getRowHoverToDraw(row)); + prov->renderCell(dc, cellRect, row, cw.type, refParent().IsThisEnabled(), drawAsSelected(row), getRowHoverToDraw(row)); } - cellAreaTL.x += cw.width_; + cellAreaTL.x += cw.width; } } } @@ -993,28 +993,29 @@ private: //row < 0 possible!!! Pressing "Menu key" simulates Mouse Right Down + Up at position 0xffff/0xffff! GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, rowHover); + const MouseSelect mouseSelectBegin{ mouseEvent, false /*complete*/ }; if (row >= 0) if (!event.RightDown() || !refParent().isSelected(row)) //do NOT start a new selection if user right-clicks on a selected area! { if (event.ControlDown()) - activeSelection_ = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row), mouseEvent); + activeSelection_ = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row) /*positive*/, mouseEvent); else if (event.ShiftDown()) { - activeSelection_ = std::make_unique<MouseSelection>(*this, selectionAnchor_, true, mouseEvent); - refParent().clearSelection(ALLOW_GRID_EVENT); + activeSelection_ = std::make_unique<MouseSelection>(*this, selectionAnchor_, true /*positive*/, mouseEvent); + refParent().clearSelectionImpl(&mouseSelectBegin, ALLOW_GRID_EVENT); } else { - activeSelection_ = std::make_unique<MouseSelection>(*this, row, true, mouseEvent); - refParent().clearSelection(ALLOW_GRID_EVENT); + activeSelection_ = std::make_unique<MouseSelection>(*this, row, true /*positive*/, mouseEvent); + refParent().clearSelectionImpl(&mouseSelectBegin, 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 - sendEventNow(mouseEvent); - Refresh(); + + //notify event *after* potential "clearSelection()" above: a client should first receive a GridSelectEvent for clearing the grid, if necessary, + //then GridClickEvent and the associated GridSelectEvent one after the other + sendEventNow(mouseEvent); } event.Skip(); //allow changing focus } @@ -1041,11 +1042,15 @@ private: } //slight deviation from Explorer: change cursor while dragging mouse! -> unify behavior with shift + direction keys - refParent().selectRangeAndNotify(activeSelection_->getStartRow (), //from - activeSelection_->getCurrentRow(), //to - activeSelection_->isPositiveSelect(), - &activeSelection_->getFirstClick()); - activeSelection_.reset(); + const ptrdiff_t rowFrom = activeSelection_->getStartRow(); + const ptrdiff_t rowTo = activeSelection_->getCurrentRow(); + const bool positive = activeSelection_->isPositiveSelect(); + const MouseSelect mouseSelect{ activeSelection_->getFirstClick(), true /*complete*/ }; + + activeSelection_.reset(); //release mouse capture *before* sending the event (which might show a modal popup dialog requiring the mouse!!!) + + + refParent().selectRangeAndNotify(rowFrom, rowTo, positive, &mouseSelect); } if (auto prov = refParent().getDataProvider()) @@ -1123,8 +1128,8 @@ private: class MouseSelection : private wxEvtHandler { public: - MouseSelection(MainWin& wnd, size_t rowStart, bool positiveSelect, const GridClickEvent& firstClick) : - wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positiveSelect), firstClick_(firstClick) + MouseSelection(MainWin& wnd, size_t rowStart, bool positive, const GridClickEvent& firstClick) : + wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positive), firstClick_(firstClick) { wnd_.CaptureMouse(); timer_.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this); @@ -1618,28 +1623,42 @@ void Grid::showRowLabel(bool show) } +void Grid::selectRow(size_t row, GridEventPolicy rangeEventPolicy) +{ + selection_.selectRow(row); + mainWin_->Refresh(); + + if (rangeEventPolicy == ALLOW_GRID_EVENT) + { + GridSelectEvent selEvent(row, row + 1, true, nullptr); + if (wxEvtHandler* evtHandler = GetEventHandler()) + evtHandler->ProcessEvent(selEvent); + } +} + + void Grid::selectAllRows(GridEventPolicy rangeEventPolicy) { selection_.selectAll(); mainWin_->Refresh(); - if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction + if (rangeEventPolicy == ALLOW_GRID_EVENT) { - GridRangeSelectEvent selEvent(0, getRowCount(), true, nullptr); + GridSelectEvent selEvent(0, getRowCount(), true /*positive*/, nullptr); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(selEvent); } } -void Grid::clearSelection(GridEventPolicy rangeEventPolicy) +void Grid::clearSelectionImpl(const MouseSelect* mouseSelect, GridEventPolicy rangeEventPolicy) { selection_.clear(); mainWin_->Refresh(); - if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction + if (rangeEventPolicy == ALLOW_GRID_EVENT) { - GridRangeSelectEvent unselectionEvent(0, getRowCount(), false, nullptr); + GridSelectEvent unselectionEvent(0, getRowCount(), false /*positive*/, mouseSelect); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(unselectionEvent); } @@ -1648,18 +1667,16 @@ void Grid::clearSelection(GridEventPolicy rangeEventPolicy) void Grid::scrollDelta(int deltaX, int deltaY) { - int scrollPosX = 0; - int scrollPosY = 0; - GetViewStart(&scrollPosX, &scrollPosY); + wxPoint scrollPos = GetViewStart(); - scrollPosX += deltaX; - scrollPosY += deltaY; + scrollPos.x += deltaX; + scrollPos.y += deltaY; - scrollPosX = std::max(0, scrollPosX); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"! - scrollPosY = std::max(0, scrollPosY); // + scrollPos.x = std::max(0, scrollPos.x); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"! + scrollPos.y = std::max(0, scrollPos.y); // - Scroll(scrollPosX, scrollPosY); //internally calls wxWindows::Update()! - updateWindowSizes(); //may show horizontal scroll bar + Scroll(scrollPos); //internally calls wxWindows::Update()! + updateWindowSizes(); //may show horizontal scroll bar if row column gets wider } @@ -1704,17 +1721,19 @@ void Grid::setRowHeight(int height) } -void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) +void Grid::setColumnConfig(const std::vector<Grid::ColAttributes>& attr) { //hold ownership of non-visible columns oldColAttributes_ = attr; std::vector<VisibleColumn> visCols; - for (const ColumnAttribute& ca : attr) + for (const ColAttributes& ca : attr) { - assert(ca.type_ != ColumnType::NONE); - if (ca.visible_) - visCols.emplace_back(ca.type_, ca.offset_, ca.stretch_); + assert(ca.stretch >= 0); + assert(ca.type != ColumnType::NONE); + + if (ca.visible) + visCols.push_back({ ca.type, ca.offset, std::max(ca.stretch, 0) }); } //"ownership" of visible columns is now within Grid @@ -1725,23 +1744,23 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) } -std::vector<Grid::ColumnAttribute> Grid::getColumnConfig() const +std::vector<Grid::ColAttributes> Grid::getColumnConfig() const { //get non-visible columns (+ outdated visible ones) - std::vector<ColumnAttribute> output = oldColAttributes_; + std::vector<ColAttributes> output = oldColAttributes_; auto iterVcols = visibleCols_.begin(); auto iterVcolsend = visibleCols_.end(); //update visible columns but keep order of non-visible ones! - for (ColumnAttribute& ca : output) - if (ca.visible_) + for (ColAttributes& ca : output) + if (ca.visible) { if (iterVcols != iterVcolsend) { - ca.type_ = iterVcols->type_; - ca.stretch_ = iterVcols->stretch_; - ca.offset_ = iterVcols->offset_; + ca.type = iterVcols->type; + ca.stretch = iterVcols->stretch; + ca.offset = iterVcols->offset; ++iterVcols; } else @@ -1807,7 +1826,7 @@ Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const int accuWidth = 0; for (size_t col = 0; col < absWidths.size(); ++col) { - accuWidth += absWidths[col].width_; + accuWidth += absWidths[col].width; if (std::abs(absPosX - accuWidth) < resizeTolerance) { ColAction out; @@ -1850,7 +1869,7 @@ ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos) const std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths for (auto itCol = absWidths.begin(); itCol != absWidths.end(); ++itCol) { - const int width = itCol->width_; //beware dreaded unsigned conversions! + const int width = itCol->width; //beware dreaded unsigned conversions! accWidth += width; if (absPosX < accWidth - width / 2) @@ -1863,7 +1882,7 @@ ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos) const ColumnType Grid::colToType(size_t col) const { if (col < visibleCols_.size()) - return visibleCols_[col].type_; + return visibleCols_[col].type; return ColumnType::NONE; } @@ -1878,9 +1897,9 @@ Grid::ColumnPosInfo Grid::getColumnAtPos(int posX) const int accWidth = 0; for (const ColumnWidth& cw : getColWidths()) { - accWidth += cw.width_; + accWidth += cw.width; if (posX < accWidth) - return { cw.type_, posX + cw.width_ - accWidth, cw.width_ }; + return { cw.type, posX + cw.width - accWidth, cw.width }; } } return { ColumnType::NONE, 0, 0 }; @@ -1892,16 +1911,16 @@ wxRect Grid::getColumnLabelArea(ColumnType colType) const 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); + 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; }); + 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_; + posX += it->width; - return wxRect(wxPoint(posX, 0), wxSize(itCol->width_, colLabelHeight_)); + return wxRect(wxPoint(posX, 0), wxSize(itCol->width, colLabelHeight_)); } return wxRect(); } @@ -1928,9 +1947,6 @@ void Grid::setGridCursor(size_t row) selection_.clear(); //clear selection, do NOT fire event selectRangeAndNotify(row, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event - - mainWin_->Refresh(); - rowLabelWin_->Refresh(); //row labels! (Kubuntu) } @@ -1943,9 +1959,6 @@ void Grid::selectWithCursor(ptrdiff_t row) selection_.clear(); //clear selection, do NOT fire event selectRangeAndNotify(anchorRow, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event - - mainWin_->Refresh(); - rowLabelWin_->Refresh(); } @@ -1954,43 +1967,45 @@ void Grid::makeRowVisible(size_t row) const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found if (labelRect.height > 0) { - int scrollPosX = 0; - GetViewStart(&scrollPosX, nullptr); - int pixelsPerUnitY = 0; GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY); - if (pixelsPerUnitY <= 0) return; - - const int clientPosY = CalcScrolledPosition(labelRect.GetTopLeft()).y; - if (clientPosY < 0) - { - const int scrollPosY = labelRect.y / pixelsPerUnitY; - Scroll(scrollPosX, scrollPosY); //internally calls wxWindows::Update()! - updateWindowSizes(); //may show horizontal scroll bar - } - else if (clientPosY + labelRect.height > rowLabelWin_->GetClientSize().GetHeight()) + if (pixelsPerUnitY > 0) { - auto execScroll = [&](int clientHeight) - { - const int scrollPosY = std::ceil((labelRect.y - clientHeight + - labelRect.height) / static_cast<double>(pixelsPerUnitY)); - Scroll(scrollPosX, scrollPosY); - updateWindowSizes(); //may show horizontal scroll bar - }; - - const int clientHeightBefore = rowLabelWin_->GetClientSize().GetHeight(); - execScroll(clientHeightBefore); + const wxPoint scrollPosOld = GetViewStart(); - //client height may decrease after scroll due to a new horizontal scrollbar, resulting in a partially visible last row - const int clientHeightAfter = rowLabelWin_->GetClientSize().GetHeight(); - if (clientHeightAfter < clientHeightBefore) - execScroll(clientHeightAfter); + const int clientPosY = CalcScrolledPosition(labelRect.GetTopLeft()).y; + if (clientPosY < 0) + { + const int scrollPosNewY = labelRect.y / pixelsPerUnitY; + Scroll(scrollPosOld.x, scrollPosNewY); //internally calls wxWindows::Update()! + updateWindowSizes(); //may show horizontal scroll bar if row column gets wider + Refresh(); + } + else if (clientPosY + labelRect.height > rowLabelWin_->GetClientSize().GetHeight()) + { + auto execScroll = [&](int clientHeight) + { + const int scrollPosNewY = std::ceil((labelRect.y - clientHeight + + labelRect.height) / static_cast<double>(pixelsPerUnitY)); + Scroll(scrollPosOld.x, scrollPosNewY); + updateWindowSizes(); //may show horizontal scroll bar if row column gets wider + Refresh(); + }; + + const int clientHeightBefore = rowLabelWin_->GetClientSize().GetHeight(); + execScroll(clientHeightBefore); + + //client height may decrease after scroll due to a new horizontal scrollbar, resulting in a partially visible last row + const int clientHeightAfter = rowLabelWin_->GetClientSize().GetHeight(); + if (clientHeightAfter < clientHeightBefore) + execScroll(clientHeightAfter); + } } } } -void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated) +void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const MouseSelect* mouseSelect) { //sort + convert to half-open range auto rowFirst = std::min(rowFrom, rowTo); @@ -2001,13 +2016,12 @@ void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positiv numeric::clamp<ptrdiff_t>(rowLast, 0, rowCount); selection_.selectRange(rowFirst, rowLast, positive); + mainWin_->Refresh(); //notify event - GridRangeSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseInitiated); + GridSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseSelect); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(selectionEvent); - - mainWin_->Refresh(); } @@ -2020,15 +2034,13 @@ void Grid::scrollTo(size_t row) GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY); if (pixelsPerUnitY > 0) { - const int scrollPosYNew = labelRect.y / pixelsPerUnitY; - int scrollPosXOld = 0; - int scrollPosYOld = 0; - GetViewStart(&scrollPosXOld, &scrollPosYOld); + const int scrollPosNewY = labelRect.y / pixelsPerUnitY; + const wxPoint scrollPosOld = GetViewStart(); - if (scrollPosYOld != scrollPosYNew) //support polling + if (scrollPosOld.y != scrollPosNewY) //support polling { - Scroll(scrollPosXOld, scrollPosYNew); //internally calls wxWindows::Update()! - updateWindowSizes(); //may show horizontal scroll bar + Scroll(scrollPosOld.x, scrollPosNewY); //internally calls wxWindows::Update()! + updateWindowSizes(); //may show horizontal scroll bar if row column gets wider Refresh(); } } @@ -2036,6 +2048,15 @@ void Grid::scrollTo(size_t row) } +size_t Grid::getTopRow() const +{ + const wxPoint absPos = CalcUnscrolledPosition(wxPoint(0, 0)); + const ptrdiff_t row = rowLabelWin_->getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + assert((getRowCount() == 0 && row == 0) || (0 <= row && row < static_cast<ptrdiff_t>(getRowCount()))); + return row; +} + + bool Grid::Enable(bool enable) { Refresh(); @@ -2053,7 +2074,7 @@ int Grid::getBestColumnSize(size_t col) const { if (dataView_ && col < visibleCols_.size()) { - const ColumnType type = visibleCols_[col].type_; + const ColumnType type = visibleCols_[col].type; wxClientDC dc(mainWin_); dc.SetFont(mainWin_->GetFont()); //harmonize with MainWin::render() @@ -2088,7 +2109,7 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve //unusual delay when enlarging the column again later width = std::max(width, COLUMN_MIN_WIDTH); - vcRs.offset_ = width - stretchedWidths[col]; //width := stretchedWidth + offset + vcRs.offset = width - stretchedWidths[col]; //width := stretchedWidth + offset //III. resizing any column should normalize *all* other stretched columns' offsets considering current mainWinWidth! // test case: @@ -2097,12 +2118,12 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve //3. shrink a fixed-size column so that the scrollbars vanish and columns cover full width again //4. now verify that the stretched column is resizing immediately if main window is enlarged again for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) - if (visibleCols_[col2].stretch_ > 0) //normalize stretched columns only - visibleCols_[col2].offset_ = std::max(visibleCols_[col2].offset_, COLUMN_MIN_WIDTH - stretchedWidths[col2]); + if (visibleCols_[col2].stretch > 0) //normalize stretched columns only + visibleCols_[col2].offset = std::max(visibleCols_[col2].offset, COLUMN_MIN_WIDTH - stretchedWidths[col2]); if (columnResizeEventPolicy == ALLOW_GRID_EVENT) { - GridColumnResizeEvent sizeEvent(vcRs.offset_, vcRs.type_); + GridColumnResizeEvent sizeEvent(vcRs.offset, vcRs.type); if (wxEvtHandler* evtHandler = GetEventHandler()) { if (notifyAsync) @@ -2125,7 +2146,7 @@ void Grid::autoSizeColumns(GridEventPolicy columnResizeEventPolicy) { const int bestWidth = getBestColumnSize(col); //return -1 on error if (bestWidth >= 0) - setColumnWidth(bestWidth, col, columnResizeEventPolicy, true); + setColumnWidth(bestWidth, col, columnResizeEventPolicy, true /*notifyAsync*/); } updateWindowSizes(); Refresh(); @@ -2140,8 +2161,8 @@ std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final widt int stretchTotal = 0; for (const VisibleColumn& vc : visibleCols_) { - assert(vc.stretch_ >= 0); - stretchTotal += vc.stretch_; + assert(vc.stretch >= 0); + stretchTotal += vc.stretch; } int remainingWidth = clientWidth; @@ -2154,7 +2175,7 @@ std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final widt { for (const VisibleColumn& vc : visibleCols_) { - const int width = clientWidth * vc.stretch_ / stretchTotal; //rounds down! + const int width = clientWidth * vc.stretch / stretchTotal; //rounds down! output.push_back(width); remainingWidth -= width; } @@ -2162,7 +2183,7 @@ std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final widt //distribute *all* of clientWidth: should suffice to enlarge the first few stretched columns; no need to minimize total absolute error of distribution if (remainingWidth > 0) for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) - if (visibleCols_[col2].stretch_ > 0) + if (visibleCols_[col2].stretch > 0) { ++output[col2]; if (--remainingWidth == 0) @@ -2189,14 +2210,14 @@ std::vector<Grid::ColumnWidth> Grid::getColWidths(int mainWinWidth) const //eval for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) { const auto& vc = visibleCols_[col2]; - int width = stretchedWidths[col2] + vc.offset_; + int width = stretchedWidths[col2] + vc.offset; - if (vc.stretch_ > 0) + if (vc.stretch > 0) width = std::max(width, COLUMN_MIN_WIDTH); //normalization really needed here: e.g. smaller main window would result in negative width else width = std::max(width, 0); //support smaller width than COLUMN_MIN_WIDTH if set via configuration - output.emplace_back(vc.type_, width); + output.push_back({ vc.type, width }); } return output; } @@ -2206,6 +2227,6 @@ int Grid::getColWidthsSum(int mainWinWidth) const { int sum = 0; for (const ColumnWidth& cw : getColWidths(mainWinWidth)) - sum += cw.width_; + sum += cw.width; return sum; } @@ -28,7 +28,7 @@ extern const wxEventType EVENT_GRID_MOUSE_LEFT_UP; //generates: GridClickEve extern const wxEventType EVENT_GRID_MOUSE_RIGHT_DOWN; // extern const wxEventType EVENT_GRID_MOUSE_RIGHT_UP; // -extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridRangeSelectEvent +extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridSelectEvent //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 @@ -41,54 +41,60 @@ struct GridClickEvent : public wxMouseEvent { 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); } + GridClickEvent* Clone() const override { return new GridClickEvent(*this); } const ptrdiff_t row_; //-1 for invalid position, >= rowCount if out of range const HoverArea hoverArea_; //may be HoverArea::NONE }; -struct GridRangeSelectEvent : public wxCommandEvent +struct MouseSelect { - GridRangeSelectEvent(size_t rowFirst, size_t rowLast, bool positive, const GridClickEvent* mouseInitiated) : + GridClickEvent click; + bool complete = false; //false if this is a preliminary "clear range" event for mouse-down, before the actual selection has happened during mouse-up +}; + +struct GridSelectEvent : public wxCommandEvent +{ + GridSelectEvent(size_t rowFirst, size_t rowLast, bool positive, const MouseSelect* mouseSelect) : 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); } + mouseSelect_(mouseSelect ? *mouseSelect : Opt<MouseSelect>()) { assert(rowFirst <= rowLast); } + GridSelectEvent* Clone() const override { return new GridSelectEvent(*this); } 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() + const Opt<MouseSelect> mouseSelect_; //filled unless selection was performed via keyboard shortcuts }; 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); } + GridLabelClickEvent* Clone() const override { return new GridLabelClickEvent(*this); } const ColumnType colType_; //may be ColumnType::NONE }; - 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); } + GridColumnResizeEvent* Clone() const override { return new GridColumnResizeEvent(*this); } const ColumnType colType_; const int offset_; }; using GridClickEventFunction = void (wxEvtHandler::*)(GridClickEvent&); -using GridRangeSelectEventFunction = void (wxEvtHandler::*)(GridRangeSelectEvent&); +using GridSelectEventFunction = void (wxEvtHandler::*)(GridSelectEvent&); using GridLabelClickEventFunction = void (wxEvtHandler::*)(GridLabelClickEvent&); using GridColumnResizeEventFunction = void (wxEvtHandler::*)(GridColumnResizeEvent&); -#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 GridClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func) +#define GridSelectEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridSelectEventFunction, &func) +#define GridLabelClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridLabelClickEventFunction, &func) #define GridColumnResizeEventHandler(func)(wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridColumnResizeEventFunction, &func) //------------------------------------------------------------------------------------------------------------ + class Grid; @@ -146,19 +152,18 @@ public: void setRowHeight(int height); - struct ColumnAttribute + struct ColAttributes { - ColumnAttribute(ColumnType type, int offset, int stretch, bool visible = true) : type_(type), visible_(visible), stretch_(std::max(stretch, 0)), offset_(offset) { assert(stretch >=0 ); } - ColumnType type_; - bool visible_; + ColumnType type = ColumnType::NONE; //first, client width is partitioned according to all available stretch factors, then "offset_" is added //universal model: a non-stretched column has stretch factor 0 with the "offset" becoming identical to final width! - int stretch_; //>= 0 - int offset_; + int offset = 0; + int stretch = 0; //>= 0 + bool visible = false; }; - void setColumnConfig(const std::vector<ColumnAttribute>& attr); //set column count + widths - std::vector<ColumnAttribute> getColumnConfig() const; + void setColumnConfig(const std::vector<ColAttributes>& attr); //set column count + widths + std::vector<ColAttributes> getColumnConfig() const; void setDataProvider(const std::shared_ptr<GridData>& dataView) { dataView_ = dataView; } /**/ GridData* getDataProvider() { return dataView_.get(); } @@ -178,8 +183,9 @@ public: void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical); std::vector<size_t> getSelectedRows() const { return selection_.get(); } - void selectAllRows (GridEventPolicy rangeEventPolicy); - void clearSelection(GridEventPolicy rangeEventPolicy); //turn off range selection event when calling this function in an event handler to avoid recursion! + void selectRow(size_t row, GridEventPolicy rangeEventPolicy); + void selectAllRows (GridEventPolicy rangeEventPolicy); //turn off range selection event when calling this function in an event handler to avoid recursion! + void clearSelection(GridEventPolicy rangeEventPolicy) { clearSelectionImpl(nullptr /*mouseSelect*/, rangeEventPolicy); } // void scrollDelta(int deltaX, int deltaY); //in scroll units @@ -193,9 +199,9 @@ public: struct ColumnPosInfo { - ColumnType colType; //ColumnType::NONE no column at x position! - int cellRelativePosX; - int colWidth; + ColumnType colType = ColumnType::NONE; //ColumnType::NONE no column at x position! + int cellRelativePosX = 0; + int colWidth = 0; }; ColumnPosInfo getColumnAtPos(int posX) const; //absolute position! @@ -208,6 +214,9 @@ public: size_t getGridCursor() const; //returns row void scrollTo(size_t row); + size_t getTopRow() const; + + void makeRowVisible(size_t row); void Refresh(bool eraseBackground = true, const wxRect* rect = nullptr) override; bool Enable(bool enable = true) override; @@ -226,7 +235,6 @@ private: void updateWindowSizes(bool updateScrollbar = true); void selectWithCursor(ptrdiff_t row); - void makeRowVisible(size_t row); void redirectRowLabelEvent(wxMouseEvent& event); @@ -247,53 +255,52 @@ private: class Selection { public: - void init(size_t rowCount) { rowSelectionValue.resize(rowCount); clear(); } + void init(size_t rowCount) { selected_.resize(rowCount); clear(); } - size_t maxSize() const { return rowSelectionValue.size(); } + size_t maxSize() const { return selected_.size(); } std::vector<size_t> get() const { std::vector<size_t> result; - for (size_t row = 0; row < rowSelectionValue.size(); ++row) - if (rowSelectionValue[row] != 0) + for (size_t row = 0; row < selected_.size(); ++row) + if (selected_[row] != 0) result.push_back(row); return result; } - void selectAll() { selectRange(0, rowSelectionValue.size(), true); } - void clear () { selectRange(0, rowSelectionValue.size(), false); } + void selectRow(size_t row) { selectRange(row, row + 1, true); } + void selectAll () { selectRange(0, selected_.size(), true); } + void clear () { selectRange(0, selected_.size(), false); } - bool isSelected(size_t row) const { return row < rowSelectionValue.size() ? rowSelectionValue[row] != 0 : false; } + bool isSelected(size_t row) const { return row < selected_.size() ? selected_[row] != 0 : false; } void selectRange(size_t rowFirst, size_t rowLast, bool positive = true) //select [rowFirst, rowLast), trims if required! { if (rowFirst <= rowLast) { - numeric::clamp<size_t>(rowFirst, 0, rowSelectionValue.size()); - numeric::clamp<size_t>(rowLast, 0, rowSelectionValue.size()); + numeric::clamp<size_t>(rowFirst, 0, selected_.size()); + numeric::clamp<size_t>(rowLast, 0, selected_.size()); - std::fill(rowSelectionValue.begin() + rowFirst, rowSelectionValue.begin() + rowLast, positive); + std::fill(selected_.begin() + rowFirst, selected_.begin() + rowLast, positive); } else assert(false); } private: - std::vector<char> rowSelectionValue; //effectively a vector<bool> of size "number of rows" + std::vector<char> selected_; //effectively a vector<bool> of size "number of rows" }; struct VisibleColumn { - VisibleColumn(ColumnType type, int offset, int stretch) : type_(type), stretch_(stretch), offset_(offset) {} - ColumnType type_; - int stretch_; //>= 0 - int offset_; + ColumnType type = ColumnType::NONE; + int offset = 0; + int stretch = 0; //>= 0 }; struct ColumnWidth { - ColumnWidth(ColumnType type, int width) : type_(type), width_(width) {} - ColumnType type_; - int width_; + ColumnType type = ColumnType::NONE; + int width = 0; }; std::vector<ColumnWidth> getColWidths() const; // std::vector<ColumnWidth> getColWidths(int mainWinWidth) const; //evaluate stretched columns @@ -304,7 +311,7 @@ private: { const auto& widths = getColWidths(); if (col < widths.size()) - return widths[col].width_; + return widths[col].width; return NoValue(); } @@ -312,7 +319,9 @@ private: wxRect getColumnLabelArea(ColumnType colType) const; //returns empty rect if column not found - void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated); //select inclusive range [rowFrom, rowTo] + notify event! + void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const MouseSelect* mouseSelect); //select inclusive range [rowFrom, rowTo] + notify event! + + void clearSelectionImpl(const MouseSelect* mouseSelect, GridEventPolicy rangeEventPolicy); bool isSelected(size_t row) const { return selection_.isSelected(row); } @@ -352,11 +361,53 @@ private: bool allowColumnMove_ = true; bool allowColumnResize_ = true; - std::vector<VisibleColumn> visibleCols_; //individual widths, type and total column count - std::vector<ColumnAttribute> oldColAttributes_; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*! + std::vector<VisibleColumn> visibleCols_; //individual widths, type and total column count + std::vector<ColAttributes> oldColAttributes_; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*! size_t rowCountOld_ = 0; //at the time of last Grid::Refresh() }; + +//------------------------------------------------------------------------------------------------------------ + +template <class ColAttrReal> +std::vector<ColAttrReal> makeConsistent(const std::vector<ColAttrReal>& attribs, const std::vector<ColAttrReal>& defaults) +{ + using ColTypeReal = decltype(ColAttrReal().type); + std::vector<ColAttrReal> output; + + std::set<ColTypeReal> usedTypes; //remove duplicates + auto appendUnique = [&](const std::vector<ColAttrReal>& attr) + { + std::copy_if(attr.begin(), attr.end(), std::back_inserter(output), + [&](const ColAttrReal& a) { return usedTypes.insert(a.type).second; }); + }; + appendUnique(attribs); + appendUnique(defaults); //make sure each type is existing! + + return output; +} + + +template <class ColAttrReal> +std::vector<Grid::ColAttributes> convertColAttributes(const std::vector<ColAttrReal>& attribs, const std::vector<ColAttrReal>& defaults) +{ + std::vector<Grid::ColAttributes> output; + for (const ColAttrReal& ca : makeConsistent(attribs, defaults)) + output.push_back({ static_cast<ColumnType>(ca.type), ca.offset, ca.stretch, ca.visible }); + return output; +} + + +template <class ColAttrReal> +std::vector<ColAttrReal> convertColAttributes(const std::vector<Grid::ColAttributes>& attribs) +{ + using ColTypeReal = decltype(ColAttrReal().type); + + std::vector<ColAttrReal> output; + for (const Grid::ColAttributes& ca : attribs) + output.push_back({ static_cast<ColTypeReal>(ca.type), ca.offset, ca.stretch, ca.visible }); + return output; +} } #endif //GRID_H_834702134831734869987 diff --git a/wx+/http.cpp b/wx+/http.cpp index dd3cb3bc..fa88bb1d 100755 --- a/wx+/http.cpp +++ b/wx+/http.cpp @@ -77,22 +77,24 @@ public: size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream! { const size_t blockSize = getBlockSize(); - assert(memBuf_.size() <= blockSize); + assert(memBuf_.size() >= blockSize); + assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size()); + char* it = static_cast<char*>(buffer); char* const itEnd = it + bytesToRead; for (;;) { - const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), memBuf_.size()); - std::copy (memBuf_.begin(), memBuf_.begin() + junkSize, it); - memBuf_.erase(memBuf_.begin(), memBuf_.begin() + junkSize); - it += junkSize; + const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufPosEnd_ - bufPos_); + std::memcpy(it, &memBuf_[0] + bufPos_, junkSize); + bufPos_ += junkSize; + it += junkSize; if (it == itEnd) break; //-------------------------------------------------------------------- - memBuf_.resize(blockSize); const size_t bytesRead = tryRead(&memBuf_[0], blockSize); //throw SysError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0 - memBuf_.resize(bytesRead); + bufPos_ = 0; + bufPosEnd_ = bytesRead; if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesRead); //throw X @@ -137,8 +139,11 @@ private: wxHTTP webAccess_; std::unique_ptr<wxInputStream> httpStream_; //must be deleted BEFORE webAccess is closed - std::vector<char> memBuf_; const IOCallback notifyUnbufferedIO_; //throw X + + std::vector<char> memBuf_ = std::vector<char>(getBlockSize()); + size_t bufPos_ = 0; //buffered I/O; see file_io.cpp + size_t bufPosEnd_ = 0; // }; diff --git a/zen/file_io.cpp b/zen/file_io.cpp index bb4848c6..1cbd970b 100755 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -131,8 +131,6 @@ size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError, E size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, ErrorFileLocked, X; return "bytesToRead" bytes unless end of stream! { - warn_static("implement PERF_AWESOME_BUFFER program wide for all buffers!?") - /* FFS 8.9-9.5 perf issues on macOS: https://www.freefilesync.org/forum/viewtopic.php?t=4808 app-level buffering is essential to optimize random data sizes; e.g. "export file list": @@ -155,13 +153,12 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, Erro for (;;) { const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufPosEnd_ - bufPos_); - std::memcpy(it, &memBuf_[bufPos_], junkSize); + std::memcpy(it, &memBuf_[0] + bufPos_ /*caveat: vector debug checks*/, junkSize); bufPos_ += junkSize; it += junkSize; if (it == itEnd) break; - //-------------------------------------------------------------------- const size_t bytesRead = tryRead(&memBuf_[0], blockSize); //throw FileError, ErrorFileLocked; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0 bufPos_ = 0; @@ -263,19 +260,18 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro if (memBuf_.size() - bufPos_ < blockSize) //support memBuf_.size() > blockSize to reduce memmove()s, but perf test shows: not really needed! // || bufPos_ == bufPosEnd_) -> not needed while memBuf_.size() == blockSize { - std::memmove(&memBuf_[0], &memBuf_[bufPos_], bufPosEnd_ - bufPos_); + std::memmove(&memBuf_[0], &memBuf_[0] + bufPos_, bufPosEnd_ - bufPos_); bufPosEnd_ -= bufPos_; bufPos_ = 0; } const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), blockSize - (bufPosEnd_ - bufPos_)); - std::memcpy(&memBuf_[bufPosEnd_], it, junkSize); + std::memcpy(&memBuf_[0] + bufPosEnd_ /*caveat: vector debug checks*/, it, junkSize); bufPosEnd_ += junkSize; it += junkSize; if (it == itEnd) return; - //-------------------------------------------------------------------- const size_t bytesWritten = tryWrite(&memBuf_[bufPos_], blockSize); //throw FileError; may return short! CONTRACT: bytesToWrite > 0 bufPos_ += bytesWritten; diff --git a/zen/zstring.h b/zen/zstring.h index 2a4a549e..b96842b5 100755 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -74,6 +74,7 @@ S ciReplaceCpy(const S& str, const T& oldTerm, const U& newTerm); //common unicode sequences const wchar_t EM_DASH = L'\u2014'; +const wchar_t EN_DASH = L'\u2013'; const wchar_t* const SPACED_DASH = L" \u2013 "; //using 'EN DASH' const wchar_t LTR_MARK = L'\u200E'; //UTF-8: E2 80 8E const wchar_t RTL_MARK = L'\u200F'; //UTF-8: E2 80 8F diff --git a/zenXml/zenxml/cvrt_struc.h b/zenXml/zenxml/cvrt_struc.h index 3a724376..87687929 100755 --- a/zenXml/zenxml/cvrt_struc.h +++ b/zenXml/zenxml/cvrt_struc.h @@ -140,6 +140,7 @@ struct ConvertElement<T, VALUE_TYPE_STL_CONTAINER> value.insert(value.end(), childVal); else success = false; + //should we support insertion of partially-loaded struct?? } return success; } |