diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:20:50 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:20:50 +0200 |
commit | 7e706cf64654aea466c059c307e5723e2423ed5d (patch) | |
tree | e85f0d28d7c81b6d21419fc38e1a654cca2212b1 | |
parent | 5.5 (diff) | |
download | FreeFileSync-7e706cf64654aea466c059c307e5723e2423ed5d.tar.gz FreeFileSync-7e706cf64654aea466c059c307e5723e2423ed5d.tar.bz2 FreeFileSync-7e706cf64654aea466c059c307e5723e2423ed5d.zip |
5.6
86 files changed, 4351 insertions, 2436 deletions
diff --git a/Application.cpp b/Application.cpp index 6f16f004..26cd3b03 100644 --- a/Application.cpp +++ b/Application.cpp @@ -11,9 +11,6 @@ #include <wx/sound.h> #include <wx/tooltip.h> //wxWidgets v2.9 #include <wx/log.h> -#include <zen/file_io.h> -#include <zen/file_handling.h> -#include <wx+/serialize.h> #include <wx+/app_main.h> #include "comparison.h" #include "algorithm.h" @@ -24,6 +21,8 @@ #include "lib/resources.h" #include "lib/ffs_paths.h" #include "lib/lock_holder.h" +#include "lib/process_xml.h" +#include "lib/error_log.h" #ifdef FFS_LINUX #include <gtk/gtk.h> @@ -32,8 +31,13 @@ using namespace zen; using namespace xmlAccess; + IMPLEMENT_APP(Application) +void runGuiMode(const xmlAccess::XmlGuiConfig& guiCfg, const XmlGlobalSettings& settings); +void runGuiMode(const std::vector<wxString>& cfgFileName, const XmlGlobalSettings& settings); +void runBatchMode(const Zstring& filename, XmlGlobalSettings& globSettings, FfsReturnCode& returnCode); + #ifdef FFS_WIN namespace @@ -156,11 +160,16 @@ void Application::OnStartApplication(wxIdleEvent&) wxToolTip::SetAutoPop(7000); //tooltip visibilty in ms, 5s seems to be default for Windows #endif + xmlAccess::XmlGlobalSettings globalSettings; //settings used by GUI, batch mode or both + setLanguage(globalSettings.programLanguage); //set default language tentatively + try //load global settings from XML: they are written on exit, so read them FIRST { if (fileExists(toZ(getGlobalConfigFile()))) - readConfig(globalSettings); + readConfig(globalSettings); //throw FfsXmlError //else: globalSettings already has default values + + setLanguage(globalSettings.programLanguage); } catch (const xmlAccess::FfsXmlError& error) { @@ -171,9 +180,6 @@ void Application::OnStartApplication(wxIdleEvent&) wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR); } - //set program language - setLanguage(globalSettings.programLanguage); - //determine FFS mode of operation std::vector<wxString> commandArgs = getCommandlineArgs(*this); @@ -232,7 +238,7 @@ void Application::OnStartApplication(wxIdleEvent&) { case MERGE_BATCH: //pure batch config files if (commandArgs.size() == 1) - runBatchMode(utfCvrtTo<Zstring>(commandArgs[0]), globalSettings); + runBatchMode(utfCvrtTo<Zstring>(commandArgs[0]), globalSettings, returnCode); else runGuiMode(commandArgs, globalSettings); break; @@ -278,12 +284,7 @@ int Application::OnRun() auto processException = [](const std::wstring& msg) { //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! - try - { - saveBinStream(getConfigDir() + Zstr("LastError.txt"), utfCvrtTo<std::string>(msg)); //throw FileError - } - catch (const FileError&) {} - + logError(utfCvrtTo<std::string>(msg)); wxSafeShowMessage(_("An exception occurred!") + L" - FFS", msg); }; @@ -306,25 +307,6 @@ int Application::OnRun() } -int Application::OnExit() -{ - //get program language - globalSettings.programLanguage = getLanguage(); - - try //save global settings to XML - { - xmlAccess::writeConfig(globalSettings); - } - catch (const xmlAccess::FfsXmlError&) - { - //wxMessageBox(error.msg(), _("Error"), wxOK | wxICON_ERROR); -> not that important/might be tedious in silent batch? - assert(false); //get info in debug build - } - - return 0; -} - - void Application::OnQueryEndSession(wxEvent& event) { //alas wxWidgets screws up once again: http://trac.wxwidgets.org/ticket/3069 @@ -336,21 +318,21 @@ void Application::OnQueryEndSession(wxEvent& event) } -void Application::runGuiMode(const xmlAccess::XmlGuiConfig& guiCfg, xmlAccess::XmlGlobalSettings& settings) +void runGuiMode(const xmlAccess::XmlGuiConfig& guiCfg, const XmlGlobalSettings& settings) { MainDialog* frame = new MainDialog(std::vector<wxString>(), guiCfg, settings, true); frame->Show(); } -void Application::runGuiMode(const std::vector<wxString>& cfgFileNames, xmlAccess::XmlGlobalSettings& settings) +void runGuiMode(const std::vector<wxString>& cfgFileNames, const XmlGlobalSettings& settings) { MainDialog* frame = new MainDialog(cfgFileNames, settings); frame->Show(); } -void Application::runBatchMode(const Zstring& filename, xmlAccess::XmlGlobalSettings& globSettings) +void runBatchMode(const Zstring& filename, XmlGlobalSettings& globSettings, FfsReturnCode& returnCode) { //load XML settings XmlBatchConfig batchCfg; //structure to receive gui settings @@ -358,9 +340,9 @@ void Application::runBatchMode(const Zstring& filename, xmlAccess::XmlGlobalSett { readConfig(filename, batchCfg); } - catch (const xmlAccess::FfsXmlError& error) + catch (const xmlAccess::FfsXmlError& e) { - wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR); //batch mode: break on errors AND even warnings! + wxMessageBox(e.toString(), _("Error"), wxOK | wxICON_ERROR); //batch mode: break on errors AND even warnings! return; } //all settings have been read successfully... @@ -449,4 +431,17 @@ void Application::runBatchMode(const Zstring& filename, xmlAccess::XmlGlobalSett } } catch (BatchAbortProcess&) {} //exit used by statusHandler + + + try //save global settings to XML: e.g. ignored warnings + { + xmlAccess::writeConfig(globSettings); //FfsXmlError + } + catch (const xmlAccess::FfsXmlError& e) + { + if (batchCfg.handleError == ON_ERROR_POPUP) + wxMessageBox(e.toString(), _("Error"), wxOK | wxICON_ERROR); //batch mode: break on errors AND even warnings! + else + logError(utfCvrtTo<std::string>(e.toString())); + } } diff --git a/Application.h b/Application.h index 0d247796..de5a7cf7 100644 --- a/Application.h +++ b/Application.h @@ -14,21 +14,15 @@ class Application : public wxApp { -public: - bool OnInit(); - int OnRun(); - int OnExit(); - bool OnExceptionInMainLoop(); +private: + virtual bool OnInit(); + virtual int OnRun(); + virtual int OnExit() { return 0; } + virtual bool OnExceptionInMainLoop(); void OnStartApplication(wxIdleEvent& event); void OnQueryEndSession(wxEvent& event); -private: - void runGuiMode(const xmlAccess::XmlGuiConfig& guiCfg, xmlAccess::XmlGlobalSettings& settings); - void runGuiMode(const std::vector<wxString>& cfgFileName, xmlAccess::XmlGlobalSettings& settings); - void runBatchMode(const Zstring& filename, xmlAccess::XmlGlobalSettings& globSettings); - - xmlAccess::XmlGlobalSettings globalSettings; //settings used by GUI, batch mode or both zen::FfsReturnCode returnCode; }; diff --git a/BUILD/Changelog.txt b/BUILD/Changelog.txt index 122f17fa..b64c9933 100644 --- a/BUILD/Changelog.txt +++ b/BUILD/Changelog.txt @@ -2,6 +2,23 @@ |FreeFileSync| -------------- +Changelog v5.6 +-------------- +Resize left and right grids equally +Allow to move middle grid position via mouse +Automatically resize file name columns +Do not follow reparse points other than symlinks and mount points +Warn if Recycle Bin is not available during manual deletion +Fixed error when saving logfile into volume root directory +Show files which differ in attributes only in the same category as "equal" files +Apply hidden attribute to lock file +Fixed potential "access denied" problem when updating the database file +Show errors when saving configuration files during exit (ignore for batch mode) +Mark begin of comparison phase in the log file +More detailed tooltip describing items that differ in attributes only +Added Scottish Gaelic translation + + Changelog v5.5 -------------- New database format for <automatic> variant: old database files are converted automatically diff --git a/BUILD/FreeFileSync.chm b/BUILD/FreeFileSync.chm Binary files differindex 94ea4923..97363d82 100644 --- a/BUILD/FreeFileSync.chm +++ b/BUILD/FreeFileSync.chm diff --git a/BUILD/Help/html/Comparison Settings.html b/BUILD/Help/html/Comparison Settings.html index 55b49830..7cf2207d 100644 --- a/BUILD/Help/html/Comparison Settings.html +++ b/BUILD/Help/html/Comparison Settings.html @@ -5,7 +5,7 @@ <TITLE></TITLE> <META NAME="GENERATOR" CONTENT="OpenOffice.org 3.2 (Win32)"> <META NAME="CREATED" CONTENT="20091206;16574000"> - <META NAME="CHANGED" CONTENT="20120511;23113800"> + <META NAME="CHANGED" CONTENT="20120702;13434800"> <META NAME="Info 1" CONTENT=""> <META NAME="Info 2" CONTENT=""> <META NAME="Info 3" CONTENT=""> @@ -149,10 +149,10 @@ called symlinks or soft links):</FONT></P> <P STYLE="margin-left: 1.46cm; margin-bottom: 0cm"><SPAN ID="Rahmen3" DIR="LTR" STYLE="float: left; width: 80%; height: 0.14cm; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> <UL> <P ALIGN=LEFT STYLE="margin-right: 0.98cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note</B></FONT></P> - <LI><P ALIGN=LEFT STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">In - Windows the symbolic link options apply to <I>"Reparse - Points"</I>. Reparse Points are a more general concept - including symbolic links, junctions and mount points.</FONT></P> + <LI><P ALIGN=LEFT STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Under + Windows the symbolic link options apply to all symbolic links, + <I>"volume mount points" </I>and <I>"NTFS junction + points"</I>.</FONT></P> <LI><P ALIGN=LEFT STYLE="margin-right: 0.98cm; margin-bottom: 0cm; font-style: normal"> <FONT FACE="Tahoma, sans-serif">Copying symbolic links requires administrator rights.</FONT></P> diff --git a/BUILD/Languages/dutch.lng b/BUILD/Languages/dutch.lng index 7c79112d..cc79add7 100644 --- a/BUILD/Languages/dutch.lng +++ b/BUILD/Languages/dutch.lng @@ -1,6 +1,6 @@ <header> <language name>Nederlands</language name> - <translator>Edwin Dierssen</translator> + <translator>Edwin Dierssen && Jochem Sparla</translator> <locale>nl_NL</locale> <flag file>holland.png</flag file> <plural forms>2</plural forms> @@ -10,8 +10,20 @@ <source>Searching for folder %x...</source> <target>Bezig met zoeken naar map %x...</target> +<source>Batch execution</source> +<target>Taak uitvoeren</target> + +<source>Items processed:</source> +<target>Onderdelen verwerkt:</target> + +<source>Items remaining:</source> +<target>Onderdelen te gaan:</target> + +<source>Total time:</source> +<target>Totale tijd:</target> + <source>Show in Explorer</source> -<target>Toon in de verkenner</target> +<target>Toon in Verkenner</target> <source>Open with default application</source> <target>Open met standaardapplicatie</target> @@ -29,10 +41,10 @@ <target>Fout</target> <source>Select alternate comparison settings</source> -<target>Selecteer alternatieve vergelijkings instellingen</target> +<target>Selecteer alternatieve vergelijkingsinstellingen</target> <source>Select alternate synchronization settings</source> -<target>Selecteer alternatieve synchronisatie instellingen</target> +<target>Selecteer alternatieve synchronisatieinstellingen</target> <source>Filter is active</source> <target>Filter is actief</target> @@ -44,16 +56,16 @@ <target>Verwijder alternatieve instellingen</target> <source>Clear filter settings</source> -<target>Verwijder filter instellingen</target> +<target>Verwijder filterinstellingen</target> <source>Create a batch job</source> <target>CreĆ«er een taaklijst</target> <source>Synchronization settings</source> -<target>Synchronisatie instellingen</target> +<target>Synchronisatieinstellingen</target> <source>Comparison settings</source> -<target>Vergelijk instellingen</target> +<target>Vergelijksinstellingen</target> <source>About</source> <target>Informatie</target> @@ -62,7 +74,7 @@ <target>Bevestig</target> <source>Configure filter</source> -<target>Filter configuratie</target> +<target>Filterconfiguratie</target> <source>Global settings</source> <target>Algemene instellingen</target> @@ -107,13 +119,13 @@ <target>Waarschuwing</target> <source>Fatal Error</source> -<target>Fatale fout</target> +<target>Kritieke fout</target> <source>Windows Error Code %x:</source> -<target>Windows Fout Code %x:</target> +<target>Windows Foutcode %x:</target> <source>Linux Error Code %x:</source> -<target>Linux Fout Code %x:</target> +<target>Linux Foutcode %x:</target> <source>Cannot resolve symbolic link %x.</source> <target>Kan snelkoppeling %x niet vinden.</target> @@ -137,16 +149,16 @@ </target> <source>Database file %x is incompatible.</source> -<target>Database bestand %x is niet compatibel.</target> +<target>Databasebestand %x is niet compatibel.</target> <source>Initial synchronization:</source> -<target>InitiĆ«le synchronisatie:</target> +<target>InitiĆ«le synchronisatie:</target> <source>Database file %x does not yet exist.</source> -<target>Database bestand %x bestaat nog niet.</target> +<target>Databasebestand %x bestaat nog niet.</target> <source>Database file is corrupt:</source> -<target></target> +<target>Databasebestand is corrupt:</target> <source>Out of memory!</source> <target>Onvoldoende geheugen!</target> @@ -158,7 +170,7 @@ <target>Kan bestand %x niet vinden.</target> <source>Database files do not share a common session.</source> -<target>Database bestanden delen geen gezamelijke sessie.</target> +<target>Databasebestanden delen geen gezamelijke sessie.</target> <source>An exception occurred!</source> <target>Er heeft een uitzondering plaatsgevonden!</target> @@ -170,7 +182,7 @@ <target>Kan geen procesinformatie verkrijgen.</target> <source>Waiting while directory is locked (%x)...</source> -<target>Wacht totdat map is vergrendeld (%x)...</target> +<target>Wachten totdat map is vergrendeld (%x)...</target> <source>Cannot set directory lock %x.</source> <target>Kan directory %x niet sluiten.</target> @@ -191,7 +203,7 @@ <target>Doorzoekt:</target> <source>Encoding extended time information: %x</source> -<target>Coderen uitgebreide tijd informatie: %x</target> +<target>Coderen uitgebreide tijdinformatie: %x</target> <source> <pluralform>[1 Thread]</pluralform> @@ -212,7 +224,7 @@ <target>Bestand %x bevat geen valide configuratie.</target> <source>Configuration file %x loaded partially only.</source> -<target>Configuratie bestand %x alleen deels geladen.</target> +<target>Configuratiebestand %x alleen deels geladen.</target> <source>Cannot access Volume Shadow Copy Service.</source> <target>Kan de Volume Schaduwkopie Service niet benaderen.</target> @@ -236,7 +248,7 @@ <target>&Open...</target> <source>Save &As...</source> -<target></target> +<target>Ops&laan als...</target> <source>&Quit</source> <target>&Afsluiten</target> @@ -248,7 +260,7 @@ <target>&Inhoud</target> <source>&About</source> -<target>&Over</target> +<target>O&ver</target> <source>&Help</source> <target>&Help</target> @@ -295,7 +307,7 @@ The command is triggered if: - new folders arrive (e.g. USB stick insert) </source> <target> -De opdracht word geactiveerd als>: +De opdracht word geactiveerd als: - bestanden of subfolders veranderen - nieuwe folders worden gevonden (bijvoorbeeld bij invoeging van USB stick) </target> @@ -313,7 +325,7 @@ De opdracht word geactiveerd als>: <target>(Build: %x)</target> <source>All files</source> -<target></target> +<target>Alle bestanden</target> <source>&Restore</source> <target>&Herstellen</target> @@ -322,19 +334,19 @@ De opdracht word geactiveerd als>: <target>&Afsluiten</target> <source>Monitoring active...</source> -<target>Observeren actief...</target> +<target>Controle actief...</target> <source>Waiting for missing directories...</source> -<target>Wacht op missende mappen...</target> +<target>Wacht op ontbrekende mappen...</target> <source>A folder input field is empty.</source> -<target></target> +<target>Een map invoerveld is leeg.</target> <source>Logging</source> <target>Loggen</target> <source>File time and size</source> -<target>Bestands tijd-en grootte</target> +<target>Bestandstijd- en grootte</target> <source>File content</source> <target>Bestandsinhoud</target> @@ -352,22 +364,7 @@ De opdracht word geactiveerd als>: <target>Aangepast</target> <source>FreeFileSync batch</source> -<target></target> - -<source>Batch execution</source> -<target>Taak uitvoeren</target> - -<source>Items processed:</source> -<target>Onderdelen verwerkt:</target> - -<source>Items remaining:</source> -<target>Onderdelen te gaan:</target> - -<source>Total time:</source> -<target>Totale tijd:</target> - -<source>Stop</source> -<target>Stop</target> +<target>FreeFileSync taak</target> <source>Synchronization aborted!</source> <target>Synchronisatie afgebroken!</target> @@ -376,7 +373,7 @@ De opdracht word geactiveerd als>: <target>Synchronisatie is met fouten afgerond!</target> <source>Nothing to synchronize!</source> -<target>Niks om te synchroniseren!</target> +<target>Niets om te synchroniseren!</target> <source>Synchronization completed successfully!</source> <target>Synchronisatie succesvol afgerond!</target> @@ -388,10 +385,10 @@ De opdracht word geactiveerd als>: <target>Bezig met omschakelen naar het FreeFileSync hoofdscherm.</target> <source>Unable to connect to sourceforge.net!</source> -<target>Niet in staat verbinding te maken met sourceforge.net!</target> +<target>Kan geen verbinding maken met sourceforge.net!</target> <source>A new version of FreeFileSync is available:</source> -<target></target> +<target>Er is een nieuwe versie van FreeFileSync beschikbaar:</target> <source>Download now?</source> <target>Nu downloaden?</target> @@ -412,7 +409,7 @@ De opdracht word geactiveerd als>: <target><Symlink></target> <source><Folder></source> -<target>Map</target> +<target><Map></target> <source>Full path</source> <target>Volledig pad</target> @@ -436,10 +433,10 @@ De opdracht word geactiveerd als>: <target>Extensie</target> <source>Size:</source> -<target></target> +<target>Grootte:</target> <source>Date:</source> -<target></target> +<target>Datum:</target> <source>Action</source> <target>Actie</target> @@ -475,7 +472,7 @@ De opdracht word geactiveerd als>: <target>&Nieuw</target> <source>&Save</source> -<target></target> +<target>O&pslaan</target> <source>&Language</source> <target>&Taal</target> @@ -544,7 +541,7 @@ De opdracht word geactiveerd als>: <target>Aantal bestanden en mappen die verwijderd zullen worden</target> <source>Total bytes to copy</source> -<target></target> +<target>Aantal bytes om te kopiĆ«ren</target> <source>Items found:</source> <target>Onderdelen gevonden:</target> @@ -553,16 +550,16 @@ De opdracht word geactiveerd als>: <target>Snelheid:</target> <source>Time remaining:</source> -<target></target> +<target>Resterende tijd:</target> <source>Time elapsed:</source> -<target></target> +<target>Verstreken tijd:</target> <source>Batch job</source> <target>Taaklijst</target> <source>Create a batch file to automate synchronization. Double-click this file or schedule in your system's task planner: FreeFileSync.exe <job name>.ffs_batch</source> -<target>Maak een batch bestand om synchronisatie te automatiseren. Dubbelklik op dit bestand of plan dit in de systeem taakplanner: FreeFileSync.exe <batch naam>.ffs_batch</target> +<target>Maak een taakbestand om synchronisatie te automatiseren. Dubbelklik op dit bestand of plan dit in de systeem taakplanner: FreeFileSync.exe <taaknaam>.ffs_batch</target> <source>Help</source> <target>Help</target> @@ -589,10 +586,10 @@ De opdracht word geactiveerd als>: <target>Maximale aantal van logbestanden:</target> <source>Select folder to save log files:</source> -<target>Selecteer de map voor het bewaren van log bestanden:</target> +<target>Selecteer de map voor het bewaren van logbestanden:</target> <source>Batch settings</source> -<target>Batch instellingen</target> +<target>Batchinstellingen</target> <source>Select variant:</source> <target>Selecteer een variant:</target> @@ -601,7 +598,7 @@ De opdracht word geactiveerd als>: <target>Identificeer en verspreid veranderingen aan beide kanten met behulp van een database. Verwijderingen, hernoemingen en conflicten worden automatisch gedetecteerd.</target> <source>Mirror backup of left folder. Right folder is modified to exactly match left folder after synchronization.</source> -<target>Spiegel backup van linker map. Rechter map is bewerkt om na synchronisatie een exacte kopie te zijn van de linker map.</target> +<target>Spiegel backup van linker map. Rechter map is na synchronisatie een exacte kopie van de linker map.</target> <source>Copy new or updated files to right folder.</source> <target>KopiĆ«er nieuwe of geupdate bestanden naar de rechter map.</target> @@ -613,7 +610,7 @@ De opdracht word geactiveerd als>: <target>Verwijder-afhandeling</target> <source>On completion:</source> -<target>Bij voltooiing</target> +<target>Bij voltooiing:</target> <source>Configuration</source> <target>Configuratie</target> @@ -649,7 +646,7 @@ Files are found equal if are the same </source> <target> -Bestanden worden als gelijk bevonden indien, +Bestanden worden als gelijk bevonden indien - de laatste schrijf tijd en datum - de bestandsgrootte gelijk zijn @@ -661,7 +658,7 @@ Files are found equal if is the same </source> <target> -Bestanden worden als gelijk beschouwd indien, +Bestanden worden als gelijk beschouwd indien - de bestandsinhoud overeenkomt </target> @@ -745,7 +742,7 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Fail-safe bestandskopie</target> <source>Write to a temporary file (*.ffs_tmp) first then rename it. This guarantees a consistent state even in case of fatal error.</source> -<target>Schrijf eerst naar een tijdelijk bestand (*.ffs_tmp) en hernoem dan. Dit garandeert een consistente toestand, zelfs in het geval van een fatale fout.</target> +<target>Schrijf eerst naar een tijdelijk bestand (*.ffs_tmp) en hernoem daarna. Dit garandeert een consistente toestand, zelfs in het geval van een fatale fout.</target> <source>Copy locked files</source> <target>KopiĆ«er vergrendelde bestanden</target> @@ -757,7 +754,7 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>KopiĆ«er toegangsrechten van bestand.</target> <source>Transfer file and folder permissions (Requires Administrator rights)</source> -<target>Overdragen van bestands en maps permissies (Administrator rechten nodig)</target> +<target>Overdragen van bestands- en mappermissies (Administrator rechten nodig)</target> <source>Restore hidden dialogs</source> <target>Herstel verborgen dialogen</target> @@ -805,7 +802,7 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Stel richting in:</target> <source>Exclude temporarily</source> -<target>Sluit tijdelijk uit</target> +<target>Tijdelijk uitsluiten</target> <source>Include temporarily</source> <target>Tijdelijk opnemen</target> @@ -820,7 +817,7 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Verwijderen</target> <source>Include all</source> -<target>Alles insluiten</target> +<target>Alles opnemen</target> <source>Exclude all</source> <target>Alles uitsluiten</target> @@ -853,16 +850,16 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Configuratie opgeslagen!</target> <source>Never save changes</source> -<target>Veranderingen nooit opslaan</target> +<target>Wijzigingen nooit opslaan</target> <source>Do you want to save changes to %x?</source> -<target></target> +<target>Wilt u de wijzigingen in %x opslaan?</target> <source>Save</source> -<target></target> +<target>Opslaan</target> <source>Don't Save</source> -<target></target> +<target>Niet opslaan</target> <source>Configuration loaded!</source> <target>Configuratie geladen!</target> @@ -901,7 +898,7 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Toon bestanden die gelijk zijn</target> <source>Hide files that are different</source> -<target>Verberg bestanden die verschillen</target> +<target>Verberg bestanden die verschillend zijn</target> <source>Show files that are different</source> <target>Toon bestanden die verschillend zijn</target> @@ -913,43 +910,43 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Toon conflicten</target> <source>Hide files that will be created on the left side</source> -<target>Verberg bestanden die aan de linkerzijde zullen worden aangemaakt</target> +<target>Verberg bestanden die aan de linkerzijde aangemaakt zullen worden</target> <source>Show files that will be created on the left side</source> <target>Toon bestanden die aan de linkerzijde aangemaakt zullen worden</target> <source>Hide files that will be created on the right side</source> -<target>Verberg bestanden die aan de rechterzijde zullen worden aangemaakt</target> +<target>Verberg bestanden die aan de rechterzijde aangemaakt zullen worden</target> <source>Show files that will be created on the right side</source> <target>Toon bestanden die aan de rechterzijde aangemaakt zullen worden</target> <source>Hide files that will be deleted on the left side</source> -<target>Verberg bestanden die aan de linkerzijde zullen worden verwijderd</target> +<target>Verberg bestanden die aan de linkerzijde aangemaakt zullen worden</target> <source>Show files that will be deleted on the left side</source> <target>Toon bestanden die van de linkerzijde verwijderd zullen worden</target> <source>Hide files that will be deleted on the right side</source> -<target>Verberg bestanden die aan de rechterzijde zullen worden verwijderd</target> +<target>Verberg bestanden die aan de rechterzijde verwijderd zullen worden</target> <source>Show files that will be deleted on the right side</source> <target>Toon bestanden die van de rechterzijde verwijderd zullen worden</target> <source>Hide files that will be overwritten on left side</source> -<target>Verberg bestanden die aan de linkerzijde zullen worden overschreven</target> +<target>Verberg bestanden die aan de linkerzijde overschreven zullen worden</target> <source>Show files that will be overwritten on left side</source> <target>Toon bestanden die aan de linkerzijde overschreven zullen worden</target> <source>Hide files that will be overwritten on right side</source> -<target>Verberg bestanden die aan de rechterzijde zullen worden overschreven</target> +<target>Verberg bestanden die aan de rechterzijde overschreven zullen worden</target> <source>Show files that will be overwritten on right side</source> <target>Toon bestanden die aan de rechterzijde overschreven zullen worden</target> <source>Hide files that won't be copied</source> -<target>Verberg bestanden die niet zullen worden gekopiĆ«erd</target> +<target>Verberg bestanden die niet gekopiĆ«erd zullen worden</target> <source>Show files that won't be copied</source> <target>Toon bestanden die niet gekopiĆ«erd zullen worden</target> @@ -958,7 +955,7 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Alle mappen zijn gesynchroniseerd!</target> <source>Comma separated list</source> -<target>Komma gescheiden lijst</target> +<target>Kommagescheiden bestand</target> <source>Legend</source> <target>Legenda</target> @@ -1090,7 +1087,7 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Integreer externe applicaties in het context menu. De volgende macros zijn beschikbaar:</target> <source>- full file or folder name</source> -<target>- volledige bestands of maps naam</target> +<target>- volledige bestands- of mapnaam</target> <source>- folder part only</source> <target>- alleen het map gedeelte</target> @@ -1102,7 +1099,7 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>- Tegenhanger andere zijde naar %dir</target> <source>Make hidden dialogs and warning messages visible again?</source> -<target>Maak verborgen dialogen en waarschuwings berichten opnieuw zichtbaar?</target> +<target>Verborgen dialogen en waarschuwings berichten opnieuw zichtbaar maken?</target> <source> <pluralform>Do you really want to move the following object to the Recycle Bin?</pluralform> @@ -1126,10 +1123,10 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Beschouw als onopgelost conflict</target> <source>Delete permanently</source> -<target>Verwijder onomkeerbaar</target> +<target>Definitief verwijderen</target> <source>Delete or overwrite files permanently</source> -<target>Verwijder of overschrijf bestanden onomkeerbaar</target> +<target>Bestanden definitief verwijderen of overschrijven</target> <source>Use Recycle Bin when deleting or overwriting files</source> <target>Gebruik de prullenbak bij verwijderen of overschrijven van bestanden</target> @@ -1186,13 +1183,13 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Kan locatie %x niet controleren.</target> <source>Conversion error:</source> -<target>Converteer fout:</target> +<target>Converteerfout:</target> <source>Cannot delete file %x.</source> <target>Kan bestand %x niet verwijderen.</target> <source>The file is locked by another process:</source> -<target>Het bestand is op afgesloten door een ander proces:</target> +<target>Het bestand is in gebruik door een ander proces:</target> <source>Cannot move file %x to %y.</source> <target>Kan bestand %x niet verplaatsen naar %y.</target> @@ -1204,7 +1201,7 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Kan bestandskenmerken van %x niet schrijven.</target> <source>Cannot write modification time of %x.</source> -<target>Kan wijzigings tijd van %x niet toevoegen.</target> +<target>Kan wijzigingstijd van %x niet toevoegen.</target> <source>Cannot find system function %x.</source> <target>Kan systeemfunctie %x niet vinden.</target> @@ -1252,11 +1249,14 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Geen veranderingen sinds de laatste synchronisatie!</target> <source>The corresponding database entries are not in sync considering current settings.</source> -<target></target> +<target>De volgende databaseregels zijn niet gesynchroniseerd volgens de huidige instellingen.</target> <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> <target>Stel standaard synchronisatie richtingen in: Oude bestanden worden door nieuwere bestanden overschreven.</target> +<source>Recycle Bin is not available for the following paths! Files will be deleted permanently instead:</source> +<target>Prullenbak is niet beschikbaar voor de volgende locaties! De bestanden worden permanent verwijderd:</target> + <source>You can ignore this error to consider the folder as empty.</source> <target>U kunt deze waarschuwing negeren om de map als leeg te laten gelden.</target> @@ -1266,6 +1266,9 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <source>Directories are dependent! Be careful when setting up synchronization rules:</source> <target>Mappen zijn afhankelijk van elkaar! Wees voorzichtig met het maken van synchronisatieregels:</target> +<source>Start comparison</source> +<target></target> + <source>Preparing synchronization...</source> <target>Synchronisatie voorbereiden</target> @@ -1278,6 +1281,9 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <source>Files %x have the same date but a different size!</source> <target>Bestanden %x hebben dezelfde datums maar een afwijkende grootte!</target> +<source>Items have different attributes</source> +<target>Items hebben verschillende attributen</target> + <source>Symbolic links %x have the same date but a different target.</source> <target>Snelkoppelingen %x hebben dezelfde datum maar een verschillend doel.</target> @@ -1293,9 +1299,6 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <source>Both sides are equal</source> <target>Beide kanten zijn gelijk</target> -<source>Items have different attributes</source> -<target>Items hebben verschillende attributen</target> - <source>Copy new item to left</source> <target>KopieĆ«r nieuw item naar de linkerkant</target> @@ -1342,13 +1345,13 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Verwijderen van snelkoppeling %x</target> <source>Moving file %x to recycle bin</source> -<target>Bezig met verplaatsen van bestand %x naar prullenbak</target> +<target>Bezig met verplaatsen van bestand %x naar de prullenbak</target> <source>Moving folder %x to recycle bin</source> -<target>Bezig met verplaatsen van map %x naar prullenbak</target> +<target>Bezig met verplaatsen van map %x naar de prullenbak</target> <source>Moving symbolic link %x to recycle bin</source> -<target>Bezig met verplaatsen van snelkoppeling %x naar prullenbak</target> +<target>Bezig met verplaatsen van snelkoppeling %x naar de prullenbak</target> <source>Moving file %x to %y</source> <target>Bezig met verplaatsen van bestand %x naar %y</target> @@ -1381,13 +1384,13 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Attributen bijwerken van %x</target> <source>Target folder input field must not be empty.</source> -<target></target> +<target>Doelmap mag niet leeg zijn.</target> <source>Folder input field for versioning must not be empty.</source> -<target></target> +<target>Bronmap voor versiebeheer mag niet leeg zijn.</target> <source>Source folder %x not found.</source> -<target></target> +<target>Bronmap %x niet gevonden.</target> <source>Unresolved conflicts existing!</source> <target>Er bestaan onopgeloste conflicten!</target> @@ -1405,22 +1408,19 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen! <target>Niet genoeg vrije schijfruimte beschikbaar op:</target> <source>Required:</source> -<target></target> +<target>Vereist:</target> <source>Available:</source> -<target></target> - -<source>Recycle Bin is not available for the following paths! Files will be deleted permanently instead:</source> -<target>Prullenbak is niet beschikbaar voor de volgende locaties! De bestanden worden permanent verwijderd:</target> +<target>Beschikbaar:</target> <source>A folder will be modified which is part of multiple folder pairs. Please review synchronization settings.</source> <target>Een map die onderdeel is van meerdere map paren word aangepast. Kijk alstublieft uw synchronisatie instellingen na.</target> -<source>Processing folder pair:</source> -<target>Verwerking van gekoppelde mappen:</target> +<source>Synchronize folder pair:</source> +<target></target> <source>Target folder %x already existing.</source> -<target></target> +<target>Doelmap %x bestaat al.</target> <source>Generating database...</source> <target>Genereren van database...</target> diff --git a/BUILD/Languages/french.lng b/BUILD/Languages/french.lng index 6db32565..db263be5 100644 --- a/BUILD/Languages/french.lng +++ b/BUILD/Languages/french.lng @@ -198,7 +198,7 @@ <pluralform>[%x Threads]</pluralform> </source> <target> -<pluralform>[1 TĆ¢che]</pluralform> +<pluralform>[%x TĆ¢che]</pluralform> <pluralform>[%x TĆ¢ches]</pluralform> </target> diff --git a/BUILD/Languages/german.lng b/BUILD/Languages/german.lng index 9e8c9a8d..ddcb4832 100644 --- a/BUILD/Languages/german.lng +++ b/BUILD/Languages/german.lng @@ -10,6 +10,18 @@ <source>Searching for folder %x...</source> <target>Suche Ordner %x...</target> +<source>Batch execution</source> +<target>Batchlauf</target> + +<source>Items processed:</source> +<target>Verarbeitete Elemente:</target> + +<source>Items remaining:</source> +<target>Verbleibende Elemente:</target> + +<source>Total time:</source> +<target>Gesamtzeit:</target> + <source>Show in Explorer</source> <target>Im Explorer anzeigen</target> @@ -354,21 +366,6 @@ Die Befehlszeile wird ausgelƶst wenn: <source>FreeFileSync batch</source> <target>FreeFileSync Batch</target> -<source>Batch execution</source> -<target>Batchlauf</target> - -<source>Items processed:</source> -<target>Verarbeitete Elemente:</target> - -<source>Items remaining:</source> -<target>Verbleibende Elemente:</target> - -<source>Total time:</source> -<target>Gesamtzeit:</target> - -<source>Stop</source> -<target>Stop</target> - <source>Synchronization aborted!</source> <target>Synchronisation abgebrochen!</target> @@ -433,7 +430,7 @@ Die Befehlszeile wird ausgelƶst wenn: <target>Datum</target> <source>Extension</source> -<target>Dateiendung</target> +<target>Erweiterung</target> <source>Size:</source> <target>GrƶĆe:</target> @@ -1257,6 +1254,9 @@ Achtung: Dateinamen mĆ¼ssen relativ zu den Basisverzeichnissen sein! <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> <target>Setze Standardwerte fĆ¼r Synchronisationsrichtungen: Alte Dateien werden durch neuere Ć¼berschrieben.</target> +<source>Recycle Bin is not available for the following paths! Files will be deleted permanently instead:</source> +<target>Der Papierkorb ist fĆ¼r die folgenden Pfade nicht verfĆ¼gbar! Die Dateien werden stattdessen permanent gelƶscht:</target> + <source>You can ignore this error to consider the folder as empty.</source> <target>Dieser Fehler kann ignoriert werden, um den Ordner als leer anzusehen.</target> @@ -1266,6 +1266,9 @@ Achtung: Dateinamen mĆ¼ssen relativ zu den Basisverzeichnissen sein! <source>Directories are dependent! Be careful when setting up synchronization rules:</source> <target>Die Verzeichnisse sind voneinander abhƤngig! Achtung beim Festlegen der Synchronisationsregeln:</target> +<source>Start comparison</source> +<target>Starte Vergleich</target> + <source>Preparing synchronization...</source> <target>Bereite Synchronisation vor...</target> @@ -1278,6 +1281,9 @@ Achtung: Dateinamen mĆ¼ssen relativ zu den Basisverzeichnissen sein! <source>Files %x have the same date but a different size!</source> <target>Die Dateien %x haben dasselbe Datum, aber unterschiedliche GrƶĆen!</target> +<source>Items have different attributes</source> +<target>Die Elemente haben unterschiedliche Attribute</target> + <source>Symbolic links %x have the same date but a different target.</source> <target>Die Symbolischen Links %x haben dasselbe Datum, aber ein unterschiedliches Ziel.</target> @@ -1293,9 +1299,6 @@ Achtung: Dateinamen mĆ¼ssen relativ zu den Basisverzeichnissen sein! <source>Both sides are equal</source> <target>Beide Seiten sind gleich</target> -<source>Items have different attributes</source> -<target>Die Elemente haben unterschiedliche Attribute</target> - <source>Copy new item to left</source> <target>Kopiere neues Element nach links</target> @@ -1410,14 +1413,11 @@ Achtung: Dateinamen mĆ¼ssen relativ zu den Basisverzeichnissen sein! <source>Available:</source> <target>VerfĆ¼gbar:</target> -<source>Recycle Bin is not available for the following paths! Files will be deleted permanently instead:</source> -<target>Der Papierkorb ist auf nachfolgenden Verzeichnissen nicht verfĆ¼gbar! Die Dateien werden stattdessen permanent gelƶscht:</target> - <source>A folder will be modified which is part of multiple folder pairs. Please review synchronization settings.</source> <target>Ein Ordner wird verƤndert werden, der Teil mehrerer Ordnerpaare ist. Bitte Ć¼berprĆ¼fen Sie die Synchronisationseinstellungen.</target> -<source>Processing folder pair:</source> -<target>Bearbeite Ordnerpaar:</target> +<source>Synchronize folder pair:</source> +<target>Synchronisiere Ordnerpaar:</target> <source>Target folder %x already existing.</source> <target>Der Zielordner %x existiert bereits.</target> diff --git a/BUILD/Languages/norwegian.lng b/BUILD/Languages/norwegian.lng index 3cf0f3d6..6ac894b0 100644 --- a/BUILD/Languages/norwegian.lng +++ b/BUILD/Languages/norwegian.lng @@ -1,6 +1,6 @@ <header> <language name>Norsk</language name> - <translator>FreewareTips</translator> + <translator>BjĆørn Snoen</translator> <locale>nb_NO</locale> <flag file>norway.png</flag file> <plural forms>2</plural forms> @@ -8,7 +8,19 @@ </header> <source>Searching for folder %x...</source> -<target></target> +<target>Leter etter mappen %x</target> + +<source>Batch execution</source> +<target>Batch-kjĆøring</target> + +<source>Items processed:</source> +<target>Elementer behandlet:</target> + +<source>Items remaining:</source> +<target>Elementer igjen:</target> + +<source>Total time:</source> +<target>Total tid:</target> <source>Show in Explorer</source> <target>Vis i Utforsker</target> @@ -17,7 +29,7 @@ <target>Ć
pne med standardprogram</target> <source>Browse directory</source> -<target>Bla gjennom mappe</target> +<target>Utforsk mappe</target> <source>Abort requested: Waiting for current operation to finish...</source> <target>Avbrytelse forespurt: Venter pĆ„ at gjeldende handling avsluttes...</target> @@ -29,10 +41,10 @@ <target>Feil</target> <source>Select alternate comparison settings</source> -<target>Velg alternative sammenligningsinnstillinger</target> +<target>Velg alternative innstillinger for sammenligning</target> <source>Select alternate synchronization settings</source> -<target>Velg alternative synkroniseringsinnstillinger</target> +<target>Velg alternative innstillinger for synkronisering</target> <source>Filter is active</source> <target>Filter er aktivt</target> @@ -44,16 +56,16 @@ <target>Fjern alternative innstillinger</target> <source>Clear filter settings</source> -<target>Fjern filterinnstillinger</target> +<target>Nullstill filterinnstillinger</target> <source>Create a batch job</source> <target>Opprett en batch-jobb</target> <source>Synchronization settings</source> -<target>Synkroniseringsinnstillinger</target> +<target>Innstillinger for synkronisering</target> <source>Comparison settings</source> -<target>Sammenligningsinnstillinger</target> +<target>Innstillinger for sammenligning</target> <source>About</source> <target>Om</target> @@ -68,7 +80,7 @@ <target>Felles innstillinger</target> <source>Summary</source> -<target></target> +<target>Oppsummering</target> <source>Find</source> <target>SĆøk</target> @@ -77,16 +89,16 @@ <target>Velg tidsomrĆ„de</target> <source>Show pop-up</source> -<target>Vis oppsprettsvindu</target> +<target>Vis pop-up</target> <source>Show pop-up on errors or warnings</source> -<target>Vis oppsprettsvindu ved feil eller advarsler</target> +<target>Vis pop-upvindu ved feil eller advarsler</target> <source>Ignore errors</source> <target>Ignorer feil</target> <source>Hide all error and warning messages</source> -<target>Skjul meldinger om feil og advarsler</target> +<target>Skjul feilmeldinger og advarsler</target> <source>Exit instantly</source> <target>Avslutt med en gang</target> @@ -116,7 +128,7 @@ <target>Linux feilkode %x:</target> <source>Cannot resolve symbolic link %x.</source> -<target>Kan ikke bestemme symbolsk lenke %x.</target> +<target>Kan ikke fĆølge symbolsk lenke %x.</target> <source>%x MB</source> <target>%x MB</target> @@ -133,7 +145,7 @@ </source> <target> <pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> +<pluralform>%x Byte</pluralform> </target> <source>Database file %x is incompatible.</source> @@ -146,7 +158,7 @@ <target>Databasefil %x finnes ikke ennĆ„.</target> <source>Database file is corrupt:</source> -<target></target> +<target>Databasefilen er korrupt</target> <source>Out of memory!</source> <target>For lite minne!</target> @@ -164,7 +176,7 @@ <target>En unntagelse skjedde!</target> <source>Cannot read file attributes of %x.</source> -<target>Kan ikke lese filattributter til %x.</target> +<target>Kan ikke lese filattributter fra %x.</target> <source>Cannot get process information.</source> <target>Kan ikke hente prosessinformasjon.</target> @@ -180,8 +192,8 @@ <pluralform>%x sec</pluralform> </source> <target> -<pluralform>1 sek</pluralform> -<pluralform>%x sek</pluralform> +<pluralform>1 sekund</pluralform> +<pluralform>%x sekunder</pluralform> </target> <source>Error parsing file %x, row %y, column %z.</source> @@ -203,7 +215,7 @@ </target> <source>/sec</source> -<target>/sek</target> +<target>/sekund</target> <source>Cannot find file %x.</source> <target>Kan ikke finne filen %x.</target> @@ -233,10 +245,10 @@ <target>Kan ikke lese fĆølgende XML-elementer:</target> <source>&Open...</source> -<target></target> +<target>&Ć
pne</target> <source>Save &As...</source> -<target></target> +<target>Lagre &som...</target> <source>&Quit</source> <target>&Avslutt</target> @@ -257,7 +269,7 @@ <target>Bruk:</target> <source>1. Select folders to watch.</source> -<target></target> +<target>1. Velg mapper Ć„ overvĆ„ke.</target> <source>2. Enter a command line.</source> <target>2. OppfĆør en kommandolinje.</target> @@ -266,10 +278,10 @@ <target>3. Trykk 'Start'.</target> <source>To get started just import a .ffs_batch file.</source> -<target></target> +<target>Importer en .ffs_barch-fil for Ć„ komme i gang</target> <source>Folders to watch</source> -<target></target> +<target>Mapper Ć„ overvĆ„ke</target> <source>Add folder</source> <target>Legg til mappe</target> @@ -281,10 +293,10 @@ <target>Velg en mappe</target> <source>Delay [seconds]</source> -<target></target> +<target>Vent [sekunder]</target> <source>Idle time between last detected change and execution of command</source> -<target></target> +<target>Ventetid mellom forrige endring og utfĆørelse av kommando</target> <source>Command line</source> <target>Kommandolinje</target> @@ -294,7 +306,11 @@ The command is triggered if: - files or subfolders change - new folders arrive (e.g. USB stick insert) </source> -<target></target> +<target> +Kommandoen utlĆøses hvis: +- filer eller mapper endres +- nye mapper legges til (f.eks. en USB-pinne kobles til) +</target> <source>Start</source> <target>Start</target> @@ -309,7 +325,7 @@ The command is triggered if: <target>(Build: %x)</target> <source>All files</source> -<target></target> +<target>Alle filer</target> <source>&Restore</source> <target>&Gjenopprett</target> @@ -324,7 +340,7 @@ The command is triggered if: <target>Venter pĆ„ manglende mapper...</target> <source>A folder input field is empty.</source> -<target></target> +<target>Et mappefelt er tomt.</target> <source>Logging</source> <target>Logging</target> @@ -348,22 +364,7 @@ The command is triggered if: <target>Brukerdefinert</target> <source>FreeFileSync batch</source> -<target></target> - -<source>Batch execution</source> -<target>Batch-kjĆøring</target> - -<source>Items processed:</source> -<target>Elementer behandlet:</target> - -<source>Items remaining:</source> -<target>Elementer igjen:</target> - -<source>Total time:</source> -<target>Total tid:</target> - -<source>Stop</source> -<target>Stopp</target> +<target>FreeFileSync batch</target> <source>Synchronization aborted!</source> <target>Synkronisering avbrutt!</target> @@ -387,7 +388,7 @@ The command is triggered if: <target>Ikke i stand til Ć„ koble til sourceforge.net!</target> <source>A new version of FreeFileSync is available:</source> -<target></target> +<target>En ny versjon av FreeFileSync er tilgjengelig</target> <source>Download now?</source> <target>Laste ned nĆ„?</target> @@ -408,7 +409,7 @@ The command is triggered if: <target><Symbolsk lenke></target> <source><Folder></source> -<target></target> +<target><Mappe></target> <source>Full path</source> <target>Full bane</target> @@ -420,7 +421,7 @@ The command is triggered if: <target>Relativ bane</target> <source>Base folder</source> -<target></target> +<target>Grunnmappe</target> <source>Size</source> <target>StĆørrelse</target> @@ -432,10 +433,10 @@ The command is triggered if: <target>Filendelse</target> <source>Size:</source> -<target></target> +<target>StĆørrelse:</target> <source>Date:</source> -<target></target> +<target>Dato:</target> <source>Action</source> <target>Handling</target> @@ -471,7 +472,7 @@ The command is triggered if: <target>&Ny</target> <source>&Save</source> -<target></target> +<target>&Lagre</target> <source>&Language</source> <target>&SprĆ„k</target> @@ -531,16 +532,16 @@ The command is triggered if: <target>Skjul filtrerte eller midlertidig ekskluderte filer</target> <source>Number of files and folders that will be created</source> -<target></target> +<target>Antall filer og mapper som vil opprettes</target> <source>Number of files that will be overwritten</source> <target>Antall filer som blir overskrevet</target> <source>Number of files and folders that will be deleted</source> -<target></target> +<target>Antall filer og mapper som vil slettes</target> <source>Total bytes to copy</source> -<target></target> +<target>Mengde bytes Ć„ kopiere</target> <source>Items found:</source> <target>Elementer funnet:</target> @@ -549,16 +550,16 @@ The command is triggered if: <target>Hastighet:</target> <source>Time remaining:</source> -<target></target> +<target>GjenstĆ„ende tid:</target> <source>Time elapsed:</source> -<target></target> +<target>Tid gĆ„tt:</target> <source>Batch job</source> <target>Batch-jobb</target> <source>Create a batch file to automate synchronization. Double-click this file or schedule in your system's task planner: FreeFileSync.exe <job name>.ffs_batch</source> -<target></target> +<target>Lag en batch-fil for Ć„ automatisere synkronisering. Dobbelklikk denne filen eller legg til i ditt systems planlegger: FreFileSync.exe <job name>.ffs_batch</target> <source>Help</source> <target>Hjelp</target> @@ -585,7 +586,7 @@ The command is triggered if: <target>Maksimale antall loggfiler:</target> <source>Select folder to save log files:</source> -<target></target> +<target>Velg en mappe for loggfiler:</target> <source>Batch settings</source> <target>Batch-innstillinger</target> @@ -615,22 +616,22 @@ The command is triggered if: <target>Innstilling</target> <source>Item exists on left side only</source> -<target></target> +<target>Element eksisterer kun pĆ„ venstre side</target> <source>Item exists on right side only</source> -<target></target> +<target>Element eksisterer kun pĆ„ hĆøyre side</target> <source>Left side is newer</source> -<target></target> +<target>Venstre side er nyere</target> <source>Right side is newer</source> -<target></target> +<target>HĆøyre side er nyere</target> <source>Items have different content</source> -<target></target> +<target>Elementer har forskjellig innhold</target> <source>Conflict/item cannot be categorized</source> -<target></target> +<target>Konflikt/element kan ikke kategoriseres</target> <source>OK</source> <target>OK</target> @@ -738,7 +739,7 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <target>&Standard</target> <source>Fail-safe file copy</source> -<target></target> +<target>Trygg filkopi</target> <source>Write to a temporary file (*.ffs_tmp) first then rename it. This guarantees a consistent state even in case of fatal error.</source> <target>Skriv til en midlertidig fil (*.ffs_tmp) fĆørst sĆ„ endre navn pĆ„ den. Dette garanterer en konsistent tilstand selv ved alvorlige feil.</target> @@ -753,7 +754,7 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <target>Kopier filadgangstillatelser</target> <source>Transfer file and folder permissions (Requires Administrator rights)</source> -<target></target> +<target>OverfĆør tilgangspreferanser for filer og mapper (krever administratortilgang)</target> <source>Restore hidden dialogs</source> <target>Gjenopprett skjulte dialoger</target> @@ -852,13 +853,13 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <target>Aldri lagre endringer</target> <source>Do you want to save changes to %x?</source> -<target></target> +<target>Vil du lagre endringene til %x?</target> <source>Save</source> -<target></target> +<target>Lagre</target> <source>Don't Save</source> -<target></target> +<target>Ikke lagre</target> <source>Configuration loaded!</source> <target>Innstilling lastet!</target> @@ -1086,10 +1087,10 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <target>Integrer eksterne programmer i hĆøyreklikkmeny. FĆølgende makroer er tilgjengelige:</target> <source>- full file or folder name</source> -<target></target> +<target>- full fil- eller mappenavn</target> <source>- folder part only</source> -<target></target> +<target>- kun mapper</target> <source>- Other side's counterpart to %name</source> <target>- Andre sides motstykke til %name</target> @@ -1134,7 +1135,7 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <target>VersjonshĆ„ndtering</target> <source>Move files into a time-stamped subfolder</source> -<target></target> +<target>Flytt filer til en undermappe med tidsstempel</target> <source>Files</source> <target>Filer</target> @@ -1230,7 +1231,7 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <target>Kan ikke lese mappen %x.</target> <source>Detected endless directory recursion.</source> -<target></target> +<target>Fant uendelig dyp mappestruktur (lenker til mapper hĆøyere opp i strukturen)</target> <source>Cannot set privilege %x.</source> <target>Kan ikke sette privilegie %x.</target> @@ -1248,16 +1249,19 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <target>Ingen endringer siden siste synkronisering!</target> <source>The corresponding database entries are not in sync considering current settings.</source> -<target></target> +<target>De tilsvarende databaseoppfĆøringene er ikke synkroniserte i forhold til innstillingene.</target> <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> <target>Stiller inn standard synkroniseringsretning: Gamle filer blir overskrevet med nyere filer.</target> +<source>Recycle Bin is not available for the following paths! Files will be deleted permanently instead:</source> +<target>Papirkurv er ikke tilgjengelig for de fĆølgende baner! Filer blir isteden slettet permanent:</target> + <source>You can ignore this error to consider the folder as empty.</source> -<target></target> +<target>Du kan ignorere denne feilen og anse mappen som top</target> <source>Cannot find folder %x.</source> -<target></target> +<target>Kan ikke finne mappen %x.</target> <source>Directories are dependent! Be careful when setting up synchronization rules:</source> <target>Mapper er avhengige av hverandre! VƦr forsiktig nĆ„r du setter opp synkroniseringsregler:</target> @@ -1274,6 +1278,9 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <source>Files %x have the same date but a different size!</source> <target>Filer %x har den samme datoen, men forskjellig stĆørrelse!</target> +<source>Items have different attributes</source> +<target>Elementer har forskjellige attributter</target> + <source>Symbolic links %x have the same date but a different target.</source> <target>Symbolske lenker %x har den samme datoen, men forskjellige mĆ„l.</target> @@ -1289,20 +1296,17 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <source>Both sides are equal</source> <target>Begge sider er like</target> -<source>Items have different attributes</source> -<target></target> - <source>Copy new item to left</source> -<target></target> +<target>Kopier nytt element til venstre</target> <source>Copy new item to right</source> -<target></target> +<target>Kopier nytt element til hĆøyre</target> <source>Delete left item</source> -<target></target> +<target>Slett venstre element</target> <source>Delete right item</source> -<target></target> +<target>Slett hĆøyre element</target> <source>Move file on left</source> <target>Flytt venstre fil</target> @@ -1311,19 +1315,19 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <target>Flytt hĆøyre fil</target> <source>Overwrite left item</source> -<target></target> +<target>Skriv over venstre element</target> <source>Overwrite right item</source> -<target></target> +<target>Skriv over hĆøyre element</target> <source>Do nothing</source> <target>Ikke gjĆør noe</target> <source>Update attributes on left</source> -<target></target> +<target>Oppdater attributter til venstre</target> <source>Update attributes on right</source> -<target></target> +<target>Oppdater attributter til hĆøyre</target> <source>Multiple...</source> <target>Flere...</target> @@ -1377,13 +1381,13 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <target>Oppdaterer attributter til %x</target> <source>Target folder input field must not be empty.</source> -<target></target> +<target>Feltet for mĆ„lmappe kan ikke vƦre tomt.</target> <source>Folder input field for versioning must not be empty.</source> -<target></target> +<target>Mappefeltet for versjon kan ikke vƦre tomt.</target> <source>Source folder %x not found.</source> -<target></target> +<target>Kildemappe %x finnes ikke.</target> <source>Unresolved conflicts existing!</source> <target>UlĆøste konflikter finnes!</target> @@ -1401,22 +1405,19 @@ Merk: Filnavn mĆ„ vƦre relative til basismapper! <target>Ikke nok ledig diskplass tilgjengelig pĆ„:</target> <source>Required:</source> -<target></target> +<target>NĆødvendig:</target> <source>Available:</source> -<target></target> - -<source>Recycle Bin is not available for the following paths! Files will be deleted permanently instead:</source> -<target>Papirkurv er ikke tilgjengelig for de fĆølgende baner! Filer blir isteden slettet permanent:</target> +<target>Tilgjengelig:</target> <source>A folder will be modified which is part of multiple folder pairs. Please review synchronization settings.</source> -<target></target> +<target>En mappe vil endres som er en del av flere mappepar. Se over innstillingene for synkronisering.</target> <source>Processing folder pair:</source> <target>Behandler mappepar:</target> <source>Target folder %x already existing.</source> -<target></target> +<target>MĆ„lmappe %x eksisterer allerede.</target> <source>Generating database...</source> <target>Oppretter database...</target> diff --git a/BUILD/Languages/portuguese.lng b/BUILD/Languages/portuguese.lng index b75ffb70..af35040b 100644 --- a/BUILD/Languages/portuguese.lng +++ b/BUILD/Languages/portuguese.lng @@ -10,6 +10,18 @@ <source>Searching for folder %x...</source> <target>Ć procura da pasta %x...</target> +<source>Batch execution</source> +<target>ExecuĆ§Ć£o do batch</target> + +<source>Items processed:</source> +<target>Elementos processados:</target> + +<source>Items remaining:</source> +<target>Elementos restantes:</target> + +<source>Total time:</source> +<target>Tempo total:</target> + <source>Show in Explorer</source> <target>Mostrar no Explorer</target> @@ -146,7 +158,7 @@ <target>Base de dados %x nĆ£o existe.</target> <source>Database file is corrupt:</source> -<target></target> +<target>Ficheiro de base de dados estĆ” corrompido:</target> <source>Out of memory!</source> <target>Sem memĆ³ria disponĆvel!</target> @@ -236,10 +248,10 @@ <target>&Abrir...</target> <source>Save &As...</source> -<target></target> +<target>Guar&dar como...</target> <source>&Quit</source> -<target>S&air</target> +<target>&Sair</target> <source>&Program</source> <target>&Programa</target> @@ -248,7 +260,7 @@ <target>C&onteĆŗdo</target> <source>&About</source> -<target>&Sobre</target> +<target>So&bre</target> <source>&Help</source> <target>A&juda</target> @@ -313,7 +325,7 @@ O comando Ć© executado se: <target>(Build: %x)</target> <source>All files</source> -<target></target> +<target>Todos os ficheiros</target> <source>&Restore</source> <target>&Restaurar</target> @@ -328,7 +340,7 @@ O comando Ć© executado se: <target>A aguardar pelos directĆ³rios em falta...</target> <source>A folder input field is empty.</source> -<target></target> +<target>Um dos campos de directĆ³rio para comparar estĆ” vazio.</target> <source>Logging</source> <target>A escrever em log</target> @@ -352,22 +364,7 @@ O comando Ć© executado se: <target>Personalizado</target> <source>FreeFileSync batch</source> -<target></target> - -<source>Batch execution</source> -<target>ExecuĆ§Ć£o do batch</target> - -<source>Items processed:</source> -<target>Elementos processados:</target> - -<source>Items remaining:</source> -<target>Elementos restantes:</target> - -<source>Total time:</source> -<target>Tempo total:</target> - -<source>Stop</source> -<target>Parar</target> +<target>FreeFileSync batch</target> <source>Synchronization aborted!</source> <target>SincronizaĆ§Ć£o abortada!</target> @@ -391,7 +388,7 @@ O comando Ć© executado se: <target>NĆ£o Ć© possĆvel ligar a sourceforge.net!</target> <source>A new version of FreeFileSync is available:</source> -<target></target> +<target>Uma nova versĆ£o do FreeFileSync estĆ” disponĆvel:</target> <source>Download now?</source> <target>Fazer download agora?</target> @@ -436,10 +433,10 @@ O comando Ć© executado se: <target>ExtensĆ£o</target> <source>Size:</source> -<target></target> +<target>Tamanho:</target> <source>Date:</source> -<target></target> +<target>Data:</target> <source>Action</source> <target>AĆ§Ć£o</target> @@ -475,7 +472,7 @@ O comando Ć© executado se: <target>&Novo</target> <source>&Save</source> -<target></target> +<target>G&uardar</target> <source>&Language</source> <target>&LĆngua</target> @@ -544,7 +541,7 @@ O comando Ć© executado se: <target>NĆŗmero de ficheiros e pastas a ser eliminados</target> <source>Total bytes to copy</source> -<target></target> +<target>Total em bytes a copiar</target> <source>Items found:</source> <target>Elementos encontrados:</target> @@ -553,10 +550,10 @@ O comando Ć© executado se: <target>Velocidade:</target> <source>Time remaining:</source> -<target></target> +<target>Tempo restante:</target> <source>Time elapsed:</source> -<target></target> +<target>Tempo decorrido</target> <source>Batch job</source> <target>Ficheiro Batch</target> @@ -855,13 +852,13 @@ Nota: Nome dos ficheiros tem que ser relativo aos diretĆ³rios base! <target>Nunca guardar alteraƧƵes</target> <source>Do you want to save changes to %x?</source> -<target></target> +<target>Deseja guardar as alteraƧƵes a %x?</target> <source>Save</source> -<target></target> +<target>Guardar</target> <source>Don't Save</source> -<target></target> +<target>NĆ£o Guardar</target> <source>Configuration loaded!</source> <target>ConfiguraĆ§Ć£o carregada!</target> @@ -1251,7 +1248,7 @@ Nota: Nome dos ficheiros tem que ser relativo aos diretĆ³rios base! <target>NĆ£o hĆ” alteraƧƵes desde a Ćŗltima sincronizaĆ§Ć£o!</target> <source>The corresponding database entries are not in sync considering current settings.</source> -<target></target> +<target>As entradas da base de dados correspondentes nĆ£o estĆ£o sincronizadas, considerando as opƧƵes actuais.</target> <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> <target>Escolher direcĆ§Ć£o de sincronizaĆ§Ć£o por defeito: Os ficheiros antigos serĆ£o substituĆdos pelos novos.</target> @@ -1380,13 +1377,13 @@ Nota: Nome dos ficheiros tem que ser relativo aos diretĆ³rios base! <target>Actualizar atributos de %x</target> <source>Target folder input field must not be empty.</source> -<target></target> +<target>Campo de directĆ³rio de destino nĆ£o deve estar vazio.</target> <source>Folder input field for versioning must not be empty.</source> -<target></target> +<target>Campo do directĆ³rio para manter versƵes nĆ£o deve estar vazio.</target> <source>Source folder %x not found.</source> -<target></target> +<target>DirectĆ³rio %x nĆ£o encontrado.</target> <source>Unresolved conflicts existing!</source> <target>Existem conflitos por resolver!</target> @@ -1404,10 +1401,10 @@ Nota: Nome dos ficheiros tem que ser relativo aos diretĆ³rios base! <target>NĆ£o hĆ” espaƧo livre suficiente em:</target> <source>Required:</source> -<target></target> +<target>Requirido:</target> <source>Available:</source> -<target></target> +<target>DisponĆvel:</target> <source>Recycle Bin is not available for the following paths! Files will be deleted permanently instead:</source> <target>Reciclagem nĆ£o disponĆvel para os seguintes caminhos! Os ficheiros serĆ£o apagados permanentemente:</target> @@ -1419,7 +1416,7 @@ Nota: Nome dos ficheiros tem que ser relativo aos diretĆ³rios base! <target>A processar o par do directorio:</target> <source>Target folder %x already existing.</source> -<target></target> +<target>DirectĆ³rio de destino %x jĆ” existe.</target> <source>Generating database...</source> <target>A gerar base de dados...</target> diff --git a/BUILD/Languages/portuguese_br.lng b/BUILD/Languages/portuguese_br.lng index 6a7f1332..bac56f55 100644 --- a/BUILD/Languages/portuguese_br.lng +++ b/BUILD/Languages/portuguese_br.lng @@ -144,7 +144,7 @@ <pluralform>%x Bytes</pluralform> </source> <target> -<pluralform>1 Byte</pluralform> +<pluralform>%x Byte</pluralform> <pluralform>%x Bytes</pluralform> </target> @@ -192,7 +192,7 @@ <pluralform>%x sec</pluralform> </source> <target> -<pluralform>1 seg</pluralform> +<pluralform>%x seg</pluralform> <pluralform>%x segs</pluralform> </target> @@ -210,7 +210,7 @@ <pluralform>[%x Threads]</pluralform> </source> <target> -<pluralform>[1 Thread]</pluralform> +<pluralform>[%x Thread]</pluralform> <pluralform>[%x Threads]</pluralform> </target> @@ -977,7 +977,7 @@ Nota: Os nomes dos arquivos devem ser relativos aos diretĆ³rios base! <pluralform>%x directories</pluralform> </source> <target> -<pluralform>1 diretĆ³rio</pluralform> +<pluralform>%x diretĆ³rio</pluralform> <pluralform>%x diretĆ³rios</pluralform> </target> @@ -986,7 +986,7 @@ Nota: Os nomes dos arquivos devem ser relativos aos diretĆ³rios base! <pluralform>%x files</pluralform> </source> <target> -<pluralform>1 arquivo</pluralform> +<pluralform>%x arquivo</pluralform> <pluralform>%x arquivos</pluralform> </target> @@ -995,7 +995,7 @@ Nota: Os nomes dos arquivos devem ser relativos aos diretĆ³rios base! <pluralform>%x of %y rows in view</pluralform> </source> <target> -<pluralform>%x de 1 linha</pluralform> +<pluralform>%x de %y linha</pluralform> <pluralform>%x de %y linhas</pluralform> </target> @@ -1157,7 +1157,7 @@ Nota: Os nomes dos arquivos devem ser relativos aos diretĆ³rios base! <pluralform>%x min</pluralform> </source> <target> -<pluralform>1 min</pluralform> +<pluralform>%x min</pluralform> <pluralform>%x mins</pluralform> </target> @@ -1166,7 +1166,7 @@ Nota: Os nomes dos arquivos devem ser relativos aos diretĆ³rios base! <pluralform>%x hours</pluralform> </source> <target> -<pluralform>1 hora</pluralform> +<pluralform>%x hora</pluralform> <pluralform>%x horas</pluralform> </target> @@ -1175,7 +1175,7 @@ Nota: Os nomes dos arquivos devem ser relativos aos diretĆ³rios base! <pluralform>%x days</pluralform> </source> <target> -<pluralform>1 dia</pluralform> +<pluralform>%x dia</pluralform> <pluralform>%x dias</pluralform> </target> diff --git a/BUILD/Languages/scottish_gaelic.lng b/BUILD/Languages/scottish_gaelic.lng new file mode 100644 index 00000000..d5f01eef --- /dev/null +++ b/BUILD/Languages/scottish_gaelic.lng @@ -0,0 +1,1450 @@ +<header> + <language name>GĆ idhlig</language name> + <translator>Michael Bauer aka Akerbeltz</translator> + <locale>gd</locale> + <flag file>scotland.png</flag file> + <plural forms>4</plural forms> + <plural definition>(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3</plural definition> +</header> + +<source>Searching for folder %x...</source> +<target>A' lorg a' phasgain %x...</target> + +<source>Batch execution</source> +<target>Cur an gnƬomh batch</target> + +<source>Items processed:</source> +<target>Nithean a tha deiseil:</target> + +<source>Items remaining:</source> +<target>Nithean a tha ri dhĆØanamh:</target> + +<source>Total time:</source> +<target>An Ć¹ine gu lĆØir:</target> + +<source>Show in Explorer</source> +<target>Seall san taisgealaiche</target> + +<source>Open with default application</source> +<target>Fosgail leis an aplacaid bhunaiteach</target> + +<source>Browse directory</source> +<target>RĆ¹raich an t-eĆ²laire</target> + +<source>Abort requested: Waiting for current operation to finish...</source> +<target>Tha thu airson sgur dheth: A' feitheamh gus an crƬochnaich an gnƬomh lĆ ithreach...</target> + +<source>RealtimeSync - Automated Synchronization</source> +<target>RealtimeSync - Sioncronachadh fĆØin-obrachail</target> + +<source>Error</source> +<target>Mearachd</target> + +<source>Select alternate comparison settings</source> +<target>Tagh roghainnean coimeasaidh eile</target> + +<source>Select alternate synchronization settings</source> +<target>Tagh roghainnean sioncronachaidh eile</target> + +<source>Filter is active</source> +<target>Tha a' chriathrag gnƬomhach</target> + +<source>No filter selected</source> +<target>Cha deach criathrag a thaghadh</target> + +<source>Remove alternate settings</source> +<target>Thoir air falbh na roghainnean eile</target> + +<source>Clear filter settings</source> +<target>Falamhaich roghainnean na criathraige</target> + +<source>Create a batch job</source> +<target>Cruthaich batch job</target> + +<source>Synchronization settings</source> +<target>Roghainnean an t-sioncronachaidh</target> + +<source>Comparison settings</source> +<target>Roghainnean a' choimeasaidh</target> + +<source>About</source> +<target>Mu dheidhinn</target> + +<source>Confirm</source> +<target>Dearbh</target> + +<source>Configure filter</source> +<target>RĆØitich a' chriathrag</target> + +<source>Global settings</source> +<target>Na roghainnean uile-choitcheann</target> + +<source>Summary</source> +<target>Gearr-chunntas</target> + +<source>Find</source> +<target>Lorg</target> + +<source>Select time span</source> +<target>Tagh an raon-ama</target> + +<source>Show pop-up</source> +<target>Seall na priob-uinneagan</target> + +<source>Show pop-up on errors or warnings</source> +<target>Seall priob-uinneagan a thaobh mhearachdan no rabhaidhean</target> + +<source>Ignore errors</source> +<target>Leig seachad mearachdan</target> + +<source>Hide all error and warning messages</source> +<target>Falaich gach teachdaireachd mu mhearachdan no rabhaidhean</target> + +<source>Exit instantly</source> +<target>FĆ g sa bhad</target> + +<source>Abort synchronization immediately</source> +<target>Sguir dhen t-sioncronachadh sa bhad</target> + +<source>Browse</source> +<target>RĆ¹raich</target> + +<source>Invalid command line:</source> +<target>Loidhne-Ć ithne mhƬ-dhligheach:</target> + +<source>Info</source> +<target>Fiosrachadh</target> + +<source>Warning</source> +<target>Rabhadh</target> + +<source>Fatal Error</source> +<target>Mearachd mharbhtach</target> + +<source>Windows Error Code %x:</source> +<target>CĆ²d mearachd Windows %x:</target> + +<source>Linux Error Code %x:</source> +<target>CĆ²d mearachd Linux %x:</target> + +<source>Cannot resolve symbolic link %x.</source> +<target>Cha ghabh an symbolic link %x fhuasgladh.</target> + +<source>%x MB</source> +<target>%x MB</target> + +<source>%x KB</source> +<target>%x KB</target> + +<source>%x GB</source> +<target>%x GB</target> + +<source> +<pluralform>1 Byte</pluralform> +<pluralform>%x Bytes</pluralform> +</source> +<target> +<pluralform>%x bhaidht</pluralform> +<pluralform>%x bhaidht</pluralform> +<pluralform>%x baidht</pluralform> +<pluralform>%x baidht</pluralform> +</target> + +<source>Database file %x is incompatible.</source> +<target>Chan eil am faidhle stĆ²ir-dhĆ ta %x co-chĆ²rdail.</target> + +<source>Initial synchronization:</source> +<target>A' chiad sioncronachadh:</target> + +<source>Database file %x does not yet exist.</source> +<target>Chan eil am faidhle stĆ²ir-dhĆ ta %x ann fhathast.</target> + +<source>Database file is corrupt:</source> +<target>Tha am faidhle stĆ²ir-dhĆ ta coirbte:</target> + +<source>Out of memory!</source> +<target>Chan eil cuimhne gu leĆ²r ann!</target> + +<source>Cannot write file %x.</source> +<target>Cha ghabh am faidhle %x a sgrƬobhadh.</target> + +<source>Cannot read file %x.</source> +<target>Cha ghabh am faidhle %x a leughadh.</target> + +<source>Database files do not share a common session.</source> +<target>Chan eil seisean an cumantas aig na faidhlichean stĆ²ir-dhĆ ta.</target> + +<source>An exception occurred!</source> +<target>Thachair eisgeachd!</target> + +<source>Cannot read file attributes of %x.</source> +<target>Cha ghabh buadhan an fhaidhle %x a leughadh.</target> + +<source>Cannot get process information.</source> +<target>Chan urrainn dhuinn greim fhaighinn air fiosrachadh a' phrĆ²iseis.</target> + +<source>Waiting while directory is locked (%x)...</source> +<target>A' feitheamh fhad 's a tha an t-eĆ²laire glaiste (%x)...</target> + +<source>Cannot set directory lock %x.</source> +<target>Cha ghabh glas an eĆ²laire %x a shuidheachadh.</target> + +<source> +<pluralform>1 sec</pluralform> +<pluralform>%x sec</pluralform> +</source> +<target> +<pluralform>%x diog</pluralform> +<pluralform>%x dhiog</pluralform> +<pluralform>%x diogan</pluralform> +<pluralform>%x diog</pluralform> +</target> + +<source>Error parsing file %x, row %y, column %z.</source> +<target>Mearachd le parsadh an fhaidhle %x, loidhne %y, colbh %z.</target> + +<source>Scanning:</source> +<target>'Ga sganadh:</target> + +<source>Encoding extended time information: %x</source> +<target>A' cĆ²dachadh fiosrachadh leudaichte an ama: %x</target> + +<source> +<pluralform>[1 Thread]</pluralform> +<pluralform>[%x Threads]</pluralform> +</source> +<target> +<pluralform>[%x snĆ ithlean]</pluralform> +<pluralform>[%x shnĆ ithlean]</pluralform> +<pluralform>[%x snĆ ithleanan]</pluralform> +<pluralform>[%x snĆ ithlean]</pluralform> +</target> + +<source>/sec</source> +<target>/diog</target> + +<source>Cannot find file %x.</source> +<target>Cha deach am faidhle %x a lorg.</target> + +<source>File %x does not contain a valid configuration.</source> +<target>Chan eil rĆØiteachadh dligheach san fhaidhle %x.</target> + +<source>Configuration file %x loaded partially only.</source> +<target>Cha deach faidhle an rĆØiteachaidh %x a luchdadh gu tur.</target> + +<source>Cannot access Volume Shadow Copy Service.</source> +<target>Chan fhaigh sinn cothrom air seirbheis lethbhreacan-sgĆ ile nan clĆ r.</target> + +<source>Please use FreeFileSync 64-bit version to create shadow copies on this system.</source> +<target>Nach cleachd sibh an tionndadh 64 biot de FreeFileSync gus lethbhreacan-sgĆ ile a chruthachadh air an t-siostam seo?</target> + +<source>Cannot load file %x.</source> +<target>Cha ghabh am faidhle %x a lorg.</target> + +<source>Path %x does not contain a volume name.</source> +<target>CHan eil ainm clĆ ir san t-slighe %x.</target> + +<source>Volume name %x not part of file name %y!</source> +<target>Chan eil an t-ainm clĆ ir %x 'na phĆ irt dhen ainm fhaidhle %y!</target> + +<source>Cannot read the following XML elements:</source> +<target>Chan urrainn dhuinn na h-eileamaidean XML a leanas a leughadh:</target> + +<source>&Open...</source> +<target>F&osgail...</target> + +<source>Save &As...</source> +<target>&SĆ bhail mar...</target> + +<source>&Quit</source> +<target>&FĆ g</target> + +<source>&Program</source> +<target>&PrĆ²gram</target> + +<source>&Content</source> +<target>S&usbaint</target> + +<source>&About</source> +<target>&Mu dheidhinn</target> + +<source>&Help</source> +<target>&Cobhair</target> + +<source>Usage:</source> +<target>Cleachdadh:</target> + +<source>1. Select folders to watch.</source> +<target>1. Tagh na pasgain air an cumar sĆ¹il.</target> + +<source>2. Enter a command line.</source> +<target>2. Cuir a-steach Ć ithne.</target> + +<source>3. Press 'Start'.</source> +<target>3. Briog air "TĆ²isich".</target> + +<source>To get started just import a .ffs_batch file.</source> +<target>Cha leig thu leas ach faidhle .ffs_batch ion-phortadh airson toiseach tĆ²iseachaidh.</target> + +<source>Folders to watch</source> +<target>Na pasgain air an cumar sĆ¹il</target> + +<source>Add folder</source> +<target>Cuir pasgan ris</target> + +<source>Remove folder</source> +<target>Thoir am pasgan air falbh</target> + +<source>Select a folder</source> +<target>Tagh pasgan</target> + +<source>Delay [seconds]</source> +<target>DĆ il [diogan]</target> + +<source>Idle time between last detected change and execution of command</source> +<target>An tĆ mh eadar an t-atharrachadh mu dheireadh agus gnƬomhachadh na h-Ć ithne</target> + +<source>Command line</source> +<target>Loidhne-Ć ithne</target> + +<source> +The command is triggered if: +- files or subfolders change +- new folders arrive (e.g. USB stick insert) +</source> +<target> +ThĆØid an loidhne-Ć ithne a chur gu dol: +- ma dh'atharraicheas faidhlichean no fo-phasgain +- ma nochdas pasgain Ć¹ra (m.e. ma chuireas tu a-steach bioran USB) +</target> + +<source>Start</source> +<target>TĆ²isich</target> + +<source>&Retry</source> +<target>&Feuch ris a-rithist</target> + +<source>Cancel</source> +<target>Sguir dheth</target> + +<source>(Build: %x)</source> +<target>(Build: %x)</target> + +<source>All files</source> +<target>Gach faidhle</target> + +<source>&Restore</source> +<target>&Aisig</target> + +<source>&Exit</source> +<target>&FĆ g an-seo</target> + +<source>Monitoring active...</source> +<target>A' cumail sĆ¹il...</target> + +<source>Waiting for missing directories...</source> +<target>A' feitheamh ris na h-eĆ²lairean a tha a dhƬth...</target> + +<source>A folder input field is empty.</source> +<target>Tha co-dhiĆ¹ aon raon pasgain ann a tha falamh.</target> + +<source>Logging</source> +<target>Logadh</target> + +<source>File time and size</source> +<target>Ceann-lĆ is meud</target> + +<source>File content</source> +<target>Susbaint an fhaidhle</target> + +<source><Automatic></source> +<target><FĆØin-obrachail></target> + +<source>Mirror ->></source> +<target>SgĆ thanaich ->></target> + +<source>Update -></source> +<target>Ćraich -></target> + +<source>Custom</source> +<target>GnĆ thaichte</target> + +<source>FreeFileSync batch</source> +<target>FreeFileSync batch</target> + +<source>Synchronization aborted!</source> +<target>Sguireadh dhen t-sioncronachadh!</target> + +<source>Synchronization completed with errors!</source> +<target>Chaidh an sioncronachadh a choileanadh ach bha mearachdan ann!</target> + +<source>Nothing to synchronize!</source> +<target>Chan eil dad ri shioncronachadh!</target> + +<source>Synchronization completed successfully!</source> +<target>Chaidh an sioncronachadh a choileanadh!</target> + +<source>Press "Switch" to resolve issues in FreeFileSync main dialog.</source> +<target>Briog air "Gearr leum" gus duilgheadasan a rĆØiteachadh sa phrƬomh-chĆ²mhradh aig FreeFileSync.</target> + +<source>Switching to FreeFileSync main dialog...</source> +<target>A' gearradh leum gu prƬomh-chĆ²mhradh FreeFileSyncs...</target> + +<source>Unable to connect to sourceforge.net!</source> +<target>Cha b' urrainn dhuinn ceangal a dhĆØanamh ri Sourceforge.net!</target> + +<source>A new version of FreeFileSync is available:</source> +<target>Tha tionndadh Ć¹r de FreeFileSync ann:</target> + +<source>Download now?</source> +<target>A bheil thu airson a luchdadh a-nuas an-drĆ sta?</target> + +<source>FreeFileSync is up to date!</source> +<target>Tha FreeFileSync cho Ć¹r 's a ghabhas!</target> + +<source>Information</source> +<target>Fiosrachadh</target> + +<source>Do you want FreeFileSync to automatically check for updates every week?</source> +<target>A bheil thu airson 's gun doir FreeFileSync sĆ¹il gach seachdain ach a bheil Ć¹rachadh ann?</target> + +<source>(Requires an Internet connection!)</source> +<target>(Tha feum air ceangal ris an eadar-lƬon!)</target> + +<source><Symlink></source> +<target><Symlink></target> + +<source><Folder></source> +<target><Pasgan></target> + +<source>Full path</source> +<target>Slighe shlan</target> + +<source>Name</source> +<target>Ainm</target> + +<source>Relative path</source> +<target>An t-slighe dhĆ imheach</target> + +<source>Base folder</source> +<target>Bun-phasgan</target> + +<source>Size</source> +<target>Meud</target> + +<source>Date</source> +<target>Ceann-lĆ </target> + +<source>Extension</source> +<target>Leudachan</target> + +<source>Size:</source> +<target>Meud:</target> + +<source>Date:</source> +<target>Ceann-lĆ :</target> + +<source>Action</source> +<target>GnƬomh</target> + +<source>Category</source> +<target>Roinn seĆ²rsa</target> + +<source>Drag && drop</source> +<target>Slaod ā leig Ć s</target> + +<source>Close progress dialog</source> +<target>DĆ¹in cĆ²mhradh an adhartais</target> + +<source>Standby</source> +<target>Cuir 'na fhuireachas</target> + +<source>Log off</source> +<target>ClĆ raich a-mach</target> + +<source>Shut down</source> +<target>DĆ¹in sƬos</target> + +<source>Hibernate</source> +<target>Geamhraich</target> + +<source>1. &Compare</source> +<target>1. &DĆØan coimeas</target> + +<source>2. &Synchronize</source> +<target>2. &DĆØan sioncronachadh</target> + +<source>&New</source> +<target>Ć&r</target> + +<source>&Save</source> +<target>&SĆ bhail</target> + +<source>&Language</source> +<target>&CĆ nan</target> + +<source>&Global settings...</source> +<target>&Na roghainnean uile-choitcheann...</target> + +<source>&Create batch job...</source> +<target>Cr&uthaich batch job...</target> + +<source>&Export file list...</source> +<target>Ćs-p&hortaich liosta nam faidhle...</target> + +<source>&Advanced</source> +<target>&Adhartach</target> + +<source>&Check for new version</source> +<target>&Thoir sĆ¹il ach a bheil tionndadh nas Ć¹ire ann</target> + +<source>Compare</source> +<target>DĆØan coimeas</target> + +<source>Compare both sides</source> +<target>DĆØan coimeas air an dĆ thaobh</target> + +<source>&Abort</source> +<target>&Sguir dheth</target> + +<source>Synchronize</source> +<target>DĆØan sioncronachadh</target> + +<source>Start synchronization</source> +<target>TĆ²isich air an t-sioncronachadh</target> + +<source>Add folder pair</source> +<target>Cuir paidhir de phasgain ris</target> + +<source>Remove folder pair</source> +<target>Thoir air falbh am paidhir seo de phasgain</target> + +<source>Swap sides</source> +<target>Cuir an dĆ thaobh an Ć ite a chĆØile</target> + +<source>Load configuration from file</source> +<target>Luchdaich an rĆØiteachadh on fhaidhle</target> + +<source>Save current configuration to file</source> +<target>SĆ bhail an rĆØiteachadh lĆ ithreach ann am faidhle</target> + +<source>Last used configurations (press DEL to remove from list)</source> +<target>An rĆØiteachadh mu dheireadh a chaidh a chleachdadh (brĆ¹th DEL gus rudan a thoirt air falbh on liosta)</target> + +<source>Hide excluded items</source> +<target>Falaich rudan a chaidh an dĆ¹nadh Ć s</target> + +<source>Hide filtered or temporarily excluded files</source> +<target>Falaich faidhlichean a chaidh a chriathradh Ć s no a chaidh a dhĆ¹nadh Ć s gu sealach</target> + +<source>Number of files and folders that will be created</source> +<target>Ćireamh nam faidhle 's nam pasgan a thĆØid a chruthachadh</target> + +<source>Number of files that will be overwritten</source> +<target>Ćireamh nam faidhle a thĆØid sgrƬobhadh thairis orra</target> + +<source>Number of files and folders that will be deleted</source> +<target>Ćireamh nam faidhle 's nam pasgan a thĆØid a sguabadh Ć s</target> + +<source>Total bytes to copy</source> +<target>Co mheud baidht a thĆØid lethbhreac a dhĆØanamh dhiubh</target> + +<source>Items found:</source> +<target>Rudan a chaidh a lorg:</target> + +<source>Speed:</source> +<target>Astar:</target> + +<source>Time remaining:</source> +<target>An Ć¹ine a tha air fhĆ gail:</target> + +<source>Time elapsed:</source> +<target>An Ć¹ine a dh'fhalbh:</target> + +<source>Batch job</source> +<target>Batch job</target> + +<source>Create a batch file to automate synchronization. Double-click this file or schedule in your system's task planner: FreeFileSync.exe <job name>.ffs_batch</source> +<target>Cruthaich faidhle batch airson sioncronachadh fĆØin-obrachail. DĆØan briogadh dĆ¹bailte air an fhaidhle seo no cuir e air sgeideal an t-siostaim agad: FreeFileSync.exe <Jobname>.ffs_batch.</target> + +<source>Help</source> +<target>Cobhair</target> + +<source>Filter files</source> +<target>Criathraich na faidhlichean</target> + +<source>Left</source> +<target>ClƬ</target> + +<source>Right</source> +<target>Deas</target> + +<source>Status feedback</source> +<target>Fiosrachadh mun staid</target> + +<source>Show progress dialog</source> +<target>Seall cĆ²mhradh an adhartais</target> + +<source>Error handling</source> +<target>LĆ imhseachadh mhearachdan</target> + +<source>Maximum number of log files:</source> +<target>An Ć ireamh as motha de dh'fhaidhlichean loga a tha ceadaichte:</target> + +<source>Select folder to save log files:</source> +<target>Tagh am pasgan far an dĆØid na logaichean a shĆ bhaladh:</target> + +<source>Batch settings</source> +<target>Roghainnean a' batch</target> + +<source>Select variant:</source> +<target>Tagh seĆ²rsa:</target> + +<source>Identify and propagate changes on both sides using a database. Deletions, renaming and conflicts are detected automatically.</source> +<target>Lorg is cuir an sĆ s atharraichean air an dĆ thaobh le stĆ²r-dĆ ta. Mothaichear do rudan a chaidh sguabadh Ć s, a chaidh ainmean Ć¹ra a thoirt orra no cĆ²mhstrithean gu fĆØin-obrachail.</target> + +<source>Mirror backup of left folder. Right folder is modified to exactly match left folder after synchronization.</source> +<target>DĆØan lethbhreac-glĆØidhidh 's tu a' sgĆ thanadh a' phasgain air an taobh chlƬ. ThĆØid am pasgan air an taobh deas a chur air gleus ach am bi e gu tur co-ionnann ris a' phasgan air an taobh chlƬ as dĆØidh dha sioncronachadh.</target> + +<source>Copy new or updated files to right folder.</source> +<target>Cuir lethbhreac de dh'fhaidhlichean a tha Ć¹r no Ć¹raichte dhan phasgan air an taobh deas.</target> + +<source>Configure your own synchronization rules.</source> +<target>SĆ²nraich riaghailtean sioncronachaidh thu fhĆØin.</target> + +<source>Deletion handling</source> +<target>Mar a dhĆØiligear ri sguabadh Ć s</target> + +<source>On completion:</source> +<target>Nuair a bhios e deiseil:</target> + +<source>Configuration</source> +<target>RĆØiteachadh</target> + +<source>Item exists on left side only</source> +<target>Chan eil an nƬ seo ann ach air an taobh chlƬ</target> + +<source>Item exists on right side only</source> +<target>Chan eil an nƬ seo ann ach air an taobh deas</target> + +<source>Left side is newer</source> +<target>Tha an taobh clƬ nas Ć¹ire</target> + +<source>Right side is newer</source> +<target>Tha an taobh deas nas Ć¹ire</target> + +<source>Items have different content</source> +<target>Tha diofar susbaint sna nithean</target> + +<source>Conflict/item cannot be categorized</source> +<target>Tha cĆ²mhstri/nƬ ann nach urrainn dhuinn aithneachadh</target> + +<source>OK</source> +<target>Ceart ma-thĆ </target> + +<source>Compare by...</source> +<target>DĆØan coimeas a-rĆØir...</target> + +<source> +Files are found equal if + - last write time and date + - file size +are the same +</source> +<target> +Bidh dĆ fhaidhle co-ionnann 'nar beachd-sa + - ma tha Ć m is ceann-lĆ an sgrƬobhaidh mu dheireadh + - ma tha am meud +co-ionnann +</target> + +<source> +Files are found equal if + - file content +is the same +</source> +<target> +Bidh dĆ fhaidhle co-ionnann 'nar beachd-sa + - susbaint an dĆ fhaidhle co-ionnann +</target> + +<source>Symbolic Link handling</source> +<target>LĆ imhseachadh nan symbolic links</target> + +<source>Synchronizing...</source> +<target>A' sioncronachadh...</target> + +<source>&Pause</source> +<target>&Cuir 'na stad</target> + +<source>Source code written in C++ utilizing:</source> +<target>Chaidh an cĆ²d tĆ¹sail a sgrƬobhadh ann an C++ le taic:</target> + +<source>If you like FreeFileSync</source> +<target>Ma tha FreeFileSync a' cĆ²rdadh riut</target> + +<source>Donate with PayPal</source> +<target>Nach doir sibh tabhartas le PayPal?</target> + +<source>Big thanks for localizing FreeFileSync goes out to:</source> +<target>Taing mhĆ²r dha na daoine a leanas a rinn eadar-theangachadh air FreeFileSync:</target> + +<source>Feedback and suggestions are welcome</source> +<target>Tha sinn a' cur fĆ ilte mhĆ²r air beachd is moladh sam bith</target> + +<source>Homepage</source> +<target>An duilleag-dhachaigh</target> + +<source>FreeFileSync at Sourceforge</source> +<target>FreeFileSync air Sourceforge</target> + +<source>Email</source> +<target>Post-d</target> + +<source>Published under the GNU General Public License</source> +<target>Air fhoillseachadh fo GNU General Public License</target> + +<source>Use Recycle Bin</source> +<target>Cleachd am biona ath-chuairteachaidh</target> + +<source>Delete on both sides</source> +<target>Sguab Ć s air an dĆ thaobh</target> + +<source>Delete on both sides even if the file is selected on one side only</source> +<target>Sguab Ć s air an dĆ thaobh fiĆ¹ mur an deach am faidhle a thaghadh ach air aon taobh</target> + +<source> +Only files that match all filter settings will be synchronized. +Note: File names must be relative to base directories! +</source> +<target> +Cha dĆØid ach na faidhlichean a fhreagras ri gach roghainn na criathraige a shioncronachadh. +An aire: Feumaidh ainmean nam faidhlichean a bhi dĆ imheach ris na bun-eĆ²lairean aca! +</target> + +<source>Include</source> +<target>Gabh a-steach</target> + +<source>Exclude</source> +<target>DĆ¹in a-mach</target> + +<source>Time span</source> +<target>Raon-ama</target> + +<source>File size</source> +<target>Meudh an fhaidhle</target> + +<source>Minimum</source> +<target>Air a' char as lugha</target> + +<source>Maximum</source> +<target>Air a' char as motha</target> + +<source>&Default</source> +<target>&Bun-roghainn</target> + +<source>Fail-safe file copy</source> +<target>DĆØan lethbhreac nach gabh fĆ illigeadh</target> + +<source>Write to a temporary file (*.ffs_tmp) first then rename it. This guarantees a consistent state even in case of fatal error.</source> +<target>SgrƬobh faidhle sealach (*.ffs_tmp) an toiseach agus cuir ainm eile air an uairsin. Bidh seo mar bharantas air staid sheasmhach, fiĆ¹ ma thachras mearachd mharbhtach.</target> + +<source>Copy locked files</source> +<target>DĆØan lethbhreac de dh'fhaidhlichean glaiste</target> + +<source>Copy shared or locked files using Volume Shadow Copy Service (Requires Administrator rights)</source> +<target>DĆØan lethbhreac de dh'fhaidhlichean glaiste no co-roinnte le seirbheis lethbhreacan-sgĆ ile nan clĆ r (feumaidh seo cĆ²raichean rianaire)</target> + +<source>Copy file access permissions</source> +<target>DĆØan lethbhreac de cheadan-inntrigidh nam faidhle</target> + +<source>Transfer file and folder permissions (Requires Administrator rights)</source> +<target>Tar-chuir ceadan nam faidhle 's nam pasgan (feumaidh seo cĆ²raichean rianaire)</target> + +<source>Restore hidden dialogs</source> +<target>Aisig na cĆ²mhraidhean falaichte</target> + +<source>External applications</source> +<target>Aplacaidean air an taobh a-muigh</target> + +<source>Description</source> +<target>Tuairisgeul</target> + +<source>Variant</source> +<target>Eugsamhail</target> + +<source>Statistics</source> +<target>Stats</target> + +<source>Do not show this dialog again</source> +<target>Na seall an cĆ²mhradh seo a-rithist</target> + +<source>Find what:</source> +<target>Lorg na leanas:</target> + +<source>Match case</source> +<target>An aire do litrichean mĆ²ra 's beaga</target> + +<source>&Find next</source> +<target>&Lorg an ath-fhear</target> + +<source>Operation aborted!</source> +<target>Sguireadh dhen ghnƬomh!</target> + +<source>Main bar</source> +<target>Am prƬomh-bhĆ r</target> + +<source>Folder pairs</source> +<target>Paidhrichean phasgan</target> + +<source>Overview</source> +<target>Foir-shealladh</target> + +<source>Select view</source> +<target>Tagh sealladh</target> + +<source>Set direction:</source> +<target>Suidhich a' chomhair:</target> + +<source>Exclude temporarily</source> +<target>DĆ¹in a-mach gu sealach</target> + +<source>Include temporarily</source> +<target>Gabh a-steach gu sealach</target> + +<source>Exclude via filter:</source> +<target>DĆ¹in a-mach le criathrag:</target> + +<source><multiple selection></source> +<target><Ioma-thaghadh></target> + +<source>Delete</source> +<target>Sguab Ć s</target> + +<source>Include all</source> +<target>Gabh a-steach na h-uile</target> + +<source>Exclude all</source> +<target>DĆ¹in a-mach na h-uile</target> + +<source>Show icons:</source> +<target>Meud nan Ƭomhaigheagan:</target> + +<source>Small</source> +<target>Beag</target> + +<source>Medium</source> +<target>Meadhanach</target> + +<source>Large</source> +<target>MĆ²r</target> + +<source>Select time span...</source> +<target>Tagh an raon-ama...</target> + +<source>Default view</source> +<target>An sealladh bunaiteach</target> + +<source>Show "%x"</source> +<target>Seall "%x"</target> + +<source><Last session></source> +<target><An seisean mu dheireadh></target> + +<source>Configuration saved!</source> +<target>Chaidh an rĆØiteachadh a shĆ bhaladh!</target> + +<source>Never save changes</source> +<target>Na sĆ bhail atharraichean idir</target> + +<source>Do you want to save changes to %x?</source> +<target>An sĆ bhail sinn dhut na h-atharraichean air %x?</target> + +<source>Save</source> +<target>SĆ bhailibh</target> + +<source>Don't Save</source> +<target>Na sĆ bhailibh</target> + +<source>Configuration loaded!</source> +<target>Chaidh an rĆØiteachadh a luchdadh!</target> + +<source>Folder Comparison and Synchronization</source> +<target>Coimeas eadar na pasgain is sioncronachadh</target> + +<source>Hide files that exist on left side only</source> +<target>Na falaich ach faidhlichean a tha air an taobh chlƬ a-mhĆ in</target> + +<source>Show files that exist on left side only</source> +<target>Na seall ach faidhlichean a tha air an taobh chlƬ a-mhĆ in</target> + +<source>Hide files that exist on right side only</source> +<target>Na falaich ach faidhlichean a tha air an taobh deas a-mhĆ in</target> + +<source>Show files that exist on right side only</source> +<target>Na seall ach faidhlichean a tha air an taobh deas a-mhĆ in</target> + +<source>Hide files that are newer on left</source> +<target>Falaich faidhlichean a tha nas Ć¹ire air an taobh chlƬ</target> + +<source>Show files that are newer on left</source> +<target>Seall faidhlichean a tha nas Ć¹ire air an taobh chlƬ</target> + +<source>Hide files that are newer on right</source> +<target>Falaich faidhlichean a tha nas Ć¹ire air an taobh deas</target> + +<source>Show files that are newer on right</source> +<target>Seall faidhlichean a tha nas Ć¹ire air an taobh deas</target> + +<source>Hide files that are equal</source> +<target>Falaich faidhlichean a tha co-ionnann</target> + +<source>Show files that are equal</source> +<target>Seall faidhlichean a tha co-ionnann</target> + +<source>Hide files that are different</source> +<target>Falaich faidhlichean a tha eadar-dhealaichte</target> + +<source>Show files that are different</source> +<target>Seall faidhlichean a tha eadar-dhealaichte</target> + +<source>Hide conflicts</source> +<target>Falaich cĆ²mhstrithean</target> + +<source>Show conflicts</source> +<target>Seall cĆ²mhstrithean</target> + +<source>Hide files that will be created on the left side</source> +<target>Falaich faidhlichean a thĆØid a chruthachadh air an taobh chlƬ</target> + +<source>Show files that will be created on the left side</source> +<target>Seall faidhlichean a thĆØid a chruthachadh air an taobh chlƬ</target> + +<source>Hide files that will be created on the right side</source> +<target>Falaich faidhlichean a thĆØid a chruthachadh air an taobh deas</target> + +<source>Show files that will be created on the right side</source> +<target>Seall faidhlichean a thĆØid a chruthachadh air an taobh deas</target> + +<source>Hide files that will be deleted on the left side</source> +<target>Falaich faidhlichean a thĆØid a sguabadh Ć s air an taobh chlƬ</target> + +<source>Show files that will be deleted on the left side</source> +<target>Seall faidhlichean a thĆØid a sguabadh Ć s air an taobh chlƬ</target> + +<source>Hide files that will be deleted on the right side</source> +<target>Falaich faidhlichean a thĆØid a sguabadh Ć s air an taobh deas</target> + +<source>Show files that will be deleted on the right side</source> +<target>Seall faidhlichean a thĆØid a sguabadh Ć s air an taobh deas</target> + +<source>Hide files that will be overwritten on left side</source> +<target>Falaich faidhlichean a thĆØid a thar-sgrƬobhadh air an taobh chlƬ</target> + +<source>Show files that will be overwritten on left side</source> +<target>Seall faidhlichean a thĆØid a thar-sgrƬobhadh air an taobh chlƬ</target> + +<source>Hide files that will be overwritten on right side</source> +<target>Falaich faidhlichean a thĆØid a thar-sgrƬobhadh air an taobh deas</target> + +<source>Show files that will be overwritten on right side</source> +<target>Seall faidhlichean a thĆØid a thar-sgrƬobhadh air an taobh deas</target> + +<source>Hide files that won't be copied</source> +<target>Falaich faidhlichean nach dĆØid lethbhreac a dhĆØanamh dhiubh</target> + +<source>Show files that won't be copied</source> +<target>Seall faidhlichean nach dĆØid lethbhreac a dhĆØanamh dhiubh</target> + +<source>All directories in sync!</source> +<target>Tha gach eĆ²laire air sioncronachadh!</target> + +<source>Comma separated list</source> +<target>Liosta air a sgaradh le cromagan</target> + +<source>Legend</source> +<target>TreĆ²ir</target> + +<source>File list exported!</source> +<target>Chaidh liosta nam faidhle Ć s-phortadh!</target> + +<source> +<pluralform>Object deleted successfully!</pluralform> +<pluralform>%x objects deleted successfully!</pluralform> +</source> +<target> +<pluralform>Chaidh %x oibseact a sguabadh Ć s!</pluralform> +<pluralform>Chaidh %x oibseact a sguabadh Ć s!</pluralform> +<pluralform>Chaidh %x oibseactan a sguabadh Ć s!</pluralform> +<pluralform>Chaidh %x oibseact a sguabadh Ć s!</pluralform> +</target> + +<source> +<pluralform>1 directory</pluralform> +<pluralform>%x directories</pluralform> +</source> +<target> +<pluralform>%x eĆ²laire</pluralform> +<pluralform>%x eĆ²laire</pluralform> +<pluralform>%x eĆ²lairean</pluralform> +<pluralform>%x eĆ²laire</pluralform> +</target> + +<source> +<pluralform>1 file</pluralform> +<pluralform>%x files</pluralform> +</source> +<target> +<pluralform>%x fhaidhle</pluralform> +<pluralform>%x fhaidhle</pluralform> +<pluralform>%x faidhlichean</pluralform> +<pluralform>%x faidhle</pluralform> +</target> + +<source> +<pluralform>%x of 1 row in view</pluralform> +<pluralform>%x of %y rows in view</pluralform> +</source> +<target> +<pluralform>A' sealltainn %x rĆ gh</pluralform> +<pluralform>A' sealltainn %x Ć %y rĆ gh</pluralform> +<pluralform>A' sealltainn %x Ć %y rĆ ghan</pluralform> +<pluralform>A' sealltainn %x Ć %y rĆ gh</pluralform> +</target> + +<source>Ignore further errors</source> +<target>Leig seachad mearachd sam bith eile</target> + +<source>&Ignore</source> +<target>&Leig seachad</target> + +<source>&Switch</source> +<target>&DĆØan suids</target> + +<source>Question</source> +<target>Ceist</target> + +<source>&Yes</source> +<target>&Tha</target> + +<source>&No</source> +<target>&Chan eil</target> + +<source>Scanning...</source> +<target>'Ga sganadh...</target> + +<source>Comparing content...</source> +<target>A' dĆØanamh coimeas eadar an cuid susbaint...</target> + +<source>Paused</source> +<target>'Na stad</target> + +<source>Initializing...</source> +<target>A' tĆ²iseachadh...</target> + +<source>Aborted</source> +<target>Air sgur dheth</target> + +<source>Completed</source> +<target>Deiseil</target> + +<source>Continue</source> +<target>Lean air</target> + +<source>Pause</source> +<target>Cuir 'na stad</target> + +<source>Cannot find %x</source> +<target>Chan urrainn dhuinn %x a lorg.</target> + +<source>Inactive</source> +<target>Neo-ghnƬomhach</target> + +<source>Today</source> +<target>An-diugh</target> + +<source>This week</source> +<target>An t-seachdain seo</target> + +<source>This month</source> +<target>Am mƬos seo</target> + +<source>This year</source> +<target>Am bliadhna</target> + +<source>Last x days</source> +<target>Na x lĆ ithean seo chaidh</target> + +<source>Byte</source> +<target>Baidht</target> + +<source>KB</source> +<target>KB</target> + +<source>MB</source> +<target>MB</target> + +<source>Filter</source> +<target>Criathrag</target> + +<source>Direct</source> +<target>DƬreach</target> + +<source>Follow</source> +<target>Lean</target> + +<source>Copy NTFS permissions</source> +<target>DĆØan lethbhreac de cheadan NTFS</target> + +<source>Integrate external applications into context menu. The following macros are available:</source> +<target>Amalaichidh seo aplacaidean air an taobh a-muigh dhan chlĆ r-taice cho-theacsail. Tha na macrothan a leanas ri lĆ imh:</target> + +<source>- full file or folder name</source> +<target>- ainm slĆ n dhen fhaidhle no dhen phasgan</target> + +<source>- folder part only</source> +<target>- cuid a' phasgain a-mhĆ in</target> + +<source>- Other side's counterpart to %name</source> +<target>- seise %s an taoibh eile</target> + +<source>- Other side's counterpart to %dir</source> +<target>- seise %dir an taoibh eile</target> + +<source>Make hidden dialogs and warning messages visible again?</source> +<target>A bheil thu airson na cĆ²mhraidhean is rabhaidhean falaichte fhaicinn a-rithist?</target> + +<source> +<pluralform>Do you really want to move the following object to the Recycle Bin?</pluralform> +<pluralform>Do you really want to move the following %x objects to the Recycle Bin?</pluralform> +</source> +<target> +<pluralform>A bheil thu cinnteach gu bheil thu airson an %x oibseact seo a chur dhan bhiona ath-chuairteachaidh?</pluralform> +<pluralform>A bheil thu cinnteach gu bheil thu airson an %x oibseact seo a chur dhan bhiona ath-chuairteachaidh?</pluralform> +<pluralform>A bheil thu cinnteach gu bheil thu airson na %x oibseactan seo a chur dhan bhiona ath-chuairteachaidh?</pluralform> +<pluralform>A bheil thu cinnteach gu bheil thu airson na %x oibseact seo a chur dhan bhiona ath-chuairteachaidh?</pluralform> +</target> + +<source> +<pluralform>Do you really want to delete the following object?</pluralform> +<pluralform>Do you really want to delete the following %x objects?</pluralform> +</source> +<target> +<pluralform>A bheil thu cinnteach gu bheil thu airson an %x oibseact seo a sguabadh Ć s?</pluralform> +<pluralform>A bheil thu cinnteach gu bheil thu airson an %x oibseact seo a sguabadh Ć s?</pluralform> +<pluralform>A bheil thu cinnteach gu bheil thu airson na %x oibseactan seo a sguabadh Ć s?</pluralform> +<pluralform>A bheil thu cinnteach gu bheil thu airson na %x oibseact seo a sguabadh Ć s?</pluralform> +</target> + +<source>Leave as unresolved conflict</source> +<target>FĆ g mar cĆ²mhstri gun rĆØiteachadh</target> + +<source>Delete permanently</source> +<target>Sguab Ć s gu buan</target> + +<source>Delete or overwrite files permanently</source> +<target>Sguab Ć s no sgrƬobh thairis air faidhlichean gu buan</target> + +<source>Use Recycle Bin when deleting or overwriting files</source> +<target>Cleachd am biona ath-chuairteachaidh nuair a thĆØid faidhlichean a sguabadh Ć s no ma thĆØid sgrƬobhadh thairis orra</target> + +<source>Versioning</source> +<target>Versioning</target> + +<source>Move files into a time-stamped subfolder</source> +<target>Gluais na faidhlichean dha fo-phasgan air a bheil stampa-ama</target> + +<source>Files</source> +<target>Faidhlichean</target> + +<source>Percentage</source> +<target>Ceudad</target> + +<source>%x TB</source> +<target>%x TB</target> + +<source>%x PB</source> +<target>%x PB</target> + +<source>%x%</source> +<target>%x%</target> + +<source> +<pluralform>1 min</pluralform> +<pluralform>%x min</pluralform> +</source> +<target> +<pluralform>%x mhionaid</pluralform> +<pluralform>%x mhionaid</pluralform> +<pluralform>%x mionaidean</pluralform> +<pluralform>%x mionaid</pluralform> +</target> + +<source> +<pluralform>1 hour</pluralform> +<pluralform>%x hours</pluralform> +</source> +<target> +<pluralform>%x uair a thƬde</pluralform> +<pluralform>%x uair a thƬde</pluralform> +<pluralform>%x uairean a thƬde</pluralform> +<pluralform>%x uair a thƬde</pluralform> +</target> + +<source> +<pluralform>1 day</pluralform> +<pluralform>%x days</pluralform> +</source> +<target> +<pluralform>%x latha</pluralform> +<pluralform>%x latha</pluralform> +<pluralform>%x lĆ ithean</pluralform> +<pluralform>%x latha</pluralform> +</target> + +<source>Cannot monitor directory %x.</source> +<target>Chan urrainn dhuinn sĆ¹il a chumail air %x.</target> + +<source>Conversion error:</source> +<target>Mearachd iompachaidh:</target> + +<source>Cannot delete file %x.</source> +<target>Cha ghabh am faidhle %x a sguabadh Ć s.</target> + +<source>The file is locked by another process:</source> +<target>Tha am faidhle glaiste aig prĆ²iseas eile:</target> + +<source>Cannot move file %x to %y.</source> +<target>Cha ghabh am faidhle %x a ghluasad dha %y.</target> + +<source>Cannot delete directory %x.</source> +<target>Cha ghabh an t-eĆ²laire %x a sguabadh Ć s.</target> + +<source>Cannot write file attributes of %x.</source> +<target>Chan urrainn dhuinn buadhan an fhaidhle %x a sgrƬobhadh.</target> + +<source>Cannot write modification time of %x.</source> +<target>Cha ghabh Ć m atharrachaidh %x a sgrƬobhadh.</target> + +<source>Cannot find system function %x.</source> +<target>Chan urrainn dhuinn foincsean an t-siostaim %x a lorg.</target> + +<source>Cannot read security context of %x.</source> +<target>Cha ghabh susbaint tĆØarainteachd %x a leughadh.</target> + +<source>Cannot write security context of %x.</source> +<target>Cha ghabh susbaint tĆØarainteachd %x a sgrƬobhadh.</target> + +<source>Cannot read permissions of %x.</source> +<target>Cha ghabh ceadan %x a leughadh.</target> + +<source>Cannot write permissions of %x.</source> +<target>Cha ghabh ceadan %x a sgrƬobhadh.</target> + +<source>Cannot create directory %x.</source> +<target>Cha ghabh an t-eĆ²laire %x a chruthachadh.</target> + +<source>Cannot copy symbolic link %x to %y.</source> +<target>Chan ghabh lethbhreac dhen symbolic link %x a chur gu %y.</target> + +<source>Cannot copy file %x to %y.</source> +<target>Cha ghabh lethbhreac an fhaidhle %x a chur gu %y.</target> + +<source>Cannot read directory %x.</source> +<target>Cha ghabh an t-eĆ²laire %x a leughadh.</target> + +<source>Detected endless directory recursion.</source> +<target>Mhothaich sinn dha ath-chĆ¹rsadh eĆ²laire gun chrƬoch.</target> + +<source>Cannot set privilege %x.</source> +<target>Cha ghabh a' phribhleid %x a shuidheachadh.</target> + +<source>Unable to move %x to the Recycle Bin!</source> +<target>Chan urrainn dhuinn %x a ghluasad dhan bhiona ath-chuairteachaidh!</target> + +<source>Both sides have changed since last synchronization!</source> +<target>Chaidh an dĆ thaobh atharrachadh on t-sioncronachadh mu dheireadh!</target> + +<source>Cannot determine sync-direction:</source> +<target>Cha ghabh comhair an t-sioncronachaidh aithneachadh:</target> + +<source>No change since last synchronization!</source> +<target>Cha deach dad atharrachadh on t-sioncronachadh mu dheireadh!</target> + +<source>The corresponding database entries are not in sync considering current settings.</source> +<target>Chan eil seisean nan innteartan san stĆ²r-dĆ ta sioncronaichte a-rĆØir nan roghainnean lĆ ithreach.</target> + +<source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> +<target>A' suidheachadh comhair bhunaiteach an t-sioncronachaidh: ThĆØid faidhlichean nas Ć¹ire a sgrƬobhadh thairis air seann-fhaidhlichean.</target> + +<source>Recycle Bin is not available for the following paths! Files will be deleted permanently instead:</source> +<target>Chan eil am biona ath-chuairteachaidh ri lĆ imh nan slighean a leanas! ThĆØid na faidhlichean a sguabhadh Ć s gu buan an Ć ite sin:</target> + +<source>You can ignore this error to consider the folder as empty.</source> +<target>'S urrainn dhut a' mhearachd seo a leigeil seachad gus am pasgan a lĆ imhseachadh mar gum biodh e falamh.</target> + +<source>Cannot find folder %x.</source> +<target>Chan urrainn dhuinn am pasgan %x a lorg.</target> + +<source>Directories are dependent! Be careful when setting up synchronization rules:</source> +<target>Tha eĆ²lairean an eisimeil a chĆØile! Bi faiceallach nuair a shuidhicheas tu na riaghailtean sioncronachaidh:</target> + +<source>Preparing synchronization...</source> +<target>Ag ullachadh an t-sioncronachaidh...</target> + +<source>Conflict detected:</source> +<target>Mhothaich sinn do chĆ²mhstri:</target> + +<source>File %x has an invalid date!</source> +<target>Tha ceann-lĆ mƬ-dhligheach aig an fhaidhle %x!</target> + +<source>Files %x have the same date but a different size!</source> +<target>Tha an dearbh cheann-lĆ aig na faidhlichean %x ach chan eil am meud co-ionnann!</target> + +<source>Items have different attributes</source> +<target>Tha diofar buadhan aig na nithean</target> + +<source>Symbolic links %x have the same date but a different target.</source> +<target>Tha an dearbh cheann-lĆ aig na symbolic links %x ach targaidean eadar-dhealaichte.</target> + +<source>Comparing content of files %x</source> +<target>A' dĆØanamh coimheas eadar na faidhlichean %x</target> + +<source>Comparing files by content failed.</source> +<target>Dh'fhĆ illig coimeasadh nam faidhlichean a thaobh susbaint.</target> + +<source>Generating file list...</source> +<target>A' gintinn liosta nam faidhle...</target> + +<source>Both sides are equal</source> +<target>Tha an dĆ thaobh co-ionnann</target> + +<source>Copy new item to left</source> +<target>Cuir lethbhreac dhen nƬ Ć¹r dhan taobh chlƬ</target> + +<source>Copy new item to right</source> +<target>Cuir lethbhreac dhen nƬ Ć¹r dhan taobh deas</target> + +<source>Delete left item</source> +<target>Sguab Ć s an nƬ air an taobh chlƬ</target> + +<source>Delete right item</source> +<target>Sguab Ć s an nƬ air an taobh deas</target> + +<source>Move file on left</source> +<target>Gluais am faidhle a tha air an taobh chlƬ</target> + +<source>Move file on right</source> +<target>Gluais am faidhle a tha air an taobh deas</target> + +<source>Overwrite left item</source> +<target>SgrƬobh thairis air an nƬ chlƬ</target> + +<source>Overwrite right item</source> +<target>SgrƬobh thairis air an nƬ deas</target> + +<source>Do nothing</source> +<target>Na dĆØan dad</target> + +<source>Update attributes on left</source> +<target>Ćraich na buadhan air an taobh chlƬ</target> + +<source>Update attributes on right</source> +<target>Ćraich na buadhan air an taobh deas</target> + +<source>Multiple...</source> +<target>Iomadh fear...</target> + +<source>Deleting file %x</source> +<target>A' sguabadh Ć s an fhaidhle %x</target> + +<source>Deleting folder %x</source> +<target>A' sguabadh Ć s a' phasgain %x</target> + +<source>Deleting symbolic link %x</source> +<target>A' sguabadh Ć s an symbolic link %x</target> + +<source>Moving file %x to recycle bin</source> +<target>A' gluasad an fhaidhle %x dhan bhiona ath-chuairteachaidh</target> + +<source>Moving folder %x to recycle bin</source> +<target>A' gluasad a' phasgain %x dhan bhiona ath-chuairteachaidh</target> + +<source>Moving symbolic link %x to recycle bin</source> +<target>A' gluasad an symbolic link %x dhan bhiona ath-chuairteachaidh</target> + +<source>Moving file %x to %y</source> +<target>A' gluasad an fhaidhle %x gu %y</target> + +<source>Moving folder %x to %y</source> +<target>A' gluasad a' phasgain %x gu %y</target> + +<source>Moving symbolic link %x to %y</source> +<target>A' gluasad an symbolic link %x gu %y</target> + +<source>Creating file %x</source> +<target>A' cruthachadh an fhaidhle %x</target> + +<source>Creating symbolic link %x</source> +<target>A' cruthachadh an symbolic link %x</target> + +<source>Creating folder %x</source> +<target>A' cruthachadh a' phasgain %x</target> + +<source>Overwriting file %x</source> +<target>A' sgrƬobhadh thairis air an fhaidhle %x</target> + +<source>Overwriting symbolic link %x</source> +<target>A' sgrƬobhadh thairis air an symbolic link %x</target> + +<source>Verifying file %x</source> +<target>A' dearbhadh an fhaidhle %x</target> + +<source>Updating attributes of %x</source> +<target>Ag Ć¹rachadh buadhan %x</target> + +<source>Target folder input field must not be empty.</source> +<target>Chan fhaod raon a' phasgain a bhith falamh.</target> + +<source>Folder input field for versioning must not be empty.</source> +<target>Chan fhaod raon a' pasgain airson versioning a bhith falamh.</target> + +<source>Source folder %x not found.</source> +<target>Cha deach am pasgan tĆ¹sail %x a lorg.</target> + +<source>Unresolved conflicts existing!</source> +<target>Tha cĆ²mhstrithean gun rĆØiteachadh ann!</target> + +<source>You can ignore conflicts and continue synchronization.</source> +<target>'S urrainn dhut na cĆ²mhstrithean a leigeil seachad is leantainn air an t-sioncronachadh.</target> + +<source>Significant difference detected:</source> +<target>Chaidh diofar mĆ²r a lorg:</target> + +<source>More than 50% of the total number of files will be copied or deleted!</source> +<target>Tha thu an impis barrachd air an dĆ rna leth dhe na faidhlichean uile sguabadh Ć s no lethbhreac a dhĆØanamh dhiubh!</target> + +<source>Not enough free disk space available in:</source> +<target>Chan eil rĆ¹m saor gu leĆ²r air an diosga:</target> + +<source>Required:</source> +<target>Na tha feum air:</target> + +<source>Available:</source> +<target>Na tha ri lĆ imh:</target> + +<source>A folder will be modified which is part of multiple folder pairs. Please review synchronization settings.</source> +<target>ThĆØid pasgan atharrachadh a tha 'na phĆ irt de dh'iomadh paidhir de phasgain. Nach doir thu sĆ¹il air roghainnean an t-sioncronachaidh?</target> + +<source>Processing folder pair:</source> +<target>A' prĆ²iseasadh a' phaidhir de phasgain:</target> + +<source>Target folder %x already existing.</source> +<target>Tha am pasgan-uidhe %x ann mu thrĆ th.</target> + +<source>Generating database...</source> +<target>A' gintinn an stĆ²ir-dhĆ ta...</target> + +<source>Data verification error: Source and target file have different content!</source> +<target>Mearachd dearbhadh an dĆ ta: Tha susbaint eadar-dhealaichte san fhaidhle tĆ¹sail is san targaid!</target> + diff --git a/BUILD/Languages/slovenian.lng b/BUILD/Languages/slovenian.lng index 21511cea..9fa57752 100644 --- a/BUILD/Languages/slovenian.lng +++ b/BUILD/Languages/slovenian.lng @@ -132,7 +132,7 @@ <pluralform>%x Bytes</pluralform> </source> <target> -<pluralform>1 Bajt</pluralform> +<pluralform>%x Bajt</pluralform> <pluralform>%x Bajta</pluralform> <pluralform>%x Bajti</pluralform> <pluralform>%x Bajtov</pluralform> @@ -182,7 +182,7 @@ <pluralform>%x sec</pluralform> </source> <target> -<pluralform>1 sek</pluralform> +<pluralform>%x sek</pluralform> <pluralform>%x sek</pluralform> <pluralform>%x sek</pluralform> <pluralform>%x sek</pluralform> @@ -202,7 +202,7 @@ <pluralform>[%x Threads]</pluralform> </source> <target> -<pluralform>[1 nit]</pluralform> +<pluralform>[%x nit]</pluralform> <pluralform>[%x niti]</pluralform> <pluralform>[%x niti]</pluralform> <pluralform>[%x niti]</pluralform> @@ -988,7 +988,7 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom! <pluralform>%x directories</pluralform> </source> <target> -<pluralform>1 imenik</pluralform> +<pluralform>%x imenik</pluralform> <pluralform>%x imenika</pluralform> <pluralform>%x imeniki</pluralform> <pluralform>%x imenikov</pluralform> @@ -999,7 +999,7 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom! <pluralform>%x files</pluralform> </source> <target> -<pluralform>1 datoteka</pluralform> +<pluralform>%x datoteka</pluralform> <pluralform>%x datoteki</pluralform> <pluralform>%x datoteke</pluralform> <pluralform>%x datotek</pluralform> @@ -1010,7 +1010,7 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom! <pluralform>%x of %y rows in view</pluralform> </source> <target> -<pluralform>%x od 1 vrstice v prikazu</pluralform> +<pluralform>%x od %y vrstice v prikazu</pluralform> <pluralform>%x od %y vrstic v prikazu</pluralform> <pluralform>%x od %y vrstic v prikazu</pluralform> <pluralform>%x od %y vrstic v prikazu</pluralform> @@ -1178,7 +1178,7 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom! <pluralform>%x min</pluralform> </source> <target> -<pluralform>1 min</pluralform> +<pluralform>%x min</pluralform> <pluralform>%x min</pluralform> <pluralform>%x min</pluralform> <pluralform>%x min</pluralform> @@ -1189,7 +1189,7 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom! <pluralform>%x hours</pluralform> </source> <target> -<pluralform>1 ura</pluralform> +<pluralform>%x ura</pluralform> <pluralform>%x uri</pluralform> <pluralform>%x ure</pluralform> <pluralform>%x ur</pluralform> @@ -1200,7 +1200,7 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom! <pluralform>%x days</pluralform> </source> <target> -<pluralform>1 dan</pluralform> +<pluralform>%x dan</pluralform> <pluralform>%x dni</pluralform> <pluralform>%x dni</pluralform> <pluralform>%x dni</pluralform> diff --git a/BUILD/Languages/ukrainian.lng b/BUILD/Languages/ukrainian.lng index 9de06b00..bdfd7db7 100644 --- a/BUILD/Languages/ukrainian.lng +++ b/BUILD/Languages/ukrainian.lng @@ -212,7 +212,7 @@ <pluralform>[%x Threads]</pluralform> </source> <target> -<pluralform>[1 ŠŠøŃŃ Š²ŠøŠŗŠ¾Š½Š°Š½Š½Ń]</pluralform> +<pluralform>[%x ŠŠøŃŃ Š²ŠøŠŗŠ¾Š½Š°Š½Š½Ń]</pluralform> <pluralform>[%x ŠŠøŃŃ Š²ŠøŠŗŠ¾Š½Š°Š½Š½Ń]</pluralform> <pluralform>[%x ŠŠøŃŠµŠ¹ Š²ŠøŠŗŠ¾Š½Š°Š½Š½Ń]</pluralform> </target> diff --git a/BUILD/Resources.zip b/BUILD/Resources.zip Binary files differindex 2f132d17..44b9749e 100644 --- a/BUILD/Resources.zip +++ b/BUILD/Resources.zip diff --git a/FreeFileSync.cbp b/FreeFileSync.cbp index e874a6e0..4c324148 100644 --- a/FreeFileSync.cbp +++ b/FreeFileSync.cbp @@ -373,6 +373,14 @@ <Option target="Release" /> <Option target="Debug-DLL" /> </Unit> + <Unit filename="ui\triple_splitter.cpp"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> + <Unit filename="ui\triple_splitter.h"> + <Option target="Release" /> + <Option target="Debug-DLL" /> + </Unit> <Unit filename="ui\wx_form_build_hide_warnings.h"> <Option target="Release" /> <Option target="Debug-DLL" /> @@ -440,10 +448,6 @@ <Option target="Debug-DLL" /> <Option target="Unit Test" /> </Unit> - <Unit filename="wx+\serialize.h"> - <Option target="Release" /> - <Option target="Debug-DLL" /> - </Unit> <Unit filename="wx+\shell_execute.h"> <Option target="Release" /> <Option target="Debug-DLL" /> @@ -499,6 +503,7 @@ <Unit filename="zen\read_txt.h" /> <Unit filename="zen\recycler.cpp" /> <Unit filename="zen\recycler.h" /> + <Unit filename="zen\serialize.h" /> <Unit filename="zen\stl_tools.h" /> <Unit filename="zen\string_base.h" /> <Unit filename="zen\string_tools.h" /> diff --git a/FreeFileSync.vcxproj b/FreeFileSync.vcxproj index cc6b4203..4d42e24e 100644 --- a/FreeFileSync.vcxproj +++ b/FreeFileSync.vcxproj @@ -259,6 +259,7 @@ <ClCompile Include="ui\taskbar.cpp" /> <ClCompile Include="ui\tray_icon.cpp" /> <ClCompile Include="ui\tree_view.cpp" /> + <ClCompile Include="ui\triple_splitter.cpp" /> <ClCompile Include="wx+\button.cpp" /> <ClCompile Include="wx+\create_pch.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> @@ -6,7 +6,7 @@ APPSHAREDIR = $(SHAREDIR)/$(APPNAME) DOCSHAREDIR = $(SHAREDIR)/doc/$(APPNAME) COMMON_COMPILE_FLAGS = -Wall -pipe -O3 -pthread -std=gnu++0x -DNDEBUG -DwxUSE_UNICODE -DFFS_LINUX -DZEN_PLATFORM_OTHER -DWXINTL_NO_GETTEXT_MACRO -I. -include "zen/i18n.h" -COMMON_LINK_FLAGS = -pthread -lrt +COMMON_LINK_FLAGS = -pthread -lrt -lz #default build CPPFLAGS = $(COMMON_COMPILE_FLAGS) `wx-config --cxxflags --debug=no --unicode=yes` @@ -62,6 +62,7 @@ CPP_LIST+=ui/search.cpp CPP_LIST+=ui/small_dlgs.cpp CPP_LIST+=ui/sync_cfg.cpp CPP_LIST+=ui/taskbar.cpp +CPP_LIST+=ui/triple_splitter.cpp CPP_LIST+=ui/tray_icon.cpp CPP_LIST+=lib/binary.cpp CPP_LIST+=lib/db_file.cpp diff --git a/RealtimeSync/application.cpp b/RealtimeSync/application.cpp index b136b720..dae44de4 100644 --- a/RealtimeSync/application.cpp +++ b/RealtimeSync/application.cpp @@ -9,13 +9,13 @@ #include <wx/event.h> #include <wx/log.h> #include <wx/msgdlg.h> -#include <wx+/serialize.h> #include <wx+/string_conv.h> -#include <zen/file_handling.h> #include "resources.h" #include "xml_ffs.h" #include "../lib/localization.h" #include "../lib/ffs_paths.h" +#include "../lib/return_codes.h" +#include "lib/error_log.h" #ifdef FFS_LINUX #include <gtk/gtk.h> @@ -101,12 +101,7 @@ int Application::OnRun() auto processException = [](const std::wstring& msg) { //it's not always possible to display a message box, e.g. corrupted stack, however low-level file output works! - try - { - saveBinStream(getConfigDir() + Zstr("LastError.txt"), utfCvrtTo<std::string>(msg)); //throw FileError - } - catch (const FileError&) {} - + logError(utfCvrtTo<std::string>(msg)); wxSafeShowMessage(_("An exception occurred!") + L" - FFS", msg); }; @@ -117,14 +112,14 @@ int Application::OnRun() catch (const std::exception& e) //catch all STL exceptions { processException(utfCvrtTo<std::wstring>(e.what())); - return -9; + return FFS_RC_EXCEPTION; } catch (...) //catch the rest { processException(L"Unknown error."); - return -9; + return FFS_RC_EXCEPTION; } - return 0; //program's return code + return FFS_RC_SUCCESS; //program's return code } diff --git a/RealtimeSync/main_dlg.cpp b/RealtimeSync/main_dlg.cpp index 7eb3be59..7c2c9f69 100644 --- a/RealtimeSync/main_dlg.cpp +++ b/RealtimeSync/main_dlg.cpp @@ -286,7 +286,7 @@ xmlAccess::XmlRealConfig MainDialog::getConfiguration() output.directories.push_back((*i)->getName()); output.commandline = m_textCtrlCommand->GetValue(); - output.delay = m_spinCtrlDelay->GetValue();; + output.delay = m_spinCtrlDelay->GetValue(); return output; } diff --git a/RealtimeSync/resources.h b/RealtimeSync/resources.h index 835125a4..7a7b1d7f 100644 --- a/RealtimeSync/resources.h +++ b/RealtimeSync/resources.h @@ -4,8 +4,8 @@ // * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef RESOURCES_H_INCLUDED -#define RESOURCES_H_INCLUDED +#ifndef RESOURCES_H_INCLUDED_870857342085670826521345 +#define RESOURCES_H_INCLUDED_870857342085670826521345 #include <map> #include <wx/bitmap.h> @@ -32,4 +32,4 @@ private: std::map<wxString, wxBitmap> bitmaps; }; -#endif // RESOURCES_H_INCLUDED +#endif //RESOURCES_H_INCLUDED_870857342085670826521345 diff --git a/algorithm.cpp b/algorithm.cpp index 6de28baa..6ca1f73a 100644 --- a/algorithm.cpp +++ b/algorithm.cpp @@ -6,7 +6,7 @@ #include "algorithm.h" #include <set> -#include <iterator> +//#include <iterator> #include <stdexcept> #include <tuple> #include "lib/resources.h" @@ -80,7 +80,7 @@ private: case FILE_CONFLICT: case FILE_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize" if (dirCfg.conflict == SYNC_DIR_NONE) - fileObj.setSyncDirConflict(getCategoryDescription(fileObj)); //take over category conflict + fileObj.setSyncDirConflict(fileObj.getCatExtraDescription()); //take over category conflict else fileObj.setSyncDir(dirCfg.conflict); break; @@ -109,7 +109,7 @@ private: case SYMLINK_CONFLICT: case SYMLINK_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize" if (dirCfg.conflict == SYNC_DIR_NONE) - linkObj.setSyncDirConflict(getCategoryDescription(linkObj)); //take over category conflict + linkObj.setSyncDirConflict(linkObj.getCatExtraDescription()); //take over category conflict else linkObj.setSyncDir(dirCfg.conflict); break; @@ -137,7 +137,7 @@ private: break; case DIR_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize" if (dirCfg.conflict == SYNC_DIR_NONE) - dirObj.setSyncDirConflict(getCategoryDescription(dirObj)); //take over category conflict + dirObj.setSyncDirConflict(dirObj.getCatExtraDescription()); //take over category conflict else dirObj.setSyncDir(dirCfg.conflict); break; @@ -162,11 +162,9 @@ struct AllEqual //test if non-equal items exist in scanned data [](const SymLinkMapping& linkObj) { return linkObj.getLinkCategory() == SYMLINK_EQUAL; }) && //symlinks std::all_of(hierObj.refSubDirs(). begin(), hierObj.refSubDirs(). end(), - [](const DirMapping& dirObj) -> bool + [](const DirMapping& dirObj) { - if (dirObj.getDirCategory() != DIR_EQUAL) - return false; - return AllEqual()(dirObj); //recurse + return dirObj.getDirCategory() == DIR_EQUAL && AllEqual()(dirObj); //short circuit-behavior! }); //directories } }; @@ -351,7 +349,7 @@ private: { return loadLastSynchronousState(baseMap); //throw FileError, FileErrorDatabaseNotExisting } - catch (FileErrorDatabaseNotExisting&) {} //let's ignore these errors for now... + catch (FileErrorDatabaseNotExisting&) {} //let's ignore this error, it seems there's no value in reporting it other than confuse users catch (FileError& error) //e.g. incompatible database version { reportWarning_(error.toString() + L" \n\n" + @@ -1111,8 +1109,7 @@ std::pair<wxString, int> zen::deleteFromGridAndHDPreview(const std::vector<FileS const std::vector<FileSystemObject*>& selectionRight, bool deleteOnBothSides) { - //fast replacement for wxString modelling exponential growth - typedef Zbase<wchar_t> zxString; //for use with UI texts + typedef Zbase<wchar_t> zxString; //fast replacement for wxString modelling exponential growth zxString filesToDelete; int totalDelCount = 0; @@ -1174,97 +1171,138 @@ std::pair<wxString, int> zen::deleteFromGridAndHDPreview(const std::vector<FileS namespace { +template <typename Function> inline +bool tryReportingError(Function cmd, DeleteFilesHandler& handler) //return "true" on success, "false" if error was ignored +{ + for (;;) + try + { + cmd(); //throw FileError + return true; + } + catch (FileError& error) + { + switch (handler.reportError(error.toString())) //may throw! + { + case DeleteFilesHandler::IGNORE_ERROR: + return false; + case DeleteFilesHandler::RETRY: + break; //continue with loop + default: + assert(false); + break; + } + } +} + + struct RemoveCallbackImpl : public zen::CallbackRemoveDir { - RemoveCallbackImpl(DeleteFilesHandler& deleteCallback) : deleteCallback_(deleteCallback) {} + RemoveCallbackImpl(DeleteFilesHandler& handler) : handler_(handler) {} - virtual void notifyFileDeletion(const Zstring& filename) { deleteCallback_.notifyDeletion(filename); } - virtual void notifyDirDeletion (const Zstring& dirname) { deleteCallback_.notifyDeletion(dirname); } + virtual void notifyFileDeletion(const Zstring& filename) { handler_.notifyDeletion(filename); } + virtual void notifyDirDeletion (const Zstring& dirname) { handler_.notifyDeletion(dirname); } private: - DeleteFilesHandler& deleteCallback_; + DeleteFilesHandler& handler_; }; -} -template <SelectedSide side, class InputIterator> -void deleteFromGridAndHDOneSide(InputIterator first, InputIterator last, +template <SelectedSide side> +struct PermanentDeleter : public FSObjectVisitor //throw FileError +{ + PermanentDeleter(DeleteFilesHandler& handler) : remCallback(handler) {} + + virtual void visit(const FileMapping& fileObj) + { + if (zen::removeFile(fileObj.getFullName<side>())) //throw FileError + remCallback.notifyFileDeletion(fileObj.getFullName<side>()); + } + + virtual void visit(const SymLinkMapping& linkObj) + { + switch (linkObj.getLinkType<side>()) + { + case LinkDescriptor::TYPE_DIR: + zen::removeDirectory(linkObj.getFullName<side>(), &remCallback); //throw FileError + break; + case LinkDescriptor::TYPE_FILE: + if (zen::removeFile(linkObj.getFullName<side>())) //throw FileError + remCallback.notifyFileDeletion(linkObj.getFullName<side>()); + break; + } + } + + virtual void visit(const DirMapping& dirObj) + { + zen::removeDirectory(dirObj.getFullName<side>(), &remCallback); //throw FileError + } + +private: + RemoveCallbackImpl remCallback; +}; + + +template <SelectedSide side> +void deleteFromGridAndHDOneSide(std::vector<FileSystemObject*>& ptrList, bool useRecycleBin, - DeleteFilesHandler& statusHandler) + DeleteFilesHandler& handler) { - for (auto iter = first; iter != last; ++iter) //VS 2010 bug prevents replacing this by std::for_each + lamba + for (auto iter = ptrList.begin(); iter != ptrList.end(); ++iter) //VS 2010 bug prevents replacing this by std::for_each + lamba { FileSystemObject& fsObj = **iter; //all pointers are required(!) to be bound + if (fsObj.isEmpty<side>()) //element may be implicitly deleted, e.g. if parent folder was deleted first + continue; - while (true) + tryReportingError([&] { - try + if (useRecycleBin) { - if (!fsObj.isEmpty<side>()) //element may become implicitly delted, e.g. if parent folder was deleted first - { - if (useRecycleBin) - { - if (zen::recycleOrDelete(fsObj.getFullName<side>())) //throw FileError - statusHandler.notifyDeletion(fsObj.getFullName<side>()); - } - else - { - RemoveCallbackImpl removeCallback(statusHandler); - - //del directories and symlinks - struct DeletePermanently : public FSObjectVisitor - { - DeletePermanently(RemoveCallbackImpl& remCallback) : remCallback_(remCallback) {} - - virtual void visit(const FileMapping& fileObj) - { - if (zen::removeFile(fileObj.getFullName<side>())) - remCallback_.notifyFileDeletion(fileObj.getFullName<side>()); - } - - virtual void visit(const SymLinkMapping& linkObj) - { - switch (linkObj.getLinkType<side>()) - { - case LinkDescriptor::TYPE_DIR: - zen::removeDirectory(linkObj.getFullName<side>(), &remCallback_); - break; - case LinkDescriptor::TYPE_FILE: - if (zen::removeFile(linkObj.getFullName<side>())) - remCallback_.notifyFileDeletion(linkObj.getFullName<side>()); - break; - } - } - - virtual void visit(const DirMapping& dirObj) - { - zen::removeDirectory(dirObj.getFullName<side>(), &remCallback_); - } - - private: - RemoveCallbackImpl& remCallback_; - } delPerm(removeCallback); - fsObj.accept(delPerm); - } - - fsObj.removeObject<side>(); //if directory: removes recursively! - } - break; + if (zen::recycleOrDelete(fsObj.getFullName<side>())) //throw FileError + handler.notifyDeletion(fsObj.getFullName<side>()); } - catch (const FileError& error) + else { - DeleteFilesHandler::Response rv = statusHandler.reportError(error.toString()); + PermanentDeleter<side> delPerm(handler); //throw FileError + fsObj.accept(delPerm); + } - if (rv == DeleteFilesHandler::IGNORE_ERROR) - break; + fsObj.removeObject<side>(); //if directory: removes recursively! + }, handler); + } +} - else if (rv == DeleteFilesHandler::RETRY) - ; //continue in loop - else - assert (false); - } + +template <SelectedSide side> +void categorize(const std::set<FileSystemObject*>& rowsIn, + std::vector<FileSystemObject*>& deletePermanent, + std::vector<FileSystemObject*>& deleteRecyler, + bool useRecycleBin, + std::map<Zstring, bool, LessFilename>& hasRecyclerBuffer) +{ + auto hasRecycler = [&](const FileSystemObject& fsObj) -> bool + { +#ifdef FFS_WIN + const Zstring& baseDirPf = fsObj.root().getBaseDirPf<side>(); + + auto iter = hasRecyclerBuffer.find(baseDirPf); + if (iter != hasRecyclerBuffer.end()) + return iter->second; + return hasRecyclerBuffer.insert(std::make_pair(baseDirPf, recycleBinStatus(baseDirPf) == STATUS_REC_EXISTS)).first->second; +#else + return true; +#endif + }; + + for (auto iter = rowsIn.begin(); iter != rowsIn.end(); ++iter) + if (!(*iter)->isEmpty<side>()) + { + if (useRecycleBin && hasRecycler(**iter)) //Windows' ::SHFileOperation() will delete permanently anyway, but we have a superior deletion routine + deleteRecyler.push_back(*iter); + else + deletePermanent.push_back(*iter); } - } +} } @@ -1274,7 +1312,8 @@ void zen::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete const std::vector<DirectionConfig>& directCfgs, bool deleteOnBothSides, bool useRecycleBin, - DeleteFilesHandler& statusHandler) + DeleteFilesHandler& statusHandler, + bool& warningRecyclerMissing) { if (folderCmp.empty()) return; @@ -1282,21 +1321,20 @@ void zen::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete throw std::logic_error("Programming Error: Contract violation!"); //build up mapping from base directory to corresponding direction config - std::map<const BaseDirMapping*, DirectionConfig> baseDirCfgs; + hash_map<const BaseDirMapping*, DirectionConfig> baseDirCfgs; for (auto iter = folderCmp.begin(); iter != folderCmp.end(); ++iter) baseDirCfgs[&** iter] = directCfgs[iter - folderCmp.begin()]; std::set<FileSystemObject*> deleteLeft (rowsToDeleteOnLeft .begin(), rowsToDeleteOnLeft .end()); std::set<FileSystemObject*> deleteRight(rowsToDeleteOnRight.begin(), rowsToDeleteOnRight.end()); - if (deleteOnBothSides) { deleteLeft.insert(deleteRight.begin(), deleteRight.end()); deleteRight = deleteLeft; } - set_remove_if(deleteLeft, std::mem_fun(&FileSystemObject::isEmpty<LEFT_SIDE>)); //remove empty rows to ensure correct statistics - set_remove_if(deleteRight, std::mem_fun(&FileSystemObject::isEmpty<RIGHT_SIDE>)); // + set_remove_if(deleteLeft, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<LEFT_SIDE >(); }); //still needed? + set_remove_if(deleteRight, [](const FileSystemObject* fsObj) { return fsObj->isEmpty<RIGHT_SIDE>(); }); // //ensure cleanup: redetermination of sync-directions and removal of invalid rows auto updateDirection = [&]() @@ -1320,7 +1358,7 @@ void zen::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete newDir = fsObj.isEmpty<LEFT_SIDE>() ? SYNC_DIR_RIGHT : SYNC_DIR_LEFT; else { - DirectionSet dirCfg = extractDirections(cfgIter->second); + const DirectionSet& dirCfg = extractDirections(cfgIter->second); newDir = fsObj.isEmpty<LEFT_SIDE>() ? dirCfg.exRightSideOnly : dirCfg.exLeftSideOnly; } @@ -1336,11 +1374,33 @@ void zen::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete }; ZEN_ON_SCOPE_EXIT(updateDirection()); //MSVC: assert is a macro and it doesn't play nice with ZEN_ON_SCOPE_EXIT, surprise... wasn't there something about macros being "evil"? - deleteFromGridAndHDOneSide<LEFT_SIDE>(deleteLeft.begin(), deleteLeft.end(), - useRecycleBin, - statusHandler); + //categorize rows into permanent deletion and recycle bin + std::vector<FileSystemObject*> deletePermanentLeft; + std::vector<FileSystemObject*> deletePermanentRight; + std::vector<FileSystemObject*> deleteRecylerLeft; + std::vector<FileSystemObject*> deleteRecylerRight; + + std::map<Zstring, bool, LessFilename> hasRecyclerBuffer; + categorize<LEFT_SIDE >(deleteLeft, deletePermanentLeft, deleteRecylerLeft, useRecycleBin, hasRecyclerBuffer); + categorize<RIGHT_SIDE>(deleteRight, deletePermanentRight, deleteRecylerRight, useRecycleBin, hasRecyclerBuffer); + + //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong + if (useRecycleBin && + std::any_of(hasRecyclerBuffer.begin(), hasRecyclerBuffer.end(), [](std::pair<Zstring, bool> item) { return !item.second; })) + { + std::wstring warningMessage = _("Recycle Bin is not available for the following paths! Files will be deleted permanently instead:"); + warningMessage += L"\n"; + + for (auto iter = hasRecyclerBuffer.begin(); iter != hasRecyclerBuffer.end(); ++iter) + if (!iter->second) + warningMessage += L"\n" + utfCvrtTo<std::wstring>(iter->first); + + statusHandler.reportWarning(warningMessage, warningRecyclerMissing); + } + + deleteFromGridAndHDOneSide<LEFT_SIDE>(deleteRecylerLeft, true, statusHandler); + deleteFromGridAndHDOneSide<LEFT_SIDE>(deletePermanentLeft, false, statusHandler); - deleteFromGridAndHDOneSide<RIGHT_SIDE>(deleteRight.begin(), deleteRight.end(), - useRecycleBin, - statusHandler); + deleteFromGridAndHDOneSide<RIGHT_SIDE>(deleteRecylerRight, true, statusHandler); + deleteFromGridAndHDOneSide<RIGHT_SIDE>(deletePermanentRight, false, statusHandler); } diff --git a/algorithm.h b/algorithm.h index 66203ec7..c2621ec0 100644 --- a/algorithm.h +++ b/algorithm.h @@ -52,7 +52,8 @@ public: IGNORE_ERROR = 10, RETRY }; - virtual Response reportError(const std::wstring& errorMessage) = 0; + virtual Response reportError (const std::wstring& msg) = 0; + virtual void reportWarning(const std::wstring& msg, bool& warningActive) = 0; //virtual void totalFilesToDelete(int objectsTotal) = 0; //informs about the total number of files to be deleted virtual void notifyDeletion(const Zstring& currentObject) = 0; //called for each file/folder that has been deleted @@ -63,7 +64,9 @@ void deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDeleteOnLef const std::vector<DirectionConfig>& directCfgs, bool deleteOnBothSides, bool useRecycleBin, - DeleteFilesHandler& statusHandler); + DeleteFilesHandler& statusHandler, + //global warnings: + bool& warningRecyclerMissing); } #endif // ALGORITHM_H_INCLUDED diff --git a/comparison.cpp b/comparison.cpp index 4c188a92..2661d67c 100644 --- a/comparison.cpp +++ b/comparison.cpp @@ -198,6 +198,8 @@ void CompareProcess::startCompareProcess(const std::vector<FolderPairCfg>& cfgLi //PERF_START; + procCallback.reportInfo(_("Start comparison")); //we want some indicator at the very beginning to make sense of "total time" + //init process: keep at beginning so that all gui elements are initialized properly procCallback.initNewPhase(-1, 0, ProcessCallback::PHASE_SCANNING); //it's not known how many files will be scanned => -1 objects @@ -348,24 +350,48 @@ void CompareProcess::startCompareProcess(const std::vector<FolderPairCfg>& cfgLi //--------------------assemble conflict descriptions--------------------------- +namespace +{ +//const wchar_t arrowLeft [] = L"\u2190"; +//const wchar_t arrowRight[] = L"\u2192"; unicode arrows -> too small +const wchar_t arrowLeft [] = L"<--"; +const wchar_t arrowRight[] = L"-->"; + + //check for very old dates or date2s in the future std::wstring getConflictInvalidDate(const Zstring& fileNameFull, Int64 utcTime) { return _("Conflict detected:") + L"\n" + - replaceCpy(_("File %x has an invalid date!"), L"%x", fmtFileName(fileNameFull)) + L"\n\n" + + replaceCpy(_("File %x has an invalid date!"), L"%x", fmtFileName(fileNameFull)) + L"\n" + _("Date:") + L" " + utcToLocalTimeString(utcTime); } -namespace -{ //check for changed files with same modification date std::wstring getConflictSameDateDiffSize(const FileMapping& fileObj) { return _("Conflict detected:") + L"\n" + - replaceCpy(_("Files %x have the same date but a different size!"), L"%x", fmtFileName(fileObj.getObjRelativeName())) + L"\n\n" + - L"<-- " + _("Date:") + L" " + utcToLocalTimeString(fileObj.getLastWriteTime<LEFT_SIDE >()) + L" " + _("Size:") + L" " + toGuiString(fileObj.getFileSize<LEFT_SIDE>()) + L"\n" + - L"--> " + _("Date:") + L" " + utcToLocalTimeString(fileObj.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(fileObj.getFileSize<RIGHT_SIDE>()); + replaceCpy(_("Files %x have the same date but a different size!"), L"%x", fmtFileName(fileObj.getObjRelativeName())) + L"\n" + + arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.getLastWriteTime<LEFT_SIDE >()) + L" " + _("Size:") + L" " + toGuiString(fileObj.getFileSize<LEFT_SIDE>()) + L"\n" + + arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(fileObj.getFileSize<RIGHT_SIDE>()); +} + + +inline +std::wstring getDescrDiffMetaShortname(const FileSystemObject& fsObj) +{ + return _("Items have different attributes") + L"\n" + + arrowLeft + L" " + fmtFileName(fsObj.getShortName<LEFT_SIDE >()) + L"\n" + + arrowRight + L" " + fmtFileName(fsObj.getShortName<RIGHT_SIDE>()); +} + + +template <class FileOrLinkMapping> inline +std::wstring getDescrDiffMetaDate(const FileOrLinkMapping& fileObj) +{ + return _("Items have different attributes") + L"\n" + + arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.template getLastWriteTime<LEFT_SIDE >()) + L"\n" + + arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.template getLastWriteTime<RIGHT_SIDE>()); } } @@ -394,9 +420,9 @@ void CompareProcess::categorizeSymlinkByTime(SymLinkMapping& linkObj) const //2. harmonize with "bool stillInSync()" in algorithm.cpp if (linkObj.getShortName<LEFT_SIDE>() == linkObj.getShortName<RIGHT_SIDE>()) - linkObj.setCategory<SYMLINK_EQUAL>(); + linkObj.setCategory<FILE_EQUAL>(); else - linkObj.setCategory<SYMLINK_DIFFERENT_METADATA>(); + linkObj.setCategoryDiffMetadata(getDescrDiffMetaShortname(linkObj)); } else linkObj.setCategoryConflict(_("Conflict detected:") + L"\n" + @@ -404,11 +430,11 @@ void CompareProcess::categorizeSymlinkByTime(SymLinkMapping& linkObj) const break; case CmpFileTime::TIME_LEFT_NEWER: - linkObj.setCategory<SYMLINK_LEFT_NEWER>(); + linkObj.setCategory<FILE_LEFT_NEWER>(); break; case CmpFileTime::TIME_RIGHT_NEWER: - linkObj.setCategory<SYMLINK_RIGHT_NEWER>(); + linkObj.setCategory<FILE_RIGHT_NEWER>(); break; case CmpFileTime::TIME_LEFT_INVALID: @@ -450,7 +476,7 @@ void CompareProcess::compareByTimeSize(const FolderPairCfg& fpConfig, BaseDirMap if (fileObj->getShortName<LEFT_SIDE>() == fileObj->getShortName<RIGHT_SIDE>()) fileObj->setCategory<FILE_EQUAL>(); else - fileObj->setCategory<FILE_DIFFERENT_METADATA>(); + fileObj->setCategoryDiffMetadata(getDescrDiffMetaShortname(*fileObj)); } else fileObj->setCategoryConflict(getConflictSameDateDiffSize(*fileObj)); //same date, different filesize @@ -496,16 +522,15 @@ void CompareProcess::categorizeSymlinkByContent(SymLinkMapping& linkObj) const //2. harmonize with "bool stillInSync()" in algorithm.cpp //symlinks have same "content" - if (linkObj.getShortName<LEFT_SIDE>() == linkObj.getShortName<RIGHT_SIDE>() && - CmpFileTime::getResult(linkObj.getLastWriteTime<LEFT_SIDE>(), - linkObj.getLastWriteTime<RIGHT_SIDE>(), - fileTimeTolerance) == CmpFileTime::TIME_EQUAL) - linkObj.setCategory<SYMLINK_EQUAL>(); + if (linkObj.getShortName<LEFT_SIDE>() != linkObj.getShortName<RIGHT_SIDE>()) + linkObj.setCategoryDiffMetadata(getDescrDiffMetaShortname(linkObj)); + else if (CmpFileTime::getResult(linkObj.getLastWriteTime<LEFT_SIDE>(), linkObj.getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance) != CmpFileTime::TIME_EQUAL) + linkObj.setCategoryDiffMetadata(getDescrDiffMetaDate(linkObj)); else - linkObj.setCategory<SYMLINK_DIFFERENT_METADATA>(); + linkObj.setCategory<FILE_EQUAL>(); } else - linkObj.setCategory<SYMLINK_DIFFERENT>(); + linkObj.setCategory<FILE_DIFFERENT>(); } @@ -574,14 +599,12 @@ void CompareProcess::compareByContent(std::vector<std::pair<FolderPairCfg, BaseD //Caveat: //1. FILE_EQUAL may only be set if short names match in case: InSyncDir's mapping tables use short name as a key! see db_file.cpp //2. harmonize with "bool stillInSync()" in algorithm.cpp - - if (fileObj->getShortName<LEFT_SIDE>() == fileObj->getShortName<RIGHT_SIDE>() && - CmpFileTime::getResult(fileObj->getLastWriteTime<LEFT_SIDE >(), - fileObj->getLastWriteTime<RIGHT_SIDE>(), - fileTimeTolerance) == CmpFileTime::TIME_EQUAL) - fileObj->setCategory<FILE_EQUAL>(); + if (fileObj->getShortName<LEFT_SIDE>() != fileObj->getShortName<RIGHT_SIDE>()) + fileObj->setCategoryDiffMetadata(getDescrDiffMetaShortname(*fileObj)); + else if (CmpFileTime::getResult(fileObj->getLastWriteTime<LEFT_SIDE>(), fileObj->getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance) != CmpFileTime::TIME_EQUAL) + fileObj->setCategoryDiffMetadata(getDescrDiffMetaDate(*fileObj)); else - fileObj->setCategory<FILE_DIFFERENT_METADATA>(); + fileObj->setCategory<FILE_EQUAL>(); } else fileObj->setCategory<FILE_DIFFERENT>(); @@ -727,7 +750,10 @@ void MergeSides::execute(const DirContainer& leftSide, const DirContainer& right [&](const DirData& dirLeft, const DirData& dirRight) //both sides { - DirMapping& newDirMap = output.addSubDir(dirLeft.first, dirRight.first); + DirMapping& newDirMap = output.addSubDir(dirLeft.first, dirRight.first, DIR_EQUAL); + if (dirLeft.first != dirRight.first) + newDirMap.setCategoryDiffMetadata(getDescrDiffMetaShortname(newDirMap)); + execute(dirLeft.second, dirRight.second, newDirMap); //recurse into subdirectories }); } diff --git a/file_hierarchy.cpp b/file_hierarchy.cpp index 6f0047eb..37fd9b18 100644 --- a/file_hierarchy.cpp +++ b/file_hierarchy.cpp @@ -32,7 +32,7 @@ void HierarchyObject::removeEmptyRec() // for (auto& subDir : refSubDirs()) // subDir.removeEmptyRec(); //recurse - std::for_each(refSubDirs().begin(), refSubDirs().end(), std::mem_fun_ref(&HierarchyObject::removeEmptyRec)); + std::for_each(refSubDirs().begin(), refSubDirs().end(), [&](HierarchyObject& hierObj) { hierObj.removeEmptyRec(); }); } namespace @@ -291,8 +291,9 @@ std::wstring zen::getCategoryDescription(CompareFilesResult cmpRes) std::wstring zen::getCategoryDescription(const FileSystemObject& fsObj) { const CompareFilesResult cmpRes = fsObj.getCategory(); - if (cmpRes == FILE_CONFLICT) - return fsObj.getCatConflict(); + if (cmpRes == FILE_CONFLICT || + cmpRes == FILE_DIFFERENT_METADATA) + return fsObj.getCatExtraDescription(); return getCategoryDescription(cmpRes); } diff --git a/file_hierarchy.h b/file_hierarchy.h index c9ef75a4..3853bf59 100644 --- a/file_hierarchy.h +++ b/file_hierarchy.h @@ -147,7 +147,8 @@ public: typedef zen::FixedList<DirMapping> SubDirVec; DirMapping& addSubDir(const Zstring& shortNameLeft, - const Zstring& shortNameRight); + const Zstring& shortNameRight, + CompareDirResult defaultCmpResult); template <SelectedSide side> DirMapping& addSubDir(const Zstring& shortName); //dir exists on one side only @@ -357,8 +358,9 @@ public: template <SelectedSide side> Zstring getFullName() const; //getFullName() == getBaseDirPf() + getRelativeName() //comparison result - virtual CompareFilesResult getCategory() const = 0; - virtual std::wstring getCatConflict() const = 0; //only filled if getCategory() == FILE_CONFLICT + CompareFilesResult getCategory() const { return cmpResult; } + std::wstring getCatExtraDescription() const; //only filled if getCategory() == FILE_CONFLICT or FILE_DIFFERENT_METADATA + //sync operation virtual SyncOperation testSyncOperation(SyncDirection testSyncDir) const; //semantics: "what if"! assumes "active, no conflict, no recursion (directory)! virtual SyncOperation getSyncOperation() const; @@ -381,8 +383,17 @@ public: const BaseDirMapping& root() const { return parent_.getRoot(); } /**/ BaseDirMapping& root() { return parent_.getRoot(); } + //for use during init in "CompareProcess" only: + template <CompareFilesResult res> void setCategory(); + void setCategoryConflict(const std::wstring& description); + void setCategoryDiffMetadata(const std::wstring& description); + protected: - FileSystemObject(const Zstring& shortNameLeft, const Zstring& shortNameRight, HierarchyObject& parentObj) : + FileSystemObject(const Zstring& shortNameLeft, + const Zstring& shortNameRight, + HierarchyObject& parentObj, + CompareFilesResult defaultCmpResult) : + cmpResult(defaultCmpResult), selectedForSynchronization(true), syncDir(SYNC_DIR_NONE), shortNameLeft_(shortNameLeft), @@ -406,6 +417,10 @@ private: virtual void removeObjectL() = 0; virtual void removeObjectR() = 0; + //categorization + CompareFilesResult cmpResult; + std::unique_ptr<std::wstring> cmpResultDescr; //only filled if getCategory() == FILE_CONFLICT or FILE_DIFFERENT_METADATA + bool selectedForSynchronization; SyncDirection syncDir; std::unique_ptr<std::wstring> syncDirConflict; //non-empty if we have a conflict setting sync-direction @@ -420,38 +435,21 @@ private: //------------------------------------------------------------------ class DirMapping : public FileSystemObject, public HierarchyObject { - friend class CompareProcess; //only CompareProcess shall be allowed to change cmpResult friend class HierarchyObject; public: virtual void accept(FSObjectVisitor& visitor) const; - virtual CompareFilesResult getCategory() const; CompareDirResult getDirCategory() const; //returns actually used subset of CompareFilesResult - virtual std::wstring getCatConflict() const; DirMapping(const Zstring& shortNameLeft, //use empty shortname if "not existing" const Zstring& shortNameRight, // - HierarchyObject& parentObj) : - FileSystemObject(shortNameLeft, shortNameRight, parentObj), + HierarchyObject& parentObj, + CompareDirResult defaultCmpResult) : + FileSystemObject(shortNameLeft, shortNameRight, parentObj, static_cast<CompareFilesResult>(defaultCmpResult)), HierarchyObject(getObjRelativeName() + FILE_NAME_SEPARATOR, parentObj.getRoot()), syncOpBuffered(SO_DO_NOTHING), - syncOpUpToDate(false) - { - assert(!shortNameLeft.empty() || !shortNameRight.empty()); - - if (shortNameRight.empty()) - cmpResult = DIR_LEFT_SIDE_ONLY; - else if (shortNameLeft.empty()) - cmpResult = DIR_RIGHT_SIDE_ONLY; - else - { - if (shortNameLeft == shortNameRight) - cmpResult = DIR_EQUAL; - else - cmpResult = DIR_DIFFERENT_METADATA; - } - } + syncOpUpToDate(false) {} virtual SyncOperation getSyncOperation() const; @@ -464,9 +462,6 @@ private: virtual void notifySyncCfgChanged() { syncOpUpToDate = false; FileSystemObject::notifySyncCfgChanged(); HierarchyObject::notifySyncCfgChanged(); } //------------------------------------------------------------------ - //categorization - CompareDirResult cmpResult; - mutable SyncOperation syncOpBuffered; //determining sync-op for directory may be expensive as it depends on child-objects -> buffer it mutable bool syncOpUpToDate; // }; @@ -474,7 +469,6 @@ private: //------------------------------------------------------------------ class FileMapping : public FileSystemObject { - friend class CompareProcess; //only CompareProcess shall be allowed to change cmpResult friend class HierarchyObject; //construction public: @@ -486,8 +480,7 @@ public: const Zstring& shortNameRight, // const FileDescriptor& right, HierarchyObject& parentObj) : - FileSystemObject(shortNameLeft, shortNameRight, parentObj), - cmpResult(defaultCmpResult), + FileSystemObject(shortNameLeft, shortNameRight, parentObj, defaultCmpResult), dataLeft(left), dataRight(right), moveFileRef(nullptr) {} @@ -499,8 +492,7 @@ public: void setMoveRef(ObjectId refId) { moveFileRef = refId; } //reference to corresponding renamed file ObjectId getMoveRef() const { return moveFileRef; } //may be nullptr - virtual CompareFilesResult getCategory() const; - virtual std::wstring getCatConflict() const; + CompareFilesResult getFileCategory() const; virtual SyncOperation testSyncOperation(SyncDirection testSyncDir) const; //semantics: "what if"! assumes "active, no conflict, no recursion (directory)! virtual SyncOperation getSyncOperation() const; @@ -508,10 +500,6 @@ public: template <SelectedSide side> void syncTo(const FileDescriptor& descrTarget, const FileDescriptor* descrSource = nullptr); //copy + update file attributes (optional) private: - template <CompareFilesResult res> - void setCategory(); - void setCategoryConflict(const std::wstring& description); - SyncOperation applyMoveOptimization(SyncOperation op) const; virtual void flip(); @@ -519,10 +507,6 @@ private: virtual void removeObjectR(); //------------------------------------------------------------------ - //categorization - CompareFilesResult cmpResult; - std::unique_ptr<std::wstring> cmpConflictDescr; //only filled if cmpResult == FILE_CONFLICT - FileDescriptor dataLeft; FileDescriptor dataRight; @@ -532,7 +516,6 @@ private: //------------------------------------------------------------------ class SymLinkMapping : public FileSystemObject //this class models a TRUE symbolic link, i.e. one that is NEVER dereferenced: deref-links should be directly placed in class File/DirMapping { - friend class CompareProcess; //only CompareProcess shall be allowed to change cmpResult friend class HierarchyObject; //construction public: @@ -542,9 +525,7 @@ public: template <SelectedSide side> LinkDescriptor::LinkType getLinkType() const; template <SelectedSide side> const Zstring& getTargetPath() const; - virtual CompareFilesResult getCategory() const; CompareSymlinkResult getLinkCategory() const; //returns actually used subset of CompareFilesResult - virtual std::wstring getCatConflict() const; SymLinkMapping(const Zstring& shortNameLeft, //use empty string if "not existing" const LinkDescriptor& left, @@ -552,8 +533,7 @@ public: const Zstring& shortNameRight, //use empty string if "not existing" const LinkDescriptor& right, HierarchyObject& parentObj) : - FileSystemObject(shortNameLeft, shortNameRight, parentObj), - cmpResult(defaultCmpResult), + FileSystemObject(shortNameLeft, shortNameRight, parentObj, static_cast<CompareFilesResult>(defaultCmpResult)), dataLeft(left), dataRight(right) {} @@ -563,16 +543,8 @@ private: virtual void flip(); virtual void removeObjectL(); virtual void removeObjectR(); - - template <CompareSymlinkResult res> - void setCategory(); - void setCategoryConflict(const std::wstring& description); //------------------------------------------------------------------ - //categorization - CompareSymlinkResult cmpResult; - std::unique_ptr<std::wstring> cmpConflictDescr; //only filled if cmpResult == SYMLINK_CONFLICT - LinkDescriptor dataLeft; LinkDescriptor dataRight; }; @@ -642,37 +614,16 @@ void SymLinkMapping::accept(FSObjectVisitor& visitor) const inline -CompareFilesResult FileMapping::getCategory() const -{ - return cmpResult; -} - - -inline -std::wstring FileMapping::getCatConflict() const -{ - return cmpConflictDescr ? *cmpConflictDescr : std::wstring(); -} - - -inline -CompareFilesResult DirMapping::getCategory() const +CompareFilesResult FileMapping::getFileCategory() const { - return convertToFilesResult(cmpResult); + return getCategory(); } inline CompareDirResult DirMapping::getDirCategory() const { - return cmpResult; -} - - -inline -std::wstring DirMapping::getCatConflict() const -{ - return std::wstring(); + return static_cast<CompareDirResult>(getCategory()); } @@ -687,6 +638,14 @@ void FileSystemObject::setSyncDir(SyncDirection newDir) inline +std::wstring FileSystemObject::getCatExtraDescription() const +{ + assert(getCategory() == FILE_CONFLICT || getCategory() == FILE_DIFFERENT_METADATA); + return cmpResultDescr ? *cmpResultDescr : std::wstring(); +} + + +inline void FileSystemObject::setSyncDirConflict(const std::wstring& description) { syncDir = SYNC_DIR_NONE; @@ -801,6 +760,7 @@ const Zstring& FileSystemObject::getBaseDirPf<RIGHT_SIDE>() const template <> inline void FileSystemObject::removeObject<LEFT_SIDE>() { + cmpResult = isEmpty<RIGHT_SIDE>() ? FILE_EQUAL : FILE_RIGHT_SIDE_ONLY; shortNameLeft_.clear(); removeObjectL(); @@ -811,6 +771,7 @@ void FileSystemObject::removeObject<LEFT_SIDE>() template <> inline void FileSystemObject::removeObject<RIGHT_SIDE>() { + cmpResult = isEmpty<LEFT_SIDE>() ? FILE_EQUAL : FILE_LEFT_SIDE_ONLY; shortNameRight_.clear(); removeObjectR(); @@ -823,6 +784,7 @@ void FileSystemObject::copyToL() { assert(!isEmpty()); shortNameLeft_ = shortNameRight_; + cmpResult = FILE_EQUAL; setSyncDir(SYNC_DIR_NONE); } @@ -832,15 +794,61 @@ void FileSystemObject::copyToR() { assert(!isEmpty()); shortNameRight_ = shortNameLeft_; + cmpResult = FILE_EQUAL; setSyncDir(SYNC_DIR_NONE); } +template <CompareFilesResult res> inline +void FileSystemObject::setCategory() +{ + cmpResult = res; +} +template <> void FileSystemObject::setCategory<FILE_CONFLICT>(); // +template <> void FileSystemObject::setCategory<FILE_DIFFERENT_METADATA>(); //not defined! +template <> void FileSystemObject::setCategory<FILE_LEFT_SIDE_ONLY>(); // +template <> void FileSystemObject::setCategory<FILE_RIGHT_SIDE_ONLY>(); // + +inline +void FileSystemObject::setCategoryConflict(const std::wstring& description) +{ + cmpResult = FILE_CONFLICT; + cmpResultDescr.reset(new std::wstring(description)); +} + +inline +void FileSystemObject::setCategoryDiffMetadata(const std::wstring& description) +{ + cmpResult = FILE_DIFFERENT_METADATA; + cmpResultDescr.reset(new std::wstring(description)); +} + inline void FileSystemObject::flip() { std::swap(shortNameLeft_, shortNameRight_); + switch (cmpResult) + { + case FILE_LEFT_SIDE_ONLY: + cmpResult = FILE_RIGHT_SIDE_ONLY; + break; + case FILE_RIGHT_SIDE_ONLY: + cmpResult = FILE_LEFT_SIDE_ONLY; + break; + case FILE_LEFT_NEWER: + cmpResult = FILE_RIGHT_NEWER; + break; + case FILE_RIGHT_NEWER: + cmpResult = FILE_LEFT_NEWER; + break; + case FILE_DIFFERENT: + case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: + case FILE_CONFLICT: + break; + } + notifySyncCfgChanged(); } @@ -856,9 +864,10 @@ void HierarchyObject::flip() inline DirMapping& HierarchyObject::addSubDir(const Zstring& shortNameLeft, - const Zstring& shortNameRight) + const Zstring& shortNameRight, + CompareDirResult defaultCmpResult) { - subDirs.emplace_back(shortNameLeft, shortNameRight, *this); + subDirs.emplace_back(shortNameLeft, shortNameRight, *this, defaultCmpResult); return subDirs.back(); } @@ -866,7 +875,7 @@ DirMapping& HierarchyObject::addSubDir(const Zstring& shortNameLeft, template <> inline DirMapping& HierarchyObject::addSubDir<LEFT_SIDE>(const Zstring& shortName) { - subDirs.emplace_back(shortName, Zstring(), *this); + subDirs.emplace_back(shortName, Zstring(), *this, DIR_LEFT_SIDE_ONLY); return subDirs.back(); } @@ -874,7 +883,7 @@ DirMapping& HierarchyObject::addSubDir<LEFT_SIDE>(const Zstring& shortName) template <> inline DirMapping& HierarchyObject::addSubDir<RIGHT_SIDE>(const Zstring& shortName) { - subDirs.emplace_back(Zstring(), shortName, *this); + subDirs.emplace_back(Zstring(), shortName, *this, DIR_RIGHT_SIDE_ONLY); return subDirs.back(); } @@ -949,30 +958,14 @@ void BaseDirMapping::flip() inline void DirMapping::flip() { - HierarchyObject ::flip(); //call base class versions FileSystemObject::flip(); // - - //swap compare result - switch (cmpResult) - { - case DIR_LEFT_SIDE_ONLY: - cmpResult = DIR_RIGHT_SIDE_ONLY; - break; - case DIR_RIGHT_SIDE_ONLY: - cmpResult = DIR_LEFT_SIDE_ONLY; - break; - case DIR_EQUAL: - case DIR_DIFFERENT_METADATA: - break; - } } inline void DirMapping::removeObjectL() { - cmpResult = isEmpty<RIGHT_SIDE>() ? DIR_EQUAL : DIR_RIGHT_SIDE_ONLY; std::for_each(refSubFiles().begin(), refSubFiles().end(), std::mem_fun_ref(&FileSystemObject::removeObject<LEFT_SIDE>)); std::for_each(refSubLinks().begin(), refSubLinks().end(), std::mem_fun_ref(&FileSystemObject::removeObject<LEFT_SIDE>)); std::for_each(refSubDirs(). begin(), refSubDirs() .end(), std::mem_fun_ref(&FileSystemObject::removeObject<LEFT_SIDE>)); @@ -982,7 +975,6 @@ void DirMapping::removeObjectL() inline void DirMapping::removeObjectR() { - cmpResult = isEmpty<LEFT_SIDE>() ? DIR_EQUAL : DIR_LEFT_SIDE_ONLY; std::for_each(refSubFiles().begin(), refSubFiles().end(), std::mem_fun_ref(&FileSystemObject::removeObject<RIGHT_SIDE>)); std::for_each(refSubLinks().begin(), refSubLinks().end(), std::mem_fun_ref(&FileSystemObject::removeObject<RIGHT_SIDE>)); std::for_each(refSubDirs(). begin(), refSubDirs(). end(), std::mem_fun_ref(&FileSystemObject::removeObject<RIGHT_SIDE>)); @@ -1007,55 +999,13 @@ inline void FileMapping::flip() { FileSystemObject::flip(); //call base class version - - //swap compare result - switch (cmpResult) - { - case FILE_LEFT_SIDE_ONLY: - cmpResult = FILE_RIGHT_SIDE_ONLY; - break; - case FILE_RIGHT_SIDE_ONLY: - cmpResult = FILE_LEFT_SIDE_ONLY; - break; - case FILE_LEFT_NEWER: - cmpResult = FILE_RIGHT_NEWER; - break; - case FILE_RIGHT_NEWER: - cmpResult = FILE_LEFT_NEWER; - break; - case FILE_DIFFERENT: - case FILE_EQUAL: - case FILE_DIFFERENT_METADATA: - case FILE_CONFLICT: - break; - } - std::swap(dataLeft, dataRight); } -template <CompareFilesResult res> inline -void FileMapping::setCategory() -{ - cmpResult = res; -} - -template <> inline -void FileMapping::setCategory<FILE_CONFLICT>(); //if conflict is detected, use setCategoryConflict! => method is not defined! - - -inline -void FileMapping::setCategoryConflict(const std::wstring& description) -{ - cmpResult = FILE_CONFLICT; - cmpConflictDescr.reset(new std::wstring(description)); -} - - inline void FileMapping::removeObjectL() { - cmpResult = isEmpty<RIGHT_SIDE>() ? FILE_EQUAL : FILE_RIGHT_SIDE_ONLY; dataLeft = FileDescriptor(); } @@ -1063,7 +1013,6 @@ void FileMapping::removeObjectL() inline void FileMapping::removeObjectR() { - cmpResult = isEmpty<LEFT_SIDE>() ? FILE_EQUAL : FILE_LEFT_SIDE_ONLY; dataRight = FileDescriptor(); } @@ -1118,7 +1067,6 @@ void FileMapping::syncTo<LEFT_SIDE>(const FileDescriptor& descrTarget, const Fil dataRight = *descrSource; moveFileRef = nullptr; - cmpResult = FILE_EQUAL; copyToL(); //copy FileSystemObject specific part } @@ -1131,7 +1079,6 @@ void FileMapping::syncTo<RIGHT_SIDE>(const FileDescriptor& descrTarget, const Fi dataLeft = *descrSource; moveFileRef = nullptr; - cmpResult = FILE_EQUAL; copyToR(); //copy FileSystemObject specific part } @@ -1140,7 +1087,6 @@ template <> inline void SymLinkMapping::copyTo<LEFT_SIDE>() //copy + update link attributes { dataLeft = dataRight; - cmpResult = SYMLINK_EQUAL; copyToL(); //copy FileSystemObject specific part } @@ -1149,7 +1095,6 @@ template <> inline void SymLinkMapping::copyTo<RIGHT_SIDE>() //copy + update link attributes { dataRight = dataLeft; - cmpResult = SYMLINK_EQUAL; copyToR(); //copy FileSystemObject specific part } @@ -1157,7 +1102,6 @@ void SymLinkMapping::copyTo<RIGHT_SIDE>() //copy + update link attributes template <> inline void DirMapping::copyTo<LEFT_SIDE>() { - cmpResult = DIR_EQUAL; copyToL(); //copy FileSystemObject specific part } @@ -1165,7 +1109,6 @@ void DirMapping::copyTo<LEFT_SIDE>() template <> inline void DirMapping::copyTo<RIGHT_SIDE>() { - cmpResult = DIR_EQUAL; copyToR(); //copy FileSystemObject specific part } @@ -1213,23 +1156,9 @@ const Zstring& SymLinkMapping::getTargetPath<RIGHT_SIDE>() const inline -CompareFilesResult SymLinkMapping::getCategory() const -{ - return convertToFilesResult(cmpResult); -} - - -inline CompareSymlinkResult SymLinkMapping::getLinkCategory() const { - return cmpResult; -} - - -inline -std::wstring SymLinkMapping::getCatConflict() const -{ - return cmpConflictDescr ? *cmpConflictDescr : std::wstring(); + return static_cast<CompareSymlinkResult>(getCategory()); } @@ -1237,28 +1166,6 @@ inline void SymLinkMapping::flip() { FileSystemObject::flip(); //call base class versions - - switch (cmpResult) - { - case SYMLINK_LEFT_SIDE_ONLY: - cmpResult = SYMLINK_RIGHT_SIDE_ONLY; - break; - case SYMLINK_RIGHT_SIDE_ONLY: - cmpResult = SYMLINK_LEFT_SIDE_ONLY; - break; - case SYMLINK_LEFT_NEWER: - cmpResult = SYMLINK_RIGHT_NEWER; - break; - case SYMLINK_RIGHT_NEWER: - cmpResult = SYMLINK_LEFT_NEWER; - break; - case SYMLINK_EQUAL: - case SYMLINK_DIFFERENT_METADATA: - case SYMLINK_DIFFERENT: - case SYMLINK_CONFLICT: - break; - } - std::swap(dataLeft, dataRight); } @@ -1266,7 +1173,6 @@ void SymLinkMapping::flip() inline void SymLinkMapping::removeObjectL() { - cmpResult = isEmpty<RIGHT_SIDE>() ? SYMLINK_EQUAL : SYMLINK_RIGHT_SIDE_ONLY; dataLeft = LinkDescriptor(); } @@ -1274,28 +1180,8 @@ void SymLinkMapping::removeObjectL() inline void SymLinkMapping::removeObjectR() { - cmpResult = isEmpty<LEFT_SIDE>() ? SYMLINK_EQUAL : SYMLINK_LEFT_SIDE_ONLY; dataRight = LinkDescriptor(); } - - -template <CompareSymlinkResult res> inline -void SymLinkMapping::setCategory() -{ - cmpResult = res; -} - - -template <> -void SymLinkMapping::setCategory<SYMLINK_CONFLICT>(); //if conflict is detected, use setCategoryConflict! => method is not defined! - - -inline -void SymLinkMapping::setCategoryConflict(const std::wstring& description) -{ - cmpResult = SYMLINK_CONFLICT; - cmpConflictDescr.reset(new std::wstring(description)); -} } #endif // FILEHIERARCHY_H_INCLUDED diff --git a/lib/binary.cpp b/lib/binary.cpp index 4fc1d408..2065e13e 100644 --- a/lib/binary.cpp +++ b/lib/binary.cpp @@ -44,7 +44,7 @@ public: operator size_t() const { return bufSize; } private: - static const size_t BUFFER_SIZE_MIN = 128 * 1024; + static const size_t BUFFER_SIZE_MIN = 64 * 1024; static const size_t BUFFER_SIZE_START = 512 * 1024; //512 kb seems to be a reasonable initial buffer size static const size_t BUFFER_SIZE_MAX = 16 * 1024 * 1024; @@ -72,9 +72,6 @@ const std::int64_t TICKS_PER_SEC = ticksPerSec(); bool zen::filesHaveSameContent(const Zstring& filename1, const Zstring& filename2, CompareCallback& callback) { - FileInput file1(filename1); //throw FileError - FileInput file2(filename2); //throw FileError - static boost::thread_specific_ptr<std::vector<char>> cpyBuf1; static boost::thread_specific_ptr<std::vector<char>> cpyBuf2; if (!cpyBuf1.get()) @@ -85,6 +82,9 @@ bool zen::filesHaveSameContent(const Zstring& filename1, const Zstring& filename std::vector<char>& memory1 = *cpyBuf1; std::vector<char>& memory2 = *cpyBuf2; + FileInput file1(filename1); //throw FileError + FileInput file2(filename2); // + BufferSize bufferSize; UInt64 bytesCompared; diff --git a/lib/db_file.cpp b/lib/db_file.cpp index 787325e2..2c299236 100644 --- a/lib/db_file.cpp +++ b/lib/db_file.cpp @@ -5,21 +5,12 @@ // ************************************************************************** #include "db_file.h" -#include <zen/file_error.h> #include <zen/file_handling.h> #include <zen/scope_guard.h> #include <zen/guid.h> #include <zen/utf.h> +#include <zen/serialize.h> #include <wx+/zlib_wrap.h> -#include <wx+/serialize.h> - -#ifdef FFS_WIN -warn_static("get rid of wx headers") -#endif -#include <wx/wfstream.h> -#include <wx/zstream.h> -#include <wx/mstream.h> -#include <zen/perf.h> #ifdef FFS_WIN #include <zen/win.h> //includes "windows.h" @@ -91,13 +82,6 @@ void saveStreams(const StreamMapping& streamList, const Zstring& filename) //thr } -#ifdef FFS_WIN -warn_static("remove after migration") -#endif - -StreamMapping loadStreams_v8(const Zstring& filename); //throw FileError - - StreamMapping loadStreams(const Zstring& filename) //throw FileError, FileErrorDatabaseNotExisting { try @@ -113,13 +97,7 @@ StreamMapping loadStreams(const Zstring& filename) //throw FileError, FileErrorD const int version = readNumber<std::int32_t>(streamIn); //throw UnexpectedEndOfStreamError if (version != FILE_FORMAT_VER) //read file format version# - //throw FileError(replaceCpy(_("Database file %x is incompatible."), L"%x", fmtFileName(filename))); - return loadStreams_v8(filename); - - #ifdef FFS_WIN -warn_static("fix after migration") -#endif - + throw FileError(replaceCpy(_("Database file %x is incompatible."), L"%x", fmtFileName(filename))); //read stream lists StreamMapping output; @@ -153,10 +131,6 @@ warn_static("fix after migration") //####################################################################################################################################### -#ifdef FFS_WIN -warn_static("remove v8Compatibilty after migration") -#endif - class StreamGenerator //for db-file back-wards compatibility we stick with two output streams until further { public: @@ -164,8 +138,7 @@ public: const Zstring& filenameL, //used for diagnostics only const Zstring& filenameR, BinaryStream& streamL, - BinaryStream& streamR, - bool v8Compatibilty) + BinaryStream& streamR) { StreamGenerator generator; @@ -210,12 +183,6 @@ public: size_t size1stPart = tmpB.size() / 2; size_t size2ndPart = tmpB.size() - size1stPart; - if (v8Compatibilty) - { - size1stPart = tmpB.size(); - size2ndPart = 0; - } - writeNumber<std::uint64_t>(outL, size1stPart); writeNumber<std::uint64_t>(outR, size2ndPart); @@ -326,13 +293,8 @@ public: bool has1stPartL = readNumber<bool>(inL); //throw UnexpectedEndOfStreamError bool has1stPartR = readNumber<bool>(inR); // - -#ifdef FFS_WIN -warn_static("restore check after migration!") -#endif - - //if (has1stPartL == has1stPartR) - // throw UnexpectedEndOfStreamError(); + if (has1stPartL == has1stPartR) + throw UnexpectedEndOfStreamError(); BinStreamIn& in1stPart = has1stPartL ? inL : inR; BinStreamIn& in2ndPart = has1stPartL ? inR : inL; @@ -766,7 +728,7 @@ void zen::saveLastSynchronousState(const BaseDirMapping& baseMapping) //throw Fi dbNameLeft, dbNameRight, updatedStreamLeft, - updatedStreamRight, false); //throw FileError + updatedStreamRight); //throw FileError //check if there is some work to do at all if (streamIterLeftOld != streamListLeft .end() && updatedStreamLeft == streamIterLeftOld ->second && @@ -803,179 +765,3 @@ void zen::saveLastSynchronousState(const BaseDirMapping& baseMapping) //throw Fi guardTempFileLeft. dismiss(); //no need to delete temp file anymore guardTempFileRight.dismiss(); // } - -#ifdef FFS_WIN -warn_static("remove after migration") -#endif - -namespace -{ -class CheckedDbReader : public CheckedReader -{ -public: - CheckedDbReader(wxInputStream& stream, const Zstring& errorObjName) : CheckedReader(stream), errorObjName_(errorObjName) {} - -private: - virtual void throwException() const { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(errorObjName_))); } - - const Zstring errorObjName_; -}; - - -class StreamParser_v8 //for db-file back-wards compatibility we stick with two output streams until further -{ -public: - static std::shared_ptr<InSyncDir> execute(const BinaryStream& streamL, const BinaryStream& streamR, //throw FileError - const Zstring& filenameL, //used for diagnostics only - const Zstring& filenameR) - { - try - { - auto output = std::make_shared<InSyncDir>(InSyncDir::STATUS_IN_SYNC); - StreamParser_v8 parser(streamL, streamR); //throw UnexpectedEndOfStreamError, std::bad_alloc - parser.recurse(*output); - return output; - } - catch (const UnexpectedEndOfStreamError&) - { - throw FileError(_("Database file is corrupt:") + L"\n" + fmtFileName(filenameL) + L"\n" + fmtFileName(filenameR)); - } - catch (const std::bad_alloc& e) - { - throw FileError(_("Out of memory!") + L" " + utfCvrtTo<std::wstring>(e.what())); - } - } - -private: - StreamParser_v8(const BinaryStream& bufferL, - const BinaryStream& bufferR) : - inputLeft (bufferL), //input is referenced only! - inputRight(bufferR) {} - - static Zstring readUtf8(BinStreamIn& input) { return utfCvrtTo<Zstring>(readContainer<Zbase<char>>(input)); } //throw UnexpectedEndOfStreamError - - static void read(BinStreamIn& input, Zstring& shortName, FileDescriptor& descr) - { - //attention: order of function argument evaluation is undefined! So do it one after the other... - shortName = readUtf8(input); - descr.lastWriteTimeRaw = readNumber<std::int64_t>(input); //throw UnexpectedEndOfStreamError - descr.fileSize = readNumber<std::uint64_t>(input); - descr.id.first = static_cast<decltype(descr.id.first )>(readNumber<std::uint64_t>(input)); // - descr.id.second = static_cast<decltype(descr.id.second)>(readNumber<std::uint64_t>(input)); //silence "loss of precision" compiler warnings - } - - static void read(BinStreamIn& input, Zstring& shortName, LinkDescriptor& descr) - { - shortName = readUtf8(input); - descr.lastWriteTimeRaw = readNumber<std::int64_t>(input); - descr.targetPath = readUtf8(input); //file name - descr.type = static_cast<LinkDescriptor::LinkType>(readNumber<std::int32_t>(input)); - } - - void recurse(InSyncDir& dir) - { - for (;;) //files - { - bool haveItemL = readNumber<bool>(inputLeft ); //remove redundancy in next db format - bool haveItemR = readNumber<bool>(inputRight); // - assert(haveItemL == haveItemR); - if (!haveItemL || !haveItemR) break; - - Zstring shortName; - FileDescriptor dataL; - FileDescriptor dataR; - read(inputLeft, shortName, dataL); - read(inputRight, shortName, dataR); - - dir.addFile(shortName, dataL, dataR, InSyncFile::IN_SYNC_ATTRIBUTES_EQUAL); - } - - for (;;) //symlinks - { - bool haveItemL = readNumber<bool>(inputLeft ); - bool haveItemR = readNumber<bool>(inputRight); - assert(haveItemL == haveItemR); - if (!haveItemL || !haveItemR) break; - - Zstring shortName; - LinkDescriptor dataL; - LinkDescriptor dataR; - read(inputLeft, shortName, dataL); - read(inputRight, shortName, dataR); - - dir.addSymlink(shortName, dataL, dataR); - } - - for (;;) //directories - { - bool haveItemL = readNumber<bool>(inputLeft ); - bool haveItemR = readNumber<bool>(inputRight); - assert(haveItemL == haveItemR); - if (!haveItemL || !haveItemR) break; - - Zstring shortName = readUtf8(inputLeft); - shortName = readUtf8(inputRight); - InSyncDir& subDir = dir.addDir(shortName, InSyncDir::STATUS_IN_SYNC); - recurse(subDir); - } - } - - BinStreamIn inputLeft; - BinStreamIn inputRight; -}; - - -StreamMapping loadStreams_v8(const Zstring& filename) //throw FileError -{ - try - { - //read format description (uncompressed) - FileInputStream rawStream(filename); //throw FileError, ErrorNotExisting - - //read FreeFileSync file identifier - char formatDescr[sizeof(FILE_FORMAT_DESCR)] = {}; - rawStream.Read(formatDescr, sizeof(formatDescr)); //throw FileError - - if (!std::equal(FILE_FORMAT_DESCR, FILE_FORMAT_DESCR + sizeof(FILE_FORMAT_DESCR), formatDescr)) - throw FileError(replaceCpy(_("Database file %x is incompatible."), L"%x", fmtFileName(filename))); - - wxZlibInputStream decompressed(rawStream, wxZLIB_ZLIB); - - CheckedDbReader cr(decompressed, filename); - - std::int32_t version = cr.readPOD<std::int32_t>(); - if (version != 8) //read file format version# - throw FileError(replaceCpy(_("Database file %x is incompatible."), L"%x", fmtFileName(filename))); - - //read stream lists - StreamMapping output; - - std::uint32_t dbCount = cr.readPOD<std::uint32_t>(); //number of databases: one for each sync-pair - while (dbCount-- != 0) - { - //DB id of partner databases - std::string sessionID = cr.readString<std::string>(); - BinaryStream stream = cr.readString<BinaryStream>(); //read db-entry stream (containing DirInformation) - - //convert streams - std::shared_ptr<InSyncDir> lastSyncState = StreamParser_v8::execute(stream, stream, filename, filename); //throw FileError - - //serialize again - BinaryStream strL; - BinaryStream strR; - StreamGenerator::execute(*lastSyncState, filename, filename, strL, strR, true); //throw FileError - output[sessionID] = std::move(strL); - } - return output; - } - catch (ErrorNotExisting&) - { - throw FileErrorDatabaseNotExisting(_("Initial synchronization:") + L" \n" + - replaceCpy(_("Database file %x does not yet exist."), L"%x", fmtFileName(filename))); - } - catch (const std::bad_alloc& e) - { - throw FileError(_("Out of memory!") + L" " + utfCvrtTo<std::wstring>(e.what())); - } -} -} diff --git a/lib/dir_lock.cpp b/lib/dir_lock.cpp index 682612a7..e385c9a8 100644 --- a/lib/dir_lock.cpp +++ b/lib/dir_lock.cpp @@ -17,7 +17,7 @@ #include <zen/assert_static.h> #include <zen/int64.h> #include <zen/file_handling.h> -#include <wx+/serialize.h> +#include <zen/serialize.h> #ifdef FFS_WIN #include <tlhelp32.h> @@ -27,7 +27,8 @@ #include <Lmcons.h> //UNLEN #elif defined FFS_LINUX -#include <sys/stat.h> +#include <fcntl.h> //::open() +#include <sys/stat.h> // #include <unistd.h> #endif @@ -484,7 +485,7 @@ void releaseLock(const Zstring& lockfilename) //throw () { try { - removeFile(lockfilename); + removeFile(lockfilename); //throw FileError } catch (...) {} } @@ -511,6 +512,8 @@ bool tryLock(const Zstring& lockfilename) //throw FileError } ::CloseHandle(fileHandle); + ::SetFileAttributes(applyLongPathPrefix(lockfilename).c_str(), FILE_ATTRIBUTE_HIDDEN); //(try to) hide it + #elif defined FFS_LINUX //O_EXCL contains a race condition on NFS file systems: http://linux.die.net/man/2/open ::umask(0); //important! -> why? diff --git a/lib/error_log.h b/lib/error_log.h new file mode 100644 index 00000000..07a8b383 --- /dev/null +++ b/lib/error_log.h @@ -0,0 +1,43 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef ERROR_LOG_89734181783491324134 +#define ERROR_LOG_89734181783491324134 + +#include <cassert> +#include <zen/serialize.h> +#include <zen/time.h> +#include "ffs_paths.h" + + +namespace zen +{ +//write error message to a file (even with corrupted stack)- call in desperate situations when no other means of error handling is available +void logError(const std::string& msg); //throw() + + + + + + + + + +//##################### implementation ############################ +inline +void logError(const std::string& msg) //throw() +{ + assert(false); //this is stuff we like to debug + const std::string logEntry = "[" + formatTime<std::string>(FORMAT_DATE) + " "+ formatTime<std::string>(FORMAT_TIME) + "] " + msg; + try + { + saveBinStream(getConfigDir() + Zstr("LastError.txt"), logEntry); //throw FileError + } + catch (const FileError&) {} +} +} + +#endif //ERROR_LOG_89734181783491324134 diff --git a/lib/generate_logfile.h b/lib/generate_logfile.h index 8feb696a..b54b4893 100644 --- a/lib/generate_logfile.h +++ b/lib/generate_logfile.h @@ -9,8 +9,8 @@ #include <zen/error_log.h> #include <zen/file_io.h> +#include <zen/serialize.h> #include <wx+/format_unit.h> -#include <wx+/serialize.h> #include "ffs_paths.h" @@ -129,7 +129,18 @@ void saveToLastSyncsLog(const Utf8String& logstream) //throw FileError } //limit file size: 128 kB (but do not truncate new log) - newStream.resize(std::min(newStream.size(), std::max<size_t>(logstream.size(), 128 * 1024))); + const size_t newSize = std::min(newStream.size(), std::max<size_t>(logstream.size(), 128 * 1024)); + + //do not cut in the middle of a row + auto iter = std::search(newStream.begin() + newSize, newStream.end(), std::begin(LINE_BREAK), std::end(LINE_BREAK) - 1); + if (iter != newStream.end()) + { + newStream.resize(iter - newStream.begin()); + + newStream += LINE_BREAK; + newStream += "[...]"; + newStream += LINE_BREAK; + } saveBinStream(filename, newStream); //throw FileError } diff --git a/lib/hard_filter.h b/lib/hard_filter.h index 90cd33fc..723c7bdf 100644 --- a/lib/hard_filter.h +++ b/lib/hard_filter.h @@ -252,7 +252,7 @@ HardFilter::FilterRef combineFilters(const HardFilter::FilterRef& first, if (first->isNull()) { if (second->isNull()) - return HardFilter::FilterRef(new NullFilter); + return std::make_shared<NullFilter>(); else return second; } @@ -261,7 +261,7 @@ HardFilter::FilterRef combineFilters(const HardFilter::FilterRef& first, if (second->isNull()) return first; else - return HardFilter::FilterRef(new CombinedFilter(first, second)); + return std::make_shared<CombinedFilter>(first, second); } } } diff --git a/lib/localization.cpp b/lib/localization.cpp index 5bbb31d1..16dcac9a 100644 --- a/lib/localization.cpp +++ b/lib/localization.cpp @@ -54,7 +54,6 @@ public: if (0 <= formNo && formNo < static_cast<int>(iter->second.size())) return iter->second[formNo]; } - return n == 1 ? singular : plural; //fallback } @@ -75,7 +74,7 @@ FFSLocale::FFSLocale(const wxString& filename, wxLanguage languageId) : langId_( std::string inputStream; try { - inputStream = loadStream(filename);; //throw XmlFileError + inputStream = loadStream(filename); //throw XmlFileError } catch (...) { @@ -92,7 +91,7 @@ FFSLocale::FFSLocale(const wxString& filename, wxLanguage languageId) : langId_( const std::wstring original = utfCvrtTo<std::wstring>(i->first); const std::wstring translation = utfCvrtTo<std::wstring>(i->second); assert(!translation.empty()); - transMapping.insert(std::make_pair(original , translation)); + transMapping.insert(std::make_pair(original, translation)); } for (lngfile::TranslationPluralMap::const_iterator i = transPluralInput.begin(); i != transPluralInput.end(); ++i) @@ -164,10 +163,10 @@ ExistingTranslations::ExistingTranslations() //default entry: ExistingTranslations::Entry newEntry; newEntry.languageID = wxLANGUAGE_ENGLISH_US; - newEntry.languageName = wxT("English (US)"); - newEntry.languageFile = wxT(""); - newEntry.translatorName = wxT("ZenJu"); - newEntry.languageFlag = wxT("usa.png"); + newEntry.languageName = L"English (US)"; + newEntry.languageFile = L""; + newEntry.translatorName = L"ZenJu"; + newEntry.languageFlag = L"usa.png"; locMapping.push_back(newEntry); } @@ -196,7 +195,7 @@ ExistingTranslations::ExistingTranslations() ExistingTranslations::Entry newEntry; newEntry.languageID = locInfo->Language; newEntry.languageName = utfCvrtTo<wxString>(lngHeader.languageName); - newEntry.languageFile = toWx(*i); + newEntry.languageFile = utfCvrtTo<wxString>(*i); newEntry.translatorName = utfCvrtTo<wxString>(lngHeader.translatorName); newEntry.languageFlag = utfCvrtTo<wxString>(lngHeader.flagFile); locMapping.push_back(newEntry); @@ -297,6 +296,7 @@ wxLanguage mapLanguageDialect(wxLanguage language) //case wxLANGUAGE_HUNGARIAN: //case wxLANGUAGE_PORTUGUESE: //case wxLANGUAGE_PORTUGUESE_BRAZILIAN: + //case wxLANGUAGE_SCOTS_GAELIC: //case wxLANGUAGE_KOREAN: //case wxLANGUAGE_UKRAINIAN: //case wxLANGUAGE_CROATIAN: @@ -365,6 +365,8 @@ private: void zen::setLanguage(int language) { + if (language == getLanguage()) return; //support polling + //(try to) retrieve language file wxString languageFile; @@ -375,13 +377,11 @@ void zen::setLanguage(int language) break; } - //handle RTL swapping: we need wxWidgets to do this static std::unique_ptr<CustomLocale> dummy; dummy.reset(); //avoid global locale lifetime overlap! wxWidgets cannot handle this and will crash! dummy.reset(new CustomLocale(languageFile.empty() ? wxLANGUAGE_ENGLISH : language)); - //reset to english language; in case of error show error message just once zen::setTranslator(); @@ -408,7 +408,7 @@ void zen::setLanguage(int language) int zen::getLanguage() { - FFSLocale* loc = dynamic_cast<FFSLocale*>(zen::getTranslator()); + const FFSLocale* loc = dynamic_cast<const FFSLocale*>(zen::getTranslator()); return loc ? loc->langId() : wxLANGUAGE_ENGLISH_US; } diff --git a/lib/perf_check.cpp b/lib/perf_check.cpp index ab0f7769..f01af061 100644 --- a/lib/perf_check.cpp +++ b/lib/perf_check.cpp @@ -154,7 +154,7 @@ z_1 + z_2 * X / m = F / m => we obtain a new (artificial) measurement with size X / m and time F / m to be used in the linear approximation above -Statistics::Statistics(const int totalObjectCount, const double totalDataAmount, const unsigned recordCount) : +Statistics::Statistics(int totalObjectCount, double totalDataAmount, unsigned recordCount) : objectsTotal(totalObjectCount), dataTotal(totalDataAmount), recordsMax(recordCount), @@ -166,7 +166,7 @@ Statistics::Statistics(const int totalObjectCount, const double totalDataAmount, dummyRecordPresent(false) {} -wxString Statistics::getRemainingTime(const int objectsCurrent, const double dataCurrent) +wxString Statistics::getRemainingTime(int objectsCurrent, double dataCurrent) { //add new measurement point const int m = objectsCurrent - objectsLast; diff --git a/lib/process_xml.cpp b/lib/process_xml.cpp index 39b1520b..f5a6a4d1 100644 --- a/lib/process_xml.cpp +++ b/lib/process_xml.cpp @@ -542,13 +542,53 @@ bool readText(const std::string& input, UnitTime& value) template <> inline void writeText(const ColumnTypeRim& value, std::string& output) { - output = numberTo<std::string>(value); + switch (value) + { + case COL_TYPE_DIRECTORY: + output = "Base"; + break; + case COL_TYPE_FULL_PATH: + output = "Full"; + break; + case COL_TYPE_REL_PATH: + output = "Rel"; + break; + case COL_TYPE_FILENAME: + output = "Name"; + break; + case COL_TYPE_SIZE: + output = "Size"; + break; + case COL_TYPE_DATE: + output = "Date"; + break; + case COL_TYPE_EXTENSION: + output = "Ext"; + break; + } } template <> inline bool readText(const std::string& input, ColumnTypeRim& value) { - value = static_cast<ColumnTypeRim>(stringTo<int>(input)); + std::string tmp = input; + zen::trim(tmp); + if (tmp == "Base") + value = COL_TYPE_DIRECTORY; + else if (tmp == "Full") + value = COL_TYPE_FULL_PATH; + else if (tmp == "Rel") + value = COL_TYPE_REL_PATH; + else if (tmp == "Name") + value = COL_TYPE_FILENAME; + else if (tmp == "Size") + value = COL_TYPE_SIZE; + else if (tmp == "Date") + value = COL_TYPE_DATE; + else if (tmp == "Ext") + value = COL_TYPE_EXTENSION; + else + return false; return true; } @@ -556,13 +596,28 @@ bool readText(const std::string& input, ColumnTypeRim& value) template <> inline void writeText(const ColumnTypeNavi& value, std::string& output) { - output = numberTo<std::string>(value); + switch (value) + { + case COL_TYPE_NAVI_BYTES: + output = "Bytes"; + break; + case COL_TYPE_NAVI_DIRECTORY: + output = "Tree"; + break; + } } template <> inline bool readText(const std::string& input, ColumnTypeNavi& value) { - value = static_cast<ColumnTypeNavi>(stringTo<int>(input)); + std::string tmp = input; + zen::trim(tmp); + if (tmp == "Bytes") + value = COL_TYPE_NAVI_BYTES; + else if (tmp == "Tree") + value = COL_TYPE_NAVI_DIRECTORY; + else + return false; return true; } @@ -651,17 +706,19 @@ bool readStruc(const XmlElement& input, ColumnAttributeRim& value) XmlIn in(input); bool rv1 = in.attribute("Type", value.type_); bool rv2 = in.attribute("Visible", value.visible_); - bool rv3 = in.attribute("Width", value.width_); - return rv1 && rv2 && rv3; + 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) { XmlOut out(output); - out.attribute("Type", value.type_); - out.attribute("Visible", value.visible_); - out.attribute("Width", value.width_); + out.attribute("Type", value.type_); + out.attribute("Visible", value.visible_); + out.attribute("Width", value.offset_); + out.attribute("Stretch", value.stretch_); } @@ -671,8 +728,9 @@ bool readStruc(const XmlElement& input, ColumnAttributeNavi& value) XmlIn in(input); bool rv1 = in.attribute("Type", value.type_); bool rv2 = in.attribute("Visible", value.visible_); - bool rv3 = in.attribute("Width", value.width_); - return rv1 && rv2 && rv3; + 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 @@ -681,7 +739,8 @@ void writeStruc(const ColumnAttributeNavi& value, XmlElement& output) XmlOut out(output); out.attribute("Type", value.type_); out.attribute("Visible", value.visible_); - out.attribute("Width", value.width_); + out.attribute("Width", value.offset_); + out.attribute("Stretch", value.stretch_); } } @@ -747,7 +806,7 @@ void readConfig(const XmlIn& in, FolderPairEnh& enhPair) CompConfig altCmpCfg; readConfig(inAltCmp, altCmpCfg); - enhPair.altCmpConfig = std::make_shared<CompConfig>(altCmpCfg);; + enhPair.altCmpConfig = std::make_shared<CompConfig>(altCmpCfg); } //########################################################### //alternate sync configuration (optional) @@ -883,12 +942,14 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) inColNavi(config.gui.columnAttribNavi); inColNavi.attribute("ShowPercentage", config.gui.showPercentBar); - inColNavi.attribute("SortByColumn", config.gui.naviLastSortColumn); - inColNavi.attribute("SortAscending", config.gui.naviLastSortAscending); + inColNavi.attribute("SortByColumn", config.gui.naviLastSortColumn); + inColNavi.attribute("SortAscending", config.gui.naviLastSortAscending); XmlIn inMainGrid = inWnd["MainGrid"]; - inMainGrid.attribute("ShowIcons", config.gui.showIcons); - inMainGrid.attribute("IconSize", config.gui.iconSize); + inMainGrid.attribute("ShowIcons", config.gui.showIcons); + inMainGrid.attribute("IconSize", config.gui.iconSize); + inMainGrid.attribute("SashOffset", config.gui.sashOffset); + XmlIn inColLeft = inMainGrid["ColumnsLeft"]; inColLeft(config.gui.columnAttribLeft); @@ -1153,12 +1214,13 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) outColNavi(config.gui.columnAttribNavi); outColNavi.attribute("ShowPercentage", config.gui.showPercentBar); - outColNavi.attribute("SortByColumn", config.gui.naviLastSortColumn); - outColNavi.attribute("SortAscending", config.gui.naviLastSortAscending); + outColNavi.attribute("SortByColumn", config.gui.naviLastSortColumn); + outColNavi.attribute("SortAscending", config.gui.naviLastSortAscending); XmlOut outMainGrid = outWnd["MainGrid"]; - outMainGrid.attribute("ShowIcons", config.gui.showIcons); - outMainGrid.attribute("IconSize", config.gui.iconSize); + outMainGrid.attribute("ShowIcons", config.gui.showIcons); + outMainGrid.attribute("IconSize", config.gui.iconSize); + outMainGrid.attribute("SashOffset", config.gui.sashOffset); XmlOut outColLeft = outMainGrid["ColumnsLeft"]; outColLeft(config.gui.columnAttribLeft); diff --git a/lib/process_xml.h b/lib/process_xml.h index 43dedb51..540b4ef3 100644 --- a/lib/process_xml.h +++ b/lib/process_xml.h @@ -147,6 +147,7 @@ struct XmlGlobalSettings dlgPos(wxDefaultCoord, wxDefaultCoord), dlgSize(wxDefaultCoord, wxDefaultCoord), isMaximized(false), + sashOffset(0), maxFolderPairsVisible(6), columnAttribNavi (zen::getDefaultColumnAttributesNavi()), columnAttribLeft (zen::getDefaultColumnAttributesLeft()), @@ -184,6 +185,7 @@ struct XmlGlobalSettings wxPoint dlgPos; wxSize dlgSize; bool isMaximized; + int sashOffset; int maxFolderPairsVisible; diff --git a/process_callback.h b/process_callback.h index c7681bb0..f67b4797 100644 --- a/process_callback.h +++ b/process_callback.h @@ -30,7 +30,7 @@ struct ProcessCallback PHASE_COMPARING_CONTENT, PHASE_SYNCHRONIZING }; - virtual void initNewPhase(int objectsTotal, zen::Int64 dataTotal, Phase phaseId) = 0; //informs about the total amount of data that will be processed in this phase + virtual void initNewPhase(int objectsTotal, zen::Int64 dataTotal, Phase phaseId) = 0; //informs about the estimated amount of data that will be processed in this phase //note: this one must NOT throw in order to properly allow undoing setting of statistics! //it is in general paired with a call to requestUiRefresh() to compensate! @@ -47,7 +47,7 @@ struct ProcessCallback //opportunity to abort must be implemented in a frequently executed method like requestUiRefresh() virtual void requestUiRefresh() = 0; //throw ? - virtual void forceUiRefresh () = 0; //throw ? - call before starting long running task which doesn't update regularly + virtual void forceUiRefresh () = 0; //throw ? - called before starting long running task which doesn't update regularly //called periodically after data was processed: expected(!) to request GUI update virtual void reportStatus(const std::wstring& text) = 0; //UI info only, should not be logged! diff --git a/structures.cpp b/structures.cpp index 3147eb4c..7becbf55 100644 --- a/structures.cpp +++ b/structures.cpp @@ -150,9 +150,9 @@ std::wstring zen::getSymbol(CompareFilesResult cmpRes) case FILE_DIFFERENT: return L"!="; case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: //= sub-category of equal! return L"'=="; //added quotation mark to avoid error in Excel cell when exporting to *.cvs case FILE_CONFLICT: - case FILE_DIFFERENT_METADATA: return L"conflict"; } assert(false); diff --git a/structures.h b/structures.h index 09d11a60..58c157b9 100644 --- a/structures.h +++ b/structures.h @@ -72,7 +72,7 @@ enum CompareSymlinkResult SYMLINK_CONFLICT = FILE_CONFLICT }; - +/* inline CompareFilesResult convertToFilesResult(CompareDirResult value) { @@ -85,7 +85,7 @@ CompareFilesResult convertToFilesResult(CompareSymlinkResult value) { return static_cast<CompareFilesResult>(value); } - +*/ std::wstring getSymbol(CompareFilesResult cmpRes); diff --git a/synchronization.cpp b/synchronization.cpp index 9dd98776..73e31e68 100644 --- a/synchronization.cpp +++ b/synchronization.cpp @@ -308,93 +308,111 @@ bool significantDifferenceDetected(const SyncStatistics& folderPairStat) //################################################################################################################# /* -class PhysicalStatistics //counts *physical* operations, disk accesses and bytes transferred +class PhysicalStatistics //counts *physical* operations, actual items processed (NOT disk accesses) and bytes transferred { public: -PhysicalStatistics(const FolderComparison& folderCmp) : accesses(0) -{ - std::for_each(begin(folderCmp), end(folderCmp), [&](const BaseDirMapping& baseMap) { recurse(baseMap); }); -} + PhysicalStatistics(const FolderComparison& folderCmp) : items(0) + { + delType =; + std::for_each(begin(folderCmp), end(folderCmp), [&](const BaseDirMapping& baseMap) { recurse(baseMap); }); + } -int getAccesses() const { return accesses; } -zen::Int64 getBytes() const { return bytes; } + int getItems() const { return items; } + Int64 getBytes() const { return bytes; } private: -void recurse(const HierarchyObject& hierObj) + enum DeletionType { -std::for_each(hierObj.refSubDirs ().begin(), hierObj.refSubDirs ().end(), [&](const DirMapping& dirObj ) { calcStats(dirObj ); }); -std::for_each(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(), [&](const FileMapping& fileObj) { calcStats(fileObj); }); -std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), [&](const SymLinkMapping& linkObj) { calcStats(linkObj); }); -} + DEL_PERMANENTLY, + DEL_RECYCLE_BIN, + VERSIONING_SAME_VOL, + VERSIONING_DIFF_VOL, +}; -void calcStats(const FileMapping& fileObj) -{ -switch (fileObj.getSyncOperation()) //evaluate comparison result and sync direction -{ - case SO_CREATE_NEW_LEFT: - accesses += 2; //read + write - bytes += to<Int64>(fileObj.getFileSize<RIGHT_SIDE>()); - break; + void recurse(const HierarchyObject& hierObj) + { + std::for_each(hierObj.refSubDirs ().begin(), hierObj.refSubDirs ().end(), [&](const DirMapping& dirObj ) { calcStats(dirObj ); }); + std::for_each(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(), [&](const FileMapping& fileObj) { calcStats(fileObj); }); + std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), [&](const SymLinkMapping& linkObj) { calcStats(linkObj); }); + } - case SO_CREATE_NEW_RIGHT: - accesses += 2; //read + write - bytes += to<Int64>(fileObj.getFileSize<LEFT_SIDE>()); - break; + void calcStats(const FileMapping& fileObj) + { + switch (fileObj.getSyncOperation()) //evaluate comparison result and sync direction + { + case SO_CREATE_NEW_LEFT: + items += 2; + bytes += to<Int64>(fileObj.getFileSize<RIGHT_SIDE>()); + break; - case SO_DELETE_LEFT: - ++accesses; - break; + case SO_CREATE_NEW_RIGHT: + ++items; + bytes += to<Int64>(fileObj.getFileSize<LEFT_SIDE>()); + break; - case SO_DELETE_RIGHT: - ++accesses; - break; + case SO_DELETE_LEFT: + switch (delType) + { + case DEL_INSTANTLY: + ++items; + break; + case DEL_COPY_DELETE: + break; + } + break; - case SO_MOVE_LEFT_TARGET: - case SO_MOVE_RIGHT_TARGET: - ++accesses; - break; + case SO_DELETE_RIGHT: + ++items; + break; - case SO_MOVE_LEFT_SOURCE: //ignore; already counted - case SO_MOVE_RIGHT_SOURCE: // - break; + case SO_MOVE_LEFT_TARGET: + case SO_MOVE_RIGHT_TARGET: + ++items; + break; - case SO_OVERWRITE_LEFT: - //todo: delete - accesses += 2; //read + write - bytes += to<Int64>(fileObj.getFileSize<RIGHT_SIDE>()); - break; + case SO_MOVE_LEFT_SOURCE: //ignore; already counted + case SO_MOVE_RIGHT_SOURCE: // + break; - case SO_OVERWRITE_RIGHT: - //todo: delete - accesses += 2; //read + write - bytes += to<Int64>(fileObj.getFileSize<LEFT_SIDE>()); - break; + case SO_OVERWRITE_LEFT: + //todo: delete + items += 2; //read + write + bytes += to<Int64>(fileObj.getFileSize<RIGHT_SIDE>()); + break; - case SO_COPY_METADATA_TO_LEFT: - case SO_COPY_METADATA_TO_RIGHT: - accesses += 2; //read + write - break; + case SO_OVERWRITE_RIGHT: + //todo: delete + items += 2; //read + write + bytes += to<Int64>(fileObj.getFileSize<LEFT_SIDE>()); + break; - case SO_UNRESOLVED_CONFLICT: - case SO_DO_NOTHING: - case SO_EQUAL: - break; -} -} + case SO_COPY_METADATA_TO_LEFT: + case SO_COPY_METADATA_TO_RIGHT: + ++items; + break; -void calcStats(const SymLinkMapping& linkObj) -{ + case SO_UNRESOLVED_CONFLICT: + case SO_DO_NOTHING: + case SO_EQUAL: + break; + } + } -} + void calcStats(const SymLinkMapping& linkObj) + { -void calcStats(const DirMapping& dirObj) -{ - //since we model physical stats, we recurse only if deletion variant is "permanently" or "user-defined + different volume", - //else deletion is done as a single physical operation -} + } + + void calcStats(const DirMapping& dirObj) + { + //since we model physical stats, we recurse only if deletion variant is "permanently" or "user-defined + different volume", + //else deletion is done as a single physical operation + } + + int items; + Int64 bytes; -int accesses; -Int64 bytes; +DeletionType delType; }; */ //################################################################################################################# @@ -437,7 +455,6 @@ SyncProcess::SyncProcess(const std::wstring& jobName, m_warnings(warnings), procCallback(handler), custDelDirShortname(utfCvrtTo<Zstring>(jobName.empty() ? timestamp : jobName + L" " + timestamp)) - { if (runWithBackgroundPriority) procBackground.reset(new ScheduleForBackgroundProcessing); @@ -1998,17 +2015,15 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf } - if (folderPairStat.getUpdate() + folderPairStat.getDelete() > 0) + if (folderPairStat.getUpdate() + folderPairStat.getDelete() > 0 && + folderPairCfg.handleDeletion == zen::MOVE_TO_CUSTOM_DIRECTORY) { - if (folderPairCfg.handleDeletion == zen::MOVE_TO_CUSTOM_DIRECTORY) + //check if user-defined directory for deletion was specified + if (folderPairCfg.custDelFolder.empty()) //already trimmed by getFormattedDirectoryName() { - //check if user-defined directory for deletion was specified - if (folderPairCfg.custDelFolder.empty()) - { - procCallback.reportFatalError(_("Folder input field for versioning must not be empty.")); - skipFolderPair[folderIndex] = true; - continue; - } + procCallback.reportFatalError(_("Folder input field for versioning must not be empty.")); + skipFolderPair[folderIndex] = true; + continue; } } @@ -2186,10 +2201,9 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf std::wstring right = _("Right") + L": "; makeSameLength(left, right); - const std::wstring statusTxt = _("Processing folder pair:") + L"\n" + - L" " + left + fmtFileName(j->getBaseDirPf<LEFT_SIDE >()) + L"\n" + - L" " + right + fmtFileName(j->getBaseDirPf<RIGHT_SIDE>()); - procCallback.reportInfo(statusTxt); + procCallback.reportInfo(_("Synchronize folder pair:") + L"\n" + + L" " + left + fmtFileName(j->getBaseDirPf<LEFT_SIDE >()) + L"\n" + + L" " + right + fmtFileName(j->getBaseDirPf<RIGHT_SIDE>())); //------------------------------------------------------------------------------------------ const size_t folderIndex = j - begin(folderCmp); @@ -2253,7 +2267,7 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf { if (folderPairCfg.inAutomaticMode) try { zen::saveLastSynchronousState(*j); } - catch (...) {} //throw FileError + catch (...) {} //throw FileError }); //guarantee removal of invalid entries (where element on both sides is empty) @@ -2358,14 +2372,19 @@ void SynchronizeFolderPair::copyFileUpdatingTo(const FileMapping& fileObj, const &callback, &newAttr); //throw FileError, ErrorFileLocked + //#################### Verification ############################# + if (verifyCopiedFiles_) + { + ScopeGuard guardTarget = makeGuard([&] { removeFile(target); }); //delete target if verification fails + verifyFileCopy(source, target); //throw FileError + guardTarget.dismiss(); + } + //#################### /Verification ############################# + //inform about the (remaining) processed amount of data if (bytesReported != expectedBytesToCpy) procCallback_.updateTotalData(0, bytesReported - expectedBytesToCpy); -#ifdef FFS_WIN - warn_static("clarify: return physical(bytesReported) or logical(newAttr) numbers") -#endif - //we model physical statistic numbers => adjust total: consider ADS, sparse, compressed files -> transferred bytes may differ from file size (which is just a rough guess)! guardStatistics.dismiss(); @@ -2397,29 +2416,6 @@ void SynchronizeFolderPair::copyFileUpdatingTo(const FileMapping& fileObj, const #else copyOperation(); #endif - - -#ifdef FFS_WIN - warn_static("make verification stats a first class citizen?") -#endif - - - - //#################### Verification ############################# - if (verifyCopiedFiles_) - { - ScopeGuard guardTarget = makeGuard([&] { removeFile(target); }); //delete target if verification fails - ScopeGuard guardStatistics = makeGuard([&] - { - procCallback_.updateProcessedData(0, -bytesReported); - bytesReported = 0; - }); - - verifyFileCopy(source, target); //throw FileError - - guardTarget.dismiss(); - guardStatistics.dismiss(); - } } diff --git a/synchronization.h b/synchronization.h index dfead072..78cd8a61 100644 --- a/synchronization.h +++ b/synchronization.h @@ -37,7 +37,7 @@ public: typedef std::vector<std::pair<Zstring, std::wstring> > ConflictTexts; // Pair(filename/conflict text) const ConflictTexts& getConflictMessages() const { return conflictMsgs; } - zen::Int64 getDataToProcess() const { return dataToProcess; } + Int64 getDataToProcess() const { return dataToProcess; } size_t getRowCount() const { return rowsTotal; } private: @@ -55,7 +55,7 @@ private: int deleteLeft, deleteRight; // int conflict; ConflictTexts conflictMsgs; //conflict texts to display as a warning message - zen::Int64 dataToProcess; + Int64 dataToProcess; size_t rowsTotal; }; diff --git a/ui/batch_config.cpp b/ui/batch_config.cpp index 75033bf8..059996e8 100644 --- a/ui/batch_config.cpp +++ b/ui/batch_config.cpp @@ -61,7 +61,7 @@ private: void OnFilesDropped(FileDropEvent& event); void addFolderPair(const std::vector<zen::FolderPairEnh>& newPairs, bool addFront = false); - void removeAddFolderPair(const int pos); + void removeAddFolderPair(int pos); void clearAddFolderPairs(); void updateGuiForFolderPair(); @@ -830,7 +830,7 @@ void BatchDialog::addFolderPair(const std::vector<zen::FolderPairEnh>& newPairs, } -void BatchDialog::removeAddFolderPair(const int pos) +void BatchDialog::removeAddFolderPair(int pos) { wxWindowUpdateLocker dummy(m_panelOverview); //avoid display distortion diff --git a/ui/batch_status_handler.cpp b/ui/batch_status_handler.cpp index c0958025..baab3ed3 100644 --- a/ui/batch_status_handler.cpp +++ b/ui/batch_status_handler.cpp @@ -127,7 +127,8 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, totalTime.Start(); //measure total time - //::wxSetEnv(L"logfile", logFile->getLogfileName()); + //if (logFile) + // ::wxSetEnv(L"logfile", utfCvrtTo<wxString>(logFile->getFilename())); -> requires a command line interpreter to take advantage of } @@ -306,6 +307,8 @@ void BatchStatusHandler::reportWarning(const std::wstring& warningMessage, bool& ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& errorMessage) { + errorLog.logMsg(errorMessage, TYPE_ERROR); //always, even for "retry" + switch (handleError_) { case xmlAccess::ON_ERROR_POPUP: @@ -321,26 +324,23 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er case ReturnErrorDlg::BUTTON_IGNORE: if (ignoreNextErrors) //falsify only handleError_ = xmlAccess::ON_ERROR_IGNORE; - errorLog.logMsg(errorMessage, TYPE_ERROR); return ProcessCallback::IGNORE_ERROR; case ReturnErrorDlg::BUTTON_RETRY: return ProcessCallback::RETRY; case ReturnErrorDlg::BUTTON_CANCEL: - errorLog.logMsg(errorMessage, TYPE_ERROR); abortThisProcess(); + break; } } break; //used if last switch didn't find a match case xmlAccess::ON_ERROR_EXIT: //abort - errorLog.logMsg(errorMessage, TYPE_ERROR); abortThisProcess(); break; case xmlAccess::ON_ERROR_IGNORE: - errorLog.logMsg(errorMessage, TYPE_ERROR); return ProcessCallback::IGNORE_ERROR; } diff --git a/ui/column_attr.h b/ui/column_attr.h index 517961f4..ed98f403 100644 --- a/ui/column_attr.h +++ b/ui/column_attr.h @@ -25,11 +25,12 @@ enum ColumnTypeRim struct ColumnAttributeRim { - ColumnAttributeRim() : type_(COL_TYPE_DIRECTORY), width_(0), visible_(false) {} - ColumnAttributeRim(ColumnTypeRim type, int width, bool visible) : type_(type), width_(width), visible_(visible) {} + ColumnAttributeRim() : type_(COL_TYPE_DIRECTORY), offset_(0), stretch_(0), visible_(false) {} + ColumnAttributeRim(ColumnTypeRim type, int offset, int stretch, bool visible) : type_(type), offset_(offset), stretch_(stretch), visible_(visible) {} ColumnTypeRim type_; - int width_; //negative value stretches proportionally! + int offset_; + int stretch_; bool visible_; }; @@ -39,26 +40,26 @@ namespace std::vector<ColumnAttributeRim> getDefaultColumnAttributesLeft() { std::vector<ColumnAttributeRim> attr; - attr.push_back(ColumnAttributeRim(COL_TYPE_FULL_PATH, 250, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_DIRECTORY, 200, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_REL_PATH, 200, true)); - attr.push_back(ColumnAttributeRim(COL_TYPE_FILENAME, 150, true)); - attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, true)); - attr.push_back(ColumnAttributeRim(COL_TYPE_DATE, 112, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_FULL_PATH, 250, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_DIRECTORY, 200, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_REL_PATH, 200, 0, true)); + attr.push_back(ColumnAttributeRim(COL_TYPE_FILENAME, -280, 1, true)); //stretch to full width and substract sum of fixed size widths! + attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, 0, true)); + attr.push_back(ColumnAttributeRim(COL_TYPE_DATE, 112, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, 0, false)); return attr; } std::vector<ColumnAttributeRim> getDefaultColumnAttributesRight() { std::vector<ColumnAttributeRim> attr; - attr.push_back(ColumnAttributeRim(COL_TYPE_FULL_PATH, 250, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_DIRECTORY, 200, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_REL_PATH, 200, false)); //already shown on left side - attr.push_back(ColumnAttributeRim(COL_TYPE_FILENAME, 150, true)); - attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, true)); - attr.push_back(ColumnAttributeRim(COL_TYPE_DATE, 112, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_FULL_PATH, 250, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_DIRECTORY, 200, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_REL_PATH, 200, 0, false)); //already shown on left side + attr.push_back(ColumnAttributeRim(COL_TYPE_FILENAME, -80, 1, true)); //stretch to full width and substract sum of fixed size widths! + attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, 0, true)); + attr.push_back(ColumnAttributeRim(COL_TYPE_DATE, 112, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, 0, false)); return attr; } } @@ -83,11 +84,12 @@ enum ColumnTypeNavi struct ColumnAttributeNavi { - ColumnAttributeNavi() : type_(COL_TYPE_NAVI_DIRECTORY), width_(0), visible_(false) {} - ColumnAttributeNavi(ColumnTypeNavi type, int width, bool visible) : type_(type), width_(width), visible_(visible) {} + ColumnAttributeNavi() : type_(COL_TYPE_NAVI_DIRECTORY), offset_(0), stretch_(0), visible_(false) {} + ColumnAttributeNavi(ColumnTypeNavi type, int offset, int stretch, bool visible) : type_(type), offset_(offset), stretch_(stretch), visible_(visible) {} ColumnTypeNavi type_; - int width_; //negative value stretches proportionally! + int offset_; + int stretch_; bool visible_; }; @@ -100,19 +102,8 @@ inline std::vector<ColumnAttributeNavi> getDefaultColumnAttributesNavi() { std::vector<ColumnAttributeNavi> attr; - - ColumnAttributeNavi newEntry; - - newEntry.type_ = COL_TYPE_NAVI_DIRECTORY; - newEntry.visible_ = true; - newEntry.width_ = -1; //stretch, old value: 280; - attr.push_back(newEntry); - - newEntry.type_ = COL_TYPE_NAVI_BYTES; - newEntry.visible_ = true; - newEntry.width_ = 60; //GTK needs a few pixels more - attr.push_back(newEntry); - + attr.push_back(ColumnAttributeNavi(COL_TYPE_NAVI_DIRECTORY, -60, 1, true)); //stretch to full width and substract sum of fixed size widths! + attr.push_back(ColumnAttributeNavi(COL_TYPE_NAVI_BYTES, 60, 0, true)); //GTK needs a few pixels width more return attr; } } diff --git a/ui/custom_grid.cpp b/ui/custom_grid.cpp index f340a819..0e27f21e 100644 --- a/ui/custom_grid.cpp +++ b/ui/custom_grid.cpp @@ -39,7 +39,8 @@ const wxColour COLOR_NOT_ACTIVE (228, 228, 228); //light grey const Zstring ICON_FILE_FOLDER = Zstr("folder"); const int CHECK_BOX_IMAGE = 12; //width of checkbox image -const int CHECK_BOX_WIDTH = CHECK_BOX_IMAGE + 2; //width of first block +const int CHECK_BOX_SPACE_LEFT = 2; +const int CHECK_BOX_WIDTH = CHECK_BOX_SPACE_LEFT + CHECK_BOX_IMAGE; //width of first block const size_t ROW_COUNT_NO_DATA = 10; @@ -58,9 +59,9 @@ class hierarchy: -void refreshCell(Grid& grid, size_t row, ColumnType colType, size_t compPos) +void refreshCell(Grid& grid, size_t row, ColumnType colType) { - wxRect cellArea = grid.getCellArea(row, colType, compPos); //returns empty rect if column not found; absolute coordinates! + wxRect cellArea = grid.getCellArea(row, colType); //returns empty rect if column not found; absolute coordinates! if (cellArea.height > 0) { cellArea.SetTopLeft(grid.CalcScrolledPosition(cellArea.GetTopLeft())); @@ -115,7 +116,7 @@ struct IconManager class GridDataBase : public GridData { public: - GridDataBase(Grid& grid) : grid_(grid) {} + GridDataBase(Grid& grid, const std::shared_ptr<const zen::GridView>& gridDataView) : grid_(grid), gridDataView_(gridDataView) {} void holdOwnership(const std::shared_ptr<GridEventManager>& evtMgr) { evtMgr_ = evtMgr; } @@ -123,18 +124,42 @@ protected: Grid& refGrid() { return grid_; } const Grid& refGrid() const { return grid_; } + const GridView* getGridDataView() const { return gridDataView_.get(); } + + const FileSystemObject* getRawData(size_t row) const + { + if (auto view = getGridDataView()) + return view->getObject(row); + return nullptr; + } + private: + virtual size_t getRowCount() const + { + if (gridDataView_) + { + if (gridDataView_->rowsTotal() == 0) + return ROW_COUNT_NO_DATA; + return gridDataView_->rowsOnView(); + } + else + return ROW_COUNT_NO_DATA; + + //return std::max(MIN_ROW_COUNT, gridDataView_ ? gridDataView_->rowsOnView() : 0); + } + std::shared_ptr<GridEventManager> evtMgr_; Grid& grid_; - + std::shared_ptr<const GridView> gridDataView_; }; + //######################################################################################################## template <SelectedSide side> class GridDataRim : public GridDataBase { public: - GridDataRim(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid, size_t compPos) : GridDataBase(grid), gridDataView_(gridDataView), compPos_(compPos) {} + GridDataRim(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataBase(grid, gridDataView) {} void setIconManager(const std::shared_ptr<IconManager>& iconMgr) { iconMgr_ = iconMgr; } @@ -165,7 +190,7 @@ public: if (iconMgr_->iconBuffer.requestFileIcon(fileName)) { //do a *full* refresh for *every* failed load to update partial DC updates while scrolling - refreshCell(refGrid(), currentRow, static_cast<ColumnType>(COL_TYPE_FILENAME), compPos_); + refreshCell(refGrid(), currentRow, static_cast<ColumnType>(COL_TYPE_FILENAME)); setFailedLoad(currentRow, false); } else //not yet in buffer: mark for async. loading @@ -203,6 +228,15 @@ protected: //accessibility, support high-contrast schemes => work with user-defined background color! const auto backCol = getBackGroundColor(row); + auto incChannel = [](unsigned char c, int diff) { return static_cast<unsigned char>(std::max(0, std::min(255, c + diff))); }; + + auto getAdjustedColor = [&](int diff) + { + return wxColor(incChannel(backCol.Red (), diff), + incChannel(backCol.Green(), diff), + incChannel(backCol.Blue (), diff)); + }; + auto colorDist = [](const wxColor& lhs, const wxColor& rhs) //just some metric { return numeric::power<2>(static_cast<int>(lhs.Red ()) - static_cast<int>(rhs.Red ())) + @@ -210,15 +244,10 @@ protected: numeric::power<2>(static_cast<int>(lhs.Blue ()) - static_cast<int>(rhs.Blue ())); }; - const int levelDiff = 20; - const int level = colorDist(backCol, *wxBLACK) < colorDist(backCol, *wxWHITE) ? - levelDiff : -levelDiff; //brighten or darken - - auto incChannel = [level](unsigned char c) { return static_cast<unsigned char>(std::max(0, std::min(255, c + level))); }; + const int signLevel = colorDist(backCol, *wxBLACK) < colorDist(backCol, *wxWHITE) ? 1 : -1; //brighten or darken - const wxColor backColAlt(incChannel(backCol.Red ()), - incChannel(backCol.Green()), - incChannel(backCol.Blue ())); + const wxColor colOutter = getAdjustedColor(signLevel * 20); + const wxColor colInner = getAdjustedColor(signLevel * 10); //clearArea(dc, rect, backColAlt); @@ -228,8 +257,8 @@ protected: wxRect rectLower = rect; rectLower.y += rectUpper.height; rectLower.height -= rectUpper.height; - dc.GradientFillLinear(rectUpper, backColAlt, getBackGroundColor(row), wxSOUTH); - dc.GradientFillLinear(rectLower, backColAlt, getBackGroundColor(row), wxNORTH); + dc.GradientFillLinear(rectUpper, colOutter, colInner, wxSOUTH); + dc.GradientFillLinear(rectLower, colOutter, colInner, wxNORTH); } else clearArea(dc, rect, getBackGroundColor(row)); @@ -259,8 +288,6 @@ protected: return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } - const FileSystemObject* getRawData(size_t row) const { return gridDataView_ ? gridDataView_->getObject(row) : nullptr; } - private: enum DisplayType { @@ -270,7 +297,6 @@ private: DISP_TYPE_INACTIVE, }; - DisplayType getRowDisplayType(size_t row) const { const FileSystemObject* fsObj = getRawData(row); @@ -306,20 +332,6 @@ private: return output; } - virtual size_t getRowCount() const - { - if (gridDataView_) - { - if (gridDataView_->rowsTotal() == 0) - return ROW_COUNT_NO_DATA; - return gridDataView_->rowsOnView(); - } - else - return ROW_COUNT_NO_DATA; - - //return std::max(MIN_ROW_COUNT, gridDataView_ ? gridDataView_->rowsOnView() : 0); - } - virtual wxString getValue(size_t row, ColumnType colType) const { if (const FileSystemObject* fsObj = getRawData(row)) @@ -348,8 +360,9 @@ private: if (!fsObj_.isEmpty<side>()) value = zen::toGuiString(fileObj.getFileSize<side>()); - //if (!fsObj_.isEmpty<side>()) -> test file id - // value = toGuiString(fileObj.getFileId<side>().second); + // -> test file id + //if (!fsObj_.isEmpty<side>()) + // value = toGuiString(fileObj.getFileId<side>().second) + L" " + toGuiString(fileObj.getFileId<side>().first); break; case COL_TYPE_DATE: //date if (!fsObj_.isEmpty<side>()) @@ -587,12 +600,12 @@ private: drawColumnLabelText(dc, rectInside, getColumnLabel(colType)); //draw sort marker - if (gridDataView_) + if (getGridDataView()) { - auto sortInfo = gridDataView_->getSortInfo(); + auto sortInfo = getGridDataView()->getSortInfo(); if (sortInfo) { - if (colType == static_cast<ColumnType>(sortInfo->type_) && (compPos_ == gridview::COMP_LEFT) == sortInfo->onLeft_) + if (colType == static_cast<ColumnType>(sortInfo->type_) && (side == LEFT_SIDE) == sortInfo->onLeft_) { const wxBitmap& marker = GlobalResources::getImage(sortInfo->ascending_ ? L"sortAscending" : L"sortDescending"); wxPoint markerBegin = rectInside.GetTopLeft() + wxPoint((rectInside.width - marker.GetWidth()) / 2, 0); @@ -639,7 +652,7 @@ private: const FileSystemObject* fsObj = getRawData(row); if (fsObj && !fsObj->isEmpty<side>()) { - toolTip = toWx(gridDataView_->getFolderPairCount() > 1 ? //gridDataView_ bound in this path + toolTip = toWx(getGridDataView() && getGridDataView()->getFolderPairCount() > 1 ? fsObj->getFullName<side>() : fsObj->getRelativeName<side>()); @@ -669,10 +682,8 @@ private: return toolTip; } - std::shared_ptr<const zen::GridView> gridDataView_; std::shared_ptr<IconManager> iconMgr_; //optional std::vector<char> failedLoads; //effectively a vector<bool> of size "number of rows" - const size_t compPos_; std::unique_ptr<wxBitmap> buffer; //avoid costs of recreating this temporal variable }; @@ -680,7 +691,7 @@ private: class GridDataLeft : public GridDataRim<LEFT_SIDE> { public: - GridDataLeft(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid, size_t compPos) : GridDataRim<LEFT_SIDE>(gridDataView, grid, compPos) {} + GridDataLeft(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataRim<LEFT_SIDE>(gridDataView, grid) {} void setNavigationMarker(std::vector<const HierarchyObject*>&& markedFiles, std::vector<const HierarchyObject*>&& markedContainer) @@ -754,20 +765,17 @@ private: class GridDataRight : public GridDataRim<RIGHT_SIDE> { public: - GridDataRight(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid, size_t compPos) : GridDataRim<RIGHT_SIDE>(gridDataView, grid, compPos) {} + GridDataRight(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataRim<RIGHT_SIDE>(gridDataView, grid) {} }; - - //######################################################################################################## class GridDataMiddle : public GridDataBase { public: GridDataMiddle(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : - GridDataBase(grid), - gridDataView_(gridDataView), + GridDataBase(grid, gridDataView), showSyncAction_(true) {} void onSelectBegin(const wxPoint& clientPos, size_t row, ColumnType colType) @@ -775,14 +783,14 @@ public: if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE && row < refGrid().getRowCount()) { - refGrid().clearSelection(gridview::COMP_MIDDLE); + refGrid().clearSelection(); dragSelection.reset(new std::pair<size_t, BlockPosition>(row, mousePosToBlock(clientPos, row))); } } void onSelectEnd(size_t rowFrom, size_t rowTo) //we cannot reuse row from "onSelectBegin": rowFrom and rowTo may be different if user is holding shift { - refGrid().clearSelection(gridview::COMP_MIDDLE); + refGrid().clearSelection(); //issue custom event if (dragSelection) @@ -826,7 +834,7 @@ public: } } - void onMouseMovement(const wxPoint& clientPos, size_t row, ColumnType colType, size_t compPos) + void onMouseMovement(const wxPoint& clientPos, size_t row, ColumnType colType) { //manage block highlighting and custom tooltip if (dragSelection) @@ -835,13 +843,13 @@ public: } else { - if (compPos == gridview::COMP_MIDDLE && static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE) + if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE) { if (highlight) //refresh old highlight - refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE); + refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); highlight.reset(new std::pair<size_t, BlockPosition>(row, mousePosToBlock(clientPos, row))); - refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE); + refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); //show custom tooltip showToolTip(row, refGrid().getMainWin().ClientToScreen(clientPos)); @@ -855,7 +863,7 @@ public: { if (highlight) { - refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE); + refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); highlight.reset(); } @@ -865,8 +873,6 @@ public: void showSyncAction(bool value) { showSyncAction_ = value; } private: - virtual size_t getRowCount() const { return 0; /*if there are multiple grid components, only the first one will be polled for row count!*/ } - virtual wxString getValue(size_t row, ColumnType colType) const { if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE) @@ -891,11 +897,15 @@ private: { if (const FileSystemObject* fsObj = getRawData(row)) { - wxRect rectInside = drawCellBorder(dc, rect); + //wxRect rectInside = drawCellBorder(dc, rect); + wxRect rectInside = rect; + + rectInside.width -= CHECK_BOX_SPACE_LEFT; + rectInside.x += CHECK_BOX_SPACE_LEFT; //draw checkbox wxRect checkBoxArea = rectInside; - checkBoxArea.SetWidth(CHECK_BOX_WIDTH); + checkBoxArea.SetWidth(CHECK_BOX_IMAGE); const bool rowHighlighted = dragSelection ? row == dragSelection->first : highlight ? row == highlight->first : false; const BlockPosition highlightBlock = dragSelection ? dragSelection->second : highlight ? highlight->second : BLOCKPOS_CHECK_BOX; @@ -905,8 +915,8 @@ private: else //default drawBitmapRtlMirror(dc, GlobalResources::getImage(fsObj->isActive() ? L"checkboxTrue" : L"checkboxFalse" ), checkBoxArea, wxALIGN_CENTER, buffer); - rectInside.width -= CHECK_BOX_WIDTH; - rectInside.x += CHECK_BOX_WIDTH; + rectInside.width -= CHECK_BOX_IMAGE; + rectInside.x += CHECK_BOX_IMAGE; //synchronization preview if (showSyncAction_) @@ -964,8 +974,6 @@ private: } } - const FileSystemObject* getRawData(size_t row) const { return gridDataView_ ? gridDataView_->getObject(row) : nullptr; } - wxColor getBackGroundColor(size_t row) const { if (const FileSystemObject* fsObj = getRawData(row)) @@ -1019,9 +1027,9 @@ private: case FILE_EQUAL: break; //usually white case FILE_CONFLICT: + case FILE_DIFFERENT_METADATA: //= sub-category of equal, but hint via background that sync direction follows conflict-setting return COLOR_YELLOW; - case FILE_DIFFERENT_METADATA: - return COLOR_YELLOW_LIGHT; + //return COLOR_YELLOW_LIGHT; } } } @@ -1042,7 +1050,7 @@ private: { const int absX = refGrid().CalcUnscrolledPosition(clientPos).x; - const wxRect rect = refGrid().getCellArea(row, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE); //returns empty rect if column not found; absolute coordinates! + const wxRect rect = refGrid().getCellArea(row, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); //returns empty rect if column not found; absolute coordinates! if (rect.width > CHECK_BOX_WIDTH && rect.height > 0) { const FileSystemObject* const fsObj = getRawData(row); @@ -1136,9 +1144,8 @@ private: case FILE_DIFFERENT: return L"different"; case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: //= sub-category of equal return L"equal"; - case FILE_DIFFERENT_METADATA: - return L"conflict"; case FILE_CONFLICT: return L"conflict"; } @@ -1155,7 +1162,6 @@ private: virtual wxString getToolTip(ColumnType colType) const { return showSyncAction_ ? _("Action") : _("Category"); } - std::shared_ptr<const zen::GridView> gridDataView_; bool showSyncAction_; std::unique_ptr<std::pair<size_t, BlockPosition>> highlight; //(row, block) current mouse highlight std::unique_ptr<std::pair<size_t, BlockPosition>> dragSelection; //(row, block) @@ -1165,58 +1171,108 @@ private: //######################################################################################################## +const wxEventType EVENT_ALIGN_SCROLLBARS = wxNewEventType(); + class GridEventManager : private wxEvtHandler { public: - GridEventManager(Grid& grid, + GridEventManager(Grid& gridL, + Grid& gridC, + Grid& gridR, GridDataLeft& provLeft, GridDataMiddle& provMiddle, - GridDataRight& provRight) : grid_(grid), provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight) + GridDataRight& provRight) : + gridL_(gridL), gridC_(gridC), gridR_(gridR), + provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight) { - grid_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumn), nullptr, this); + 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); + + 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); + + //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); + + //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), NULL, this); + gridC_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridC), NULL, this); + gridR_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridR), NULL, this); + + gridL_.Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + + gridR_.Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + + Connect(EVENT_ALIGN_SCROLLBARS, wxEventHandler(GridEventManager::onAlignScrollBars), NULL, this); + } - grid_.getMainWin().Connect(wxEVT_MOTION, wxMouseEventHandler(GridEventManager::onMouseMovement), nullptr, this); - grid_.getMainWin().Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(GridEventManager::onMouseLeave ), nullptr, this); - grid_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (GridEventManager::onKeyDown ), nullptr, this); +private: + void onCenterSelectBegin(GridClickEvent& event) + { - grid_.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler (GridEventManager::onSelectBegin), nullptr, this); - grid_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onSelectEnd ), nullptr, this); + provMiddle_.onSelectBegin(event.GetPosition(), event.row_, event.colType_); + event.Skip(); } -private: - void onMouseMovement(wxMouseEvent& event) + void onCenterSelectEnd(GridRangeSelectEvent& event) { - const wxPoint& topLeftAbs = grid_.CalcUnscrolledPosition(event.GetPosition()); - const int row = grid_.getRowAtPos(topLeftAbs.y); //returns < 0 if column not found; absolute coordinates! - if (auto colInfo = grid_.getColumnAtPos(topLeftAbs.x)) //(column type, component position) + if (event.positive_) //we do NOT want to react on GridRangeSelectEvent() within Grid::clearSelectionAll() directly following right mouse click! + provMiddle_.onSelectEnd(event.rowFrom_, event.rowTo_); + event.Skip(); + } + + void onCenterMouseMovement(wxMouseEvent& event) + { + const wxPoint& topLeftAbs = gridC_.CalcUnscrolledPosition(event.GetPosition()); + const int row = gridC_.getRowAtPos(topLeftAbs.y); //returns < 0 if column not found; absolute coordinates! + if (auto colInfo = gridC_.getColumnAtPos(topLeftAbs.x)) //(column type, component position) { //redirect mouse movement to middle grid component - provMiddle_.onMouseMovement(event.GetPosition(), row, colInfo->first, colInfo->second); + provMiddle_.onMouseMovement(event.GetPosition(), row, colInfo->first); } event.Skip(); } - void onMouseLeave(wxMouseEvent& event) + void onCenterMouseLeave(wxMouseEvent& event) { provMiddle_.onMouseLeave(); event.Skip(); } - void onSelectBegin(GridClickEvent& event) - { - if (event.compPos_ == gridview::COMP_MIDDLE) - provMiddle_.onSelectBegin(event.GetPosition(), event.row_, event.colType_); - event.Skip(); - } + void onGridSelectionL(GridRangeSelectEvent& event) { onGridSelection(gridL_, gridR_); event.Skip(); } + void onGridSelectionR(GridRangeSelectEvent& event) { onGridSelection(gridR_, gridL_); event.Skip(); } - void onSelectEnd(GridRangeSelectEvent& event) + void onGridSelection(const Grid& grid, Grid& other) { - if (event.compPos_ == gridview::COMP_MIDDLE) - provMiddle_.onSelectEnd(event.rowFrom_, event.rowTo_); - event.Skip(); + if (!wxGetKeyState(WXK_CONTROL)) //clear other grid unless user is holding CTRL + other.clearSelection(); } - void onKeyDown(wxKeyEvent& event) + void onKeyDownL(wxKeyEvent& event) { onKeyDown(event, gridL_); } + void onKeyDownC(wxKeyEvent& event) { onKeyDown(event, gridC_); } + void onKeyDownR(wxKeyEvent& event) { onKeyDown(event, gridR_); } + + void onKeyDown(wxKeyEvent& event, const Grid& grid) { int keyCode = event.GetKeyCode(); if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) @@ -1233,7 +1289,7 @@ private: //skip middle component when navigating via keyboard - const auto row = grid_.getGridCursor().first; + const auto row = grid.getGridCursor().first; if (event.ShiftDown()) ; @@ -1244,47 +1300,119 @@ private: { case WXK_LEFT: case WXK_NUMPAD_LEFT: - grid_.setGridCursor(row, gridview::COMP_LEFT); + gridL_.setGridCursor(row); + gridL_.SetFocus(); return; //swallow event case WXK_RIGHT: case WXK_NUMPAD_RIGHT: - grid_.setGridCursor(row, gridview::COMP_RIGHT); + gridR_.setGridCursor(row); + gridR_.SetFocus(); return; //swallow event } event.Skip(); } - void onResizeColumn(GridColumnResizeEvent& event) + void onResizeColumnL(GridColumnResizeEvent& event) { resizeOtherSide(gridL_, gridR_, event.colType_, event.offset_); } + void onResizeColumnR(GridColumnResizeEvent& event) { resizeOtherSide(gridR_, gridL_, event.colType_, event.offset_); } + + void resizeOtherSide(const Grid& src, Grid& trg, ColumnType type, ptrdiff_t offset) { - auto resizeOtherSide = [&](size_t compPosOther) + //find stretch factor of resized column: type is unique due to makeConsistent()! + std::vector<Grid::ColumnAttribute> cfgSrc = src.getColumnConfig(); + auto iter = std::find_if(cfgSrc.begin(), cfgSrc.end(), [&](Grid::ColumnAttribute& ca) { return ca.type_ == type; }); + if (iter == cfgSrc.end()) + return; + const ptrdiff_t stretchSrc = iter->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(); + std::for_each(cfgTrg.begin(), cfgTrg.end(), [&](Grid::ColumnAttribute& ca) { - std::vector<Grid::ColumnAttribute> colAttr = grid_.getColumnConfig(compPosOther); + if (ca.type_ == type && ca.stretch_ == stretchSrc) + ca.offset_ = offset; + }); + trg.setColumnConfig(cfgTrg); + } - std::for_each(colAttr.begin(), colAttr.end(), [&](Grid::ColumnAttribute& ca) - { - if (ca.type_ == event.colType_) - ca.width_ = event.width_; - }); + void onGridAccessL(wxEvent& event) { gridL_.SetFocus(); event.Skip(); } + void onGridAccessR(wxEvent& event) { gridR_.SetFocus(); event.Skip(); } + + void onPaintGridL(wxEvent& event) { onPaintGrid(gridL_); event.Skip(); } + void onPaintGridC(wxEvent& event) { onPaintGrid(gridC_); event.Skip(); } + void onPaintGridR(wxEvent& event) { onPaintGrid(gridR_); event.Skip(); } - grid_.setColumnConfig(colAttr, compPosOther); //set column count + widths + void onPaintGrid(const Grid& grid) + { + //align scroll positions of all three grids *synchronously* during paint event! (wxGTK has visible delay when this is done asynchronously, no delay on Windows) + + //determine lead grid + const Grid* lead = nullptr; + Grid* follow1 = nullptr; + Grid* follow2 = nullptr; + auto setGrids = [&](const Grid& l, Grid& f1, Grid& f2) { lead = &l; follow1 = &f1; follow2 = &f2; }; + + if (wxWindow::FindFocus() == &gridC_.getMainWin()) + setGrids(gridC_, gridL_, gridR_); + else if (wxWindow::FindFocus() == &gridR_.getMainWin()) + setGrids(gridR_, gridL_, gridC_); + else //default: left panel + setGrids(gridL_, gridC_, gridR_); + + //align other grids only while repainting the lead grid to avoid scrolling and updating a grid at the same time! + if (lead != &grid) return; + + auto scroll = [](Grid& target, int y) //support polling + { + //scroll vertically only - scrolling horizontally becomes annoying if left and right sides have different widths; + //e.g. h-scroll on left would be undone when scrolling vertically on right which doesn't have a h-scrollbar + int yOld = 0; + target.GetViewStart(nullptr, &yOld); + if (yOld != y) + target.Scroll(-1, y); }; + int y = 0; + lead->GetViewStart(nullptr, &y); + scroll(*follow1, y); + scroll(*follow2, y); + + //harmonize placement of horizontal scrollbar to avoid grids getting out of sync! + //since this affects the grid that is currently repainted as well, we do work asynchronously! + //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) + 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! + } - switch (event.compPos_) + void onAlignScrollBars(wxEvent& event) + { + auto needsHorizontalScrollbars = [](Grid& grid) -> bool { - case gridview::COMP_LEFT: - resizeOtherSide(gridview::COMP_RIGHT); - break; - case gridview::COMP_MIDDLE: - break; - case gridview::COMP_RIGHT: - resizeOtherSide(gridview::COMP_LEFT); - break; - } + const wxWindow& mainWin = grid.getMainWin(); + return mainWin.GetVirtualSize().GetWidth() > mainWin.GetClientSize().GetWidth(); + //assuming Grid::updateWindowSizes() does its job well, this should suffice! + //CAVEAT: if horizontal and vertical scrollbar are circular dependent from each other + //(h-scrollbar is shown due to v-scrollbar consuming horizontal width, ect...) + //while in fact both are NOT needed, this special case results in a bogus need for scrollbars! + //see https://sourceforge.net/tracker/?func=detail&aid=3514183&group_id=234430&atid=1093083 + // => since we're outside the Grid abstraction, we should not duplicate code to handle this special case as it seems to be insignificant + }; + + Grid::ScrollBarStatus sbStatusX = needsHorizontalScrollbars(gridL_) || + needsHorizontalScrollbars(gridR_) ? + Grid::SB_SHOW_ALWAYS : Grid::SB_SHOW_NEVER; + gridL_.showScrollBars(sbStatusX, Grid::SB_SHOW_NEVER); + gridC_.showScrollBars(sbStatusX, Grid::SB_SHOW_NEVER); + gridR_.showScrollBars(sbStatusX, Grid::SB_SHOW_AUTOMATIC); } - Grid& grid_; + Grid& gridL_; + Grid& gridC_; + Grid& gridR_; GridDataLeft& provLeft_; GridDataMiddle& provMiddle_; GridDataRight& provRight_; @@ -1293,32 +1421,36 @@ private: //######################################################################################################## -void gridview::init(Grid& grid, const std::shared_ptr<const zen::GridView>& gridDataView) +void gridview::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std::shared_ptr<const zen::GridView>& gridDataView) { - grid.setComponentCount(3); - - auto provLeft_ = std::make_shared<GridDataLeft >(gridDataView, grid, gridview::COMP_LEFT ); - auto provMiddle_ = std::make_shared<GridDataMiddle>(gridDataView, grid); - auto provRight_ = std::make_shared<GridDataRight >(gridDataView, grid, gridview::COMP_RIGHT); + auto provLeft_ = std::make_shared<GridDataLeft >(gridDataView, gridLeft); + auto provMiddle_ = std::make_shared<GridDataMiddle>(gridDataView, gridCenter); + auto provRight_ = std::make_shared<GridDataRight >(gridDataView, gridRight); - grid.setDataProvider(provLeft_ , gridview::COMP_LEFT); //data providers reference grid => - grid.setDataProvider(provMiddle_, gridview::COMP_MIDDLE); //ownership must belong *exclusively* to grid! - grid.setDataProvider(provRight_ , gridview::COMP_RIGHT); + gridLeft .setDataProvider(provLeft_); //data providers reference grid => + gridCenter.setDataProvider(provMiddle_); //ownership must belong *exclusively* to grid! + gridRight .setDataProvider(provRight_); - auto evtMgr = std::make_shared<GridEventManager>(grid, *provLeft_, *provMiddle_, *provRight_); + auto evtMgr = std::make_shared<GridEventManager>(gridLeft, gridCenter, gridRight, *provLeft_, *provMiddle_, *provRight_); provLeft_ ->holdOwnership(evtMgr); provMiddle_->holdOwnership(evtMgr); provRight_ ->holdOwnership(evtMgr); - grid.enableColumnMove (false, gridview::COMP_MIDDLE); - grid.enableColumnResize(false, gridview::COMP_MIDDLE); + gridCenter.enableColumnMove (false); + gridCenter.enableColumnResize(false); - std::vector<Grid::ColumnAttribute> attribMiddle; - attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5)); - attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), 60)); - attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5)); + gridCenter.showRowLabel(false); + gridRight .showRowLabel(false); - grid.setColumnConfig(attribMiddle, gridview::COMP_MIDDLE); + //gridLeft .showScrollBars(Grid::SB_SHOW_AUTOMATIC, Grid::SB_SHOW_NEVER); -> redundant: configuration happens in GridEventManager::onAlignScrollBars() + //gridCenter.showScrollBars(Grid::SB_SHOW_NEVER, Grid::SB_SHOW_NEVER); + + gridCenter.SetSize(60 /*+ 2 * 5*/, -1); + std::vector<Grid::ColumnAttribute> attribMiddle; + //attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5, 0, true)); + attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), 60, 0, true)); + //attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5, 0, true)); + gridCenter.setColumnConfig(attribMiddle); } @@ -1348,7 +1480,7 @@ std::vector<Grid::ColumnAttribute> gridview::convertConfig(const std::vector<Col std::vector<Grid::ColumnAttribute> output; std::transform(attribClean.begin(), attribClean.end(), std::back_inserter(output), - [&](const ColumnAttributeRim& a) { return Grid::ColumnAttribute(static_cast<ColumnType>(a.type_), a.width_, a.visible_); }); + [&](const ColumnAttributeRim& ca) { return Grid::ColumnAttribute(static_cast<ColumnType>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); return output; } @@ -1359,7 +1491,7 @@ std::vector<ColumnAttributeRim> gridview::convertConfig(const std::vector<Grid:: std::vector<ColumnAttributeRim> output; std::transform(attribs.begin(), attribs.end(), std::back_inserter(output), - [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeRim(static_cast<ColumnTypeRim>(ca.type_), ca.width_, ca.visible_); }); + [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeRim(static_cast<ColumnTypeRim>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); return makeConsistent(output); } @@ -1392,13 +1524,14 @@ private: }; } -void gridview::setupIcons(Grid& grid, bool show, IconBuffer::IconSize sz) +void gridview::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, bool show, IconBuffer::IconSize sz) { - auto* provLeft = dynamic_cast<GridDataLeft*>(grid.getDataProvider(gridview::COMP_LEFT)); - auto* provRight = dynamic_cast<GridDataRight*>(grid.getDataProvider(gridview::COMP_RIGHT)); + auto* provLeft = dynamic_cast<GridDataLeft*>(gridLeft .getDataProvider()); + auto* provRight = dynamic_cast<GridDataRight*>(gridRight.getDataProvider()); if (provLeft && provRight) { + int newRowHeight = 0; if (show) { auto iconMgr = std::make_shared<IconManager>(sz); @@ -1406,44 +1539,53 @@ void gridview::setupIcons(Grid& grid, bool show, IconBuffer::IconSize sz) provLeft ->setIconManager(iconMgr); provRight->setIconManager(iconMgr); - grid.setRowHeight(iconMgr->iconBuffer.getSize() + 1); //+ 1 for line between rows + newRowHeight = iconMgr->iconBuffer.getSize() + 1; //+ 1 for line between rows } else { provLeft ->setIconManager(nullptr); provRight->setIconManager(nullptr); - grid.setRowHeight(IconBuffer(IconBuffer::SIZE_SMALL).getSize() + 1); //+ 1 for line between rows + newRowHeight = IconBuffer(IconBuffer::SIZE_SMALL).getSize() + 1; //+ 1 for line between rows } - grid.Refresh(); + gridLeft .setRowHeight(newRowHeight); + gridCenter.setRowHeight(newRowHeight); + gridRight .setRowHeight(newRowHeight); } else assert(false); } -void gridview::clearSelection(Grid& grid) +void gridview::clearSelection(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) +{ + gridLeft .clearSelection(); + gridCenter.clearSelection(); + gridRight .clearSelection(); +} + +void gridview::refresh(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) { - grid.clearSelection(gridview::COMP_LEFT); - grid.clearSelection(gridview::COMP_MIDDLE); - grid.clearSelection(gridview::COMP_RIGHT); + gridLeft .Refresh(); + gridCenter.Refresh(); + gridRight .Refresh(); } -void gridview::setNavigationMarker(Grid& grid, +void gridview::setNavigationMarker(Grid& gridLeft, std::vector<const HierarchyObject*>&& markedFiles, std::vector<const HierarchyObject*>&& markedContainer) { - if (auto* provLeft = dynamic_cast<GridDataLeft*>(grid.getDataProvider(gridview::COMP_LEFT))) + if (auto* provLeft = dynamic_cast<GridDataLeft*>(gridLeft.getDataProvider())) provLeft->setNavigationMarker(std::move(markedFiles), std::move(markedContainer)); else assert(false); - grid.Refresh(); + gridLeft.Refresh(); } -void gridview::showSyncAction(Grid& grid, bool value) +void gridview::showSyncAction(Grid& gridCenter, bool value) { - if (auto* provMiddle = dynamic_cast<GridDataMiddle*>(grid.getDataProvider(gridview::COMP_MIDDLE))) + if (auto* provMiddle = dynamic_cast<GridDataMiddle*>(gridCenter.getDataProvider())) provMiddle->showSyncAction(value); else assert(false); @@ -1503,9 +1645,9 @@ wxBitmap zen::getCmpResultImage(CompareFilesResult cmpResult) case FILE_DIFFERENT: return GlobalResources::getImage(L"differentSmall"); case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: //= sub-category of equal return GlobalResources::getImage(L"equalSmall"); case FILE_CONFLICT: - case FILE_DIFFERENT_METADATA: return GlobalResources::getImage(L"conflictSmall"); } return wxNullBitmap; diff --git a/ui/custom_grid.h b/ui/custom_grid.h index 58a421e6..98092ade 100644 --- a/ui/custom_grid.h +++ b/ui/custom_grid.h @@ -17,23 +17,20 @@ namespace zen //setup grid to show grid view within three components: namespace gridview { -static const size_t COMP_LEFT = 0; -static const size_t COMP_MIDDLE = 1; -static const size_t COMP_RIGHT = 2; - -void init(Grid& grid, const std::shared_ptr<const GridView>& gridDataView); +void init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std::shared_ptr<const GridView>& gridDataView); std::vector<Grid::ColumnAttribute> convertConfig(const std::vector<ColumnAttributeRim>& attribs); //+ make consistent std::vector<ColumnAttributeRim> convertConfig(const std::vector<Grid::ColumnAttribute>& attribs); // -void showSyncAction(Grid& grid, bool value); +void showSyncAction(Grid& gridCenter, bool value); -void setupIcons(Grid& grid, bool show, IconBuffer::IconSize sz); +void setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, bool show, IconBuffer::IconSize sz); -void clearSelection(Grid& grid); //clear all components +void clearSelection(Grid& gridLeft, Grid& gridCenter, Grid& gridRight); //clear all components +void refresh(Grid& gridLeft, Grid& gridCenter, Grid& gridRight); //mark rows selected in navigation/compressed tree and navigate to leading object -void setNavigationMarker(Grid& grid, +void setNavigationMarker(Grid& gridLeft, std::vector<const HierarchyObject*>&& markedFiles, //mark files/symlinks directly within a container std::vector<const HierarchyObject*>&& markedContainer); //mark full container including child-objects } diff --git a/ui/grid_view.cpp b/ui/grid_view.cpp index 2162394f..8764e233 100644 --- a/ui/grid_view.cpp +++ b/ui/grid_view.cpp @@ -161,11 +161,11 @@ GridView::StatusCmpResult GridView::updateCmpResult(bool hideFiltered, //maps so if (!differentFilesActive) return false; break; case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: //= sub-category of equal output.existsEqual = true; if (!equalFilesActive) return false; break; case FILE_CONFLICT: - case FILE_DIFFERENT_METADATA: //no extra button on screen output.existsConflict = true; if (!conflictFilesActive) return false; break; diff --git a/ui/gui_generated.cpp b/ui/gui_generated.cpp index c0f83cec..1f58d6a5 100644 --- a/ui/gui_generated.cpp +++ b/ui/gui_generated.cpp @@ -359,9 +359,27 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer1711; bSizer1711 = new wxBoxSizer( wxVERTICAL ); - m_gridMain = new zen::Grid( m_panelCenter, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); - m_gridMain->SetScrollRate( 5, 5 ); - bSizer1711->Add( m_gridMain, 1, wxEXPAND, 5 ); + m_splitterMain = new zen::TripleSplitter( m_panelCenter, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + wxBoxSizer* bSizer1781; + bSizer1781 = new wxBoxSizer( wxHORIZONTAL ); + + m_gridMainL = new zen::Grid( m_splitterMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_gridMainL->SetScrollRate( 5, 5 ); + bSizer1781->Add( m_gridMainL, 1, wxEXPAND, 5 ); + + m_gridMainC = new zen::Grid( m_splitterMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_gridMainC->SetScrollRate( 5, 5 ); + bSizer1781->Add( m_gridMainC, 0, wxEXPAND, 5 ); + + m_gridMainR = new zen::Grid( m_splitterMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_gridMainR->SetScrollRate( 5, 5 ); + bSizer1781->Add( m_gridMainR, 1, wxEXPAND, 5 ); + + + m_splitterMain->SetSizer( bSizer1781 ); + m_splitterMain->Layout(); + bSizer1781->Fit( m_splitterMain ); + bSizer1711->Add( m_splitterMain, 1, wxEXPAND, 5 ); m_panelStatusBar = new wxPanel( m_panelCenter, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSTATIC_BORDER|wxTAB_TRAVERSAL ); wxBoxSizer* bSizer451; @@ -2676,22 +2694,25 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer184; bSizer184 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer178; + bSizer178 = new wxBoxSizer( wxVERTICAL ); + m_staticText83 = new wxStaticText( m_panel39, wxID_ANY, _("If you like FreeFileSync"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText83->Wrap( -1 ); m_staticText83->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 93, 92, false, wxEmptyString ) ); m_staticText83->SetForegroundColour( wxColour( 0, 0, 0 ) ); - bSizer184->Add( m_staticText83, 0, wxALL, 5 ); - - - bSizer184->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer178->Add( m_staticText83, 0, wxALL, 5 ); m_hyperlink3 = new wxHyperlinkCtrl( m_panel39, wxID_ANY, _("Donate with PayPal"), wxT("https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=zhnmju123@gmx.de&lc=US¤cy_code=EUR"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink3->SetFont( wxFont( 10, 70, 90, 92, true, wxEmptyString ) ); m_hyperlink3->SetBackgroundColour( wxColour( 221, 221, 255 ) ); m_hyperlink3->SetToolTip( _("https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=zhnmju123@gmx.de&lc=US¤cy_code=EUR") ); - bSizer184->Add( m_hyperlink3, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer178->Add( m_hyperlink3, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + + bSizer184->Add( bSizer178, 1, wxALIGN_CENTER_VERTICAL, 5 ); m_bitmapPaypal = new wxStaticBitmap( m_panel39, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); m_bitmapPaypal->SetToolTip( _("Donate with PayPal") ); @@ -2699,9 +2720,6 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer184->Add( m_bitmapPaypal, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - bSizer184->Add( 0, 0, 1, wxEXPAND, 5 ); - - m_panel39->SetSizer( bSizer184 ); m_panel39->Layout(); bSizer184->Fit( m_panel39 ); diff --git a/ui/gui_generated.h b/ui/gui_generated.h index fa388377..29ef091d 100644 --- a/ui/gui_generated.h +++ b/ui/gui_generated.h @@ -16,6 +16,7 @@ #include "folder_history_box.h" #include "../wx+/dir_picker.h" #include "../wx+/grid.h" +#include "triple_splitter.h" #include "../wx+/toggle_button.h" #include "exec_finished_box.h" #include "../wx+/graph.h" @@ -106,7 +107,10 @@ protected: wxBoxSizer* bSizerAddFolderPairs; zen::Grid* m_gridNavi; wxPanel* m_panelCenter; - zen::Grid* m_gridMain; + zen::TripleSplitter* m_splitterMain; + zen::Grid* m_gridMainL; + zen::Grid* m_gridMainC; + zen::Grid* m_gridMainR; wxPanel* m_panelStatusBar; wxBoxSizer* bSizerStatusLeftDirectories; wxStaticBitmap* m_bitmapSmallDirectoryLeft; diff --git a/ui/gui_status_handler.cpp b/ui/gui_status_handler.cpp index 57785403..b25e4249 100644 --- a/ui/gui_status_handler.cpp +++ b/ui/gui_status_handler.cpp @@ -279,37 +279,38 @@ void SyncStatusHandler::reportInfo(const std::wstring& text) ProcessCallback::Response SyncStatusHandler::reportError(const std::wstring& errorMessage) { + errorLog.logMsg(errorMessage, TYPE_ERROR); //always, even for "retry" + switch (handleError_) { case ON_GUIERROR_POPUP: - break; - case ON_GUIERROR_IGNORE: - errorLog.logMsg(errorMessage, TYPE_ERROR); - return ProcessCallback::IGNORE_ERROR; - } + { + PauseTimers dummy(syncStatusFrame); + forceUiRefresh(); - PauseTimers dummy(syncStatusFrame); - forceUiRefresh(); + bool ignoreNextErrors = false; + switch (showErrorDlg(parentDlg_, + ReturnErrorDlg::BUTTON_IGNORE | ReturnErrorDlg::BUTTON_RETRY | ReturnErrorDlg::BUTTON_CANCEL, + errorMessage, + &ignoreNextErrors)) + { + case ReturnErrorDlg::BUTTON_IGNORE: + if (ignoreNextErrors) //falsify only + handleError_ = ON_GUIERROR_IGNORE; + return ProcessCallback::IGNORE_ERROR; - bool ignoreNextErrors = false; - switch (showErrorDlg(parentDlg_, - ReturnErrorDlg::BUTTON_IGNORE | ReturnErrorDlg::BUTTON_RETRY | ReturnErrorDlg::BUTTON_CANCEL, - errorMessage, - &ignoreNextErrors)) - { - case ReturnErrorDlg::BUTTON_IGNORE: - if (ignoreNextErrors) //falsify only - handleError_ = ON_GUIERROR_IGNORE; - errorLog.logMsg(errorMessage, TYPE_ERROR); - return ProcessCallback::IGNORE_ERROR; + case ReturnErrorDlg::BUTTON_RETRY: + return ProcessCallback::RETRY; - case ReturnErrorDlg::BUTTON_RETRY: - return ProcessCallback::RETRY; + case ReturnErrorDlg::BUTTON_CANCEL: + abortThisProcess(); + break; + } + } + break; - case ReturnErrorDlg::BUTTON_CANCEL: - errorLog.logMsg(errorMessage, TYPE_ERROR); - abortThisProcess(); - break; + case ON_GUIERROR_IGNORE: + return ProcessCallback::IGNORE_ERROR; } assert(false); diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp index 03a94e33..7681cde2 100644 --- a/ui/main_dlg.cpp +++ b/ui/main_dlg.cpp @@ -36,6 +36,7 @@ #include "grid_view.h" #include "../lib/resources.h" #include <zen/file_handling.h> +#include <zen/serialize.h> #include <zen/file_id.h> #include <zen/recycler.h> #include "../lib/resolve_path.h" @@ -43,7 +44,6 @@ #include <wx+/toggle_button.h> #include "folder_pair.h" #include <wx+/rtl.h> -#include <wx+/serialize.h> #include "search.h" #include "../lib/help_provider.h" #include "batch_config.h" @@ -54,6 +54,7 @@ #include <wx+/image_tools.h> #include <wx+/no_flicker.h> #include <wx+/grid.h> +#include "../lib/error_log.h" using namespace zen; using namespace std::rel_ops; @@ -91,32 +92,18 @@ public: DirectoryNameMainImpl(MainDialog& mainDlg, wxWindow& dropWindow1, Grid& dropGrid, - size_t compPos, //accept left or right half only! wxDirPickerCtrl& dirPicker, FolderHistoryBox& dirName, wxStaticText& staticText) : DirectoryName(dropWindow1, dirPicker, dirName, &staticText, &dropGrid.getMainWin()), mainDlg_(mainDlg), - dropGrid_(dropGrid), - compPos_(compPos) {} + dropGrid_(dropGrid) {} virtual bool acceptDrop(const std::vector<wxString>& droppedFiles, const wxPoint& clientPos, const wxWindow& wnd) { if (droppedFiles.empty()) return false; - if (&wnd == &dropGrid_.getMainWin()) - { - const wxPoint absPos = dropGrid_.CalcUnscrolledPosition(clientPos); - - const Opt<std::pair<ColumnType, size_t>> colInfo = dropGrid_.Grid::getColumnAtPos(absPos.x); - const bool dropOnLeft = colInfo ? colInfo->second != gridview::COMP_RIGHT : false; - - if ((compPos_ == gridview::COMP_LEFT && !dropOnLeft) || //accept left or right half of m_gridMain only! - (compPos_ == gridview::COMP_RIGHT && dropOnLeft)) // - return false; - } - switch (xmlAccess::getMergeType(toZ(droppedFiles))) //throw() { case xmlAccess::MERGE_BATCH: @@ -140,7 +127,6 @@ private: MainDialog& mainDlg_; Grid& dropGrid_; - size_t compPos_; }; //------------------------------------------------------------------ @@ -240,15 +226,13 @@ public: //prepare drag & drop dirNameLeft(mainDialog, *mainDialog.m_panelTopLeft, - *mainDialog.m_gridMain, - gridview::COMP_LEFT, + *mainDialog.m_gridMainL, *mainDialog.m_dirPickerLeft, *mainDialog.m_directoryLeft, *mainDialog.m_staticTextFinalPathLeft), dirNameRight(mainDialog, *mainDialog.m_panelTopRight, - *mainDialog.m_gridMain, - gridview::COMP_RIGHT, + *mainDialog.m_gridMainR, *mainDialog.m_dirPickerRight, *mainDialog.m_directoryRight, *mainDialog.m_staticTextFinalPathRight) {} @@ -337,7 +321,7 @@ public: paneInfo.IsFloating()) return false; //prevent main dialog move - return true;; //allow dialog move + return true; //allow dialog move } private: @@ -347,7 +331,7 @@ private: //################################################################################################################################## -MainDialog::MainDialog(const std::vector<wxString>& cfgFileNames, xmlAccess::XmlGlobalSettings& settings) : +MainDialog::MainDialog(const std::vector<wxString>& cfgFileNames, const xmlAccess::XmlGlobalSettings& globalSettings) : MainDialogGenerated(nullptr) { xmlAccess::XmlGuiConfig guiCfg; //structure to receive gui settings, already defaulted!! @@ -357,7 +341,7 @@ MainDialog::MainDialog(const std::vector<wxString>& cfgFileNames, xmlAccess::Xml filenames = cfgFileNames; else //next: use last used selection { - filenames = settings.gui.lastUsedConfigFiles; //2. now try last used files + filenames = globalSettings.gui.lastUsedConfigFiles; //2. now try last used files //------------------------------------------------------------------------------------------ //check existence of all directories in parallel! @@ -403,7 +387,7 @@ MainDialog::MainDialog(const std::vector<wxString>& cfgFileNames, xmlAccess::Xml const bool startComparisonImmediately = !cfgFileNames.empty() && loadCfgSuccess; init(guiCfg, - settings, + globalSettings, startComparisonImmediately); setLastUsedConfig(filenames, loadCfgSuccess ? guiCfg : xmlAccess::XmlGuiConfig()); //simulate changed config on parsing errors @@ -412,12 +396,12 @@ MainDialog::MainDialog(const std::vector<wxString>& cfgFileNames, xmlAccess::Xml MainDialog::MainDialog(const std::vector<wxString>& referenceFiles, const xmlAccess::XmlGuiConfig& guiCfg, - xmlAccess::XmlGlobalSettings& settings, + const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison) : MainDialogGenerated(nullptr) { init(guiCfg, - settings, + globalSettings, startComparison); setLastUsedConfig(referenceFiles, guiCfg); @@ -426,40 +410,44 @@ MainDialog::MainDialog(const std::vector<wxString>& referenceFiles, MainDialog::~MainDialog() { - wxWindowUpdateLocker dummy(this); - - writeGlobalSettings(); //set before saving last used config since "activeConfigFiles" will be replaced + try //save "GlobalSettings.xml" + { + xmlAccess::writeConfig(getGlobalCfgBeforeExit()); //throw FfsXmlError + } + catch (const xmlAccess::FfsXmlError& e) + { + wxMessageBox(e.toString().c_str(), _("Error"), wxOK | wxICON_ERROR, this); + } - //save "LastRun.ffs_gui" configuration - const xmlAccess::XmlGuiConfig guiCfg = getConfig(); - try + try //save "LastRun.ffs_gui" { - xmlAccess::writeConfig(guiCfg, toZ(lastRunConfigName())); - //setLastUsedConfig(lastRunConfigName(), guiCfg); -> may be removed!? + xmlAccess::writeConfig(getConfig(), toZ(lastRunConfigName())); //throw FfsXmlError } - //don't annoy users on read-only drives: no error checking should be fine since this is not a config the user explicitly wanted to save + //don't annoy users on read-only drives: it's enough to show a single error message when saving global config catch (const xmlAccess::FfsXmlError&) {} //important! event source wxTheApp is NOT dependent on this instance -> disconnect! wxTheApp->Disconnect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::OnGlobalKeyEvent), nullptr, this); wxTheApp->Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::OnGlobalKeyEvent), nullptr, this); - //no need for wxEventHandler::Disconnect() here; event sources are components of this window and are destroyed, too - auiMgr.UnInit(); + + //no need for wxEventHandler::Disconnect() here; event sources are components of this window and are destroyed, too } void MainDialog::onQueryEndSession() { - writeGlobalSettings(); + try { xmlAccess::writeConfig(getGlobalCfgBeforeExit()); } + catch (const xmlAccess::FfsXmlError&) {} //we try our best do to something useful in this extreme situation - no reason to notify or even log errors here! + try { xmlAccess::writeConfig(getConfig(), toZ(lastRunConfigName())); } catch (const xmlAccess::FfsXmlError&) {} } void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, - xmlAccess::XmlGlobalSettings& settings, + const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison) { showSyncAction_ = false; @@ -470,6 +458,10 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, m_directoryLeft ->init(folderHistoryLeft); m_directoryRight->init(folderHistoryRight); + //setup sash: detach + reparent: + m_splitterMain->SetSizer(nullptr); //alas wxFormbuilder doesn't allow us to have child windows without a sizer, so we have to remove it here + m_splitterMain->setupWindows(m_gridMainL, m_gridMainC, m_gridMainR); + wxWindowUpdateLocker dummy(this); //avoid display distortion //---------------- support for dockable gui style -------------------------------- @@ -532,23 +524,30 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, //---------------------------------------------------------------------------------- //register context: quick variant selection - m_bpButtonCmpConfig ->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnCompSettingsContext), nullptr, this); - m_bpButtonSyncConfig->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnSyncSettingsContext), nullptr, this); + m_bpButtonCmpConfig ->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler (MainDialog::OnCompSettingsContext), nullptr, this); + m_bpButtonSyncConfig->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler (MainDialog::OnSyncSettingsContext), nullptr, this); m_bpButtonFilter ->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(MainDialog::OnGlobalFilterContext), nullptr, this); //sort grids - m_gridMain->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClick ), nullptr, this ); - m_gridMain->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContext), nullptr, this ); + m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickL ), nullptr, this ); + m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickC ), nullptr, this ); + m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickR ), nullptr, this ); + + m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextL ), nullptr, this ); + m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextC ), nullptr, this ); + m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextR ), nullptr, this ); //grid context menu - m_gridMain->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContext), nullptr, this); - m_gridNavi->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onNaviGridContext), nullptr, this); + m_gridMainL->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextL), nullptr, this); + m_gridMainC->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextC), nullptr, this); + m_gridMainR->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextR), nullptr, this); + m_gridNavi ->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onNaviGridContext ), nullptr, this); - m_gridMain->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onGridDoubleClick), 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_gridNavi->Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(MainDialog::onNaviSelection), nullptr, this); - globalSettings = &settings; gridDataView.reset(new zen::GridView); treeDataView.reset(new zen::TreeView); @@ -570,11 +569,11 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, initViewFilterButtons(); //init grid settings - gridview::init(*m_gridMain, gridDataView); + gridview::init(*m_gridMainL, *m_gridMainC, *m_gridMainR, gridDataView); treeview::init(*m_gridNavi, treeDataView); //initialize and load configuration - readGlobalSettings(); + setGlobalCfgOnInit(globalSettings); setConfig(guiCfg); //set icons for this dialog @@ -645,7 +644,10 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, }); //support for CTRL + C and DEL on grids - m_gridMain->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEvent), nullptr, this); + m_gridMainL->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventL), nullptr, this); + m_gridMainC->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventC), nullptr, this); + m_gridMainR->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventR), nullptr, this); + m_gridNavi->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onTreeButtonEvent), nullptr, this); //register global hotkeys (without explicit menu entry) @@ -674,8 +676,8 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, OnResizeStatisticsPanel(dummy3); // //event handler for manual (un-)checking of rows and setting of sync direction - m_gridMain->Connect(EVENT_GRID_CHECK_ROWS, CheckRowsEventHandler (MainDialog::onCheckRows), nullptr, this); - m_gridMain->Connect(EVENT_GRID_SYNC_DIRECTION, SyncDirectionEventHandler(MainDialog::onSetSyncDirection), nullptr, this); + m_gridMainC->Connect(EVENT_GRID_CHECK_ROWS, CheckRowsEventHandler (MainDialog::onCheckRows), nullptr, this); + m_gridMainC->Connect(EVENT_GRID_SYNC_DIRECTION, SyncDirectionEventHandler(MainDialog::onSetSyncDirection), nullptr, this); //mainly to update row label sizes... updateGui(); @@ -740,41 +742,46 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, } -void MainDialog::readGlobalSettings() +void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSettings) { + globalCfg = globalSettings; + + setLanguage(globalSettings.programLanguage); + //set dialog size and position: test ALL parameters at once, since width/height are invalid if the window is minimized (eg x,y == -32000; height = 28, width = 160) //note: negative values for x and y are possible when using multiple monitors! - if (globalSettings->gui.dlgSize.GetWidth () > 0 && - globalSettings->gui.dlgSize.GetHeight() > 0 && - globalSettings->gui.dlgPos.x >= -3360 && - globalSettings->gui.dlgPos.y >= -200) - //wxDisplay::GetFromPoint(globalSettings->gui.dlgPos) != wxNOT_FOUND) //make sure upper left corner is in visible view -> not required - SetSize(wxRect(globalSettings->gui.dlgPos, globalSettings->gui.dlgSize)); + if (globalSettings.gui.dlgSize.GetWidth () > 0 && + globalSettings.gui.dlgSize.GetHeight() > 0 && + globalSettings.gui.dlgPos.x >= -3360 && + globalSettings.gui.dlgPos.y >= -200) + //wxDisplay::GetFromPoint(globalSettings.gui.dlgPos) != wxNOT_FOUND) //make sure upper left corner is in visible view -> not required + SetSize(wxRect(globalSettings.gui.dlgPos, globalSettings.gui.dlgSize)); else Centre(); - Maximize(globalSettings->gui.isMaximized); + Maximize(globalSettings.gui.isMaximized); //set column attributes - m_gridMain->setColumnConfig(gridview::convertConfig(globalSettings->gui.columnAttribLeft), gridview::COMP_LEFT); - m_gridMain->setColumnConfig(gridview::convertConfig(globalSettings->gui.columnAttribRight), gridview::COMP_RIGHT); + m_gridMainL ->setColumnConfig(gridview::convertConfig(globalSettings.gui.columnAttribLeft)); + m_gridMainR ->setColumnConfig(gridview::convertConfig(globalSettings.gui.columnAttribRight)); + m_splitterMain->setSashOffset(globalSettings.gui.sashOffset); - m_gridNavi->setColumnConfig(treeview::convertConfig(globalSettings->gui.columnAttribNavi)); - treeview::setShowPercentage(*m_gridNavi, globalSettings->gui.showPercentBar); + m_gridNavi->setColumnConfig(treeview::convertConfig(globalSettings.gui.columnAttribNavi)); + treeview::setShowPercentage(*m_gridNavi, globalSettings.gui.showPercentBar); - treeDataView->setSortDirection(globalSettings->gui.naviLastSortColumn, globalSettings->gui.naviLastSortAscending); + treeDataView->setSortDirection(globalSettings.gui.naviLastSortColumn, globalSettings.gui.naviLastSortAscending); //load list of last used configuration files - std::vector<wxString> cfgFileNames = globalSettings->gui.cfgFileHistory; + std::vector<wxString> cfgFileNames = globalSettings.gui.cfgFileHistory; cfgFileNames.push_back(lastRunConfigName()); //make sure <Last session> is always part of history list addFileToCfgHistory(cfgFileNames); //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.folderHistoryLeft, globalSettings.gui.folderHistMax); + *folderHistoryRight = FolderHistory(globalSettings.gui.folderHistoryRight, globalSettings.gui.folderHistMax); //show/hide file icons - gridview::setupIcons(*m_gridMain, globalSettings->gui.showIcons, convert(globalSettings->gui.iconSize)); + gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalSettings.gui.showIcons, convert(globalSettings.gui.iconSize)); //------------------------------------------------------------------------------------------------ //wxAuiManager erroneously loads panel captions, we don't want that @@ -784,7 +791,7 @@ void MainDialog::readGlobalSettings() for (size_t i = 0; i < paneArray.size(); ++i) captionNameMap.push_back(std::make_pair(paneArray[i].caption, paneArray[i].name)); - auiMgr.LoadPerspective(globalSettings->gui.guiPerspectiveLast); + auiMgr.LoadPerspective(globalSettings.gui.guiPerspectiveLast); //restore original captions for (CaptionNameMapping::const_iterator i = captionNameMap.begin(); i != captionNameMap.end(); ++i) @@ -793,30 +800,38 @@ void MainDialog::readGlobalSettings() } -void MainDialog::writeGlobalSettings() +xmlAccess::XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit() { + Freeze(); //no need to Thaw() again!! + // wxWindowUpdateLocker dummy(this); + + xmlAccess::XmlGlobalSettings globalSettings = globalCfg; + + globalSettings.programLanguage = getLanguage(); + //write global settings to (global) variable stored in application instance if (IsIconized()) //we need to (reliably) retrieve non-iconized, non-maximized size and position Iconize(false); - globalSettings->gui.isMaximized = IsMaximized(); //evaluate AFTER uniconizing! + globalSettings.gui.isMaximized = IsMaximized(); //evaluate AFTER uniconizing! if (IsMaximized()) Maximize(false); - globalSettings->gui.dlgSize = GetSize(); - globalSettings->gui.dlgPos = GetPosition(); + globalSettings.gui.dlgSize = GetSize(); + globalSettings.gui.dlgPos = GetPosition(); //retrieve column attributes - globalSettings->gui.columnAttribLeft = gridview::convertConfig(m_gridMain->getColumnConfig(gridview::COMP_LEFT)); - globalSettings->gui.columnAttribRight = gridview::convertConfig(m_gridMain->getColumnConfig(gridview::COMP_RIGHT)); + globalSettings.gui.columnAttribLeft = gridview::convertConfig(m_gridMainL->getColumnConfig()); + globalSettings.gui.columnAttribRight = gridview::convertConfig(m_gridMainR->getColumnConfig()); + globalSettings.gui.sashOffset = m_splitterMain->getSashOffset(); - globalSettings->gui.columnAttribNavi = treeview::convertConfig(m_gridNavi->getColumnConfig()); - globalSettings->gui.showPercentBar = treeview::getShowPercentage(*m_gridNavi); + globalSettings.gui.columnAttribNavi = treeview::convertConfig(m_gridNavi->getColumnConfig()); + globalSettings.gui.showPercentBar = treeview::getShowPercentage(*m_gridNavi); - const auto sortInfo = treeDataView->getSortDirection(); - globalSettings->gui.naviLastSortColumn = sortInfo.first; - globalSettings->gui.naviLastSortAscending = sortInfo.second; + const std::pair<ColumnTypeNavi, bool> sortInfo = treeDataView->getSortDirection(); + globalSettings.gui.naviLastSortColumn = sortInfo.first; + globalSettings.gui.naviLastSortAscending = sortInfo.second; //write list of last used configuration files std::vector<wxString> cfgFileHistory; @@ -824,14 +839,16 @@ void MainDialog::writeGlobalSettings() if (auto clientString = dynamic_cast<wxClientDataString*>(m_listBoxHistory->GetClientObject(i))) cfgFileHistory.push_back(clientString->name_); - globalSettings->gui.cfgFileHistory = cfgFileHistory; - globalSettings->gui.lastUsedConfigFiles = activeConfigFiles; + globalSettings.gui.cfgFileHistory = cfgFileHistory; + globalSettings.gui.lastUsedConfigFiles = activeConfigFiles; //write list of last used folders - globalSettings->gui.folderHistoryLeft = folderHistoryLeft ->getList(); - globalSettings->gui.folderHistoryRight = folderHistoryRight->getList(); + globalSettings.gui.folderHistoryLeft = folderHistoryLeft ->getList(); + globalSettings.gui.folderHistoryRight = folderHistoryRight->getList(); + + globalSettings.gui.guiPerspectiveLast = auiMgr.SavePerspective(); - globalSettings->gui.guiPerspectiveLast = auiMgr.SavePerspective(); + return globalSettings; } @@ -877,16 +894,15 @@ void MainDialog::copySelectionToClipboard() { zxString clipboardString; //perf: wxString doesn't model exponential growth and so is out - auto addSelection = [&](size_t compPos) + auto addSelection = [&](const Grid& grid) { - auto prov = m_gridMain->getDataProvider(compPos); - if (prov) + if (auto prov = grid.getDataProvider()) { - std::vector<Grid::ColumnAttribute> colAttr = m_gridMain->getColumnConfig(compPos); + std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig(); vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); if (!colAttr.empty()) { - const std::vector<size_t> selection = m_gridMain->getSelectedRows(compPos); + const std::vector<size_t> selection = grid.getSelectedRows(); std::for_each(selection.begin(), selection.end(), [&](size_t row) { @@ -903,8 +919,8 @@ void MainDialog::copySelectionToClipboard() } }; - addSelection(gridview::COMP_LEFT); - addSelection(gridview::COMP_RIGHT); + addSelection(*m_gridMainL); + addSelection(*m_gridMainR); //finally write to clipboard if (!clipboardString.empty()) @@ -920,17 +936,17 @@ std::vector<FileSystemObject*> MainDialog::getGridSelection(bool fromLeft, bool { std::set<size_t> selectedRows; - auto addSelection = [&](size_t compPos) + auto addSelection = [&](const Grid& grid) { - const std::vector<size_t>& sel = m_gridMain->getSelectedRows(compPos); + const std::vector<size_t>& sel = grid.getSelectedRows(); selectedRows.insert(sel.begin(), sel.end()); }; if (fromLeft) - addSelection(gridview::COMP_LEFT); + addSelection(*m_gridMainL); if (fromRight) - addSelection(gridview::COMP_RIGHT); + addSelection(*m_gridMainR); return gridDataView->getAllFileRef(selectedRows); } @@ -997,18 +1013,16 @@ public: mainDlg->enableAllElements(); } - virtual Response reportError(const std::wstring& errorMessage) + virtual Response reportError(const std::wstring& msg) { - if (abortRequested) - throw AbortDeleteProcess(); - if (ignoreErrors) return DeleteFilesHandler::IGNORE_ERROR; + updateGUI(); bool ignoreNextErrors = false; switch (showErrorDlg(mainDlg, ReturnErrorDlg::BUTTON_IGNORE | ReturnErrorDlg::BUTTON_RETRY | ReturnErrorDlg::BUTTON_CANCEL, - errorMessage, &ignoreNextErrors)) + msg, &ignoreNextErrors)) { case ReturnErrorDlg::BUTTON_IGNORE: ignoreErrors = ignoreNextErrors; @@ -1023,10 +1037,35 @@ public: return DeleteFilesHandler::IGNORE_ERROR; //dummy return value } + virtual void reportWarning(const std::wstring& msg, bool& warningActive) + { + if (!warningActive || ignoreErrors) + return; + + updateGUI(); + bool dontWarnAgain = false; + switch (showWarningDlg(mainDlg, ReturnWarningDlg::BUTTON_IGNORE | ReturnWarningDlg::BUTTON_CANCEL, msg, dontWarnAgain)) + { + case ReturnWarningDlg::BUTTON_SWITCH: + assert(false); + case ReturnWarningDlg::BUTTON_CANCEL: + throw AbortDeleteProcess(); + + case ReturnWarningDlg::BUTTON_IGNORE: + warningActive = !dontWarnAgain; + break; + } + } + virtual void notifyDeletion(const Zstring& currentObject) //called for each file/folder that has been deleted { ++deletionCount; + updateGUI(); + } +private: + void updateGUI() + { if (updateUiIsAllowed()) //test if specific time span between ui updates is over { mainDlg->setStatusInformation(replaceCpy(_P("Object deleted successfully!", "%x objects deleted successfully!", deletionCount), @@ -1038,7 +1077,7 @@ public: throw AbortDeleteProcess(); } -private: + //context: C callstack message loop => throw()! void OnAbortDeletion(wxCommandEvent& event) //handle abort button click { abortRequested = true; //don't throw exceptions in a GUI-Callback!!! (throw zen::AbortThisProcess()) @@ -1056,7 +1095,6 @@ private: event.Skip(); } - MainDialog* const mainDlg; bool abortRequested; @@ -1076,8 +1114,8 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec if (zen::showDeleteDialog(this, selectionLeft, selectionRight, - globalSettings->gui.deleteOnBothSides, - globalSettings->gui.useRecyclerForManualDeletion) == ReturnSmallDlg::BUTTON_OKAY) + globalCfg.gui.deleteOnBothSides, + globalCfg.gui.useRecyclerForManualDeletion) == ReturnSmallDlg::BUTTON_OKAY) { try { @@ -1088,9 +1126,12 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec selectionRight, folderCmp, extractDirectionCfg(getConfig().mainCfg), - globalSettings->gui.deleteOnBothSides, - globalSettings->gui.useRecyclerForManualDeletion, - statusHandler); + globalCfg.gui.deleteOnBothSides, + globalCfg.gui.useRecyclerForManualDeletion, + statusHandler, + globalCfg.optDialogs.warningRecyclerMissing); + + gridview::clearSelection(*m_gridMainL, *m_gridMainC, *m_gridMainR); //do not clear, if aborted! } catch (AbortDeleteProcess&) {} @@ -1099,8 +1140,6 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec //redraw grid neccessary to update new dimensions and for UI-Backend data linkage updateGui(); //call immediately after deleteFromGridAndHD!!! - - gridview::clearSelection(*m_gridMain); } } } @@ -1118,26 +1157,11 @@ wxString extractLastValidDir(const FileSystemObject& fsObj) } -void MainDialog::openExternalApplication(const wxString& commandline, const zen::FileSystemObject* fsObj, size_t compPos) //fsObj may be nullptr +void MainDialog::openExternalApplication(const wxString& commandline, const zen::FileSystemObject* fsObj, bool leftSide) //fsObj may be nullptr { if (commandline.empty()) return; - bool leftSide = true; - switch (compPos) - { - case gridview::COMP_LEFT: - break; - case gridview::COMP_MIDDLE: - return; //we don't want to start external apps when double-clicking here! - case gridview::COMP_RIGHT: - leftSide = false; - break; - default: //area to the right of main grid - leftSide = false; - fsObj = nullptr; //do not evaluate row when outside grid! - } - wxString name; wxString nameCo; wxString dir; @@ -1275,8 +1299,9 @@ void MainDialog::disableAllElements(bool enableAbort) m_panelConfig ->Disable(); m_bpButtonSyncConfig ->Disable(); m_buttonStartSync ->Disable(); - m_gridMain ->Disable(); - m_gridMain ->Disable(); + m_gridMainL ->Disable(); + m_gridMainC ->Disable(); + m_gridMainR ->Disable(); m_panelStatistics ->Disable(); m_gridNavi ->Disable(); m_panelDirectoryPairs->Disable(); @@ -1311,8 +1336,9 @@ void MainDialog::enableAllElements() m_panelConfig ->Enable(); m_bpButtonSyncConfig ->Enable(); m_buttonStartSync ->Enable(); - m_gridMain ->Enable(); - m_gridMain ->Enable(); + m_gridMainL ->Enable(); + m_gridMainC ->Enable(); + m_gridMainR ->Enable(); m_panelStatistics ->Enable(); m_gridNavi ->Enable(); m_panelDirectoryPairs->Enable(); @@ -1402,7 +1428,7 @@ void MainDialog::OnResizeFolderPairs(wxEvent& event) void MainDialog::onTreeButtonEvent(wxKeyEvent& event) { int keyCode = event.GetKeyCode(); - if (m_gridMain->GetLayoutDirection() == wxLayout_RightToLeft) + if (m_gridNavi->GetLayoutDirection() == wxLayout_RightToLeft) { if (keyCode == WXK_LEFT) keyCode = WXK_RIGHT; @@ -1459,10 +1485,23 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event) } -void MainDialog::onGridButtonEvent(wxKeyEvent& event) +void MainDialog::onGridButtonEventL(wxKeyEvent& event) +{ + onGridButtonEvent(event, *m_gridMainL, true); +} +void MainDialog::onGridButtonEventC(wxKeyEvent& event) +{ + onGridButtonEvent(event, *m_gridMainC, true); +} +void MainDialog::onGridButtonEventR(wxKeyEvent& event) +{ + onGridButtonEvent(event, *m_gridMainR, false); +} + +void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) { int keyCode = event.GetKeyCode(); - if (m_gridMain->GetLayoutDirection() == wxLayout_RightToLeft) + if (grid.GetLayoutDirection() == wxLayout_RightToLeft) { if (keyCode == WXK_LEFT) keyCode = WXK_RIGHT; @@ -1488,19 +1527,19 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event) { case WXK_NUMPAD_LEFT: case WXK_LEFT: //ALT + <- - setSyncDirManually(getGridSelection(), zen::SYNC_DIR_LEFT); + setSyncDirManually(getGridSelection(), SYNC_DIR_LEFT); return; case WXK_NUMPAD_RIGHT: case WXK_RIGHT: //ALT + -> - setSyncDirManually(getGridSelection(), zen::SYNC_DIR_RIGHT); + setSyncDirManually(getGridSelection(), SYNC_DIR_RIGHT); return; case WXK_NUMPAD_UP: case WXK_NUMPAD_DOWN: case WXK_UP: /* ALT + /|\ */ case WXK_DOWN: /* ALT + \|/ */ - setSyncDirManually(getGridSelection(), zen::SYNC_DIR_NONE); + setSyncDirManually(getGridSelection(), SYNC_DIR_NONE); return; } @@ -1524,13 +1563,12 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event) case WXK_RETURN: case WXK_NUMPAD_ENTER: - if (!globalSettings->gui.externelApplications.empty()) + if (!globalCfg.gui.externelApplications.empty()) { - const wxString commandline = globalSettings->gui.externelApplications[0].second; //open with first external application - auto cursorPos = m_gridMain->getGridCursor(); - const size_t row = cursorPos.first; - const size_t compPos = cursorPos.second; - openExternalApplication(commandline, gridDataView->getObject(row), compPos); + const wxString commandline = globalCfg.gui.externelApplications[0].second; //open with first external application + auto cursorPos = grid.getGridCursor(); + const size_t row = cursorPos.first; + openExternalApplication(commandline, gridDataView->getObject(row), leftSide); } return; } @@ -1552,11 +1590,13 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou { //avoid recursion!!! -> this ugly construct seems to be the only (portable) way to avoid re-entrancy //recursion may happen in multiple situations: e.g. modal dialogs, wxGrid::ProcessEvent()! - if (processingGlobalKeyEvent || - !IsShown() || - !IsActive() || - !IsEnabled() || //only handle if main window is in use - !m_gridMain->IsEnabled()) // + if (processingGlobalKeyEvent || + !IsShown() || + !IsActive() || + !IsEnabled() || + !m_gridMainL->IsEnabled() || // + !m_gridMainC->IsEnabled() || //only handle if main window is in use + !m_gridMainR->IsEnabled()) // { event.Skip(); return; @@ -1573,7 +1613,7 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou switch (keyCode) { case 'F': //CTRL + F - zen::startFind(this, *m_gridMain, gridview::COMP_LEFT, gridview::COMP_RIGHT, globalSettings->gui.textSearchRespectCase); + zen::startFind(this, *m_gridMainL, *m_gridMainR, globalCfg.gui.textSearchRespectCase); return; //-> swallow event! } @@ -1581,7 +1621,7 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou { case WXK_F3: //F3 case WXK_NUMPAD_F3: // - zen::findNext(this, *m_gridMain, gridview::COMP_LEFT, gridview::COMP_RIGHT, globalSettings->gui.textSearchRespectCase); + zen::findNext(this, *m_gridMainL, *m_gridMainR, globalCfg.gui.textSearchRespectCase); return; //-> swallow event! case WXK_F8: //F8 @@ -1608,15 +1648,17 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou case WXK_NUMPAD_END: { const wxWindow* focus = wxWindow::FindFocus(); - if (!isPartOf(focus, m_gridMain ) && //don't propagate keyboard commands if grid is already in focus + if (!isPartOf(focus, m_gridMainL ) && // + !isPartOf(focus, m_gridMainC ) && //don't propagate keyboard commands if grid is already in focus + !isPartOf(focus, m_gridMainR ) && // !isPartOf(focus, m_listBoxHistory) && //don't propagate if selecting config !isPartOf(focus, m_directoryLeft) && //don't propagate if changing directory field !isPartOf(focus, m_directoryRight) && !isPartOf(focus, m_gridNavi ) && !isPartOf(focus, m_scrolledWindowFolderPairs)) - if (wxEvtHandler* evtHandler = m_gridMain->GetEventHandler()) + if (wxEvtHandler* evtHandler = m_gridMainL->GetEventHandler()) { - m_gridMain->SetFocus(); + m_gridMainL->SetFocus(); evtHandler->ProcessEvent(event); //propagating event catched at wxTheApp to child leads to recursion, but we prevented it... event.Skip(false); //definitively handled now! return; @@ -1648,7 +1690,16 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) } if (leadRow >= 0) - m_gridMain->scrollTo(std::max(0, leadRow - 1)); //scroll one more row + { + leadRow =std::max(0, leadRow - 1); //scroll one more row + + m_gridMainL->scrollTo(leadRow); + m_gridMainC->scrollTo(leadRow); + m_gridMainR->scrollTo(leadRow); + + m_gridNavi->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 std::vector<const HierarchyObject*> markedFiles; //mark files/symlinks directly within a container @@ -1669,7 +1720,7 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) } }); - gridview::setNavigationMarker(*m_gridMain, std::move(markedFiles), std::move(markedContainer)); + gridview::setNavigationMarker(*m_gridMainL, std::move(markedFiles), std::move(markedContainer)); event.Skip(); } @@ -1740,138 +1791,145 @@ void MainDialog::onNaviGridContext(GridClickEvent& event) } -void MainDialog::onMainGridContext(GridClickEvent& event) +void MainDialog::onMainGridContextC(GridClickEvent& event) { - const auto& selection = getGridSelection(); //referenced by lambdas! ContextMenu menu; - switch (event.compPos_) + menu.addItem(_("Include all"), [&] { - case gridview::COMP_MIDDLE: - menu.addItem(_("Include all"), [&] - { - zen::setActiveStatus(true, folderCmp); - updateGui(); - }, nullptr, gridDataView->rowsTotal() > 0); + zen::setActiveStatus(true, folderCmp); + updateGui(); + }, nullptr, gridDataView->rowsTotal() > 0); - menu.addItem(_("Exclude all"), [&] - { - zen::setActiveStatus(false, folderCmp); - updateGuiAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true - }, nullptr, gridDataView->rowsTotal() > 0); - break; - - case gridview::COMP_LEFT: - case gridview::COMP_RIGHT: - default: //area to the right of main grid - //---------------------------------------------------------------------------------------------------- - if (showSyncAction_ && !selection.empty()) - { - auto getImage = [&](SyncDirection dir, SyncOperation soDefault) - { - return mirrorIfRtl(getSyncOpImage(selection[0]->getSyncOperation() != SO_EQUAL ? - selection[0]->testSyncOperation(dir) : soDefault)); - }; - const wxBitmap opRight = getImage(SYNC_DIR_RIGHT, SO_OVERWRITE_RIGHT); - const wxBitmap opNone = getImage(SYNC_DIR_NONE, SO_DO_NOTHING ); - const wxBitmap opLeft = getImage(SYNC_DIR_LEFT, SO_OVERWRITE_LEFT ); - - wxString shortCutLeft = L"\tAlt+Left"; - wxString shortCutRight = L"\tAlt+Right"; - if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) - std::swap(shortCutLeft, shortCutRight); - - menu.addItem(_("Set direction:") + L" ->" + shortCutRight, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_RIGHT); }, &opRight); - menu.addItem(_("Set direction:") + L" -" L"\tAlt+Up", [this, &selection] { setSyncDirManually(selection, SYNC_DIR_NONE); }, &opNone); - menu.addItem(_("Set direction:") + L" <-" + shortCutLeft, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_LEFT); }, &opLeft); - //Gtk needs a direction, "<-", because it has no context menu icons! - //Gtk requires "no spaces" for shortcut identifiers! - menu.addSeparator(); - } - //---------------------------------------------------------------------------------------------------- - if (!selection.empty()) - { - if (selection[0]->isActive()) - menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); - else - menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); - } - else - menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); + menu.addItem(_("Exclude all"), [&] + { + zen::setActiveStatus(false, folderCmp); + updateGuiAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true + }, nullptr, gridDataView->rowsTotal() > 0); - //---------------------------------------------------------------------------------------------------- - //EXCLUDE FILTER - if (selection.size() == 1) - { - ContextMenu submenu; + menu.popup(*this); +} - //by extension - if (dynamic_cast<const DirMapping*>(selection[0]) == nullptr) //non empty && no directory - { - const Zstring filename = afterLast(selection[0]->getObjRelativeName(), FILE_NAME_SEPARATOR); - if (contains(filename, Zchar('.'))) //be careful: AfterLast would return the whole string if '.' were not found! - { - const Zstring extension = afterLast(filename, Zchar('.')); +void MainDialog::onMainGridContextL(GridClickEvent& event) +{ + onMainGridContextRim(true); +} - submenu.addItem(L"*." + utfCvrtTo<wxString>(extension), - [this, extension] { excludeExtension(extension); }); - } - } +void MainDialog::onMainGridContextR(GridClickEvent& event) +{ + onMainGridContextRim(false); +} - //by short name - submenu.addItem(utfCvrtTo<wxString>(Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getObjShortName()), - [this, &selection] { excludeShortname(*selection[0]); }); +void MainDialog::onMainGridContextRim(bool leftSide) +{ + const auto& selection = getGridSelection(); //referenced by lambdas! + ContextMenu menu; - //by relative path - submenu.addItem(utfCvrtTo<wxString>(FILE_NAME_SEPARATOR + selection[0]->getObjRelativeName()), - [this, &selection] { excludeItems(selection); }); + if (showSyncAction_ && !selection.empty()) + { + auto getImage = [&](SyncDirection dir, SyncOperation soDefault) + { + return mirrorIfRtl(getSyncOpImage(selection[0]->getSyncOperation() != SO_EQUAL ? + selection[0]->testSyncOperation(dir) : soDefault)); + }; + const wxBitmap opRight = getImage(SYNC_DIR_RIGHT, SO_OVERWRITE_RIGHT); + const wxBitmap opNone = getImage(SYNC_DIR_NONE, SO_DO_NOTHING ); + const wxBitmap opLeft = getImage(SYNC_DIR_LEFT, SO_OVERWRITE_LEFT ); - menu.addSubmenu(_("Exclude via filter:"), submenu, &GlobalResources::getImage(L"filterSmall")); - } - else if (selection.size() > 1) + wxString shortCutLeft = L"\tAlt+Left"; + wxString shortCutRight = L"\tAlt+Right"; + if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) + std::swap(shortCutLeft, shortCutRight); + + menu.addItem(_("Set direction:") + L" ->" + shortCutRight, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_RIGHT); }, &opRight); + menu.addItem(_("Set direction:") + L" -" L"\tAlt+Up", [this, &selection] { setSyncDirManually(selection, SYNC_DIR_NONE); }, &opNone); + menu.addItem(_("Set direction:") + L" <-" + shortCutLeft, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_LEFT); }, &opLeft); + //Gtk needs a direction, "<-", because it has no context menu icons! + //Gtk requires "no spaces" for shortcut identifiers! + menu.addSeparator(); + } + //---------------------------------------------------------------------------------------------------- + if (!selection.empty()) + { + if (selection[0]->isActive()) + menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); + else + menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); + } + else + menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); + + //---------------------------------------------------------------------------------------------------- + //EXCLUDE FILTER + if (selection.size() == 1) + { + ContextMenu submenu; + + //by extension + if (dynamic_cast<const DirMapping*>(selection[0]) == nullptr) //non empty && no directory + { + const Zstring filename = afterLast(selection[0]->getObjRelativeName(), FILE_NAME_SEPARATOR); + if (contains(filename, Zchar('.'))) //be careful: AfterLast would return the whole string if '.' were not found! { - //by relative path - menu.addItem(_("Exclude via filter:") + L" " + _("<multiple selection>"), - [this, &selection] { excludeItems(selection); }, &GlobalResources::getImage(L"filterSmall")); + const Zstring extension = afterLast(filename, Zchar('.')); + + submenu.addItem(L"*." + utfCvrtTo<wxString>(extension), + [this, extension] { excludeExtension(extension); }); } + } - //---------------------------------------------------------------------------------------------------- - //CONTEXT_EXTERNAL_APP - if (!globalSettings->gui.externelApplications.empty()) - { - menu.addSeparator(); + //by short name + submenu.addItem(utfCvrtTo<wxString>(Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getObjShortName()), + [this, &selection] { excludeShortname(*selection[0]); }); - for (auto iter = globalSettings->gui.externelApplications.begin(); - iter != globalSettings->gui.externelApplications.end(); - ++iter) - { - //some trick to translate default external apps on the fly: 1. "open in explorer" 2. "start directly" - wxString description = zen::implementation::translate(iter->first); - if (description.empty()) - description = L" "; //wxWidgets doesn't like empty items + //by relative path + submenu.addItem(utfCvrtTo<wxString>(FILE_NAME_SEPARATOR + selection[0]->getObjRelativeName()), + [this, &selection] { excludeItems(selection); }); - const wxString command = iter->second; + menu.addSubmenu(_("Exclude via filter:"), submenu, &GlobalResources::getImage(L"filterSmall")); + } + else if (selection.size() > 1) + { + //by relative path + menu.addItem(_("Exclude via filter:") + L" " + _("<multiple selection>"), + [this, &selection] { excludeItems(selection); }, &GlobalResources::getImage(L"filterSmall")); + } - auto openApp = [this, &selection, command, event] { openExternalApplication(command, selection.empty() ? nullptr : selection[0], event.compPos_); }; + //---------------------------------------------------------------------------------------------------- + //CONTEXT_EXTERNAL_APP + if (!globalCfg.gui.externelApplications.empty()) + { + menu.addSeparator(); - if (iter == globalSettings->gui.externelApplications.begin()) - menu.addItem(description + L"\tEnter", openApp); - else - menu.addItem(description, openApp, nullptr, !selection.empty()); - } - } - //---------------------------------------------------------------------------------------------------- - //CONTEXT_DELETE_FILES - menu.addSeparator(); + for (auto iter = globalCfg.gui.externelApplications.begin(); + iter != globalCfg.gui.externelApplications.end(); + ++iter) + { + //some trick to translate default external apps on the fly: 1. "open in explorer" 2. "start directly" + wxString description = zen::implementation::translate(iter->first); + if (description.empty()) + description = L" "; //wxWidgets doesn't like empty items - menu.addItem(_("Delete") + L"\tDel", [this] - { - deleteSelectedFiles( - getGridSelection(true, false), - getGridSelection(false, true)); - }, nullptr, !selection.empty()); - break; + const wxString command = iter->second; + + auto openApp = [this, &selection, leftSide, command] { openExternalApplication(command, selection.empty() ? nullptr : selection[0], leftSide); }; + + if (iter == globalCfg.gui.externelApplications.begin()) + menu.addItem(description + L"\tEnter", openApp); + else + menu.addItem(description, openApp, nullptr, !selection.empty()); + } } + //---------------------------------------------------------------------------------------------------- + //CONTEXT_DELETE_FILES + menu.addSeparator(); + + menu.addItem(_("Delete") + L"\tDel", [this] + { + deleteSelectedFiles( + getGridSelection(true, false), + getGridSelection(false, true)); + }, nullptr, !selection.empty()); menu.popup(*this); } @@ -1951,92 +2009,99 @@ void MainDialog::excludeItems(const std::vector<FileSystemObject*>& selection) } -void MainDialog::onGridLabelContext(GridClickEvent& event) +void MainDialog::onGridLabelContextC(GridClickEvent& event) { ContextMenu menu; + menu.addItem(_("Category") + L"\tF8", [&] { showSyncAction(false); }, showSyncAction_ ? nullptr : &GlobalResources::getImage(L"compareSmall")); + menu.addItem(_("Action"), [&] { showSyncAction(true ); }, showSyncAction_ ? &GlobalResources::getImage(L"syncSmall") : nullptr); + menu.popup(*this); +} - if (event.compPos_ == gridview::COMP_LEFT || - event.compPos_ == gridview::COMP_RIGHT) - { - auto toggleColumn = [&](const Grid::ColumnAttribute& ca, size_t compPos) - { - auto colAttr = m_gridMain->getColumnConfig(compPos); - for (auto iter = colAttr.begin(); iter != colAttr.end(); ++iter) - if (iter->type_ == ca.type_) - { - iter->visible_ = !ca.visible_; - m_gridMain->setColumnConfig(colAttr, compPos); - return; - } - }; +void MainDialog::onGridLabelContextL(GridClickEvent& event) +{ + onGridLabelContext(*m_gridMainL, static_cast<ColumnTypeRim>(event.colType_), getDefaultColumnAttributesLeft()); +} +void MainDialog::onGridLabelContextR(GridClickEvent& event) +{ + onGridLabelContext(*m_gridMainR, static_cast<ColumnTypeRim>(event.colType_), getDefaultColumnAttributesRight()); +} - if (auto prov = m_gridMain->getDataProvider(event.compPos_)) - { - const auto& colAttr = m_gridMain->getColumnConfig(event.compPos_); - for (auto iter = colAttr.begin(); iter != colAttr.end(); ++iter) - { - const Grid::ColumnAttribute& ca = *iter; - const size_t compPos = event.compPos_; - menu.addCheckBox(prov->getColumnLabel(ca.type_), [ca, compPos, toggleColumn] { toggleColumn(ca, compPos); }, - ca.visible_, ca.type_ != static_cast<ColumnType>(COL_TYPE_FILENAME)); //do not allow user to hide file name column! - } - } - //---------------------------------------------------------------------------------------------- - menu.addSeparator(); +void MainDialog::onGridLabelContext(Grid& grid, ColumnTypeRim type, const std::vector<ColumnAttributeRim>& defaultColumnAttributes) +{ + ContextMenu menu; - auto setDefault = [&] - { - m_gridMain->setColumnConfig(gridview::convertConfig(event.compPos_ == gridview::COMP_LEFT ? - getDefaultColumnAttributesLeft() : - getDefaultColumnAttributesRight()), event.compPos_); - }; - menu.addItem(_("&Default"), setDefault); //'&' -> reuse text from "default" buttons elsewhere - //---------------------------------------------------------------------------------------------- - menu.addSeparator(); - menu.addCheckBox(_("Show icons:"), [&] - { - globalSettings->gui.showIcons = !globalSettings->gui.showIcons; - gridview::setupIcons(*m_gridMain, globalSettings->gui.showIcons, convert(globalSettings->gui.iconSize)); + auto toggleColumn = [&](const Grid::ColumnAttribute& ca) + { + auto colAttr = grid.getColumnConfig(); - }, globalSettings->gui.showIcons); + for (auto iter = colAttr.begin(); iter != colAttr.end(); ++iter) + if (iter->type_ == ca.type_) + { + iter->visible_ = !ca.visible_; + grid.setColumnConfig(colAttr); + return; + } + }; - auto setIconSize = [&](xmlAccess::FileIconSize sz) - { - globalSettings->gui.iconSize = sz; - gridview::setupIcons(*m_gridMain, globalSettings->gui.showIcons, convert(sz)); - }; - auto addSizeEntry = [&](const wxString& label, xmlAccess::FileIconSize sz) - { - auto setIconSize2 = setIconSize; //bring into scope - menu.addRadio(label, [sz, setIconSize2] { setIconSize2(sz); }, globalSettings->gui.iconSize == sz, globalSettings->gui.showIcons); - }; - addSizeEntry(L" " + _("Small" ), xmlAccess::ICON_SIZE_SMALL ); - addSizeEntry(L" " + _("Medium"), xmlAccess::ICON_SIZE_MEDIUM); - addSizeEntry(L" " + _("Large" ), xmlAccess::ICON_SIZE_LARGE ); - //---------------------------------------------------------------------------------------------- - if (static_cast<ColumnTypeRim>(event.colType_) == COL_TYPE_DATE) + if (auto prov = grid.getDataProvider()) + { + const auto& colAttr = grid.getColumnConfig(); + for (auto iter = colAttr.begin(); iter != colAttr.end(); ++iter) { - menu.addSeparator(); + const Grid::ColumnAttribute& ca = *iter; - auto selectTimeSpan = [&] - { - if (showSelectTimespanDlg(this, manualTimeSpanFrom, manualTimeSpanTo) == ReturnSmallDlg::BUTTON_OKAY) - { - applyTimeSpanFilter(folderCmp, manualTimeSpanFrom, manualTimeSpanTo); //overwrite current active/inactive settings - updateGuiAfterFilterChange(400); - } - }; - menu.addItem(_("Select time span..."), selectTimeSpan); + menu.addCheckBox(prov->getColumnLabel(ca.type_), [ca, toggleColumn] { toggleColumn(ca); }, + ca.visible_, ca.type_ != static_cast<ColumnType>(COL_TYPE_FILENAME)); //do not allow user to hide file name column! } } + //---------------------------------------------------------------------------------------------- + menu.addSeparator(); - else if (event.compPos_ == gridview::COMP_MIDDLE) + auto setDefault = [&] { - menu.addItem(_("Category") + L"\tF8", [&] { showSyncAction(false); }, showSyncAction_ ? nullptr : &GlobalResources::getImage(L"compareSmall")); - menu.addItem(_("Action"), [&] { showSyncAction(true ); }, showSyncAction_ ? &GlobalResources::getImage(L"syncSmall") : nullptr); + grid.setColumnConfig(gridview::convertConfig(defaultColumnAttributes)); + }; + menu.addItem(_("&Default"), setDefault); //'&' -> reuse text from "default" buttons elsewhere + //---------------------------------------------------------------------------------------------- + menu.addSeparator(); + menu.addCheckBox(_("Show icons:"), [&] + { + globalCfg.gui.showIcons = !globalCfg.gui.showIcons; + gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg.gui.showIcons, convert(globalCfg.gui.iconSize)); + + }, globalCfg.gui.showIcons); + + auto setIconSize = [&](xmlAccess::FileIconSize sz) + { + globalCfg.gui.iconSize = sz; + gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg.gui.showIcons, convert(sz)); + }; + auto addSizeEntry = [&](const wxString& label, xmlAccess::FileIconSize sz) + { + auto setIconSize2 = setIconSize; //bring into scope + menu.addRadio(label, [sz, setIconSize2] { setIconSize2(sz); }, globalCfg.gui.iconSize == sz, globalCfg.gui.showIcons); + }; + addSizeEntry(L" " + _("Small" ), xmlAccess::ICON_SIZE_SMALL ); + addSizeEntry(L" " + _("Medium"), xmlAccess::ICON_SIZE_MEDIUM); + addSizeEntry(L" " + _("Large" ), xmlAccess::ICON_SIZE_LARGE ); + //---------------------------------------------------------------------------------------------- + if (type == COL_TYPE_DATE) + { + menu.addSeparator(); + + auto selectTimeSpan = [&] + { + if (showSelectTimespanDlg(this, manualTimeSpanFrom, manualTimeSpanTo) == ReturnSmallDlg::BUTTON_OKAY) + { + applyTimeSpanFilter(folderCmp, manualTimeSpanFrom, manualTimeSpanTo); //overwrite current active/inactive settings + updateGuiAfterFilterChange(400); + } + }; + menu.addItem(_("Select time span..."), selectTimeSpan); } + menu.popup(*this); } @@ -2048,6 +2113,8 @@ void MainDialog::OnContextSetLayout(wxMouseEvent& event) menu.addItem(_("Default view"), [&] { auiMgr.LoadPerspective(defaultPerspective); + + m_splitterMain->setSashOffset(0); updateGuiForFolderPair(); }); //---------------------------------------------------------------------------------------- @@ -2296,13 +2363,13 @@ bool MainDialog::saveOldConfig() //return false on user abort if (lastConfigurationSaved != getConfig()) { //notify user about changed settings - if (globalSettings->optDialogs.popupOnConfigChange) + if (globalCfg.optDialogs.popupOnConfigChange) if (activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName()) //only if check is active and non-default config file loaded { const wxString filename = activeConfigFiles[0]; - bool neverSave = !globalSettings->optDialogs.popupOnConfigChange; + bool neverSave = !globalCfg.optDialogs.popupOnConfigChange; CheckBox cb(_("Never save changes"), neverSave); switch (showQuestionDlg(this, @@ -2320,7 +2387,7 @@ bool MainDialog::saveOldConfig() //return false on user abort nullptr); case ReturnQuestionDlg::BUTTON_NO: - globalSettings->optDialogs.popupOnConfigChange = !neverSave; + globalCfg.optDialogs.popupOnConfigChange = !neverSave; break; case ReturnQuestionDlg::BUTTON_CANCEL: @@ -2645,8 +2712,11 @@ void MainDialog::updateGuiAfterFilterChange(int delay) if (currentCfg.hideFilteredElements) { - m_gridMain->Refresh(); - m_gridMain->Update(); + gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); + m_gridMainL->Update(); + m_gridMainC->Update(); + m_gridMainR->Update(); + wxMilliSleep(delay); //some delay to show user the rows he has filtered out before they are removed } @@ -2941,9 +3011,16 @@ void MainDialog::OnCompare(wxCommandEvent& event) wxBusyCursor dummy; //show hourglass cursor + wxWindow* oldFocus = wxWindow::FindFocus(); + ZEN_ON_SCOPE_EXIT( if (oldFocus) oldFocus->SetFocus(); ); //e.g. keep focus on main grid after pressing F5 + int scrollPosX = 0; int scrollPosY = 0; - m_gridMain->GetViewStart(&scrollPosX, &scrollPosY); //preserve current scroll position + m_gridMainL->GetViewStart(&scrollPosX, &scrollPosY); //preserve current scroll position + ZEN_ON_SCOPE_EXIT( + m_gridMainL->Scroll(scrollPosX, scrollPosY); // + m_gridMainR->Scroll(scrollPosX, scrollPosY); //restore + m_gridMainC->Scroll(-1, scrollPosY); ) // clearGrid(false); //avoid memory peak by clearing old data @@ -2956,7 +3033,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization std::unique_ptr<LockHolder> dummy2; - if (globalSettings->createLockFile) + if (globalCfg.createLockFile) { dummy2.reset(new LockHolder(true)); //allow pw prompt for (auto iter = cmpConfig.begin(); iter != cmpConfig.end(); ++iter) @@ -2967,23 +3044,18 @@ void MainDialog::OnCompare(wxCommandEvent& event) } //begin comparison - zen::CompareProcess compProc(globalSettings->fileTimeTolerance, - globalSettings->optDialogs, + zen::CompareProcess compProc(globalCfg.fileTimeTolerance, + globalCfg.optDialogs, true, //allow pw prompt - globalSettings->runWithBackgroundPriority, + globalCfg.runWithBackgroundPriority, statusHandler); //technical representation of comparison data - compProc.startCompareProcess(cmpConfig, folderCmp); //throw - - //play (optional) sound notification after sync has completed (GUI and batch mode) - const wxString soundFile = toWx(zen::getResourceDir()) + wxT("Compare_Complete.wav"); - if (fileExists(toZ(soundFile))) - wxSound::Play(soundFile, wxSOUND_ASYNC); + compProc.startCompareProcess(cmpConfig, folderCmp); //throw GuiAbortProcess } catch (GuiAbortProcess&) { - if (m_buttonCompare->IsShownOnScreen()) m_buttonCompare->SetFocus(); + // if (m_buttonCompare->IsShownOnScreen()) m_buttonCompare->SetFocus(); updateGui(); //refresh grid in ANY case! (also on abort) return; } @@ -2991,15 +3063,19 @@ void MainDialog::OnCompare(wxCommandEvent& event) gridDataView->setData(folderCmp); //update view on data treeDataView->setData(folderCmp); // updateGui(); - m_gridMain->Scroll(scrollPosX, scrollPosY); //restore + updateSyncEnabledStatus(); //enable the sync button - if (m_buttonStartSync->IsShownOnScreen()) - m_buttonStartSync->SetFocus(); + // if (m_buttonStartSync->IsShownOnScreen()) m_buttonStartSync->SetFocus(); - gridview::clearSelection(*m_gridMain); + gridview::clearSelection(*m_gridMainL, *m_gridMainC, *m_gridMainR); m_gridNavi->clearSelection(); + //play (optional) sound notification after sync has completed (GUI and batch mode) + const wxString soundFile = toWx(zen::getResourceDir()) + L"Compare_Complete.wav"; + if (fileExists(toZ(soundFile))) + wxSound::Play(soundFile, wxSOUND_ASYNC); + //add to folder history after successful comparison only folderHistoryLeft ->addItem(toZ(m_directoryLeft->GetValue())); folderHistoryRight->addItem(toZ(m_directoryRight->GetValue())); @@ -3070,8 +3146,8 @@ void MainDialog::updateStatistics() void MainDialog::OnSyncSettings(wxCommandEvent& event) { ExecWhenFinishedCfg ewfCfg = { ¤tCfg.mainCfg.onCompletion, - &globalSettings->gui.onCompletionHistory, - globalSettings->gui.onCompletionHistoryMax + &globalCfg.gui.onCompletionHistory, + globalCfg.gui.onCompletionHistoryMax }; if (showSyncConfigDlg(this, @@ -3145,7 +3221,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) } //show sync preview screen - if (globalSettings->optDialogs.showSummaryBeforeSync) + if (globalCfg.optDialogs.showSummaryBeforeSync) { bool dontShowAgain = false; @@ -3155,7 +3231,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) dontShowAgain) != ReturnSmallDlg::BUTTON_OKAY) return; - globalSettings->optDialogs.showSummaryBeforeSync = !dontShowAgain; + globalCfg.optDialogs.showSummaryBeforeSync = !dontShowAgain; } wxBusyCursor dummy; //show hourglass cursor @@ -3175,11 +3251,11 @@ void MainDialog::OnStartSync(wxCommandEvent& event) currentCfg.handleError, xmlAccess::extractJobName(activeFileName), guiCfg.mainCfg.onCompletion, - globalSettings->gui.onCompletionHistory); + globalCfg.gui.onCompletionHistory); //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization std::unique_ptr<LockHolder> dummy2; - if (globalSettings->createLockFile) + if (globalCfg.createLockFile) { dummy2.reset(new LockHolder(true)); //allow pw prompt for (auto iter = begin(folderCmp); iter != end(folderCmp); ++iter) @@ -3192,12 +3268,12 @@ void MainDialog::OnStartSync(wxCommandEvent& event) //start synchronization and mark all elements processed zen::SyncProcess syncProc(xmlAccess::extractJobName(activeFileName), formatTime<std::wstring>(L"%Y-%m-%d %H%M%S"), - globalSettings->optDialogs, - globalSettings->verifyFileCopy, - globalSettings->copyLockedFiles, - globalSettings->copyFilePermissions, - globalSettings->transactionalFileCopy, - globalSettings->runWithBackgroundPriority, + globalCfg.optDialogs, + globalCfg.verifyFileCopy, + globalCfg.copyLockedFiles, + globalCfg.copyFilePermissions, + globalCfg.transactionalFileCopy, + globalCfg.runWithBackgroundPriority, statusHandler); const std::vector<zen::FolderPairSyncCfg> syncProcessCfg = zen::extractSyncCfg(guiCfg.mainCfg); @@ -3225,48 +3301,52 @@ void MainDialog::OnStartSync(wxCommandEvent& event) } -void MainDialog::onGridDoubleClick(GridClickEvent& event) +void MainDialog::onGridDoubleClickL(GridClickEvent& event) { - if (!globalSettings->gui.externelApplications.empty()) - openExternalApplication(globalSettings->gui.externelApplications[0].second, - gridDataView->getObject(event.row_), //optional - event.compPos_); + onGridDoubleClickRim(event.row_, true); +} +void MainDialog::onGridDoubleClickR(GridClickEvent& event) +{ + onGridDoubleClickRim(event.row_, false); } - -void MainDialog::onGridLabelLeftClick(GridClickEvent& event) +void MainDialog::onGridDoubleClickRim(int row, bool leftSide) { - auto sortSide = [&](bool onLeft) - { - auto sortInfo = gridDataView->getSortInfo(); + if (!globalCfg.gui.externelApplications.empty()) + openExternalApplication(globalCfg.gui.externelApplications[0].second, + gridDataView->getObject(row), //optional + leftSide); +} - ColumnTypeRim type = static_cast<ColumnTypeRim>(event.colType_); - bool sortAscending = GridView::getDefaultSortDirection(type); - if (sortInfo && sortInfo->onLeft_ == onLeft && sortInfo->type_ == type) - sortAscending = !sortInfo->ascending_; +void MainDialog::onGridLabelLeftClick(bool onLeft, ColumnTypeRim type) +{ + auto sortInfo = gridDataView->getSortInfo(); - gridDataView->sortView(type, onLeft, sortAscending); + bool sortAscending = GridView::getDefaultSortDirection(type); + if (sortInfo && sortInfo->onLeft_ == onLeft && sortInfo->type_ == type) + sortAscending = !sortInfo->ascending_; - gridview::clearSelection(*m_gridMain); - updateGui(); //refresh gridDataView - }; + gridDataView->sortView(type, onLeft, sortAscending); - switch (event.compPos_) - { - case gridview::COMP_LEFT: - sortSide(true); - break; + gridview::clearSelection(*m_gridMainL, *m_gridMainC, *m_gridMainR); + updateGui(); //refresh gridDataView +} + +void MainDialog::onGridLabelLeftClickL(GridClickEvent& event) +{ + onGridLabelLeftClick(true, static_cast<ColumnTypeRim>(event.colType_)); +} +void MainDialog::onGridLabelLeftClickR(GridClickEvent& event) +{ + onGridLabelLeftClick(false, static_cast<ColumnTypeRim>(event.colType_)); +} - case gridview::COMP_MIDDLE: - //sorting middle grid is more or less useless: therefore let's toggle view instead! - showSyncAction(!showSyncAction_); //toggle view - break; - case gridview::COMP_RIGHT: - sortSide(false); - break; - } +void MainDialog::onGridLabelLeftClickC(GridClickEvent& event) +{ + //sorting middle grid is more or less useless: therefore let's toggle view instead! + showSyncAction(!showSyncAction_); //toggle view } @@ -3434,7 +3514,7 @@ void MainDialog::updateGridViewData() m_panelViewFilter->Hide(); } //all three grids retrieve their data directly via gridDataView - m_gridMain->Refresh(); + gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); //navigation tree if (showSyncAction_) @@ -3510,7 +3590,7 @@ void MainDialog::applySyncConfig() zen::redetermineSyncDirection(getConfig().mainCfg, folderCmp, [&](const std::wstring& warning) { - bool& warningActive = globalSettings->optDialogs.warningSyncDatabase; + bool& warningActive = globalCfg.optDialogs.warningSyncDatabase; if (warningActive) { bool dontWarnAgain = false; @@ -3618,7 +3698,7 @@ void MainDialog::updateGuiForFolderPair() { int pairHeight = additionalFolderPairs[0]->GetSize().GetHeight(); addPairMinimalHeight = std::min<double>(1.5, additionalFolderPairs.size()) * pairHeight; //have 0.5 * height indicate that more folders are there - addPairOptimalHeight = std::min<double>(globalSettings->gui.maxFolderPairsVisible - 1 + 0.5, //subtract first/main folder pair and add 0.5 to indicate additional folders + addPairOptimalHeight = std::min<double>(globalCfg.gui.maxFolderPairsVisible - 1 + 0.5, //subtract first/main folder pair and add 0.5 to indicate additional folders additionalFolderPairs.size()) * pairHeight; addPairOptimalHeight = std::max(addPairOptimalHeight, addPairMinimalHeight); //implicitly handle corrupted values for "maxFolderPairsVisible" @@ -3719,7 +3799,7 @@ void MainDialog::removeAddFolderPair(size_t pos) //set size of scrolled window //const size_t additionalRows = additionalFolderPairs.size(); - //if (additionalRows <= globalSettings->gui.addFolderPairCountMax) //up to "addFolderPairCountMax" additional pairs shall be shown + //if (additionalRows <= globalCfg.gui.addFolderPairCountMax) //up to "addFolderPairCountMax" additional pairs shall be shown // m_scrolledWindowFolderPairs->SetMinSize(wxSize(-1, pairHeight * static_cast<int>(additionalRows))); //update controls @@ -3756,7 +3836,7 @@ void MainDialog::clearAddFolderPairs() //menu events void MainDialog::OnMenuGlobalSettings(wxCommandEvent& event) { - zen::showGlobalSettingsDlg(this, *globalSettings); + zen::showGlobalSettingsDlg(this, globalCfg); } @@ -3816,13 +3896,13 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) buffer += '\n'; //write header - auto provLeft = m_gridMain->getDataProvider(gridview::COMP_LEFT); - auto provMiddle = m_gridMain->getDataProvider(gridview::COMP_MIDDLE); - auto provRight = m_gridMain->getDataProvider(gridview::COMP_RIGHT); + auto provLeft = m_gridMainL->getDataProvider(); + auto provMiddle = m_gridMainC->getDataProvider(); + auto provRight = m_gridMainR->getDataProvider(); - auto colAttrLeft = m_gridMain->getColumnConfig(gridview::COMP_LEFT); - auto colAttrMiddle = m_gridMain->getColumnConfig(gridview::COMP_MIDDLE); - auto colAttrRight = m_gridMain->getColumnConfig(gridview::COMP_RIGHT); + auto colAttrLeft = m_gridMainL->getColumnConfig(); + auto colAttrMiddle = m_gridMainC->getColumnConfig(); + auto colAttrRight = m_gridMainR->getColumnConfig(); vector_remove_if(colAttrLeft , [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); vector_remove_if(colAttrMiddle, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); @@ -3863,7 +3943,7 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) buffer += '\n'; //main grid - const size_t rowCount = m_gridMain->getRowCount(); + const size_t rowCount = m_gridMainL->getRowCount(); for (size_t row = 0; row < rowCount; ++row) { std::for_each(colAttrLeft.begin(), colAttrLeft.end(), @@ -3921,8 +4001,8 @@ void MainDialog::OnMenuBatchJob(wxCommandEvent& event) referenceFile, batchCfg, folderHistoryLeft, folderHistoryRight, - globalSettings->gui.onCompletionHistory, - globalSettings->gui.onCompletionHistoryMax); + globalCfg.gui.onCompletionHistory, + globalCfg.gui.onCompletionHistoryMax); } @@ -3937,7 +4017,7 @@ void MainDialog::OnRegularUpdateCheck(wxIdleEvent& event) //execute just once per startup! Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), nullptr, this); - zen::checkForUpdatePeriodically(this, globalSettings->gui.lastUpdateCheck); + zen::checkForUpdatePeriodically(this, globalCfg.gui.lastUpdateCheck); } @@ -3978,18 +4058,13 @@ void MainDialog::OnMenuQuit(wxCommandEvent& event) //######################################################################################################### //language selection -void MainDialog::switchProgramLanguage(const int langID) +void MainDialog::switchProgramLanguage(int langID) { //create new dialog with respect to new language zen::setLanguage(langID); //language is a global attribute - const xmlAccess::XmlGuiConfig currentGuiCfg = getConfig(); - auto activeFiles = activeConfigFiles; - - writeGlobalSettings(); //updating global settings before creating new dialog - //create new main window and delete old one - MainDialog* frame = new MainDialog(activeFiles, currentGuiCfg, *globalSettings, false); + MainDialog* frame = new MainDialog(activeConfigFiles, getConfig(), getGlobalCfgBeforeExit(), false); frame->Show(); Destroy(); @@ -4011,7 +4086,7 @@ void MainDialog::showSyncAction(bool value) showSyncAction_ = value; //toggle display of sync preview in middle grid - gridview::showSyncAction(*m_gridMain, value); + gridview::showSyncAction(*m_gridMainC, value); updateGui(); } diff --git a/ui/main_dlg.h b/ui/main_dlg.h index 0e9a7830..63eef25a 100644 --- a/ui/main_dlg.h +++ b/ui/main_dlg.h @@ -29,11 +29,11 @@ class MainDialog : public MainDialogGenerated { public: MainDialog(const std::vector<wxString>& cfgFileNames, //default behavior, application start - xmlAccess::XmlGlobalSettings& settings); + const xmlAccess::XmlGlobalSettings& globalSettings); //take over ownership => save on exit MainDialog(const std::vector<wxString>& referenceFiles, const xmlAccess::XmlGuiConfig& guiCfg, - xmlAccess::XmlGlobalSettings& settings, + const xmlAccess::XmlGlobalSettings& globalSettings, //take over ownership => save on exit bool startComparison); ~MainDialog(); @@ -41,7 +41,7 @@ public: void disableAllElements(bool enableAbort); //dis-/enables all elements (except abort button) that might receive user input void enableAllElements(); //during long-running processes: comparison, deletion - void onQueryEndSession(); //last chance to do something before getting killed! + void onQueryEndSession(); //last chance to do something useful before killing the application! private: friend class CompareStatusHandler; @@ -56,7 +56,7 @@ private: MainDialog(); void init(const xmlAccess::XmlGuiConfig& guiCfg, - xmlAccess::XmlGlobalSettings& settings, + const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison); //configuration load/save @@ -66,6 +66,9 @@ private: xmlAccess::XmlGuiConfig getConfig() const; void setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg); + void setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSettings); //messes with Maximize(), window sizes, so call just once! + xmlAccess::XmlGlobalSettings getGlobalCfgBeforeExit(); //destructive "get" thanks to "Iconize(false), Maximize(false)" + void loadConfiguration(const wxString& filename); void loadConfiguration(const std::vector<wxString>& filenames); @@ -78,9 +81,6 @@ private: //used when saving configuration std::vector<wxString> activeConfigFiles; //name of currently loaded config file (may be more than 1) - void readGlobalSettings(); - void writeGlobalSettings(); - void initViewFilterButtons(); void updateFilterButtons(); @@ -106,7 +106,7 @@ private: void deleteSelectedFiles(const std::vector<zen::FileSystemObject*>& selectionLeft, const std::vector<zen::FileSystemObject*>& selectionRight); - void openExternalApplication(const wxString& commandline, const zen::FileSystemObject* fsObj, size_t compPos); //fsObj may be nullptr + void openExternalApplication(const wxString& commandline, const zen::FileSystemObject* fsObj, bool leftSide); //fsObj may be nullptr //work to be done in idle time void OnIdleEvent(wxEvent& event); @@ -116,7 +116,11 @@ private: void flashStatusInformation(const wxString& msg); //temporarily show different status //events - void onGridButtonEvent (wxKeyEvent& event); + void onGridButtonEventL(wxKeyEvent& event); + void onGridButtonEventC(wxKeyEvent& event); + void onGridButtonEventR(wxKeyEvent& event); + void onGridButtonEvent(wxKeyEvent& event, zen::Grid& grid, bool leftSide); + void onTreeButtonEvent (wxKeyEvent& event); void OnContextSetLayout (wxMouseEvent& event); void OnGlobalKeyEvent (wxKeyEvent& event); @@ -128,7 +132,11 @@ private: void applyCompareConfig(bool changePreviewStatus = true); //context menu handler methods - void onMainGridContext(zen::GridClickEvent& event); + void onMainGridContextL(zen::GridClickEvent& event); + void onMainGridContextC(zen::GridClickEvent& event); + void onMainGridContextR(zen::GridClickEvent& event); + void onMainGridContextRim(bool leftSide); + void onNaviGridContext(zen::GridClickEvent& event); void onNaviSelection(zen::GridRangeSelectEvent& event); @@ -139,9 +147,19 @@ private: void onCheckRows (zen::CheckRowsEvent& event); void onSetSyncDirection(zen::SyncDirectionEvent& event); - void onGridDoubleClick (zen::GridClickEvent& event); - void onGridLabelLeftClick (zen::GridClickEvent& event); - void onGridLabelContext(zen::GridClickEvent& event); + void onGridDoubleClickL(zen::GridClickEvent& event); + void onGridDoubleClickR(zen::GridClickEvent& event); + void onGridDoubleClickRim(int row, bool leftSide); + + void onGridLabelLeftClickC (zen::GridClickEvent& event); + void onGridLabelLeftClickL (zen::GridClickEvent& event); + void onGridLabelLeftClickR (zen::GridClickEvent& event); + void onGridLabelLeftClick(bool onLeft, zen::ColumnTypeRim type); + + void onGridLabelContextL(zen::GridClickEvent& event); + void onGridLabelContextC(zen::GridClickEvent& event); + void onGridLabelContextR(zen::GridClickEvent& event); + void onGridLabelContext(zen::Grid& grid, zen::ColumnTypeRim type, const std::vector<zen::ColumnAttributeRim>& defaultColumnAttributes); void OnLeftOnlyFiles( wxCommandEvent& event); void OnRightOnlyFiles( wxCommandEvent& event); @@ -209,7 +227,7 @@ private: void OnMenuQuit( wxCommandEvent& event); void OnMenuLanguageSwitch( wxCommandEvent& event); - void switchProgramLanguage(const int langID); + void switchProgramLanguage(int langID); typedef int MenuItemID; typedef int LanguageID; @@ -218,8 +236,8 @@ private: //*********************************************** //application variables are stored here: - //global settings used by GUI and batch mode - xmlAccess::XmlGlobalSettings* globalSettings; //always bound! + //global settings shared by GUI and batch mode + xmlAccess::XmlGlobalSettings globalCfg; //UI view of FolderComparison structure (partially owns folderCmp) std::shared_ptr<zen::GridView> gridDataView; //always bound! diff --git a/ui/search.cpp b/ui/search.cpp index 80e4aa26..2a9aabfb 100644 --- a/ui/search.cpp +++ b/ui/search.cpp @@ -107,13 +107,12 @@ private: template <bool respectCase> ptrdiff_t findRow(const Grid& grid, //return -1 if no matching row found - size_t compPos, const wxString& searchString, size_t rowFirst, //specify area to search: size_t rowLast) // [rowFirst, rowLast) { - auto prov = grid.getDataProvider(compPos); - std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig(compPos); + auto prov = grid.getDataProvider(); + std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig(); vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); if (!colAttr.empty() && prov) { @@ -129,16 +128,15 @@ ptrdiff_t findRow(const Grid& grid, //return -1 if no matching row found //syntactic sugar... -ptrdiff_t findRow(const Grid& grid, - size_t compPos, +ptrdiff_t findRow(Grid& grid, bool respectCase, const wxString& searchString, size_t rowFirst, //specify area to search: size_t rowLast) // [rowFirst, rowLast) { return respectCase ? - findRow<true>( grid, compPos, searchString, rowFirst, rowLast) : - findRow<false>(grid, compPos, searchString, rowFirst, rowLast); + findRow<true>( grid, searchString, rowFirst, rowLast) : + findRow<false>(grid, searchString, rowFirst, rowLast); } @@ -148,8 +146,7 @@ wxString lastSearchString; //this variable really is conceptionally global... void executeSearch(bool forceShowDialog, bool& respectCase, wxWindow* parent, - Grid& grid, - size_t compPosLeft, size_t compPosRight) + Grid* gridL, Grid* gridR) { bool searchDialogWasShown = false; @@ -162,36 +159,35 @@ void executeSearch(bool forceShowDialog, searchDialogWasShown = true; } - const size_t rowCount = grid.getRowCount(); - auto cursorPos = grid.getGridCursor(); //(row, component pos) + if (wxWindow::FindFocus() == &gridR->getMainWin()) + std::swap(gridL, gridR); //select side to start with - size_t cursorRow = cursorPos.first; - if (cursorRow >= rowCount) - cursorRow = 0; - - if (cursorPos.second == compPosRight) - std::swap(compPosLeft, compPosRight); //select side to start with - else if (cursorPos.second != compPosLeft) - cursorRow = 0; + const size_t rowCountL = gridL->getRowCount(); + const size_t rowCountR = gridR->getRowCount(); + auto cursorPos = gridL->getGridCursor(); //(row, component pos) + size_t cursorRowL = cursorPos.first; + if (cursorRowL >= rowCountL) + cursorRowL = 0; { wxBusyCursor showHourGlass; - auto finishSearch = [&](size_t compPos, size_t rowFirst, size_t rowLast) -> bool + auto finishSearch = [&](Grid& grid, size_t rowFirst, size_t rowLast) -> bool { - const ptrdiff_t targetRow = findRow(grid, compPos, respectCase, lastSearchString, rowFirst, rowLast); + const ptrdiff_t targetRow = findRow(grid, respectCase, lastSearchString, rowFirst, rowLast); if (targetRow >= 0) { - grid.setGridCursor(targetRow, compPos); + //gridOther.clearSelection(); -> not needed other grids are automatically cleared after selection + grid.setGridCursor(targetRow); grid.SetFocus(); return true; } return false; }; - if (finishSearch(compPosLeft , cursorRow + 1, rowCount) || - finishSearch(compPosRight, 0, rowCount) || - finishSearch(compPosLeft , 0, cursorRow + 1)) + if (finishSearch(*gridL, cursorRowL + 1, rowCountL) || + finishSearch(*gridR, 0, rowCountR) || + finishSearch(*gridL, 0, cursorRowL + 1)) return; } @@ -199,18 +195,18 @@ void executeSearch(bool forceShowDialog, //show search dialog again if (searchDialogWasShown) - executeSearch(true, respectCase, parent, grid, compPosLeft, compPosRight); + executeSearch(true, respectCase, parent, gridL, gridR); } //########################################################################################### -void zen::startFind(wxWindow* parent, Grid& grid, size_t compPosLeft, size_t compPosRight, bool& respectCase) //Strg + F +void zen::startFind(wxWindow* parent, Grid& gridL, Grid& gridR, bool& respectCase) //Strg + F { - executeSearch(true, respectCase, parent, grid, compPosLeft, compPosRight); + executeSearch(true, respectCase, parent, &gridL, &gridR); } -void zen::findNext(wxWindow* parent, Grid& grid, size_t compPosLeft, size_t compPosRight, bool& respectCase) //F3 +void zen::findNext(wxWindow* parent, Grid& gridL, Grid& gridR, bool& respectCase) //F3 { - executeSearch(false, respectCase, parent, grid, compPosLeft, compPosRight); + executeSearch(false, respectCase, parent, &gridL, &gridR); } diff --git a/ui/search.h b/ui/search.h index 6120562e..95811244 100644 --- a/ui/search.h +++ b/ui/search.h @@ -11,8 +11,8 @@ namespace zen { -void startFind(wxWindow* parent, Grid& grid, size_t compPosLeft, size_t compPosRight, bool& respectCase); //Strg + F -void findNext( wxWindow* parent, Grid& grid, size_t compPosLeft, size_t compPosRight, bool& respectCase); //F3 +void startFind(wxWindow* parent, Grid& gridL, Grid& gridR, bool& respectCase); //Strg + F +void findNext( wxWindow* parent, Grid& gridL, Grid& gridR, bool& respectCase); //F3 } #endif // SEARCH_H_INCLUDED diff --git a/ui/tree_view.cpp b/ui/tree_view.cpp index ea73d979..e4dc022e 100644 --- a/ui/tree_view.cpp +++ b/ui/tree_view.cpp @@ -478,9 +478,9 @@ void TreeView::updateCmpResult(bool hideFiltered, case FILE_DIFFERENT: return differentFilesActive; case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: //= sub-category of equal return equalFilesActive; case FILE_CONFLICT: - case FILE_DIFFERENT_METADATA: return conflictFilesActive; } assert(false); @@ -612,8 +612,8 @@ const wxColour COLOR_LEVEL11(0xff, 0xcc, 0x99); const wxColour COLOR_PERCENTAGE_BORDER(198, 198, 198); const wxColour COLOR_PERCENTAGE_BACKGROUND(0xf8, 0xf8, 0xf8); -//const wxColor COLOR_TREE_SELECTION_GRADIENT_FROM = wxColor( 89, 255, 99); //green: H:88 S:255 V:172 -//const wxColor COLOR_TREE_SELECTION_GRADIENT_TO = wxColor(225, 255, 227); // H:88 S:255 V:240 +//const wxColor COLOR_TREE_SELECTION_GRADIENT_FROM = wxColor( 89, 255, 99); //green: HSV: 88, 255, 172 +//const wxColor COLOR_TREE_SELECTION_GRADIENT_TO = wxColor(225, 255, 227); // HSV: 88, 255, 240 const wxColor COLOR_TREE_SELECTION_GRADIENT_FROM = getColorSelectionGradientFrom(); const wxColor COLOR_TREE_SELECTION_GRADIENT_TO = getColorSelectionGradientTo (); @@ -1182,7 +1182,7 @@ std::vector<Grid::ColumnAttribute> treeview::convertConfig(const std::vector<Col std::vector<Grid::ColumnAttribute> output; std::transform(attribClean.begin(), attribClean.end(), std::back_inserter(output), - [&](const ColumnAttributeNavi& a) { return Grid::ColumnAttribute(static_cast<ColumnType>(a.type_), a.width_, a.visible_); }); + [&](const ColumnAttributeNavi& ca) { return Grid::ColumnAttribute(static_cast<ColumnType>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); return output; } @@ -1193,7 +1193,7 @@ std::vector<ColumnAttributeNavi> treeview::convertConfig(const std::vector<Grid: std::vector<ColumnAttributeNavi> output; std::transform(attribs.begin(), attribs.end(), std::back_inserter(output), - [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeNavi(static_cast<ColumnTypeNavi>(ca.type_), ca.width_, ca.visible_); }); + [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeNavi(static_cast<ColumnTypeNavi>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); return makeConsistent(output); } diff --git a/ui/tree_view.h b/ui/tree_view.h index 01c737bc..331e1f30 100644 --- a/ui/tree_view.h +++ b/ui/tree_view.h @@ -147,7 +147,7 @@ private: template <bool ascending> static void sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeNavi columnType); template <bool ascending> struct LessShortName; - std::vector<TreeLine> flatTree; //collapsable/expandable sub-tree of partial view -> always sorted! + std::vector<TreeLine> flatTree; //collapsable/expandable sub-tree of folderCmpView -> always sorted! /* /|\ | (update...) | */ diff --git a/ui/triple_splitter.cpp b/ui/triple_splitter.cpp new file mode 100644 index 00000000..5783bc4f --- /dev/null +++ b/ui/triple_splitter.cpp @@ -0,0 +1,230 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "triple_splitter.h" +#include <algorithm> + +using namespace zen; + + +namespace +{ +//------------ Grid Constants ------------------------------- +const int SASH_HIT_TOLERANCE = 5; //currently onla a placebo! +const int SASH_SIZE = 10; +const double SASH_GRAVITY = 0.5; //value within [0, 1]; 1 := resize left only, 0 := resize right only +const int CHILD_WINDOW_MIN_SIZE = 50; //min. size of managed windows + +const wxColor COLOR_SASH_GRADIENT_FROM = wxColour(192, 192, 192); //light grey +const wxColor COLOR_SASH_GRADIENT_TO = *wxWHITE; +} + +TripleSplitter::TripleSplitter(wxWindow* parent, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + long style) : wxWindow(parent, id, pos, size, style | wxTAB_TRAVERSAL), //tab between windows + centerOffset(0), + windowL(nullptr), + windowC(nullptr), + windowR(nullptr) +{ + Connect(wxEVT_PAINT, wxPaintEventHandler(TripleSplitter::onPaintEvent ), nullptr, this); + Connect(wxEVT_SIZE, wxSizeEventHandler (TripleSplitter::onSizeEvent ), nullptr, this); + //http://wiki.wxwidgets.org/Flicker-Free_Drawing + Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(TripleSplitter::onEraseBackGround), nullptr, this); +#if wxCHECK_VERSION(2, 9, 1) + SetBackgroundStyle(wxBG_STYLE_PAINT); +#else + SetBackgroundStyle(wxBG_STYLE_CUSTOM); +#endif + Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(TripleSplitter::onMouseLeftDown ), nullptr, this); + Connect(wxEVT_LEFT_UP, wxMouseEventHandler(TripleSplitter::onMouseLeftUp ), nullptr, this); + Connect(wxEVT_MOTION, wxMouseEventHandler(TripleSplitter::onMouseMovement ), nullptr, this); + Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(TripleSplitter::onLeaveWindow ), nullptr, this); + Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(TripleSplitter::onMouseCaptureLost), nullptr, this); + Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(TripleSplitter::onMouseLeftDouble), nullptr, this); +} + + +void TripleSplitter::updateWindowSizes() +{ + if (windowL && windowC && windowR) + { + const int centerPosX = getCenterPosX(); + const int centerWidth = getCenterWidth(); + + const wxRect clientRect = GetClientRect(); + + const int widthL = centerPosX; + const int windowRposX = widthL + centerWidth; + const int widthR = clientRect.width - windowRposX; + + windowL->SetSize(0, 0, widthL, clientRect.height); + windowC->SetSize(widthL + SASH_SIZE, 0, windowC->GetSize().GetWidth(), clientRect.height); + windowR->SetSize(windowRposX, 0, widthR, clientRect.height); + + wxClientDC dc(this); + drawSash(dc); + } +} + + +class TripleSplitter::SashMove +{ +public: + SashMove(wxWindow& wnd, int mousePosX, int centerPosX) : wnd_(wnd), mousePosX_(mousePosX), centerPosX_(centerPosX) + { + wnd_.SetCursor(wxCURSOR_SIZEWE); + wnd_.CaptureMouse(); + } + ~SashMove() + { + wnd_.SetCursor(*wxSTANDARD_CURSOR); + if (wnd_.HasCapture()) + wnd_.ReleaseMouse(); + } + int getMousePosXStart () { return mousePosX_; } + int getCenterPosXStart() { return centerPosX_; } + +private: + wxWindow& wnd_; + const int mousePosX_; + const int centerPosX_; +}; + + +inline +int TripleSplitter::getCenterWidth() const +{ + return 2 * SASH_SIZE + (windowC ? windowC->GetSize().GetWidth() : 0); +} + + +int TripleSplitter::getCenterPosXOptimal() const +{ + const wxRect clientRect = GetClientRect(); + const int centerWidth = getCenterWidth(); + return (clientRect.width - centerWidth) * SASH_GRAVITY; //allowed to be negative for extreme client widths! +} + +int TripleSplitter::getCenterPosX() const +{ + const wxRect clientRect = GetClientRect(); + const int centerWidth = getCenterWidth(); + const int centerPosXOptimal = getCenterPosXOptimal(); + + //normalize "centerPosXOptimal + centerOffset" + if (clientRect.width < 2 * CHILD_WINDOW_MIN_SIZE + centerWidth) //continuous transition between conditional branches! + //use fixed "centeroffset" when "clientRect.width == 2 * CHILD_WINDOW_MIN_SIZE + centerWidth" + return centerPosXOptimal + CHILD_WINDOW_MIN_SIZE - static_cast<int>(2 * CHILD_WINDOW_MIN_SIZE * SASH_GRAVITY); //avoid rounding error + else + return std::max(CHILD_WINDOW_MIN_SIZE, //make sure centerPosXOptimal + offset is within bounds + std::min(centerPosXOptimal + centerOffset, clientRect.width - CHILD_WINDOW_MIN_SIZE - centerWidth)); +} + + +void TripleSplitter::drawSash(wxDC& dc) +{ + const int centerPosX = getCenterPosX(); + const int centerWidth = getCenterWidth(); + + auto draw = [&](wxRect rect) + { + const int sash2ndHalf = 3; + rect.width -= sash2ndHalf; + dc.GradientFillLinear(rect, COLOR_SASH_GRADIENT_FROM, COLOR_SASH_GRADIENT_TO, wxEAST); + + rect.x += rect.width; + rect.width = sash2ndHalf; + dc.GradientFillLinear(rect, COLOR_SASH_GRADIENT_FROM, COLOR_SASH_GRADIENT_TO, wxWEST); + + static_assert(SASH_SIZE > sash2ndHalf, ""); + }; + + const wxRect rectSashL(centerPosX, 0, SASH_SIZE, GetClientRect().height); + const wxRect rectSashR(centerPosX + centerWidth - SASH_SIZE, 0, SASH_SIZE, GetClientRect().height); + + draw(rectSashL); + draw(rectSashR); +} + + +bool TripleSplitter::hitOnSashLine(int posX) const +{ + const int centerPosX = getCenterPosX(); + const int centerWidth = getCenterWidth(); + + //we don't get events outside of sash, so SASH_HIT_TOLERANCE is currently *useless* + auto hitSash = [&](int sashX) { return sashX - SASH_HIT_TOLERANCE <= posX && posX < sashX + SASH_SIZE + SASH_HIT_TOLERANCE; }; + + return hitSash(centerPosX) || hitSash(centerPosX + centerWidth - SASH_SIZE); //hit one of the two sash lines +} + + +void TripleSplitter::onMouseLeftDown(wxMouseEvent& event) +{ + activeMove.reset(); + + const int posX = event.GetPosition().x; + if (hitOnSashLine(posX)) + activeMove.reset(new SashMove(*this, posX, getCenterPosX())); + event.Skip(); +} + + +void TripleSplitter::onMouseLeftUp(wxMouseEvent& event) +{ + activeMove.reset(); //nothing else to do, actual work done by onMouseMovement() + event.Skip(); +} + + +void TripleSplitter::onMouseMovement(wxMouseEvent& event) +{ + if (activeMove) + { + centerOffset = activeMove->getCenterPosXStart() - getCenterPosXOptimal() + event.GetPosition().x - activeMove->getMousePosXStart(); + + updateWindowSizes(); + Update(); //no time to wait until idle event! + } + else + { + //we receive those only while above the sash, not the managed windows! + SetCursor(wxCURSOR_SIZEWE); //set window-local only! + } + event.Skip(); +} + + +void TripleSplitter::onLeaveWindow(wxMouseEvent& event) +{ + //even called when moving from sash over to managed windows! + if (!activeMove) + SetCursor(*wxSTANDARD_CURSOR); + event.Skip(); +} + + +void TripleSplitter::onMouseCaptureLost(wxMouseCaptureLostEvent& event) +{ + activeMove.reset(); + updateWindowSizes(); + //event.Skip(); -> we DID handle it! +} + + +void TripleSplitter::onMouseLeftDouble(wxMouseEvent& event) +{ + const int posX = event.GetPosition().x; + if (hitOnSashLine(posX)) + { + centerOffset = 0; //reset sash according to gravity + updateWindowSizes(); + } + event.Skip(); +} diff --git a/ui/triple_splitter.h b/ui/triple_splitter.h new file mode 100644 index 00000000..e95671c7 --- /dev/null +++ b/ui/triple_splitter.h @@ -0,0 +1,87 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef TRIPPLE_SPLIT_HEADER_8257804292846842573942534254 +#define TRIPPLE_SPLIT_HEADER_8257804292846842573942534254 + +#include <cassert> +#include <memory> +#include <wx/window.h> +#include <wx/dcclient.h> + +//a not-so-crappy splitter window + +/* manage three contained windows: + 1. left and right window are stretched + 2. middle window is fixed size + 3. middle window position can be changed via mouse with two sash lines + ----------------- + | | | | + | | | | + | | | | + ----------------- +*/ + +namespace zen +{ +class TripleSplitter : public wxWindow +{ +public: + TripleSplitter(wxWindow* parent, + wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0); + + void setupWindows(wxWindow* winL, wxWindow* winC, wxWindow* winR) + { + assert(winL->GetParent() == this && winC->GetParent() == this && winR->GetParent() == this && !GetSizer()); + windowL = winL; + windowC = winC; + windowR = winR; + updateWindowSizes(); + } + + int getSashOffset() const { return centerOffset; } + void setSashOffset(int off) { centerOffset = off; updateWindowSizes(); } + +private: + void onEraseBackGround(wxEraseEvent& event) {} + void onSizeEvent(wxSizeEvent& event) { updateWindowSizes(); event.Skip(); } + + void onPaintEvent(wxPaintEvent& event) + { + wxPaintDC dc(this); + drawSash(dc); + } + + void updateWindowSizes(); + int getCenterWidth() const; + int getCenterPosX() const; //return normalized posX + int getCenterPosXOptimal() const; + + void drawSash(wxDC& dc); + bool hitOnSashLine(int posX) const; + + void onMouseLeftDown(wxMouseEvent& event); + void onMouseLeftUp(wxMouseEvent& event); + void onMouseMovement(wxMouseEvent& event); + void onLeaveWindow(wxMouseEvent& event); + void onMouseCaptureLost(wxMouseCaptureLostEvent& event); + void onMouseLeftDouble(wxMouseEvent& event); + + class SashMove; + std::unique_ptr<SashMove> activeMove; + + int centerOffset; //offset to add after "gravity" stretching + + wxWindow* windowL; + wxWindow* windowC; + wxWindow* windowR; +}; +} + +#endif //TRIPPLE_SPLIT_HEADER_8257804292846842573942534254 diff --git a/version/version.h b/version/version.h index 23741258..fc0bbc3b 100644 --- a/version/version.h +++ b/version/version.h @@ -3,7 +3,7 @@ namespace zen { -const wchar_t currentVersion[] = L"5.5"; //internal linkage! +const wchar_t currentVersion[] = L"5.6"; //internal linkage! } #endif diff --git a/version/version.rc b/version/version.rc index 91d2e0f9..c7955130 100644 --- a/version/version.rc +++ b/version/version.rc @@ -1,2 +1,2 @@ -#define VER_FREEFILESYNC 5,5,0,0 -#define VER_FREEFILESYNC_STR "5.5\0" +#define VER_FREEFILESYNC 5,6,0,0 +#define VER_FREEFILESYNC_STR "5.6\0" diff --git a/wx+/file_drop.h b/wx+/file_drop.h index 0e9ba538..cdb19f4b 100644 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -91,8 +91,7 @@ private: { //create a custom event on drop window: execute event after file dropping is completed! (after mouse is released) FileDropEvent evt(filenames, dropWindow_, wxPoint(x, y)); - auto handler = dropWindow_.GetEventHandler(); - if (handler) + if (wxEvtHandler* handler = dropWindow_.GetEventHandler()) handler->AddPendingEvent(evt); } return true; diff --git a/wx+/graph.cpp b/wx+/graph.cpp index fd68b548..6ba794b0 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -96,7 +96,7 @@ void drawYLabel(wxDC& dc, double& yMin, double& yMax, const wxRect& clientArea, if (clientArea.GetHeight() <= 0 || clientArea.GetWidth() <= 0) return; - int optimalBlockHeight = 3 * dc.GetMultiLineTextExtent(L"1").GetHeight();; + int optimalBlockHeight = 3 * dc.GetMultiLineTextExtent(L"1").GetHeight(); double valRangePerBlock = (yMax - yMin) * optimalBlockHeight / clientArea.GetHeight(); valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock); @@ -244,7 +244,7 @@ Graph2D::Graph2D(wxWindow* parent, wxPanel(parent, winid, pos, size, style, name) { Connect(wxEVT_PAINT, wxPaintEventHandler(Graph2D::onPaintEvent), nullptr, this); - Connect(wxEVT_SIZE, wxEventHandler(Graph2D::onRefreshRequired), nullptr, this); + Connect(wxEVT_SIZE, wxSizeEventHandler (Graph2D::onSizeEvent ), nullptr, this); //http://wiki.wxwidgets.org/Flicker-Free_Drawing Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Graph2D::onEraseBackGround), nullptr, this); @@ -291,9 +291,9 @@ void Graph2D::OnMouseLeftUp(wxMouseEvent& event) if (activeSel->getStartPos() != activeSel->refCurrentPos()) //if it's just a single mouse click: discard selection { //fire off GraphSelectEvent - GraphSelectEvent evt(activeSel->refSelection()); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->AddPendingEvent(evt); + GraphSelectEvent selEvent(activeSel->refSelection()); + if (wxEvtHandler* handler = GetEventHandler()) + handler->AddPendingEvent(selEvent); oldSel.push_back(activeSel->refSelection()); } diff --git a/wx+/graph.h b/wx+/graph.h index db52753b..70da5dc3 100644 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -279,20 +279,15 @@ private: void OnMouseLeftUp (wxMouseEvent& event); void OnMouseCaptureLost(wxMouseCaptureLostEvent& event); - void onPaintEvent(wxPaintEvent& evt) + void onPaintEvent(wxPaintEvent& event) { wxAutoBufferedPaintDC dc(this); //this one happily fucks up for RTL layout by not drawing the first column (x = 0)! //wxPaintDC dc(this); render(dc); } - void onRefreshRequired(wxEvent& evt) - { - Refresh(); - evt.Skip(); - } - - void onEraseBackGround(wxEraseEvent& evt) {} + void onSizeEvent(wxSizeEvent& event) { Refresh(); event.Skip(); } + void onEraseBackGround(wxEraseEvent& event) {} void render(wxDC& dc) const; diff --git a/wx+/grid.cpp b/wx+/grid.cpp index e1208109..33419c8e 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -18,6 +18,7 @@ #include <zen/scope_guard.h> #include <zen/utf.h> #include "format_unit.h" +#include "image_tools.h" #ifdef FFS_LINUX #include <gtk/gtk.h> @@ -26,8 +27,8 @@ using namespace zen; -wxColor zen::getColorSelectionGradientFrom() { return wxColor(137, 172, 255); } //blue: H:158 S:255 V:196 -wxColor zen::getColorSelectionGradientTo () { return wxColor(225, 234, 255); } // H:158 S:255 V:240 +wxColor zen::getColorSelectionGradientFrom() { return wxColor(137, 172, 255); } //blue: HSL: 158, 255, 196 HSV: 222, 0.46, 1 +wxColor zen::getColorSelectionGradientTo () { return wxColor(225, 234, 255); } // HSL: 158, 255, 240 HSV: 222, 0.12, 1 void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) { @@ -38,16 +39,17 @@ void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) namespace { -//------------ Grid Constants ------------------------------------------------------------------------------------ +//------------ Grid Constants -------------------------------- const double MOUSE_DRAG_ACCELERATION = 1.5; //unit: [rows / (pixel * sec)] -> same value as Explorer! const int DEFAULT_ROW_HEIGHT = 20; -const int DEFAULT_COL_LABEL_HEIGHT = 25; +const int DEFAULT_COL_LABEL_HEIGHT = 24; const int COLUMN_BORDER_LEFT = 4; //for left-aligned text const int COLUMN_LABEL_BORDER = COLUMN_BORDER_LEFT; const int COLUMN_MOVE_DELAY = 5; //unit: [pixel] (from Explorer) const int COLUMN_MIN_WIDTH = 40; //only honored when resizing manually! const int ROW_LABEL_BORDER = 3; const int COLUMN_RESIZE_TOLERANCE = 6; //unit [pixel] +const int COLUMN_FILL_GAP_TOLERANCE = 10; //enlarge column to fill full width when resizing const wxColor COLOR_SELECTION_GRADIENT_NO_FOCUS_FROM = wxColour(192, 192, 192); //light grey wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW); const wxColor COLOR_SELECTION_GRADIENT_NO_FOCUS_TO = wxColour(228, 228, 228); @@ -62,7 +64,7 @@ const wxColor COLOR_LABEL_GRADIENT_TO_FOCUS = COLOR_LABEL_GRADIENT_TO; wxColor getColorMainWinBackground() { return wxListBox::GetClassDefaultAttributes().colBg; } //cannot be initialized statically on wxGTK! const wxColor colorGridLine = wxColour(192, 192, 192); //light grey -//---------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------ //a fix for a poor wxWidgets implementation (wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active) @@ -328,7 +330,7 @@ public: parent_(parent) { Connect(wxEVT_PAINT, wxPaintEventHandler(SubWindow::onPaintEvent), nullptr, this); - Connect(wxEVT_SIZE, wxEventHandler(SubWindow::onSizeEvent), nullptr, this); + Connect(wxEVT_SIZE, wxSizeEventHandler (SubWindow::onSizeEvent), nullptr, this); //http://wiki.wxwidgets.org/Flicker-Free_Drawing Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(SubWindow::onEraseBackGround), nullptr, this); @@ -411,7 +413,7 @@ private: virtual void onKeyUp (wxKeyEvent& event) { event.Skip(); } virtual void onKeyDown(wxKeyEvent& event) { event.Skip(); } - void onPaintEvent(wxPaintEvent& evt) + void onPaintEvent(wxPaintEvent& event) { //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)! BufferedPaintDC dc(*this, buffer); @@ -423,13 +425,13 @@ private: render(dc, iter.GetRect()); } - void onSizeEvent(wxEvent& evt) + void onSizeEvent(wxSizeEvent& event) { Refresh(); - evt.Skip(); + event.Skip(); } - void onEraseBackGround(wxEraseEvent& evt) {} + void onEraseBackGround(wxEraseEvent& event) {} Grid& parent_; std::unique_ptr<wxBitmap> buffer; @@ -595,12 +597,15 @@ class ColumnResizing { public: ColumnResizing(wxWindow& wnd, size_t col, size_t compPos, ptrdiff_t startWidth, int clientPosX) : - wnd_(wnd), - col_(col), - compPos_(compPos), - startWidth_(startWidth), - clientPosX_(clientPosX) { wnd_.CaptureMouse(); } - ~ColumnResizing() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); } + wnd_(wnd), col_(col), compPos_(compPos), startWidth_(startWidth), clientPosX_(clientPosX) + { + wnd_.CaptureMouse(); + } + ~ColumnResizing() + { + if (wnd_.HasCapture()) + wnd_.ReleaseMouse(); + } size_t getColumn () const { return col_; } size_t getComponentPos() const { return compPos_; } @@ -674,7 +679,7 @@ private: wxPoint labelAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0)).x, 0); //client coordinates - std::vector<std::vector<VisibleColumn>> compAbsWidths = refParent().getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = refParent().getColWidths(); //resolve stretched widths for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol) { @@ -725,17 +730,9 @@ private: { if (action->wantResize) { - if (event.LeftDClick()) //auto-size visible range on double-click - { - const auto bestWidth = refParent().getBestColumnSize(action->col, action->compPos); //return -1 on error - if (bestWidth >= 0) - refParent().setColWidth(action->col, action->compPos, std::max<ptrdiff_t>(COLUMN_MIN_WIDTH, bestWidth)); - } - else - { - if (Opt<size_t> colWidth = refParent().getAbsoluteWidth(action->col, action->compPos)) + if (!event.LeftDClick()) //double-clicks never seem to arrive here; why is this checked at all??? + if (Opt<ptrdiff_t> colWidth = refParent().getColWidth(action->col, action->compPos)) activeResizing.reset(new ColumnResizing(*this, action->col, action->compPos, *colWidth, event.GetPosition().x)); - } } else //a move or single click activeMove.reset(new ColumnMove(*this, action->col, action->compPos, event.GetPosition().x)); @@ -786,20 +783,17 @@ private: virtual void onMouseLeftDouble(wxMouseEvent& event) { - const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()); - if (action && action->wantResize) - { - //auto-size visible range on double-click - const auto bestWidth = refParent().getBestColumnSize(action->col, action->compPos); //return -1 on error - if (bestWidth >= 0) + if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) + if (action->wantResize) { - const auto newWidth = std::max<ptrdiff_t>(COLUMN_MIN_WIDTH, bestWidth); - refParent().setColWidth(action->col, action->compPos, newWidth); - - if (const Opt<ColumnType> colType = refParent().colToType(action->col, action->compPos)) - sendEventNow(GridColumnResizeEvent(newWidth, *colType, action->compPos)); //notify column resize + //auto-size visible range on double-click + const ptrdiff_t bestWidth = refParent().getBestColumnSize(action->col, action->compPos); //return -1 on error + if (bestWidth >= 0) + { + refParent().setColWidthAndNotify(bestWidth, action->col, action->compPos); + refParent().Refresh(); //refresh main grid as well! + } } - } event.Skip(); } @@ -810,19 +804,17 @@ private: const auto col = activeResizing->getColumn(); const auto compPos = activeResizing->getComponentPos(); - if (Opt<size_t> colWidth = refParent().getAbsoluteWidth(col, compPos)) - { - const size_t newWidth = std::max<ptrdiff_t>(COLUMN_MIN_WIDTH, activeResizing->getStartWidth() + event.GetPosition().x - activeResizing->getStartPosX()); - if (newWidth != *colWidth) - { - refParent().setColWidth(col, compPos, newWidth); + const ptrdiff_t newWidth = activeResizing->getStartWidth() + event.GetPosition().x - activeResizing->getStartPosX(); - if (const Opt<ColumnType> colType = refParent().colToType(col, compPos)) - sendEventNow(GridColumnResizeEvent(static_cast<int>(newWidth), *colType, compPos)); //notify column resize + //set width tentatively + refParent().setColWidthAndNotify(newWidth, col, compPos); - refParent().Refresh(); - } - } + //check if there's a small gap after last column, if yes, fill it + int gapWidth = GetClientSize().GetWidth() - refParent().getColWidthsSum(GetClientSize().GetWidth()); + if (std::abs(gapWidth) < COLUMN_FILL_GAP_TOLERANCE) + refParent().setColWidthAndNotify(newWidth + gapWidth, col, compPos); + + refParent().Refresh(); //refresh columns on main grid as well! } else if (activeMove) { @@ -982,11 +974,11 @@ private: wxPoint cellAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates - std::vector<std::vector<VisibleColumn>> compAbsWidths = refParent().getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = refParent().getColWidths(); //resolve stretched widths for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) { const ptrdiff_t compWidth = std::accumulate(iterComp->begin(), iterComp->end(), static_cast<ptrdiff_t>(0), - [](ptrdiff_t sum, const VisibleColumn& vc) { return sum + vc.width_; }); + [](ptrdiff_t sum, const ColumnWidth& cw) { return sum + cw.width_; }); const size_t compPos = iterComp - compAbsWidths.begin(); if (auto prov = refParent().getDataProvider(compPos)) @@ -1058,7 +1050,7 @@ private: void onMouseDown(wxMouseEvent& event) //handle left and right mouse button clicks (almost) the same { - if (FindFocus() != this) //doesn't seem to happen automatically for right mouse button + if (wxWindow::FindFocus() != this) //doesn't seem to happen automatically for right mouse button SetFocus(); const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); @@ -1115,9 +1107,9 @@ private: const auto rowTo = activeSelection->getCurrentRow(); const auto compPos = activeSelection->getComponentPos(); const bool positive = activeSelection->isPositiveSelect(); - refParent().selectRange(rowFrom, rowTo, compPos, positive); cursor.first = activeSelection->getCurrentRow(); //slight deviation from Explorer: change cursor while dragging mouse! -> unify behavior with shift + direction keys + refParent().selectRange(rowFrom, rowTo, compPos, positive); activeSelection.reset(); } @@ -1425,8 +1417,7 @@ private: //=> we cannot use CalcUnscrolledPosition() here which gives the wrong/outdated value!!! //=> we need to update asynchronously: wxCommandEvent scrollEvent(EVENT_GRID_HAS_SCROLLED); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->AddPendingEvent(scrollEvent); + AddPendingEvent(scrollEvent); //asynchronously call updateAfterScroll() } void updateAfterScroll(wxCommandEvent&) @@ -1453,8 +1444,8 @@ Grid::Grid(wxWindow* parent, const wxSize& size, long style, const wxString& name) : wxScrolledWindow(parent, id, pos, size, style | wxWANTS_CHARS, name), - showScrollbarX(true), - showScrollbarY(true), + showScrollbarX(SB_SHOW_AUTOMATIC), + showScrollbarY(SB_SHOW_AUTOMATIC), colLabelHeight(DEFAULT_COL_LABEL_HEIGHT), drawRowLabel(true), comp(1), @@ -1462,7 +1453,7 @@ Grid::Grid(wxWindow* parent, { Connect(wxEVT_PAINT, wxPaintEventHandler(Grid::onPaintEvent ), nullptr, this); Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Grid::onEraseBackGround), nullptr, this); - Connect(wxEVT_SIZE, wxEventHandler (Grid::onSizeEvent ), nullptr, this); + Connect(wxEVT_SIZE, wxSizeEventHandler (Grid::onSizeEvent ), nullptr, this); cornerWin_ = new CornerWin (*this); // rowLabelWin_ = new RowLabelWin(*this); //owership handled by "this" @@ -1480,67 +1471,129 @@ Grid::Grid(wxWindow* parent, void Grid::updateWindowSizes(bool updateScrollbar) { - /* We have to deal with a nasty circular dependency: - + /* We have to deal with TWO nasty circular dependencies: + 1. + rowLabelWidth + /|\ + mainWin::client width + /|\ + SetScrollbars -> show/hide horizontal scrollbar depending on client width + /|\ + mainWin::client height -> possibly trimmed by horizontal scrollbars + /|\ + rowLabelWidth + + 2. mainWin_->GetClientSize() - /|\ + /|\ SetScrollbars -> show/hide scrollbars depending on whether client size is big enough - /|\ - GetClientRect(); -> possibly trimmed by scrollbars - /|\ - mainWin_->GetClientSize() -> also trimmed, since it's a sub-window ! + /|\ + GetClientSize(); -> possibly trimmed by scrollbars + /|\ + mainWin_->GetClientSize() -> also trimmed, since it's a sub-window! */ - //update scrollbars: showing/hiding scrollbars changes client size! - if (updateScrollbar) + //break this vicious circle: + + //1. calculate row label width independent from scrollbars + const int mainWinHeightGross = std::max(GetSize().GetHeight() - colLabelHeight, 0); //independent from client sizes and scrollbars! + const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); // + + int rowLabelWidth = 0; + if (drawRowLabel && logicalHeight > 0) { - //help SetScrollbars() do a better job - //mainWin_->SetSize(std::max(0, GetSize().GetWidth () - rowLabelWidth), -> not working! - // std::max(0, GetSize().GetHeight() - colLabelHeight)); + ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; + ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ; + numeric::confine<ptrdiff_t>(yFrom, 0, logicalHeight - 1); + numeric::confine<ptrdiff_t>(yTo, 0, logicalHeight - 1); + + const ptrdiff_t rowFrom = rowLabelWin_->getRowAtPos(yFrom); + const ptrdiff_t rowTo = rowLabelWin_->getRowAtPos(yTo); + if (rowFrom >= 0 && rowTo >= 0) + rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo); + } - int scrollPosX = 0; - int scrollPosY = 0; - GetViewStart(&scrollPosX, &scrollPosY); //preserve current scroll position + auto getMainWinSize = [&](const wxSize& clientSize) { return wxSize(std::max(0, clientSize.GetWidth() - rowLabelWidth), std::max(0, clientSize.GetHeight() - colLabelHeight)); }; - const int pixPerScrollUnitY = rowLabelWin_->getRowHeight(); - const int pixPerScrollUnitX = pixPerScrollUnitY; + auto setScrollbars2 = [&](int logWidth, int logHeight) //replace SetScrollbars, which loses precision to pixelsPerUnitX for some brain-dead reason + { + int ppsuX = 0; //pixel per scroll unit + int ppsuY = 0; + GetScrollPixelsPerUnit(&ppsuX, &ppsuY); - const int scrollUnitsX = std::ceil(static_cast<double>( getMinAbsoluteWidthTotal()) / pixPerScrollUnitX); - const int scrollUnitsY = std::ceil(static_cast<double>(rowLabelWin_->getLogicalHeight()) / pixPerScrollUnitY); + const int ppsuNew = rowLabelWin_->getRowHeight(); + if (ppsuX != ppsuNew || ppsuY != ppsuNew) //support polling! + SetScrollRate(ppsuNew, ppsuNew); //internally calls AdjustScrollbars()! - SetScrollbars(pixPerScrollUnitX, pixPerScrollUnitY, //another abysmal wxWidgets design decision: why is precision needlessly reduced to "pixelsPerUnit"???? - scrollUnitsX, scrollUnitsY, - scrollPosX, scrollPosY); - } + mainWin_->SetVirtualSize(logWidth, logHeight); + AdjustScrollbars(); //lousy wxWidgets design decision: internally calls mainWin_->GetClientSize() without considering impact of scrollbars! + //Attention: setting scrollbars triggers *synchronous* resize event if scrollbars are shown or hidden! => updateWindowSizes() recursion! (Windows) + }; - const wxRect clientRect = GetClientRect(); + //2. update managed windows' sizes: just assume scrollbars are already set correctly, even if they may not be (yet)! + //this ensures mainWin_->SetVirtualSize() and AdjustScrollbars() are working with the correct main window size, unless sb change later, which triggers a recalculation anyway! + const wxSize mainWinSize = getMainWinSize(GetClientSize()); + + cornerWin_ ->SetSize(0, 0, rowLabelWidth, colLabelHeight); + rowLabelWin_->SetSize(0, colLabelHeight, rowLabelWidth, mainWinSize.GetHeight()); + colLabelWin_->SetSize(rowLabelWidth, 0, mainWinSize.GetWidth(), colLabelHeight); + mainWin_ ->SetSize(rowLabelWidth, colLabelHeight, mainWinSize.GetWidth(), mainWinSize.GetHeight()); - const int mainWinHeight = std::max(0, clientRect.height - colLabelHeight); + //avoid flicker in wxWindowMSW::HandleSize() when calling ::EndDeferWindowPos() where the sub-windows are moved only although they need to be redrawn! + colLabelWin_->Refresh(); + mainWin_ ->Refresh(); - int rowLabelWidth = 0; //calculate optimal row label width - if (drawRowLabel) + //3. update scrollbars: "guide wxScrolledHelper to not screw up too much" + if (updateScrollbar) { - const auto heightTotal = rowLabelWin_->getLogicalHeight(); - if (heightTotal > 0) + const int mainWinWidthGross = getMainWinSize(GetSize()).GetWidth(); + + if (logicalHeight <= mainWinHeightGross && + getColWidthsSum(mainWinWidthGross) <= mainWinWidthGross && + //this special case needs to be considered *only* when both scrollbars are flexible: + showScrollbarX == SB_SHOW_AUTOMATIC && + showScrollbarY == SB_SHOW_AUTOMATIC) + setScrollbars2(0, 0); //no scrollbars required at all! -> wxScrolledWindow requires active help to detect this special case! + else { - ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; - ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeight - 1)).y ; - numeric::confine<ptrdiff_t>(yFrom, 0, heightTotal - 1); - numeric::confine<ptrdiff_t>(yTo, 0, heightTotal - 1); - - const ptrdiff_t rowFrom = rowLabelWin_->getRowAtPos(yFrom); - const ptrdiff_t rowTo = rowLabelWin_->getRowAtPos(yTo); - if (rowFrom >= 0 && rowTo >= 0) - rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo); + const int logicalWidthTmp = getColWidthsSum(mainWinSize.GetWidth()); //assuming vertical scrollbar stays as it is... + setScrollbars2(logicalWidthTmp, logicalHeight); //if scrollbars are shown or hidden a new resize event recurses into updateWindowSizes() + /* + is there a risk of endless recursion? No, 2-level recursion at most, consider the following 6 cases: + + <----------gw----------> + <----------nw------> + ------------------------ /|\ /|\ + | | | | | + | main window | | nh | + | | | | gh + ------------------------ \|/ | + | | | | + ------------------------ \|/ + gw := gross width + nw := net width := gross width - sb size + gh := gross height + nh := net height := gross height - sb size + + There are 6 cases that can occur: + --------------------------------- + lw := logical width + lh := logical height + + 1. lw <= gw && lh <= gh => no scrollbars needed + + 2. lw > gw && lh > gh => need both scrollbars + + 3. lh > gh + 4.1 lw <= nw => need vertical scrollbar only + 4.2 nw < lw <= gw => need both scrollbars + + 4. lw > gw + 3.1 lh <= nh => need horizontal scrollbar only + 3.2 nh < lh <= gh => need both scrollbars + */ } } - - const int mainWinWidth = std::max(0, clientRect.width - rowLabelWidth); - - cornerWin_ ->SetSize(0, 0, rowLabelWidth, colLabelHeight); - rowLabelWin_->SetSize(0, colLabelHeight, rowLabelWidth, mainWinHeight); - colLabelWin_->SetSize(rowLabelWidth, 0, mainWinWidth, colLabelHeight); - mainWin_ ->SetSize(rowLabelWidth, colLabelHeight, mainWinWidth, mainWinHeight); } @@ -1622,6 +1675,7 @@ void Grid::setRowHeight(size_t height) { rowLabelWin_->setRowHeight(height); updateWindowSizes(); + Refresh(); } @@ -1637,7 +1691,7 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr, size_ [&](const ColumnAttribute& ca) { if (ca.visible_) - visibleCols.push_back(Grid::VisibleColumn(ca.type_, ca.width_)); + visibleCols.push_back(Grid::VisibleColumn(ca.type_, ca.offset_, ca.stretch_)); }); //set ownership of visible columns @@ -1673,7 +1727,8 @@ std::vector<Grid::ColumnAttribute> Grid::getColumnConfig(size_t compPos) const { ca.visible_ = true; //paranoia ca.type_ = iterVcols->type_; - ca.width_ = iterVcols->width_; + ca.stretch_ = iterVcols->stretch_; + ca.offset_ = iterVcols->offset_; ++iterVcols; } } @@ -1687,30 +1742,74 @@ std::vector<Grid::ColumnAttribute> Grid::getColumnConfig(size_t compPos) const } -void Grid::showScrollBars(bool horizontal, bool vertical) +void Grid::showScrollBars(Grid::ScrollBarStatus horizontal, Grid::ScrollBarStatus vertical) { -#ifdef FFS_WIN +#if wxCHECK_VERSION(2, 9, 1) + int weShouldMigrateToWxWidgetsShowScrollBarsInstead; //lousy compile-time warning, I know ;) +#endif + + if (showScrollbarX == horizontal && + showScrollbarY == vertical) return; //support polling! + showScrollbarX = horizontal; showScrollbarY = vertical; - updateWindowSizes(); -#elif defined FFS_LINUX //get rid of scrollbars, but preserve scrolling behavior! +#ifdef FFS_LINUX //get rid of scrollbars, but preserve scrolling behavior! + //the following wxGTK approach is pretty much identical to wxWidgets 2.9 ShowScrollbars() code! + + auto mapStatus = [](ScrollBarStatus sbStatus) -> GtkPolicyType + { + switch (sbStatus) + { + case SB_SHOW_AUTOMATIC: + return GTK_POLICY_AUTOMATIC; + case SB_SHOW_ALWAYS: + return GTK_POLICY_ALWAYS; + case SB_SHOW_NEVER: + return GTK_POLICY_NEVER; + } + assert(false); + return GTK_POLICY_AUTOMATIC; + }; + GtkWidget* gridWidget = wxWindow::m_widget; GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(gridWidget); gtk_scrolled_window_set_policy(scrolledWindow, - horizontal ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER, - vertical ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER); + mapStatus(horizontal), + mapStatus(vertical)); #endif + updateWindowSizes(); } #ifdef FFS_WIN //get rid of scrollbars, but preserve scrolling behavior! void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) { - if ((orientation == wxHORIZONTAL && !showScrollbarX) || (orientation == wxVERTICAL && !showScrollbarY)) - wxWindow::SetScrollbar(orientation, 0, 0, 0, refresh); + ScrollBarStatus sbStatus = SB_SHOW_AUTOMATIC; + if (orientation == wxHORIZONTAL) + sbStatus = showScrollbarX; + else if (orientation == wxVERTICAL) + sbStatus = showScrollbarY; else - wxWindow::SetScrollbar(orientation, position, thumbSize, range, refresh); + assert(false); + + switch (sbStatus) + { + case SB_SHOW_AUTOMATIC: + wxScrolledWindow::SetScrollbar(orientation, position, thumbSize, range, refresh); + break; + + case SB_SHOW_ALWAYS: + if (range <= 1) //scrollbars hidden if range == 0 or 1 + wxScrolledWindow::SetScrollbar(orientation, 0, 199999, 200000, refresh); + else + wxScrolledWindow::SetScrollbar(orientation, position, thumbSize, range, refresh); + break; + + case SB_SHOW_NEVER: + wxScrolledWindow::SetScrollbar(orientation, 0, 0, 0, refresh); + break; + } } @@ -1735,7 +1834,7 @@ WXLRESULT Grid::MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) scrollDelta(rotations * linesPerRotation, 0); //in scroll units } - return wxScrolledWindow::MSWDefWindowProc(nMsg, wParam, lParam);; + return wxScrolledWindow::MSWDefWindowProc(nMsg, wParam, lParam); } #endif @@ -1748,23 +1847,23 @@ wxWindow& Grid::getMainWin () { return *mainWin_; } wxRect Grid::getColumnLabelArea(ColumnType colType, size_t compPos) const { - std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = getColWidths(); //resolve negative/stretched widths if (compPos < compAbsWidths.size()) { auto iterComp = compAbsWidths.begin() + compPos; - auto iterCol = std::find_if(iterComp->begin(), iterComp->end(), [&](const VisibleColumn& vc) { return vc.type_ == colType; }); + auto iterCol = std::find_if(iterComp->begin(), iterComp->end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }); if (iterCol != iterComp->end()) { ptrdiff_t posX = std::accumulate(compAbsWidths.begin(), iterComp, static_cast<ptrdiff_t>(0), - [](ptrdiff_t sum, const std::vector<VisibleColumn>& cols) + [](ptrdiff_t sum, const std::vector<ColumnWidth>& cols) { return sum + std::accumulate(cols.begin(), cols.end(), static_cast<ptrdiff_t>(0), - [](ptrdiff_t val2, const VisibleColumn& vc) { return val2 + vc.width_; }); + [](ptrdiff_t val2, const ColumnWidth& cw) { return val2 + cw.width_; }); }); posX += std::accumulate(iterComp->begin(), iterCol, static_cast<ptrdiff_t>(0), - [](ptrdiff_t sum, const VisibleColumn& vc) { return sum + vc.width_; }); + [](ptrdiff_t sum, const ColumnWidth& cw) { return sum + cw.width_; }); return wxRect(wxPoint(posX, 0), wxSize(iterCol->width_, colLabelHeight)); } @@ -1780,7 +1879,7 @@ Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const { ptrdiff_t accuWidth = 0; - std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = getColWidths(); //resolve stretched widths for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) { const size_t compPos = iterComp - compAbsWidths.begin(); @@ -1833,17 +1932,17 @@ void Grid::moveColumn(size_t colFrom, size_t colTo, size_t compPos) ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos, size_t compPos) const { - std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = getColWidths(); //resolve negative/stretched widths if (compPos < compAbsWidths.size()) { auto iterComp = compAbsWidths.begin() + compPos; const int absPosX = CalcUnscrolledPosition(pos).x; ptrdiff_t accuWidth = std::accumulate(compAbsWidths.begin(), iterComp, static_cast<ptrdiff_t>(0), - [](ptrdiff_t sum, const std::vector<VisibleColumn>& cols) + [](ptrdiff_t sum, const std::vector<ColumnWidth>& cols) { return sum + std::accumulate(cols.begin(), cols.end(), static_cast<ptrdiff_t>(0), - [](ptrdiff_t sum2, const VisibleColumn& vc) { return sum2 + vc.width_; }); + [](ptrdiff_t sum2, const ColumnWidth& cw) { return sum2 + cw.width_; }); }); for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol) @@ -1862,12 +1961,8 @@ ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos, size_t compPos) Opt<ColumnType> Grid::colToType(size_t col, size_t compPos) const { - if (compPos < comp.size()) - { - auto& visibleCols = comp[compPos].visibleCols; - if (col < visibleCols.size()) - return visibleCols[col].type_; - } + if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + return comp[compPos].visibleCols[col].type_; return NoValue(); } @@ -1879,7 +1974,7 @@ Opt<std::pair<ColumnType, size_t>> Grid::getColumnAtPos(int posX) const { if (posX >= 0) { - std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = getColWidths(); //resolve negative/stretched widths ptrdiff_t accWidth = 0; for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) @@ -1905,15 +2000,26 @@ wxRect Grid::getCellArea(size_t row, ColumnType colType, size_t compPos) const } +void Grid::clearSelection(size_t compPos) +{ + if (compPos < comp.size()) + { + comp[compPos].selection.clear(); + mainWin_->Refresh(); + } +} + + void Grid::setGridCursor(size_t row, size_t compPos) { if (compPos < comp.size()) { + mainWin_->setCursor(row, compPos); + mainWin_->makeRowVisible(row); + std::for_each(comp.begin(), comp.end(), [](Grid::Component& c) { c.selection.clear(); }); //clear selection, do NOT fire event selectRange(row, row, compPos); //set new selection + fire event - mainWin_->setCursor(row, compPos); - mainWin_->makeRowVisible(row); mainWin_->Refresh(); rowLabelWin_->Refresh(); //row labels! (Kubuntu) } @@ -1938,12 +2044,17 @@ void Grid::selectRange(ptrdiff_t rowFrom, ptrdiff_t rowTo, size_t compPos, bool void Grid::clearSelectionAll() { - std::for_each(comp.begin(), comp.end(), [](Grid::Component& c) { c.selection.clear(); }); + for (auto iter = comp.begin(); iter != comp.end(); ++iter) + { + Grid::Component& c = *iter; + c.selection.clear(); - //notify event - GridRangeSelectEvent unselectionEvent(-1, -1, static_cast<size_t>(-1), false); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->ProcessEvent(unselectionEvent); + //notify event + const size_t compPos = iter - comp.begin(); + GridRangeSelectEvent unselectionEvent(-1, -1, compPos, false); + if (wxEvtHandler* evtHandler = GetEventHandler()) + evtHandler->ProcessEvent(unselectionEvent); + } } @@ -1956,13 +2067,17 @@ void Grid::scrollTo(size_t row) GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY); if (pixelsPerUnitY > 0) { - int scrollPosX = 0; - GetViewStart(&scrollPosX, nullptr); - int scrollPosY = labelRect.GetTopLeft().y / pixelsPerUnitY; + const int scrollPosYNew = labelRect.GetTopLeft().y / pixelsPerUnitY; + int scrollPosXOld = 0; + int scrollPosYOld = 0; + GetViewStart(&scrollPosXOld, &scrollPosYOld); - Scroll(scrollPosX, scrollPosY); - updateWindowSizes(); //may show horizontal scroll bar - Refresh(); + if (scrollPosYOld != scrollPosYNew) //support polling + { + Scroll(scrollPosXOld, scrollPosYNew); + updateWindowSizes(); //may show horizontal scroll bar + Refresh(); + } } } } @@ -2000,6 +2115,43 @@ ptrdiff_t Grid::getBestColumnSize(size_t col, size_t compPos) const } +void Grid::setColWidthAndNotify(ptrdiff_t width, size_t col, size_t compPos, bool notifyAsync) +{ + if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + { + VisibleColumn& vcRs = comp[compPos].visibleCols[col]; + const int mainWinWidth = mainWin_->GetClientSize().GetWidth(); + const ptrdiff_t stretchTotal = getStretchTotal(); + + const ptrdiff_t offset = width - getColStretchedWidth(vcRs.stretch_, stretchTotal, mainWinWidth); //width := stretchedWidth + (normalized) offset + vcRs.offset_ = offset; + + //I. width may be < COLUMN_MIN_WIDTH: for non-stretched columns this doesn't matter, since it's normalized in getColWidths() anyway, + // for stretched columns on the other hand negative width would be evaluated *before* normalization! => need to normalize here! + //II. worse: resizing any column should normalize *all* other stretched columns' offsets considering current mainWinWidth! + // Testcase: 1. make main window so small in width that horizontal scrollbars are shown despite existing streched column. + // 2. resize a fixed size column so that scrollbars vanish. 3. verify that the stretched column is resizing immediately while main dialog is enlarged + std::for_each(comp.begin(), comp.end(), [&](Component& c) + { + std::for_each(c.visibleCols.begin(), c.visibleCols.end(), [&](VisibleColumn& vc) + { + const ptrdiff_t stretchedWidth = Grid::getColStretchedWidth(vc.stretch_, stretchTotal, mainWinWidth); + vc.offset_ = std::max(vc.offset_, COLUMN_MIN_WIDTH - stretchedWidth); //it would suffice to normalize stretched columns only + }); + }); + + GridColumnResizeEvent sizeEvent(offset, vcRs.type_, compPos); + if (wxEvtHandler* evtHandler = GetEventHandler()) + { + if (notifyAsync) + evtHandler->AddPendingEvent(sizeEvent); + else + evtHandler->ProcessEvent(sizeEvent); + } + } +} + + void Grid::autoSizeColumns(size_t compPos) { if (compPos < comp.size() && comp[compPos].allowColumnResize) @@ -2007,18 +2159,10 @@ void Grid::autoSizeColumns(size_t compPos) auto& visibleCols = comp[compPos].visibleCols; for (auto iter = visibleCols.begin(); iter != visibleCols.end(); ++iter) { - const int col = iter - visibleCols.begin(); - const int bestWidth = getBestColumnSize(col, compPos); //return -1 on error + const size_t col = iter - visibleCols.begin(); + const ptrdiff_t bestWidth = getBestColumnSize(col, compPos); //return -1 on error if (bestWidth >= 0) - { - const auto newWidth = std::max(COLUMN_MIN_WIDTH, bestWidth); - iter->width_ = newWidth; - - //notify column resize (asynchronously!) - GridColumnResizeEvent sizeEvent(newWidth, iter->type_, compPos); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->AddPendingEvent(sizeEvent); - } + setColWidthAndNotify(bestWidth, col, compPos, true); } updateWindowSizes(); Refresh(); @@ -2026,80 +2170,59 @@ void Grid::autoSizeColumns(size_t compPos) } -ptrdiff_t Grid::getMinAbsoluteWidthTotal() const +ptrdiff_t Grid::getStretchTotal() const //sum of all stretch factors { - ptrdiff_t minWidthTotal = 0; - //bool haveStretchedCols = false; - std::for_each(comp.begin(), comp.end(), - [&](const Component& c) + return std::accumulate(comp.begin(), comp.end(), static_cast<ptrdiff_t>(0), + [](ptrdiff_t sum, const Component& c) { - std::for_each(c.visibleCols.begin(), c.visibleCols.end(), - [&](const VisibleColumn& vc) - { - if (vc.width_ >= 0) - minWidthTotal += vc.width_; - else - { - //haveStretchedCols = true; - minWidthTotal += COLUMN_MIN_WIDTH; //use "min width" if column is stretched - } - }); + return sum + std::accumulate(c.visibleCols.begin(), c.visibleCols.end(), static_cast<ptrdiff_t>(0), + [](ptrdiff_t val2, const Grid::VisibleColumn& vc) { return val2 + vc.stretch_; }); }); - return minWidthTotal; } -std::vector<std::vector<Grid::VisibleColumn>> Grid::getAbsoluteWidths() const //evaluate negative widths as stretched absolute values! structure matches "comp" +ptrdiff_t Grid::getColStretchedWidth(ptrdiff_t stretch, ptrdiff_t stretchTotal, int mainWinWidth) //final width = stretchedWidth + (normalized) offset { - std::vector<std::vector<VisibleColumn>> output; + return stretchTotal > 0 ? mainWinWidth * stretch / stretchTotal : 0; //rounds down! => not all of clientWidth is correctly distributed according to stretch factors +} - std::vector<std::pair<ptrdiff_t, VisibleColumn*>> stretchedCols; //(factor, column to stretch) - ptrdiff_t factorTotal = 0; - ptrdiff_t minWidthTotal = 0; - output.reserve(comp.size()); - std::for_each(comp.begin(), comp.end(), - [&](const Component& c) +std::vector<std::vector<Grid::ColumnWidth>> Grid::getColWidths() const +{ + return getColWidths(mainWin_->GetClientSize().GetWidth()); +} + + +std::vector<std::vector<Grid::ColumnWidth>> Grid::getColWidths(int mainWinWidth) const //evaluate stretched columns; structure matches "comp" +{ + std::vector<std::vector<ColumnWidth>> output; + + const ptrdiff_t stretchTotal = getStretchTotal(); + + std::for_each(comp.begin(), comp.end(), [&](const Component& c) { - output.push_back(std::vector<VisibleColumn>()); + output.push_back(std::vector<ColumnWidth>()); auto& compWidths = output.back(); - compWidths.reserve(c.visibleCols.size()); - std::for_each(c.visibleCols.begin(), c.visibleCols.end(), - [&](const VisibleColumn& vc) + std::for_each(c.visibleCols.begin(), c.visibleCols.end(), [&](const VisibleColumn& vc) { - if (vc.width_ >= 0) - { - compWidths.push_back(Grid::VisibleColumn(vc.type_, vc.width_)); - minWidthTotal += vc.width_; - } - else //stretched column - { - compWidths.push_back(Grid::VisibleColumn(vc.type_, COLUMN_MIN_WIDTH)); //use "min width" if column is stretched - minWidthTotal += COLUMN_MIN_WIDTH; + const ptrdiff_t stretchedWidth = Grid::getColStretchedWidth(vc.stretch_, stretchTotal, mainWinWidth); + const ptrdiff_t widthNormalized = std::max(stretchedWidth + vc.offset_, static_cast<ptrdiff_t>(COLUMN_MIN_WIDTH)); - stretchedCols.push_back(std::make_pair(vc.width_, &compWidths.back())); - factorTotal += vc.width_; - } + compWidths.push_back(Grid::ColumnWidth(vc.type_, widthNormalized)); }); }); - - if (!stretchedCols.empty()) - { - const ptrdiff_t widthToFill = mainWin_->GetClientSize().GetWidth() - minWidthTotal; - if (widthToFill > 0) - { - int widthRemaining = widthToFill; - for (auto iter = stretchedCols.begin(); iter != stretchedCols.end(); ++iter) - { - const ptrdiff_t addWidth = (widthToFill * iter->first) / factorTotal; //round down - iter->second->width_ += addWidth; - widthRemaining -= addWidth; - } - - if (widthRemaining > 0) //should be empty, except for rounding errors - stretchedCols.back().second->width_ += widthRemaining; - } - } return output; } + + +ptrdiff_t Grid::getColWidthsSum(int mainWinWidth) const +{ + auto widths = getColWidths(mainWinWidth); + return std::accumulate(widths.begin(), widths.end(), static_cast<ptrdiff_t>(0), + [](ptrdiff_t sum, const std::vector<Grid::ColumnWidth>& cols) + { + return sum + std::accumulate(cols.begin(), cols.end(), static_cast<ptrdiff_t>(0), + [](ptrdiff_t val2, const Grid::ColumnWidth& cw) { return val2 + cw.width_; }); + }); +}; @@ -50,11 +50,11 @@ struct GridClickEvent : public wxMouseEvent struct GridColumnResizeEvent : public wxCommandEvent { - GridColumnResizeEvent(int width, ColumnType colType, size_t compPos) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), width_(width), compPos_(compPos) {} + GridColumnResizeEvent(ptrdiff_t offset, ColumnType colType, size_t compPos) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), offset_(offset), compPos_(compPos) {} virtual wxEvent* Clone() const { return new GridColumnResizeEvent(*this); } const ColumnType colType_; - const int width_; + const ptrdiff_t offset_; const size_t compPos_; }; @@ -138,10 +138,13 @@ public: struct ColumnAttribute { - ColumnAttribute(ColumnType type, int width, bool visible = true) : type_(type), width_(width), visible_(visible) {} + ColumnAttribute(ColumnType type, ptrdiff_t offset, ptrdiff_t stretch, bool visible = true) : type_(type), visible_(visible), stretch_(std::max<ptrdiff_t>(stretch, 0)), offset_(offset) {} ColumnType type_; - int width_; //if negative, treat as proportional stretch! bool visible_; + //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! + ptrdiff_t stretch_; //>= 0 + ptrdiff_t offset_; }; void setColumnConfig(const std::vector<ColumnAttribute>& attr, size_t compPos = 0); //set column count + widths @@ -155,10 +158,17 @@ public: void setColumnLabelHeight(int height); void showRowLabel(bool visible); - void showScrollBars(bool horizontal, bool vertical); + enum ScrollBarStatus + { + SB_SHOW_AUTOMATIC, + SB_SHOW_ALWAYS, + SB_SHOW_NEVER, + }; + //alternative until wxScrollHelper::ShowScrollbars() becomes available in wxWidgets 2.9 + void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical); std::vector<size_t> getSelectedRows(size_t compPos = 0) const; - void clearSelection(size_t compPos = 0) { if (compPos < comp.size()) comp[compPos].selection.clear(); } + void clearSelection(size_t compPos = 0); void scrollDelta(int deltaX, int deltaY); //in scroll units @@ -187,7 +197,7 @@ public: private: void onPaintEvent(wxPaintEvent& event); void onEraseBackGround(wxEraseEvent& event) {} //[!] - void onSizeEvent(wxEvent& evt) { updateWindowSizes(); } + void onSizeEvent(wxSizeEvent& event) { updateWindowSizes(); event.Skip(); } void updateWindowSizes(bool updateScrollbar = true); @@ -242,9 +252,10 @@ private: struct VisibleColumn { - VisibleColumn(ColumnType type, ptrdiff_t width) : type_(type), width_(width) {} + VisibleColumn(ColumnType type, ptrdiff_t offset, ptrdiff_t stretch) : type_(type), stretch_(stretch), offset_(offset) {} ColumnType type_; - ptrdiff_t width_; //may be NEGATIVE => treat as proportional stretch! use getAbsoluteWidths() to evaluate!!! + ptrdiff_t stretch_; //>= 0 + ptrdiff_t offset_; }; struct Component @@ -260,22 +271,67 @@ private: std::vector<ColumnAttribute> oldColAttributes; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*! }; - ptrdiff_t getMinAbsoluteWidthTotal() const; //assigns minimum width to stretched columns - std::vector<std::vector<VisibleColumn>> getAbsoluteWidths() const; //evaluate negative widths as stretched absolute values! structure matches "comp" + struct ColumnWidth + { + ColumnWidth(ColumnType type, ptrdiff_t width) : type_(type), width_(width) {} + ColumnType type_; + ptrdiff_t width_; + }; + std::vector<std::vector<ColumnWidth>> getColWidths() const; // + std::vector<std::vector<ColumnWidth>> getColWidths(int mainWinWidth) const; //evaluate stretched columns; structure matches "comp" + ptrdiff_t getColWidthsSum(int mainWinWidth) const; - Opt<size_t> getAbsoluteWidth(size_t col, size_t compPos) const //resolve stretched columns + Opt<ptrdiff_t> getColWidth(size_t col, size_t compPos) const { - const auto& absWidth = getAbsoluteWidths(); - if (compPos < absWidth.size() && col < absWidth[compPos].size()) - return absWidth[compPos][col].width_; + const auto& widths = getColWidths(); + if (compPos < widths.size() && col < widths[compPos].size()) + return widths[compPos][col].width_; return NoValue(); } - void setColWidth(size_t col, size_t compPos, ptrdiff_t width) //width may be >= 0: absolute, or < 0: stretched - { - if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) - comp[compPos].visibleCols[col].width_ = width; - } + ptrdiff_t getStretchTotal() const; //sum of all stretch factors + static ptrdiff_t getColStretchedWidth(ptrdiff_t stretch, ptrdiff_t stretchTotal, int mainWinWidth); //final width = stretchedWidth + (normalized) offset + + void setColWidthAndNotify(ptrdiff_t width, size_t col, size_t compPos, bool notifyAsync = false); + + //ptrdiff_t getNormalizedColOffset(ptrdiff_t offset, ptrdiff_t stretchedWidth) const; //normalize so that "stretchedWidth + offset" gives reasonable width! + + //Opt<ptrdiff_t> getColOffsetNorm(size_t col, size_t compPos) const //returns *normalized* offset! + // { + // if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + // { + // const VisibleColumn& vc = comp[compPos].visibleCols[col]; + // return getNormalizedColOffset(vc.offset_, getColStretchedWidth(vc.stretch_)); + // } + // return NoValue(); + // } + + //Opt<VisibleColumn> getColAttrib(size_t col, size_t compPos) const + //{ + // if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + // return comp[compPos].visibleCols[col]; + // return NoValue(); + //} + + //Opt<ptrdiff_t> getColStretchedWidth(size_t col, size_t compPos) const + // { + // if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + // { + // const VisibleColumn& vc = comp[compPos].visibleCols[col]; + // return getColStretchedWidth(vc.stretch_); + // } + // return NoValue(); + // } + + + //void setColOffset(size_t col, size_t compPos, ptrdiff_t offset) + // { + // if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + // { + // VisibleColumn& vc = comp[compPos].visibleCols[col]; + // vc.offset_ = offset; + // } + // } wxRect getColumnLabelArea(ColumnType colType, size_t compPos) const; //returns empty rect if column not found @@ -313,8 +369,8 @@ private: ColLabelWin* colLabelWin_; MainWin* mainWin_; - bool showScrollbarX; - bool showScrollbarY; + ScrollBarStatus showScrollbarX; + ScrollBarStatus showScrollbarY; int colLabelHeight; bool drawRowLabel; diff --git a/wx+/image_tools.h b/wx+/image_tools.h index 8a2a43ed..772189a6 100644 --- a/wx+/image_tools.h +++ b/wx+/image_tools.h @@ -27,6 +27,7 @@ bool isEqual(const wxBitmap& lhs, const wxBitmap& rhs); //pixel-wise equality (r wxColor gradient(const wxColor& from, const wxColor& to, double fraction); //maps fraction within [0, 1] to an intermediate color +wxColour hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1] @@ -164,7 +165,54 @@ wxColor gradient(const wxColor& from, const wxColor& to, double fraction) from.Blue () + (to.Blue () - from.Blue ()) * fraction, from.Alpha() + (to.Alpha() - from.Alpha()) * fraction); } -} +inline +wxColour hsvColor(double h, double s, double v) //h within [0, 360), s, v within [0, 1] +{ + //http://de.wikipedia.org/wiki/HSV-Farbraum + + //make input values fit into bounds + if (h > 360) + h -= static_cast<int>(h / 360) * 360; + else if (h < 0) + h -= static_cast<int>(h / 360) * 360 - 360; + numeric::confine<double>(s, 0, 1); + numeric::confine<double>(v, 0, 1); + //------------------------------------ + const int h_i = h / 60; + const float f = h / 60 - h_i; + + auto polish = [](double val) -> unsigned char + { + int result = numeric::round(val * 255); + numeric::confine(result, 0, 255); + return static_cast<unsigned char>(result); + }; + + const unsigned char p = polish(v * (1 - s)); + const unsigned char q = polish(v * (1 - s * f)); + const unsigned char t = polish(v * (1 - s * (1 - f))); + const unsigned char vi = polish(v); + + switch (h_i) + { + case 0: + return wxColour(vi, t, p); + case 1: + return wxColour(q, vi, p); + case 2: + return wxColour(p, vi, t); + case 3: + return wxColour(p, q, vi); + case 4: + return wxColour(t, p, vi); + case 5: + return wxColour(vi, p, q); + } + assert(false); + return *wxBLACK; +} +} + #endif //IMAGE_TOOLS_HEADER_45782456427634254 diff --git a/wx+/zlib_wrap.cpp b/wx+/zlib_wrap.cpp index 22285bab..a27a4fa4 100644 --- a/wx+/zlib_wrap.cpp +++ b/wx+/zlib_wrap.cpp @@ -6,7 +6,7 @@ #include "zlib_wrap.h" #ifdef FFS_WIN -#include <../src/zlib/zlib.h> //not really a "nice" place to look for a stable solution +#include <wx/../../src/zlib/zlib.h> //not really a "nice" place to look for a stable solution #elif defined FFS_LINUX #include <zlib.h> //let's pray this is the same version wxWidgets is linking against! #endif diff --git a/wx+/zlib_wrap.h b/wx+/zlib_wrap.h index 7b196a75..c229a589 100644 --- a/wx+/zlib_wrap.h +++ b/wx+/zlib_wrap.h @@ -7,7 +7,7 @@ #ifndef SIMPLE_H_INCLUDED_18134135134135345489 #define SIMPLE_H_INCLUDED_18134135134135345489 -#include <wx+/serialize.h> +#include <zen/serialize.h> namespace zen { diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp index 19f43998..a6e82617 100644 --- a/zen/FindFilePlus/find_file_plus.cpp +++ b/zen/FindFilePlus/find_file_plus.cpp @@ -107,13 +107,13 @@ bool findplus::initDllBinding() //evaluate in ::DllMain() when attaching process class findplus::FileSearcher { public: - FileSearcher(const wchar_t* dirname); //throw FileError + FileSearcher(const wchar_t* dirname); //throw NtFileError ~FileSearcher(); - void readDir(FileInformation& output); //throw FileError + void readDir(FileInformation& output); //throw NtFileError private: - template <class QueryPolicy> void readDirImpl(FileInformation& output); //throw FileError + template <class QueryPolicy> void readDirImpl(FileInformation& output); //throw NtFileError UNICODE_STRING dirnameNt; //it seems hDir implicitly keeps a reference to this, at least ::FindFirstFile() does no cleanup before ::FindClose()! HANDLE hDir; @@ -136,7 +136,7 @@ FileSearcher::FileSearcher(const wchar_t* dirname) : dirnameNt.Length = 0; dirnameNt.MaximumLength = 0; - zen::ScopeGuard guardConstructor = zen::makeGuard([&]() { this->~FileSearcher(); }); + zen::ScopeGuard guardConstructor = zen::makeGuard([&] { this->~FileSearcher(); }); //-------------------------------------------------------------------------------------------------------------- //convert dosFileName, e.g. C:\Users or \\?\C:\Users to ntFileName \??\C:\Users @@ -222,11 +222,11 @@ struct DirQueryFileId inline -void FileSearcher::readDir(FileInformation& output) { readDirImpl<DirQueryFileId>(output); } //throw FileError +void FileSearcher::readDir(FileInformation& output) { readDirImpl<DirQueryFileId>(output); } //throw NtFileError template <class QueryPolicy> -void FileSearcher::readDirImpl(FileInformation& output) //throw FileError +void FileSearcher::readDirImpl(FileInformation& output) //throw NtFileError { //although FILE_ID_FULL_DIR_INFORMATION should suffice for our purposes, there are problems on Windows XP for certain directories, e.g. "\\Vboxsvr\build" //making NtQueryDirectoryFile() return with STATUS_INVALID_PARAMETER while other directories, e.g. "C:\" work fine for some reason @@ -335,6 +335,11 @@ void FileSearcher::readDirImpl(FileInformation& output) //throw FileError output.fileAttributes = dirInfo.FileAttributes; output.shortNameLength = dirInfo.FileNameLength / sizeof(TCHAR); //FileNameLength is in bytes! + if (dirInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) //analog to FindFirstFile(), confirmed for Win XP and Win 7 + output.reparseTag = dirInfo.EaSize; // + else + output.reparseTag = 0; + if (dirInfo.FileNameLength + sizeof(TCHAR) > sizeof(output.shortName)) //this may actually happen if ::NtQueryDirectoryFile() decides to return a throw NtFileError(STATUS_BUFFER_OVERFLOW); //short name of length MAX_PATH + 1, 0-termination is not required! @@ -352,7 +357,7 @@ FindHandle findplus::openDir(const wchar_t* dirname) { try { - return new FileSearcher(dirname); //throw FileError + return new FileSearcher(dirname); //throw NtFileError } catch (const NtFileError& e) { @@ -366,7 +371,7 @@ bool findplus::readDir(FindHandle hnd, FileInformation& output) { try { - hnd->readDir(output); //throw FileError + hnd->readDir(output); //throw NtFileError return true; } catch (const NtFileError& e) diff --git a/zen/FindFilePlus/find_file_plus.h b/zen/FindFilePlus/find_file_plus.h index 3799a1e1..49b18733 100644 --- a/zen/FindFilePlus/find_file_plus.h +++ b/zen/FindFilePlus/find_file_plus.h @@ -36,6 +36,7 @@ struct FileInformation ULARGE_INTEGER fileSize; ULARGE_INTEGER fileId; //optional: may be 0 if not supported DWORD fileAttributes; + DWORD reparseTag; //set if "fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT" DWORD shortNameLength; WCHAR shortName[MAX_PATH + 1]; //shortName is 0-terminated }; //no need for #pragma pack -> all members are perfectly 4, 8 byte aligned! diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp index 7c75a8e8..b7bc9f9c 100644 --- a/zen/IFileOperation/file_op.cpp +++ b/zen/IFileOperation/file_op.cpp @@ -247,7 +247,7 @@ std::vector<std::wstring> getLockingProcesses(const wchar_t* filename) //throw W &buffer[0], //__out LPTSTR lpExeName, &bufferSize)) //__inout PDWORD lpdwSize if (bufferSize < buffer.size()) - processName += std::wstring(L" - ") + L"\"" + &buffer[0] + L"\""; + processName += std::wstring(L" - ") + L"\'" + &buffer[0] + L"\'"; } } output.push_back(processName); diff --git a/zen/assert_static.h b/zen/assert_static.h index b96b5909..d61dd1e3 100644 --- a/zen/assert_static.h +++ b/zen/assert_static.h @@ -37,7 +37,7 @@ struct CompileTimeError<true> {}; #endif */ -//C++11: at least get rid of this pointless string literal requirement +//C++11: please get rid of this pointless string literal requirement! #define assert_static(X) \ static_assert(X, "Static assert has failed!"); diff --git a/zen/basic_math.h b/zen/basic_math.h index dbc2d922..6678b91e 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -134,7 +134,7 @@ const T& max(const T& a, const T& b, const T& c) template <class T> inline -void confine(T& val, const T& minVal, const T& maxVal) +void confine(T& val, const T& minVal, const T& maxVal) //name trim? { assert(minVal <= maxVal); if (val < minVal) diff --git a/zen/debug_log.h b/zen/debug_log.h index 6be81e89..d65f5e36 100644 --- a/zen/debug_log.h +++ b/zen/debug_log.h @@ -56,7 +56,7 @@ public: const std::string& message) { const std::string logEntry = "[" + formatTime<std::string>(FORMAT_TIME) + "] " + afterLast(sourceFile, ZEN_FILE_NAME_SEPARATOR) + - ", line " + numberTo<std::string>(sourceRow) + ": " + message + "\n"; + " (" + numberTo<std::string>(sourceRow) + "): " + message + "\n"; const size_t bytesWritten = ::fwrite(logEntry.c_str(), 1, logEntry.size(), handle); if (std::ferror(handle) != 0 || bytesWritten != logEntry.size()) diff --git a/zen/debug_new.h b/zen/debug_new.h index ca46cc8e..8d616360 100644 --- a/zen/debug_new.h +++ b/zen/debug_new.h @@ -18,9 +18,14 @@ /*overwrite "operator new" to get more detailed error messages on bad_alloc, detect memory leaks and write memory dumps Usage: - Include everywhere before any other file: $(ProjectDir)\shared\debug_new.h + For Minidumps: -- Compile "debug_new.cpp" -- Compile with debugging symbols and optimization deactivated +------------- +1. Compile "debug_new.cpp" +2. Compile "release" build with: + - debugging symbols + - optimization deactivated + - do not suppress frame pointer(/Oy-) - avoid call stack mess up */ namespace mem_check diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index e54c1b5b..29a2a3cf 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -222,7 +222,7 @@ public: } catch (boost::thread_interrupted&) { - throw; //this is the only reasonable exception! + throw; //this is the only exception expected! } } diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index ebfe4d19..e176f3a6 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -74,8 +74,14 @@ bool zen::dirExists(const Zstring& dirname) bool zen::symlinkExists(const Zstring& linkname) { #ifdef FFS_WIN - const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(linkname).c_str()); - return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + WIN32_FIND_DATA fileInfo = {}; + { + const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(linkname).c_str(), &fileInfo); + if (searchHandle == INVALID_HANDLE_VALUE) + return false; + ::FindClose(searchHandle); + } + return isSymlink(fileInfo); #elif defined FFS_LINUX struct stat fileInfo = {}; @@ -115,8 +121,7 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl // GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, // &sourceAttr)) //__out LPVOID lpFileInformation - const bool isSymbolicLink = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; - if (!isSymbolicLink || procSl == SYMLINK_DIRECT) + if (!isSymlink(fileInfo) || procSl == SYMLINK_DIRECT) { //####################################### DST hack ########################################### const bool isDirectory = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; @@ -1531,7 +1536,21 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat void zen::makeNewDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions) //FileError, ErrorTargetExisting { - //remove trailing separator +#ifdef FFS_WIN + //special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS! + const Zstring dirTmp = removeLongPathPrefix(directory); + if (dirTmp.size() == 3 && + std::iswalpha(dirTmp[0]) && endsWith(dirTmp, L":\\")) + { + const ErrorCode lastError = dirExists(dirTmp) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; + + const std::wstring msg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(dirTmp)) + L"\n\n" + getLastErrorFormatted(lastError); + if (lastError == ERROR_ALREADY_EXISTS) + throw ErrorTargetExisting(msg); + throw FileError(msg); + } +#endif + //remove trailing separator (except for volume root directories!) const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ? beforeLast(directory, FILE_NAME_SEPARATOR) : directory; @@ -1550,11 +1569,14 @@ void zen::makeDirectory(const Zstring& directory) { makeNewDirectory(directory, Zstring(), false); //FileError, ErrorTargetExisting } - catch (const ErrorTargetExisting&) + catch (const FileError& e) { - if (dirExists(directory)) + assert(dynamic_cast<const ErrorTargetExisting*>(&e)); (void)e; + //could there be situations where a directory/network path exists, but creation fails with + //error different than "ErrorTargetExisting"?? => better catch all "FileError" and check existence again + if (dirExists(directory)) //technically a file system race-condition! return; - throw; //clash with file (dir symlink is okay) + throw; } } diff --git a/zen/file_handling.h b/zen/file_handling.h index d6444da3..d1dcca22 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -48,7 +48,7 @@ UInt64 getFilesize(const Zstring& filename); //throw FileError UInt64 getFreeDiskSpace(const Zstring& path); //throw FileError //file handling -bool removeFile(const Zstring& filename); //return "true" if file was actually deleted; throw FileError +bool removeFile(const Zstring& filename); //throw FileError; return "true" if file was actually deleted void removeDirectory(const Zstring& directory, CallbackRemoveDir* callback = nullptr); //throw FileError diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 77fcb691..f935de7a 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -24,7 +24,7 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExis filename_(filename) { #ifdef FFS_WIN - fileHandle = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(), + fileHandle = ::CreateFile(applyLongPathPrefix(filename).c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, @@ -123,31 +123,54 @@ FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw Fil filename_(filename) { #ifdef FFS_WIN - fileHandle = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(), - GENERIC_READ | GENERIC_WRITE, - /* http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx - quote: When an application creates a file across a network, it is better - to use GENERIC_READ | GENERIC_WRITE for dwDesiredAccess than to use GENERIC_WRITE alone. - The resulting code is faster, because the redirector can use the cache manager and send fewer SMBs with more data. - This combination also avoids an issue where writing to a file across a network can occasionally return ERROR_ACCESS_DENIED. */ - FILE_SHARE_DELETE, //FILE_SHARE_DELETE is required to rename file while handle is open! - nullptr, - access == ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, - nullptr); + const DWORD dwCreationDisposition = access == FileOutput::ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW; + + auto getHandle = [&](DWORD dwFlagsAndAttributes) + { + return ::CreateFile(applyLongPathPrefix(filename).c_str(), + GENERIC_READ | GENERIC_WRITE, + /* http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx + quote: When an application creates a file across a network, it is better + to use GENERIC_READ | GENERIC_WRITE for dwDesiredAccess than to use GENERIC_WRITE alone. + The resulting code is faster, because the redirector can use the cache manager and send fewer SMBs with more data. + This combination also avoids an issue where writing to a file across a network can occasionally return ERROR_ACCESS_DENIED. */ + FILE_SHARE_DELETE, //FILE_SHARE_DELETE is required to rename file while handle is open! + nullptr, + dwCreationDisposition, + dwFlagsAndAttributes | FILE_FLAG_SEQUENTIAL_SCAN, + nullptr); + }; + + fileHandle = getHandle(FILE_ATTRIBUTE_NORMAL); if (fileHandle == INVALID_HANDLE_VALUE) { - const DWORD lastError = ::GetLastError(); - const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + zen::getLastErrorFormatted(lastError); - - if (lastError == ERROR_FILE_EXISTS || //confirmed to be used - lastError == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 - throw ErrorTargetExisting(errorMessage); - - if (lastError == ERROR_PATH_NOT_FOUND) - throw ErrorTargetPathMissing(errorMessage); - - throw FileError(errorMessage); + DWORD lastError = ::GetLastError(); + + //CREATE_ALWAYS fails with ERROR_ACCESS_DENIED if the existing file is hidden or "system" http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx + if (lastError == ERROR_ACCESS_DENIED && + dwCreationDisposition == CREATE_ALWAYS) + { + const DWORD attrib = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); + if (attrib != INVALID_FILE_ATTRIBUTES) + { + fileHandle = getHandle(attrib); //retry + lastError = ::GetLastError(); + } + } + //"regular" error handling + if (fileHandle == INVALID_HANDLE_VALUE) + { + const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + zen::getLastErrorFormatted(lastError); + + if (lastError == ERROR_FILE_EXISTS || //confirmed to be used + lastError == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 + throw ErrorTargetExisting(errorMessage); + + if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } } #elif defined FFS_LINUX diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index aa46d4f0..986e0ad8 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -225,7 +225,6 @@ struct Win32Traverser return true; } - template <class FindData> static void extractFileInfo(const FindData& fileInfo, DWORD volumeSerial, TraverseCallback::FileInfo& output) { output.fileSize = UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); @@ -233,23 +232,12 @@ struct Win32Traverser output.id = FileId(); } - template <class FindData> - static Int64 getModTime(const FindData& fileInfo) { return toTimeT(fileInfo.ftLastWriteTime); } - - template <class FindData> - static const FILETIME& getModTimeRaw(const FindData& fileInfo) { return fileInfo.ftLastWriteTime; } - - template <class FindData> + static Int64 getModTime (const FindData& fileInfo) { return toTimeT(fileInfo.ftLastWriteTime); } + static const FILETIME& getModTimeRaw (const FindData& fileInfo) { return fileInfo.ftLastWriteTime; } static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.ftCreationTime; } - - template <class FindData> - static const wchar_t* getShortName(const FindData& fileInfo) { return fileInfo.cFileName; } - - template <class FindData> - static bool isDirectory(const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - - template <class FindData> - static bool isSymlink(const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; } + static const wchar_t* getShortName (const FindData& fileInfo) { return fileInfo.cFileName; } + static bool isDirectory (const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } + static bool isSymlink (const FindData& fileInfo) { return zen::isSymlink(fileInfo); } //[!] keep namespace }; @@ -295,10 +283,10 @@ struct FilePlusTraverser //else we have a problem... report it: throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (+)"); } + return true; } - template <class FindData> static void extractFileInfo(const FindData& fileInfo, DWORD volumeSerial, TraverseCallback::FileInfo& output) { output.fileSize = fileInfo.fileSize.QuadPart; @@ -306,23 +294,12 @@ struct FilePlusTraverser output.id = extractFileID(volumeSerial, fileInfo.fileId); } - template <class FindData> - static Int64 getModTime(const FindData& fileInfo) { return toTimeT(fileInfo.lastWriteTime); } - - template <class FindData> - static const FILETIME& getModTimeRaw(const FindData& fileInfo) { return fileInfo.lastWriteTime; } - - template <class FindData> + static Int64 getModTime (const FindData& fileInfo) { return toTimeT(fileInfo.lastWriteTime); } + static const FILETIME& getModTimeRaw (const FindData& fileInfo) { return fileInfo.lastWriteTime; } static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.creationTime; } - - template <class FindData> - static const wchar_t* getShortName(const FindData& fileInfo) { return fileInfo.shortName; } - - template <class FindData> - static bool isDirectory(const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - - template <class FindData> - static bool isSymlink(const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; } + static const wchar_t* getShortName (const FindData& fileInfo) { return fileInfo.shortName; } + static bool isDirectory (const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } + static bool isSymlink (const FindData& fileInfo) { return zen::isSymlink(fileInfo.fileAttributes, fileInfo.reparseTag); } //[!] keep namespace }; diff --git a/zen/fixed_list.h b/zen/fixed_list.h index 04a680ad..60d7e0e7 100644 --- a/zen/fixed_list.h +++ b/zen/fixed_list.h @@ -19,7 +19,7 @@ class FixedList struct Node { Node() : next(nullptr), val() {} - //no variadic templates on VC2010... :( + //no variadic templates on VC2010... :( template <class A> Node(A&& a) : next(nullptr), val(std::forward<A>(a)) {} template <class A, class B> Node(A&& a, B&& b) : next(nullptr), val(std::forward<A>(a), std::forward<B>(b)) {} template <class A, class B, class C> Node(A&& a, B&& b, C&& c) : next(nullptr), val(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c)) {} diff --git a/zen/recycler.h b/zen/recycler.h index 1778eb2e..955dfbee 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -29,7 +29,7 @@ Linker flags: `pkg-config --libs gio-2.0` Already included in package "gtk+-2.0"! */ -//move a file or folder to Recycle Bin (deletes permanently if recycle is not available) -> crappy semantics, but we have no choice thanks to Windows' design +//move a file or folder to Recycle Bin (deletes permanently if recycler is not available) -> crappy semantics, but we have no choice thanks to Windows' design bool recycleOrDelete(const Zstring& filename); //throw FileError, return "true" if file/dir was actually deleted diff --git a/wx+/serialize.h b/zen/serialize.h index 030b55c7..ff9695b1 100644 --- a/wx+/serialize.h +++ b/zen/serialize.h @@ -11,12 +11,6 @@ #include <zen/string_base.h> #include <zen/file_io.h> -#ifdef FFS_WIN -warn_static("get rid of wx, then move to zen") -#endif - -#include <wx/stream.h> - namespace zen { @@ -33,7 +27,7 @@ binary container for data storage: must support "basic" std::vector interface (e typedef Zbase<char> Utf8String; //ref-counted + COW text stream + guaranteed performance: exponential growth class BinaryStream; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?) -class BinaryStream //essentially a std::vector<char> with ref-counted semantics +class BinaryStream //essentially a std::vector<char> with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite { public: BinaryStream() : buffer(std::make_shared<std::vector<char>>()) {} @@ -148,11 +142,6 @@ template < class BinInputStream> void readArray (BinInputStream& stre - - - - - //-----------------------implementation------------------------------- template <class BinContainer> inline void saveBinStream(const Zstring& filename, const BinContainer& cont) //throw FileError @@ -206,7 +195,7 @@ void writeNumber(BinOutputStream& stream, const N& num) template <class C, class BinOutputStream> inline -void writeContainer(BinOutputStream& stream, const C& cont) //don't even consider UTF8 conversions here! "string" is expected to handle arbitrary binary data! +void writeContainer(BinOutputStream& stream, const C& cont) //don't even consider UTF8 conversions here, we're handling arbitrary binary data! { const auto len = cont.size(); writeNumber(stream, static_cast<std::uint32_t>(len)); @@ -250,289 +239,6 @@ C readContainer(BinInputStream& stream) } return cont; } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#ifdef FFS_WIN -warn_static("get rid of wx, then move to zen") -#endif - - - -//unchecked, unformatted serialization -template <class T> T readPOD (wxInputStream& stream); -template <class T> void readPOD (wxInputStream& stream, T& pod); -template <class T> void writePOD(wxOutputStream& stream, const T& pod); - -template <class S> S readString (wxInputStream& stream); -template <class S> void readString (wxInputStream& stream, S& str); -template <class S> void writeString(wxOutputStream& stream, const S& str); - - -//############# wxWidgets stream adapter ############# -class FileInputStream : public wxInputStream -{ -public: - FileInputStream(const Zstring& filename) : fileObj(filename) {} //throw FileError - -private: - virtual size_t OnSysRead(void* buffer, size_t bufsize) { return fileObj.read(buffer, bufsize); } //throw FileError - - zen::FileInput fileObj; -}; - - -class FileOutputStream : public wxOutputStream -{ -public: - FileOutputStream(const Zstring& filename) : fileObj(filename, zen::FileOutput::ACC_OVERWRITE) {} //throw FileError - -private: - virtual size_t OnSysWrite(const void* buffer, size_t bufsize) - { - fileObj.write(buffer, bufsize); //throw FileError - return bufsize; - } - - zen::FileOutput fileObj; -}; - - -class CheckedIo -{ -public: - virtual void throwException() const = 0; - -protected: - CheckedIo(wxStreamBase& stream) : stream_(stream) {} - - void check() const - { - if (stream_.GetLastError() != wxSTREAM_NO_ERROR) - throwException(); - } - -private: - wxStreamBase& stream_; -}; - - -//wxInputStream proxy throwing exception on error -class CheckedReader : public CheckedIo -{ -public: - CheckedReader(wxInputStream& stream) : CheckedIo(stream), stream_(stream) {} - - template <class T> - T readPOD() const; //throw! - - template <class T> - void readPOD(T& pod) const; //throw! - - template <class S> - S readString() const; //throw! - - template <class S> - void readString(S& str) const; //throw! - - void readArray(void* data, size_t len) const; //throw! - -private: - wxInputStream& stream_; -}; - - -//wxOutputStream proxy throwing FileError on error -class CheckedWriter : public CheckedIo -{ -public: - CheckedWriter(wxOutputStream& stream) : CheckedIo(stream), stream_(stream) {} - - template <class T> - void writePOD(const T& pod) const; //throw! - - template <class S> - void writeString(const S& str) const; //throw! - - void writeArray(const void* data, size_t len) const; //throw! - -private: - wxOutputStream& stream_; -}; - - -template <class T> inline -T readPOD(wxInputStream& stream) -{ - T pod = 0; - readPOD(stream, pod); - return pod; -} - - -template <class T> inline -void readPOD(wxInputStream& stream, T& pod) -{ - stream.Read(reinterpret_cast<char*>(&pod), sizeof(T)); -} - - -template <class T> inline -void writePOD(wxOutputStream& stream, const T& pod) -{ - stream.Write(reinterpret_cast<const char*>(&pod), sizeof(T)); -} - - -template <class S> inline -S readString(wxInputStream& stream) -{ - S str; - readString(stream, str); - return str; -} - - -template <class S> inline -void readString(wxInputStream& stream, S& str) -{ - //don't even consider UTF8 conversions here! "string" is expected to handle arbitrary binary data! - - const auto strLength = readPOD<std::uint32_t>(stream); - str.resize(strLength); //throw std::bad_alloc - if (strLength > 0) - stream.Read(&*str.begin(), sizeof(typename S::value_type) * strLength); -} - - -template <class S> inline -void writeString(wxOutputStream& stream, const S& str) -{ - const auto strLength = str.length(); - writePOD(stream, static_cast<std::uint32_t>(strLength)); - if (strLength > 0) - stream.Write(&*str.begin(), sizeof(typename S::value_type) * strLength); //don't use c_str(), but access uniformly via STL interface -} - - -inline -void CheckedReader::readArray(void* data, size_t len) const //throw! -{ - stream_.Read(data, len); - check(); -} - - -template <class T> inline -T CheckedReader::readPOD() const //checked read operation -{ - T pod = 0; - readPOD(pod); - return pod; -} - - -template <class T> inline -void CheckedReader::readPOD(T& pod) const //checked read operation -{ - readArray(&pod, sizeof(T)); -} - - -template <class S> inline -S CheckedReader::readString() const //checked read operation -{ - S str; - readString(str); - return str; -} - - -template <class S> inline -void CheckedReader::readString(S& str) const //checked read operation -{ - try - { - zen::readString<S>(stream_, str); //throw std::bad_alloc - } - catch (std::exception&) { throwException(); } - check(); - if (stream_.LastRead() != str.size() * sizeof(typename S::value_type)) //some additional check - throwException(); -} - - -inline -void CheckedWriter::writeArray(const void* data, size_t len) const //throw! -{ - stream_.Write(data, len); - check(); -} - - -template <class T> inline -void CheckedWriter::writePOD(const T& pod) const //checked write opera -{ - writeArray(&pod, sizeof(T)); -} - - -template <class S> inline -void CheckedWriter::writeString(const S& str) const //checked write operation -{ - zen::writeString(stream_, str); - check(); - //warn_static("buggy check if length 0!") - if (stream_.LastWrite() != str.length() * sizeof(typename S::value_type)) //some additional check - throwException(); -} } #endif //SERIALIZE_H_INCLUDED diff --git a/zen/string_base.h b/zen/string_base.h index 4c339d56..e2e9f19a 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -196,7 +196,7 @@ public: Zbase(const Char* source, size_t length); Zbase(const Zbase& source); Zbase(Zbase&& tmp); - explicit Zbase(Char source); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but no error + explicit Zbase(Char source); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but not a compiler error! //allow explicit construction from different string type, prevent ambiguity via SFINAE template <class S> explicit Zbase(const S& other, typename S::value_type = 0); ~Zbase(); diff --git a/zen/string_tools.h b/zen/string_tools.h index 00eb50ee..80232086 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -458,30 +458,29 @@ S formatInteger(Num n, bool hasMinus) { assert(n >= 0); typedef typename GetCharType<S>::Type CharType; + CharType buffer[2 + 5 * sizeof(Num) / 2]; //it's generally faster to use a buffer than to rely on String::operator+=() (in)efficiency + //minimum required chars (+ sign char): 1 + ceil(ln_10 (256^sizeof(n))) =~ 1 + ceil(sizeof(n) * 2.4082) < 2 + floor(sizeof(n) * 2.5) - const size_t bufferSize = 64; //sufficient for signed 128-bit numbers - CharType buffer[bufferSize]; //it's generally faster to use a buffer than to rely on String::operator+=() (in)efficiency - assert_static(2 + 5 * sizeof(n) / 2 <= bufferSize); - //minimum required chars (+ sign char): 1 + ceil(ln_10 (256^sizeof(n))) =~ 1 + ceil(sizeof(n) * 2.4082) <= 2 + floor(sizeof(n) * 2.5) - - size_t startPos = bufferSize; + auto iter = std::end(buffer); do { - buffer[--startPos] = static_cast<char>('0' + n % 10); - n /= 10; + const Num tmp = n / 10; + *--iter = static_cast<CharType>('0' + (n - tmp * 10)); //8% faster than using modulus operator! + n = tmp; } while (n != 0); if (hasMinus) - buffer[--startPos] = static_cast<CharType>('-'); + *--iter = static_cast<CharType>('-'); - return S(buffer + startPos, bufferSize - startPos); + return S(&*iter, std::end(buffer) - iter); } template <class S, class Num> inline S numberTo(const Num& number, Int2Type<NUM_TYPE_SIGNED_INT>) { return formatInteger<S>(number < 0 ? -number : number, number < 0); + //bug for "INT_MIN"! technically -INT_MIN == INT_MIN -> not worth the trouble } diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 17ae1916..6d44d845 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -21,6 +21,33 @@ #endif +namespace zen +{ +#ifdef FFS_WIN +bool isSymlink(const WIN32_FIND_DATA& data); //*not* a simple FILE_ATTRIBUTE_REPARSE_POINT check! +bool isSymlink(DWORD fileAttributes, DWORD reparseTag); +#endif + +Zstring getSymlinkRawTargetString(const Zstring& linkPath); //throw FileError +} + + + + + + + + + + + + + + + + + +//################################ implementation ################################ #ifdef _MSC_VER //I don't have Windows Driver Kit at hands right now, so unfortunately we need to redefine this structures and cross fingers... typedef struct _REPARSE_DATA_BUFFER //from ntifs.h { @@ -55,10 +82,11 @@ typedef struct _REPARSE_DATA_BUFFER //from ntifs.h #define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) #endif + namespace { //retrieve raw target data of symlink or junction -Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError +Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileError { using namespace zen; #ifdef FFS_WIN @@ -136,4 +164,38 @@ Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError } } + +namespace zen +{ +inline +Zstring getSymlinkRawTargetString(const Zstring& linkPath) { return getSymlinkRawTargetString_impl(linkPath); } + + +#ifdef FFS_WIN +/* + Reparse Point Tags + http://msdn.microsoft.com/en-us/library/windows/desktop/aa365511(v=vs.85).aspx + WIN32_FIND_DATA structure + http://msdn.microsoft.com/en-us/library/windows/desktop/aa365740(v=vs.85).aspx + + The only surrogate reparse points are; + IO_REPARSE_TAG_MOUNT_POINT + IO_REPARSE_TAG_SYMLINK +*/ + +inline +bool isSymlink(DWORD fileAttributes, DWORD reparseTag) +{ + return (fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && + IsReparseTagNameSurrogate(reparseTag); +} + +inline +bool isSymlink(const WIN32_FIND_DATA& data) +{ + return isSymlink(data.dwFileAttributes, data.dwReserved0); +} +#endif +} + #endif // SYMLINK_WIN_H_INCLUDED |