summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Application.cpp71
-rw-r--r--Application.h16
-rw-r--r--BUILD/Changelog.txt17
-rw-r--r--BUILD/FreeFileSync.chmbin667844 -> 667830 bytes
-rw-r--r--BUILD/Help/html/Comparison Settings.html10
-rw-r--r--BUILD/Languages/dutch.lng204
-rw-r--r--BUILD/Languages/french.lng2
-rw-r--r--BUILD/Languages/german.lng48
-rw-r--r--BUILD/Languages/norwegian.lng195
-rw-r--r--BUILD/Languages/portuguese.lng75
-rw-r--r--BUILD/Languages/portuguese_br.lng18
-rw-r--r--BUILD/Languages/scottish_gaelic.lng1450
-rw-r--r--BUILD/Languages/slovenian.lng18
-rw-r--r--BUILD/Languages/ukrainian.lng2
-rw-r--r--BUILD/Resources.zipbin263865 -> 264614 bytes
-rw-r--r--FreeFileSync.cbp13
-rw-r--r--FreeFileSync.vcxproj1
-rw-r--r--Makefile3
-rw-r--r--RealtimeSync/application.cpp17
-rw-r--r--RealtimeSync/main_dlg.cpp2
-rw-r--r--RealtimeSync/resources.h6
-rw-r--r--algorithm.cpp248
-rw-r--r--algorithm.h7
-rw-r--r--comparison.cpp78
-rw-r--r--file_hierarchy.cpp7
-rw-r--r--file_hierarchy.h298
-rw-r--r--lib/binary.cpp8
-rw-r--r--lib/db_file.cpp226
-rw-r--r--lib/dir_lock.cpp9
-rw-r--r--lib/error_log.h43
-rw-r--r--lib/generate_logfile.h15
-rw-r--r--lib/hard_filter.h4
-rw-r--r--lib/localization.cpp22
-rw-r--r--lib/perf_check.cpp4
-rw-r--r--lib/process_xml.cpp104
-rw-r--r--lib/process_xml.h2
-rw-r--r--process_callback.h4
-rw-r--r--structures.cpp2
-rw-r--r--structures.h4
-rw-r--r--synchronization.cpp214
-rw-r--r--synchronization.h4
-rw-r--r--ui/batch_config.cpp4
-rw-r--r--ui/batch_status_handler.cpp10
-rw-r--r--ui/column_attr.h57
-rw-r--r--ui/custom_grid.cpp448
-rw-r--r--ui/custom_grid.h15
-rw-r--r--ui/grid_view.cpp2
-rw-r--r--ui/gui_generated.cpp40
-rw-r--r--ui/gui_generated.h6
-rw-r--r--ui/gui_status_handler.cpp49
-rw-r--r--ui/main_dlg.cpp925
-rw-r--r--ui/main_dlg.h50
-rw-r--r--ui/search.cpp56
-rw-r--r--ui/search.h4
-rw-r--r--ui/tree_view.cpp10
-rw-r--r--ui/tree_view.h2
-rw-r--r--ui/triple_splitter.cpp230
-rw-r--r--ui/triple_splitter.h87
-rw-r--r--version/version.h2
-rw-r--r--version/version.rc4
-rw-r--r--wx+/file_drop.h3
-rw-r--r--wx+/graph.cpp10
-rw-r--r--wx+/graph.h11
-rw-r--r--wx+/grid.cpp553
-rw-r--r--wx+/grid.h100
-rw-r--r--wx+/image_tools.h50
-rw-r--r--wx+/zlib_wrap.cpp2
-rw-r--r--wx+/zlib_wrap.h2
-rw-r--r--zen/FindFilePlus/find_file_plus.cpp21
-rw-r--r--zen/FindFilePlus/find_file_plus.h1
-rw-r--r--zen/IFileOperation/file_op.cpp2
-rw-r--r--zen/assert_static.h2
-rw-r--r--zen/basic_math.h2
-rw-r--r--zen/debug_log.h2
-rw-r--r--zen/debug_new.h9
-rw-r--r--zen/dir_watcher.cpp2
-rw-r--r--zen/file_handling.cpp38
-rw-r--r--zen/file_handling.h2
-rw-r--r--zen/file_io.cpp71
-rw-r--r--zen/file_traverser.cpp45
-rw-r--r--zen/fixed_list.h2
-rw-r--r--zen/recycler.h2
-rw-r--r--zen/serialize.h (renamed from wx+/serialize.h)298
-rw-r--r--zen/string_base.h2
-rw-r--r--zen/string_tools.h19
-rw-r--r--zen/symlink_target.h64
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
index 94ea4923..97363d82 100644
--- a/BUILD/FreeFileSync.chm
+++ b/BUILD/FreeFileSync.chm
Binary files differ
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>&quot;Reparse
- Points&quot;</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>&quot;volume mount points&quot; </I>and <I>&quot;NTFS junction
+ points&quot;</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
index 2f132d17..44b9749e 100644
--- a/BUILD/Resources.zip
+++ b/BUILD/Resources.zip
Binary files differ
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>
diff --git a/Makefile b/Makefile
index ded925f3..17a4dda9 100644
--- a/Makefile
+++ b/Makefile
@@ -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&currency_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&currency_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 = { &currentCfg.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_; });
+ });
+};
diff --git a/wx+/grid.h b/wx+/grid.h
index 4f89bd9d..e5284cdb 100644
--- a/wx+/grid.h
+++ b/wx+/grid.h
@@ -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
bgstack15