summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Application.cpp10
-rw-r--r--BUILD/Changelog.txt26
-rw-r--r--BUILD/FreeFileSync.chmbin433358 -> 433358 bytes
-rw-r--r--BUILD/Languages/german.lng33
-rw-r--r--BUILD/Resources.zipbin225594 -> 226783 bytes
-rw-r--r--FreeFileSync.vcxproj9
-rw-r--r--RealtimeSync/RealtimeSync.vcxproj3
-rw-r--r--RealtimeSync/application.cpp2
-rw-r--r--RealtimeSync/gui_generated.cpp42
-rw-r--r--RealtimeSync/gui_generated.h6
-rw-r--r--RealtimeSync/main_dlg.cpp31
-rw-r--r--RealtimeSync/main_dlg.h21
-rw-r--r--RealtimeSync/tray_menu.cpp6
-rw-r--r--RealtimeSync/watcher.cpp4
-rw-r--r--RealtimeSync/xml_ffs.cpp2
-rw-r--r--algorithm.cpp3
-rw-r--r--comparison.cpp89
-rw-r--r--file_hierarchy.h20
-rw-r--r--lib/Batch.icobin134332 -> 116037 bytes
-rw-r--r--lib/ShadowCopy/Shadow_Server2003.vcxproj7
-rw-r--r--lib/ShadowCopy/Shadow_Windows7.vcxproj7
-rw-r--r--lib/ShadowCopy/Shadow_XP.vcxproj7
-rw-r--r--lib/Thumbnail/Thumbnail.vcxproj7
-rw-r--r--lib/binary.cpp12
-rw-r--r--lib/db_file.cpp6
-rw-r--r--lib/dir_lock.cpp16
-rw-r--r--lib/generate_logfile.h174
-rw-r--r--lib/localization.cpp2
-rw-r--r--lib/lock_holder.h67
-rw-r--r--lib/parallel_scan.cpp2
-rw-r--r--lib/process_xml.cpp12
-rw-r--r--lib/process_xml.h2
-rw-r--r--lib/resolve_path.cpp2
-rw-r--r--lib/shadow.cpp3
-rw-r--r--lib/status_handler.cpp2
-rw-r--r--synchronization.cpp107
-rw-r--r--synchronization.h1
-rw-r--r--ui/IFileDialog_Vista/IFileDialog_Vista.vcxproj13
-rw-r--r--ui/Taskbar_Seven/Taskbar_Seven.vcxproj15
-rw-r--r--ui/batch_config.cpp2
-rw-r--r--ui/batch_status_handler.cpp31
-rw-r--r--ui/batch_status_handler.h2
-rw-r--r--ui/column_attr.h5
-rw-r--r--ui/custom_grid.cpp80
-rw-r--r--ui/custom_grid.h12
-rw-r--r--ui/dir_name.cpp2
-rw-r--r--ui/gui_generated.cpp110
-rw-r--r--ui/gui_generated.h10
-rw-r--r--ui/gui_status_handler.cpp17
-rw-r--r--ui/gui_status_handler.h2
-rw-r--r--ui/main_dlg.cpp297
-rw-r--r--ui/main_dlg.h2
-rw-r--r--ui/progress_indicator.cpp411
-rw-r--r--ui/small_dlgs.cpp6
-rw-r--r--ui/tree_view.cpp15
-rw-r--r--version/version.h2
-rw-r--r--version/version.rc4
-rw-r--r--wx+/button.cpp5
-rw-r--r--wx+/context_menu.h4
-rw-r--r--wx+/file_drop.h14
-rw-r--r--wx+/grid.cpp329
-rw-r--r--wx+/grid.h87
-rw-r--r--wx+/zlib_wrap.h2
-rw-r--r--zen/FindFilePlus/FindFilePlus.vcxproj7
-rw-r--r--zen/FindFilePlus/find_file_plus.cpp2
-rw-r--r--zen/IFileOperation/FileOperation_Vista.vcxproj7
-rw-r--r--zen/IFileOperation/file_op.cpp14
-rw-r--r--zen/basic_math.h14
-rw-r--r--zen/debug_memory_leaks.cpp31
-rw-r--r--zen/debug_minidump.cpp (renamed from zen/debug_new.cpp)10
-rw-r--r--zen/debug_minidump.h (renamed from zen/debug_new.h)0
-rw-r--r--zen/dir_watcher.cpp50
-rw-r--r--zen/error_log.h44
-rw-r--r--zen/file_handling.cpp98
-rw-r--r--zen/file_io.cpp170
-rw-r--r--zen/file_io.h69
-rw-r--r--zen/file_io_base.h64
-rw-r--r--zen/file_traverser.cpp14
-rw-r--r--zen/perf.h2
-rw-r--r--zen/scroll_window_under_cursor.cpp15
-rw-r--r--zen/serialize.h2
-rw-r--r--zen/string_base.h20
-rw-r--r--zen/string_tools.h33
-rw-r--r--zen/thread.h27
-rw-r--r--zen/tick_count.h30
-rw-r--r--zen/time.h9
-rw-r--r--zen/zstring.h2
87 files changed, 1939 insertions, 1017 deletions
diff --git a/Application.cpp b/Application.cpp
index 327681bf..aa2fa856 100644
--- a/Application.cpp
+++ b/Application.cpp
@@ -59,7 +59,7 @@ void onTerminationRequested()
void crtInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) { assert(false); }
#endif
}
-#endif
+#endif
bool Application::OnInit()
@@ -386,6 +386,7 @@ void runBatchMode(const Zstring& filename, FfsReturnCode& returnCode)
timeStamp,
batchCfg.logFileDirectory,
batchCfg.logfilesCountLimit,
+ globalCfg.lastSyncsLogFileSizeMax,
batchCfg.handleError,
switchBatchToGui,
returnCode,
@@ -410,13 +411,14 @@ void runBatchMode(const Zstring& filename, FfsReturnCode& returnCode)
std::unique_ptr<LockHolder> dummy;
if (globalCfg.createLockFile)
{
- dummy.reset(new LockHolder(allowPwPrompt));
+ std::vector<Zstring> dirnames;
std::for_each(cmpConfig.begin(), cmpConfig.end(),
[&](const FolderPairCfg& fpCfg)
{
- dummy->addDir(fpCfg.leftDirectoryFmt, statusHandler);
- dummy->addDir(fpCfg.rightDirectoryFmt, statusHandler);
+ dirnames.push_back(fpCfg.leftDirectoryFmt);
+ dirnames.push_back(fpCfg.rightDirectoryFmt);
});
+ dummy = make_unique<LockHolder>(dirnames, statusHandler, allowPwPrompt);
}
//COMPARE DIRECTORIES
diff --git a/BUILD/Changelog.txt b/BUILD/Changelog.txt
index 6696584b..21a0bbe1 100644
--- a/BUILD/Changelog.txt
+++ b/BUILD/Changelog.txt
@@ -2,20 +2,40 @@
|FreeFileSync|
--------------
+Changelog v5.10
+---------------
+Show synchronization log as a grid in results dialog
+Improved grid scrolling performance (most noticeable on Linux)
+Allow grid selection starting from outside of the grid
+RealtimeSync: Support drag & drop on main dialog for *.ffs_real and *.ffs_batch files
+Optimized memory consumption when generating log for millions of items
+Optimized memory consumption when exporting to CSV file
+Have grid row height match window default font size
+Catch out of memory when copying huge lists into clipboard
+Fixed failure to resume aborted sync after having FFS implicitly create target directory
+Fixed horizontal mouse wheel scrolling direction for RTL languages (Hebrew)
+RealtimeSync: Fixed drag and drop not working (Linux)
+Set maximum size of LastSyncs.log in GlobalSettings.xml element <LastSyncsFileSizeMax>
+Show error when trying to copy a named pipe rather than hang (Linux)
+Improved copy routine minimizing file accesses (Linux)
+Copy file access permissions by default (Linux)
+Fixed unexpected "File or Directory not existing" error during file copy (Linux)
+
+
Changelog v5.9
--------------
-Scroll grid directly under mouse cursor
+Scroll grid under mouse cursor
Move files directly to recycle bin without parent "FFS 2012-05-15 131513" temporary folders
Offer $HOME directory alias in directory dropdown list (Linux)
Support for tilde (~) character in input folder paths (Linux)
New environment variables for RealtimeSync: %change_action%, "%change_path%
Use Internet Explorer proxy settings for new version check (Windows)
Show proper error message after failed symlink creation
-Start comparison directly when double-clicking config list
+Start comparison upon double-clicking config list
New batch return code: "Synchronization completed with warnings"
Hide files that won't be copied by default if direction "none" is part of the rule set (e.g. update variant)
-New sync completion sound
Remember save config and folder picker dialog positions separately
+New sync completion sound
Fixed sync completion sound not playing (Ubuntu)
diff --git a/BUILD/FreeFileSync.chm b/BUILD/FreeFileSync.chm
index e4eba632..07e52459 100644
--- a/BUILD/FreeFileSync.chm
+++ b/BUILD/FreeFileSync.chm
Binary files differ
diff --git a/BUILD/Languages/german.lng b/BUILD/Languages/german.lng
index cdb0622f..d2fca6cf 100644
--- a/BUILD/Languages/german.lng
+++ b/BUILD/Languages/german.lng
@@ -58,8 +58,8 @@
<source>Clear filter settings</source>
<target>Filtereinstellungen löschen</target>
-<source>Create a batch job</source>
-<target>Batch-Job erstellen</target>
+<source>Save as batch job</source>
+<target>Speichern als Batch-Job</target>
<source>Comparison settings</source>
<target>Vergleichseinstellungen</target>
@@ -345,6 +345,9 @@ Die Befehlszeile wird ausgelöst wenn:
<source>FreeFileSync batch</source>
<target>FreeFileSync Batch</target>
+<source>Saving log file %x</source>
+<target>Speichere Logdatei %x</target>
+
<source>Synchronization aborted!</source>
<target>Synchronisation abgebrochen!</target>
@@ -453,6 +456,9 @@ Die Befehlszeile wird ausgelöst wenn:
<source>&Save</source>
<target>&Speichern</target>
+<source>Save as &batch job...</source>
+<target>Speichern als &Batch-Job...</target>
+
<source>1. &Compare</source>
<target>1. &Vergleichen</target>
@@ -465,9 +471,6 @@ Die Befehlszeile wird ausgelöst wenn:
<source>&Global settings...</source>
<target>&Globale Einstellungen...</target>
-<source>&Create batch job...</source>
-<target>&Batch-Job erstellen...</target>
-
<source>&Export file list...</source>
<target>Dateiliste e&xportieren...</target>
@@ -501,8 +504,8 @@ Die Befehlszeile wird ausgelöst wenn:
<source>Swap sides</source>
<target>Seiten vertauschen</target>
-<source>Open...</source>
-<target>Öffnen...</target>
+<source>Open</source>
+<target>Öffnen</target>
<source>Save</source>
<target>Speichern</target>
@@ -989,8 +992,8 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein!
<pluralform>%x objects deleted successfully!</pluralform>
</source>
<target>
-<pluralform>Objekt erfolgreich gelöscht!</pluralform>
-<pluralform>%x Objekte erfolgreich gelöscht!</pluralform>
+<pluralform>Element erfolgreich gelöscht!</pluralform>
+<pluralform>%x Elemente erfolgreich gelöscht!</pluralform>
</target>
<source>
@@ -1044,6 +1047,9 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein!
<source>Comparing content...</source>
<target>Vergleiche Dateiinhalt...</target>
+<source>Copy</source>
+<target>Kopieren</target>
+
<source>Paused</source>
<target>Angehalten</target>
@@ -1200,6 +1206,9 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein!
<source>Cannot copy file %x to %y.</source>
<target>Die Datei %x kann nicht nach %y kopiert werden.</target>
+<source>Type of item %x is not supported:</source>
+<target>Der Typ des Elements %x wird nicht unterstützt:</target>
+
<source>Cannot open directory %x.</source>
<target>Das Verzeichnis %x kann nicht geöffnet werden.</target>
@@ -1398,6 +1407,9 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein!
<source>Updating attributes of %x</source>
<target>Aktualisiere Attribute von %x</target>
+<source>Target folder %x already existing.</source>
+<target>Der Zielordner %x existiert bereits.</target>
+
<source>Target folder input field must not be empty.</source>
<target>Das Eingabefeld für den Zielordner darf nicht leer sein.</target>
@@ -1434,9 +1446,6 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein!
<source>Synchronizing folder pair:</source>
<target>Synchronisiere Ordnerpaar:</target>
-<source>Target folder %x already existing.</source>
-<target>Der Zielordner %x existiert bereits.</target>
-
<source>Generating database...</source>
<target>Erzeuge Synchronisationsdatenbank...</target>
diff --git a/BUILD/Resources.zip b/BUILD/Resources.zip
index 1f602205..6ce935e9 100644
--- a/BUILD/Resources.zip
+++ b/BUILD/Resources.zip
Binary files differ
diff --git a/FreeFileSync.vcxproj b/FreeFileSync.vcxproj
index 5b5aeb49..60c622f6 100644
--- a/FreeFileSync.vcxproj
+++ b/FreeFileSync.vcxproj
@@ -109,6 +109,7 @@
<ForcedIncludeFiles>zen/warn_static.h;wx+/pch.h</ForcedIncludeFiles>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<MinimalRebuild>false</MinimalRebuild>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -116,6 +117,8 @@
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalDependencies>wxmsw28ud_aui.lib;wxmsw28ud_adv.lib;wxmsw28ud_core.lib;wxbase28ud_net.lib;wxbase28ud.lib;wxpngd.lib;wxzlibd.lib;comctl32.lib;ws2_32.lib;Rpcrt4.lib;winmm.lib;%(AdditionalDependencies);Wininet.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x86_debug_dll</AdditionalLibraryDirectories>
+ <LinkStatus>
+ </LinkStatus>
</Link>
<ResourceCompile>
<AdditionalIncludeDirectories>C:\Program Files\C++\wxWidgets\include</AdditionalIncludeDirectories>
@@ -137,6 +140,7 @@
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<SuppressStartupBanner>true</SuppressStartupBanner>
<MinimalRebuild>false</MinimalRebuild>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -176,6 +180,8 @@
<AdditionalDependencies>wxmsw28u_aui.lib;wxmsw28u_adv.lib;wxmsw28u_core.lib;wxbase28u.lib;wxpng.lib;wxzlib.lib;wxbase28u_net.lib;comctl32.lib;ws2_32.lib;winmm.lib;Rpcrt4.lib;%(AdditionalDependencies);Wininet.lib</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x86_release_lib</AdditionalLibraryDirectories>
+ <LinkStatus>
+ </LinkStatus>
</Link>
<ResourceCompile>
<AdditionalIncludeDirectories>C:\Program Files\C++\wxWidgets\include</AdditionalIncludeDirectories>
@@ -207,6 +213,8 @@
<AdditionalDependencies>wxmsw28u_aui.lib;wxmsw28u_adv.lib;wxmsw28u_core.lib;wxbase28u.lib;wxpng.lib;wxzlib.lib;wxbase28u_net.lib;comctl32.lib;ws2_32.lib;winmm.lib;Rpcrt4.lib;%(AdditionalDependencies);Wininet.lib</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x64_release_lib</AdditionalLibraryDirectories>
+ <LinkStatus>
+ </LinkStatus>
</Link>
<ResourceCompile>
<AdditionalIncludeDirectories>C:\Program Files\C++\wxWidgets\include</AdditionalIncludeDirectories>
@@ -272,6 +280,7 @@
<ClCompile Include="wx+\tooltip.cpp" />
<ClCompile Include="wx+\zlib_wrap.cpp" />
<ClCompile Include="zenxml\unit_test.cpp" />
+ <ClCompile Include="zen\debug_memory_leaks.cpp" />
<ClCompile Include="zen\dst_hack.cpp" />
<ClCompile Include="zen\file_handling.cpp" />
<ClCompile Include="zen\file_id.cpp" />
diff --git a/RealtimeSync/RealtimeSync.vcxproj b/RealtimeSync/RealtimeSync.vcxproj
index 764ec0a9..3a2ebf8b 100644
--- a/RealtimeSync/RealtimeSync.vcxproj
+++ b/RealtimeSync/RealtimeSync.vcxproj
@@ -109,6 +109,7 @@
<ForcedIncludeFiles>zen/warn_static.h;wx+/pch.h</ForcedIncludeFiles>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<MinimalRebuild>false</MinimalRebuild>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -138,6 +139,7 @@
<SuppressStartupBanner>true</SuppressStartupBanner>
<MinimalRebuild>false</MinimalRebuild>
<ShowIncludes>false</ShowIncludes>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -229,6 +231,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\wx+\mouse_move_dlg.cpp" />
+ <ClCompile Include="..\zen\debug_memory_leaks.cpp" />
<ClCompile Include="..\zen\dir_watcher.cpp" />
<ClCompile Include="..\zen\dst_hack.cpp" />
<ClCompile Include="..\zen\file_handling.cpp" />
diff --git a/RealtimeSync/application.cpp b/RealtimeSync/application.cpp
index 435a6b86..23e7ab25 100644
--- a/RealtimeSync/application.cpp
+++ b/RealtimeSync/application.cpp
@@ -18,7 +18,7 @@
#include "lib/error_log.h"
#ifdef FFS_WIN
-#include <zen/win_ver.h>
+#include <zen/win_ver.h>
#elif defined FFS_LINUX
#include <gtk/gtk.h>
#endif
diff --git a/RealtimeSync/gui_generated.cpp b/RealtimeSync/gui_generated.cpp
index 4651099c..1e817367 100644
--- a/RealtimeSync/gui_generated.cpp
+++ b/RealtimeSync/gui_generated.cpp
@@ -84,9 +84,11 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
bSizer1->Add( bSizer13, 0, wxEXPAND|wxRIGHT|wxLEFT, 20 );
m_staticline2 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
- bSizer1->Add( m_staticline2, 0, wxEXPAND|wxTOP|wxBOTTOM, 10 );
+ bSizer1->Add( m_staticline2, 0, wxEXPAND|wxTOP, 5 );
- sbSizerDirToWatch2 = new wxStaticBoxSizer( new wxStaticBox( m_panelMain, wxID_ANY, _("Folders to watch") ), wxVERTICAL );
+ m_staticText7 = new wxStaticText( m_panelMain, wxID_ANY, _("Folders to watch"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText7->Wrap( -1 );
+ bSizer1->Add( m_staticText7, 0, wxALL, 5 );
m_panelMainFolder = new wxPanel( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizer10;
@@ -134,43 +136,51 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
m_panelMainFolder->SetSizer( bSizer10 );
m_panelMainFolder->Layout();
bSizer10->Fit( m_panelMainFolder );
- sbSizerDirToWatch2->Add( m_panelMainFolder, 0, wxEXPAND, 5 );
+ bSizer1->Add( m_panelMainFolder, 0, wxRIGHT|wxLEFT|wxEXPAND, 5 );
m_scrolledWinFolders = new wxScrolledWindow( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL );
- m_scrolledWinFolders->SetScrollRate( 5, 5 );
+ m_scrolledWinFolders->SetScrollRate( 10, 10 );
bSizerFolders = new wxBoxSizer( wxVERTICAL );
m_scrolledWinFolders->SetSizer( bSizerFolders );
m_scrolledWinFolders->Layout();
bSizerFolders->Fit( m_scrolledWinFolders );
- sbSizerDirToWatch2->Add( m_scrolledWinFolders, 0, wxEXPAND, 5 );
+ bSizer1->Add( m_scrolledWinFolders, 1, wxRIGHT|wxLEFT|wxEXPAND, 5 );
+ m_staticline212 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer1->Add( m_staticline212, 0, wxEXPAND|wxTOP, 5 );
- bSizer1->Add( sbSizerDirToWatch2, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 );
+ wxBoxSizer* bSizer14;
+ bSizer14 = new wxBoxSizer( wxHORIZONTAL );
- wxStaticBoxSizer* sbSizer4;
- sbSizer4 = new wxStaticBoxSizer( new wxStaticBox( m_panelMain, wxID_ANY, _("Delay [seconds]") ), wxVERTICAL );
+ m_staticText8 = new wxStaticText( m_panelMain, wxID_ANY, _("Delay [seconds]"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText8->Wrap( -1 );
+ bSizer14->Add( m_staticText8, 0, wxALIGN_CENTER_VERTICAL, 5 );
+
+
+ bSizer14->Add( 0, 0, 1, wxEXPAND, 5 );
m_spinCtrlDelay = new wxSpinCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 2000000000, 0 );
m_spinCtrlDelay->SetToolTip( _("Idle time between last detected change and execution of command") );
- sbSizer4->Add( m_spinCtrlDelay, 0, wxALIGN_CENTER_HORIZONTAL, 5 );
+ bSizer14->Add( m_spinCtrlDelay, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
+
+ bSizer1->Add( bSizer14, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 );
- bSizer1->Add( sbSizer4, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 );
+ m_staticline211 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer1->Add( m_staticline211, 0, wxEXPAND|wxTOP, 5 );
- wxStaticBoxSizer* sbSizer3;
- sbSizer3 = new wxStaticBoxSizer( new wxStaticBox( m_panelMain, wxID_ANY, _("Command line") ), wxVERTICAL );
+ m_staticText6 = new wxStaticText( m_panelMain, wxID_ANY, _("Command line"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText6->Wrap( -1 );
+ bSizer1->Add( m_staticText6, 0, wxALL, 5 );
m_textCtrlCommand = new wxTextCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_textCtrlCommand->SetMaxLength( 0 );
m_textCtrlCommand->SetToolTip( _("The command is triggered if:\n- files or subfolders change\n- new folders arrive (e.g. USB stick insert)") );
- sbSizer3->Add( m_textCtrlCommand, 0, wxEXPAND, 5 );
-
-
- bSizer1->Add( sbSizer3, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 );
+ bSizer1->Add( m_textCtrlCommand, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 );
m_buttonStart = new zen::BitmapButton( m_panelMain, wxID_OK, _("Start"), wxDefaultPosition, wxSize( -1,50 ), 0 );
m_buttonStart->SetDefault();
diff --git a/RealtimeSync/gui_generated.h b/RealtimeSync/gui_generated.h
index aa267a02..56488f86 100644
--- a/RealtimeSync/gui_generated.h
+++ b/RealtimeSync/gui_generated.h
@@ -59,7 +59,7 @@ protected:
wxStaticText* m_staticText5;
wxStaticText* m_staticText811;
wxStaticLine* m_staticline2;
- wxStaticBoxSizer* sbSizerDirToWatch2;
+ wxStaticText* m_staticText7;
wxPanel* m_panelMainFolder;
wxStaticText* m_staticTextFinalPath;
wxBitmapButton* m_bpButtonAddFolder;
@@ -68,7 +68,11 @@ protected:
wxButton* m_buttonSelectDirMain;
wxScrolledWindow* m_scrolledWinFolders;
wxBoxSizer* bSizerFolders;
+ wxStaticLine* m_staticline212;
+ wxStaticText* m_staticText8;
wxSpinCtrl* m_spinCtrlDelay;
+ wxStaticLine* m_staticline211;
+ wxStaticText* m_staticText6;
wxTextCtrl* m_textCtrlCommand;
zen::BitmapButton* m_buttonStart;
wxButton* m_buttonCancel;
diff --git a/RealtimeSync/main_dlg.cpp b/RealtimeSync/main_dlg.cpp
index bb9ac75b..ca6fa4fa 100644
--- a/RealtimeSync/main_dlg.cpp
+++ b/RealtimeSync/main_dlg.cpp
@@ -28,6 +28,21 @@
using namespace zen;
+class DirectoryPanel : public FolderGenerated
+{
+public:
+ DirectoryPanel(wxWindow* parent) :
+ FolderGenerated(parent),
+ dirName(*this, *m_buttonSelectDir, *m_txtCtrlDirectory) {}
+
+ void setName(const wxString& dirname) { dirName.setName(dirname); }
+ wxString getName() const { return dirName.getName(); }
+
+private:
+ zen::DirectoryName<wxTextCtrl> dirName;
+};
+
+
MainDialog::MainDialog(wxDialog* dlg, const wxString& cfgFileName)
: MainDlgGenerated(dlg)
{
@@ -59,7 +74,7 @@ MainDialog::MainDialog(wxDialog* dlg, const wxString& cfgFileName)
if (!cfgFileName.empty() || wxFileExists(lastConfigFileName()))
try
{
- rts::readRealOrBatchConfig(toZ(currentConfigFile), newConfig);
+ rts::readRealOrBatchConfig(toZ(currentConfigFile), newConfig); //throw FfsXmlError
loadCfgSuccess = true;
}
catch (const xmlAccess::FfsXmlError& error)
@@ -92,6 +107,10 @@ MainDialog::MainDialog(wxDialog* dlg, const wxString& cfgFileName)
}
else
m_buttonStart->SetFocus(); //don't "steal" focus if program is running from sys-tray"
+
+ //drag and drop .ffs_real and .ffs_batch on main dialog
+ setupFileDrop(*m_panelMain);
+ m_panelMain->Connect(EVENT_DROP_FILE, FileDropEventHandler(MainDialog::onFilesDropped), nullptr, this);
}
@@ -102,7 +121,7 @@ MainDialog::~MainDialog()
try //write config to XML
{
- writeRealConfig(currentCfg, toZ(lastConfigFileName()));
+ writeRealConfig(currentCfg, toZ(lastConfigFileName())); //throw FfsXmlError
}
catch (const xmlAccess::FfsXmlError& error)
{
@@ -255,6 +274,14 @@ void MainDialog::OnConfigLoad(wxCommandEvent& event)
}
+void MainDialog::onFilesDropped(FileDropEvent& event)
+{
+ const auto& files = event.getFiles();
+ if (!files.empty())
+ loadConfig(files[0]);
+}
+
+
void MainDialog::setConfiguration(const xmlAccess::XmlRealConfig& cfg)
{
//clear existing folders
diff --git a/RealtimeSync/main_dlg.h b/RealtimeSync/main_dlg.h
index 3e1b807e..82d27944 100644
--- a/RealtimeSync/main_dlg.h
+++ b/RealtimeSync/main_dlg.h
@@ -10,28 +10,14 @@
#include "gui_generated.h"
#include <vector>
#include <memory>
+#include <wx+/file_drop.h>
#include "../ui/dir_name.h"
namespace xmlAccess
{
struct XmlRealConfig;
}
-
-
-class DirectoryPanel : public FolderGenerated
-{
-public:
- DirectoryPanel(wxWindow* parent) :
- FolderGenerated(parent),
- dirName(*this, *m_buttonSelectDir, *m_txtCtrlDirectory) {}
-
- void setName(const wxString& dirname) { dirName.setName(dirname); }
- wxString getName() const { return dirName.getName(); }
-
-private:
- zen::DirectoryName<wxTextCtrl> dirName;
-};
-
+class DirectoryPanel;
class MainDialog: public MainDlgGenerated
@@ -40,9 +26,9 @@ public:
MainDialog(wxDialog* dlg, const wxString& cfgFileName);
~MainDialog();
+private:
void loadConfig(const wxString& filename);
-private:
virtual void OnClose (wxCloseEvent& event) { Destroy(); }
virtual void OnQuit (wxCommandEvent& event) { Destroy(); }
virtual void OnShowHelp (wxCommandEvent& event);
@@ -54,6 +40,7 @@ private:
virtual void OnStart (wxCommandEvent& event);
virtual void OnConfigSave (wxCommandEvent& event);
virtual void OnConfigLoad (wxCommandEvent& event);
+ void onFilesDropped(zen::FileDropEvent& event);
void setConfiguration(const xmlAccess::XmlRealConfig& cfg);
xmlAccess::XmlRealConfig getConfiguration();
diff --git a/RealtimeSync/tray_menu.cpp b/RealtimeSync/tray_menu.cpp
index a30081bf..3319f427 100644
--- a/RealtimeSync/tray_menu.cpp
+++ b/RealtimeSync/tray_menu.cpp
@@ -129,9 +129,9 @@ class TrayIconHolder
{
public:
TrayIconHolder(const wxString& jobname, AbortCallback& abortCb) :
- jobName_(jobname)
+ jobName_(jobname),
+ trayMenu(new RtsTrayIconRaw(abortCb))
{
- trayMenu = new RtsTrayIconRaw(abortCb); //not in initialization list: give it a valid parent object!
showIconActive();
}
@@ -175,8 +175,8 @@ public:
}
private:
- RtsTrayIconRaw* trayMenu;
const wxString jobName_; //RTS job name, may be empty
+ RtsTrayIconRaw* trayMenu;
};
diff --git a/RealtimeSync/watcher.cpp b/RealtimeSync/watcher.cpp
index bfdb79c2..8ed37ea3 100644
--- a/RealtimeSync/watcher.cpp
+++ b/RealtimeSync/watcher.cpp
@@ -30,7 +30,7 @@ TickVal lastExec = getTicks();
bool rts::updateUiIsAllowed()
{
const TickVal now = getTicks(); //0 on error
- if (now - lastExec >= TICKS_UPDATE_INTERVAL) //perform ui updates not more often than necessary
+ if (dist(lastExec, now) >= TICKS_UPDATE_INTERVAL) //perform ui updates not more often than necessary
{
lastExec = now;
return true;
@@ -98,7 +98,7 @@ rts::WaitResult rts::waitForChanges(const std::vector<Zstring>& dirNamesNonFmt,
const bool checkDirExistNow = [&]() -> bool //checking once per sec should suffice
{
const TickVal now = getTicks(); //0 on error
- if (now - lastCheck >= TICKS_DIR_CHECK_INTERVAL)
+ if (dist(lastCheck, now) >= TICKS_DIR_CHECK_INTERVAL)
{
lastCheck = now;
return true;
diff --git a/RealtimeSync/xml_ffs.cpp b/RealtimeSync/xml_ffs.cpp
index 11151a09..8126ec05 100644
--- a/RealtimeSync/xml_ffs.cpp
+++ b/RealtimeSync/xml_ffs.cpp
@@ -79,7 +79,7 @@ int rts::getProgramLanguage()
{
xmlAccess::readConfig(settings);
}
- catch (const xmlAccess::FfsXmlError&) {} //user default language if error occured
+ catch (const xmlAccess::FfsXmlError&) {} //user default language if error occurred
return settings.programLanguage;
}
diff --git a/algorithm.cpp b/algorithm.cpp
index 3bfc14e2..a39473a4 100644
--- a/algorithm.cpp
+++ b/algorithm.cpp
@@ -997,8 +997,7 @@ private:
void zen::addHardFiltering(BaseDirMapping& baseMap, const Zstring& excludeFilter)
{
- ApplyHardFilter<STRATEGY_AND>(*HardFilter::FilterRef(
- new NameFilter(FilterConfig().includeFilter, excludeFilter))).execute(baseMap);
+ ApplyHardFilter<STRATEGY_AND>(NameFilter(FilterConfig().includeFilter, excludeFilter)).execute(baseMap);
}
diff --git a/comparison.cpp b/comparison.cpp
index bae14c41..3e99e366 100644
--- a/comparison.cpp
+++ b/comparison.cpp
@@ -82,19 +82,88 @@ void determineExistentDirs(const std::set<Zstring, LessFilename>& dirnames,
bool allowUserInteraction,
ProcessCallback& callback)
{
- std::for_each(dirnames.begin(), dirnames.end(),
- [&](const Zstring& dirname)
+ std::vector<Zstring> dirs(dirnames.begin(), dirnames.end());
+ vector_remove_if(dirs, [](const Zstring& dir) { return dir.empty(); });
+
+
+
+ warn_static("finish")
+ /*
+ //check existence of all directories in parallel! (avoid adding up search times if multiple network drives are not reachable)
+ FixedList<boost::unique_future<bool>> asyncDirChecks;
+ std::for_each(dirs.begin(), dirs.end(), [&](const Zstring& dirname)
{
- if (!dirname.empty())
- {
- if (tryReportingError([&]
+ asyncDirChecks.emplace_back(async([=]() -> bool
+ {
+ #ifdef FFS_WIN
+ //1. login to network share, if necessary
+ loginNetworkShare(dirname, allowUserInteraction);
+ #endif
+ //2. check dir existence
+ return zen::dirExists(dirname);
+ }));
+ });
+
+ auto timeMax = boost::get_system_time() + boost::posix_time::seconds(10); //limit total directory search time
+
+ auto iterCheckDir = asyncDirChecks.begin();
+ for (auto iter = dirs.begin(); iter != dirs.end(); ++iter, ++iterCheckDir)
+ {
+ const Zstring& dirname = *iter;
+ callback.reportStatus(replaceCpy(_("Searching for folder %x..."), L"%x", fmtFileName(dirname), false));
+
+ while (boost::get_system_time() < timeMax && !iterCheckDir->timed_wait(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL)))
+ callback.requestUiRefresh(); //may throw!
+
+ //only (still) existing files should be included in the list
+ if (iterCheckDir->is_ready() && iterCheckDir->get())
+ dirnamesExisting.insert(dirname);
+ else
+ {
+
+ switch (callback.reportError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtFileName(dirname)) + L"\n\n" +
+ _("You can ignore this error to consider the folder as empty."))) //may throw!
{
- if (!dirExistsUpdating(dirname, allowUserInteraction, callback))
- throw FileError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtFileName(dirname)) + L"\n\n" +
- _("You can ignore this error to consider the folder as empty."));
- }, callback))
- dirnamesExisting.insert(dirname);
+ case ProcessCallback::IGNORE_ERROR:
+ break;
+ case ProcessCallback::RETRY:
+ break; //continue with loop
}
+
+
+ if (tryReportingError([&]
+ {
+ if (!dirExistsUpdating(dirname, allowUserInteraction, callback))
+ throw FileError();
+
+
+
+
+ }, callback))
+ dirnamesExisting.insert(dirname);
+ }
+ }
+ */
+
+
+
+
+
+
+
+
+
+
+ std::for_each(dirs.begin(), dirs.end(),
+ [&](const Zstring& dirname)
+ {
+ if (tryReportingError([&]
+ {
+ if (!dirExistsUpdating(dirname, allowUserInteraction, callback))
+ throw FileError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtFileName(dirname)) + L"\n\n" +
+ _("You can ignore this error to consider the folder as empty."));
+ }, callback))
+ dirnamesExisting.insert(dirname);
});
}
diff --git a/file_hierarchy.h b/file_hierarchy.h
index 5a5b5818..18f46ea3 100644
--- a/file_hierarchy.h
+++ b/file_hierarchy.h
@@ -245,7 +245,8 @@ public:
template <SelectedSide side> const Zstring& getBaseDirPf() const; //base sync directory postfixed with FILE_NAME_SEPARATOR (or empty!)
static void removeEmpty(BaseDirMapping& baseDir) { baseDir.removeEmptyRec(); }; //physically remove all invalid entries (where both sides are empty) recursively
- template <SelectedSide side> bool wasExisting() const; //status of directory existence at the time of comparison!
+ template <SelectedSide side> bool isExisting() const; //status of directory existence at the time of comparison!
+ template <SelectedSide side> void setExisting(bool value); //update after creating the directory in FFS
//get settings which were used while creating BaseDirMapping
const HardFilter& getFilter() const { return *filter_; }
@@ -988,18 +989,29 @@ void DirMapping::removeObjectR()
template <> inline
-bool BaseDirMapping::wasExisting<LEFT_SIDE>() const
+bool BaseDirMapping::isExisting<LEFT_SIDE>() const
{
return dirExistsLeft_;
}
-
template <> inline
-bool BaseDirMapping::wasExisting<RIGHT_SIDE>() const
+bool BaseDirMapping::isExisting<RIGHT_SIDE>() const
{
return dirExistsRight_;
}
+template <> inline
+void BaseDirMapping::setExisting<LEFT_SIDE>(bool value)
+{
+ dirExistsLeft_ = value;
+}
+
+template <> inline
+void BaseDirMapping::setExisting<RIGHT_SIDE>(bool value)
+{
+ dirExistsRight_ = value;
+}
+
inline
void FileMapping::flip()
diff --git a/lib/Batch.ico b/lib/Batch.ico
index 14e2bb10..1856b1fb 100644
--- a/lib/Batch.ico
+++ b/lib/Batch.ico
Binary files differ
diff --git a/lib/ShadowCopy/Shadow_Server2003.vcxproj b/lib/ShadowCopy/Shadow_Server2003.vcxproj
index 3e70cd7a..50a3a830 100644
--- a/lib/ShadowCopy/Shadow_Server2003.vcxproj
+++ b/lib/ShadowCopy/Shadow_Server2003.vcxproj
@@ -100,6 +100,7 @@
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -112,6 +113,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -134,6 +136,7 @@
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -146,6 +149,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -180,6 +184,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -217,9 +222,11 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
+ <ClCompile Include="..\..\zen\debug_memory_leaks.cpp" />
<ClCompile Include="dll_main.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
diff --git a/lib/ShadowCopy/Shadow_Windows7.vcxproj b/lib/ShadowCopy/Shadow_Windows7.vcxproj
index 985936b4..aa32253b 100644
--- a/lib/ShadowCopy/Shadow_Windows7.vcxproj
+++ b/lib/ShadowCopy/Shadow_Windows7.vcxproj
@@ -100,6 +100,7 @@
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -112,6 +113,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -134,6 +136,7 @@
<DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_WINDOWS7;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -146,6 +149,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -180,6 +184,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -217,9 +222,11 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
+ <ClCompile Include="..\..\zen\debug_memory_leaks.cpp" />
<ClCompile Include="dll_main.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
diff --git a/lib/ShadowCopy/Shadow_XP.vcxproj b/lib/ShadowCopy/Shadow_XP.vcxproj
index b49bff4c..70b792ec 100644
--- a/lib/ShadowCopy/Shadow_XP.vcxproj
+++ b/lib/ShadowCopy/Shadow_XP.vcxproj
@@ -100,6 +100,7 @@
<FavorSizeOrSpeed>Neither</FavorSizeOrSpeed>
<DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -112,6 +113,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -135,6 +137,7 @@
<FavorSizeOrSpeed>Neither</FavorSizeOrSpeed>
<DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -147,6 +150,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -181,6 +185,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -218,9 +223,11 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
+ <ClCompile Include="..\..\zen\debug_memory_leaks.cpp" />
<ClCompile Include="dll_main.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
diff --git a/lib/Thumbnail/Thumbnail.vcxproj b/lib/Thumbnail/Thumbnail.vcxproj
index e3909ff8..3baa2d61 100644
--- a/lib/Thumbnail/Thumbnail.vcxproj
+++ b/lib/Thumbnail/Thumbnail.vcxproj
@@ -98,6 +98,7 @@
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -109,6 +110,7 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -131,6 +133,7 @@
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -142,6 +145,7 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -175,6 +179,7 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -211,9 +216,11 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
+ <ClCompile Include="..\..\zen\debug_memory_leaks.cpp" />
<ClCompile Include="dll_main.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
diff --git a/lib/binary.cpp b/lib/binary.cpp
index b9e3028d..10994cc9 100644
--- a/lib/binary.cpp
+++ b/lib/binary.cpp
@@ -44,7 +44,7 @@ public:
private:
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_START = 128 * 1024; //initial buffer size
static const size_t BUFFER_SIZE_MAX = 16 * 1024 * 1024;
/*Tests on Win7 x64 show that buffer size does NOT matter if files are located on different physical disks!
@@ -99,23 +99,23 @@ bool zen::filesHaveSameContent(const Zstring& filename1, const Zstring& filename
const size_t length1 = file1.read(&memory1[0], bufferSize); //returns actual number of bytes read; throw FileError()
const size_t length2 = file2.read(&memory2[0], bufferSize); //
- const TickVal stopTime = getTicks();
+ const TickVal now = getTicks();
//-------- dynamically set buffer size to keep callback interval between 100 - 500ms ---------------------
if (TICKS_PER_SEC > 0)
{
- const std::int64_t loopTime = (stopTime - startTime) * 1000 / TICKS_PER_SEC; //unit: [ms]
+ const std::int64_t loopTime = dist(startTime, now) * 1000 / TICKS_PER_SEC; //unit: [ms]
if (loopTime < 100)
{
- if ((stopTime - lastDelayViolation) / TICKS_PER_SEC > 2) //avoid "flipping back": e.g. DVD-Roms read 32MB at once, so first read may be > 500 ms, but second one will be 0ms!
+ if (dist(lastDelayViolation, now) / TICKS_PER_SEC > 2) //avoid "flipping back": e.g. DVD-Roms read 32MB at once, so first read may be > 500 ms, but second one will be 0ms!
{
- lastDelayViolation = stopTime;
+ lastDelayViolation = now;
bufferSize.inc();
}
}
else if (loopTime > 500)
{
- lastDelayViolation = stopTime;
+ lastDelayViolation = now;
bufferSize.dec();
}
}
diff --git a/lib/db_file.cpp b/lib/db_file.cpp
index 53830afe..f81ada21 100644
--- a/lib/db_file.cpp
+++ b/lib/db_file.cpp
@@ -636,12 +636,12 @@ std::shared_ptr<InSyncDir> zen::loadLastSynchronousState(const BaseDirMapping& b
const Zstring fileNameLeft = getDBFilename<LEFT_SIDE >(baseMapping);
const Zstring fileNameRight = getDBFilename<RIGHT_SIDE>(baseMapping);
- if (!baseMapping.wasExisting<LEFT_SIDE >() ||
- !baseMapping.wasExisting<RIGHT_SIDE>())
+ if (!baseMapping.isExisting<LEFT_SIDE >() ||
+ !baseMapping.isExisting<RIGHT_SIDE>())
{
//avoid race condition with directory existence check: reading sync.ffs_db may succeed although first dir check had failed => conflicts!
//https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3531351&group_id=234430
- const Zstring filename = !baseMapping.wasExisting<LEFT_SIDE>() ? fileNameLeft : fileNameRight;
+ const Zstring filename = !baseMapping.isExisting<LEFT_SIDE>() ? fileNameLeft : fileNameRight;
throw FileErrorDatabaseNotExisting(_("Initial synchronization:") + L" \n" + //it could be due to a to-be-created target directory not yet existing => FileErrorDatabaseNotExisting
replaceCpy(_("Database file %x does not yet exist."), L"%x", fmtFileName(filename)));
}
diff --git a/lib/dir_lock.cpp b/lib/dir_lock.cpp
index bbd97454..5041b37e 100644
--- a/lib/dir_lock.cpp
+++ b/lib/dir_lock.cpp
@@ -105,7 +105,7 @@ public:
#elif defined FFS_LINUX
const int fileHandle = ::open(lockfilename_.c_str(), O_WRONLY | O_APPEND);
- if (fileHandle == -1)
+ if (fileHandle < 0)
return;
ZEN_ON_SCOPE_EXIT(::close(fileHandle));
@@ -418,20 +418,20 @@ void waitOnDirLock(const Zstring& lockfilename, DirLockCallback* callback) //thr
while (true)
{
- const TickVal currentTime = getTicks();
+ const TickVal now = getTicks();
const UInt64 fileSizeNew = ::getLockFileSize(lockfilename); //throw FileError, ErrorNotExisting
- if (TICKS_PER_SEC <= 0 || !lastLifeSign.isValid() || !currentTime.isValid())
+ if (TICKS_PER_SEC <= 0 || !lastLifeSign.isValid() || !now.isValid())
throw FileError(L"System Timer failed!"); //no i18n: "should" never throw ;)
if (fileSizeNew != fileSizeOld) //received life sign from lock
{
fileSizeOld = fileSizeNew;
- lastLifeSign = currentTime;
+ lastLifeSign = now;
}
if (lockOwnderDead || //no need to wait any longer...
- (currentTime - lastLifeSign) / TICKS_PER_SEC > DETECT_ABANDONED_INTERVAL)
+ dist(lastLifeSign, now) / TICKS_PER_SEC > DETECT_ABANDONED_INTERVAL)
{
DirLock dummy(deleteAbandonedLockName(lockfilename), callback); //throw FileError
@@ -458,11 +458,9 @@ void waitOnDirLock(const Zstring& lockfilename, DirLockCallback* callback) //thr
if (callback)
{
//one signal missed: it's likely this is an abandoned lock => show countdown
- if ((currentTime - lastLifeSign) / TICKS_PER_SEC > EMIT_LIFE_SIGN_INTERVAL)
+ if (dist(lastLifeSign, now) / TICKS_PER_SEC > EMIT_LIFE_SIGN_INTERVAL)
{
- int remainingSeconds = DETECT_ABANDONED_INTERVAL - (getTicks() - lastLifeSign) / TICKS_PER_SEC;
- remainingSeconds = std::max(0, remainingSeconds);
-
+ const int remainingSeconds = std::max<int>(0, DETECT_ABANDONED_INTERVAL - dist(lastLifeSign, getTicks()) / TICKS_PER_SEC);
const std::wstring remSecMsg = replaceCpy(_P("1 sec", "%x sec", remainingSeconds), L"%x", numberTo<std::wstring>(remainingSeconds));
callback->reportInfo(infoMsg + L" " + remSecMsg);
}
diff --git a/lib/generate_logfile.h b/lib/generate_logfile.h
index beb4f5d3..875d5b98 100644
--- a/lib/generate_logfile.h
+++ b/lib/generate_logfile.h
@@ -16,16 +16,24 @@
namespace zen
{
-Utf8String generateLogStream(const ErrorLog& log,
- const std::wstring& jobName, //may be empty
- const std::wstring& finalStatus,
- int itemsSynced, Int64 dataSynced,
- int itemsTotal, Int64 dataTotal,
- long totalTime); //unit: [sec]
-
-void saveToLastSyncsLog(const Utf8String& logstream); //throw FileError
+struct SummaryInfo
+{
+ std::wstring jobName; //may be empty
+ std::wstring finalStatus;
+ int itemsSynced;
+ Int64 dataSynced;
+ int itemsTotal;
+ Int64 dataTotal;
+ long totalTime; //unit: [sec]
+};
+void saveLogToFile(const SummaryInfo& summary, //throw FileError
+ const ErrorLog& log,
+ FileOutput& fileOut);
+void saveToLastSyncsLog(const SummaryInfo& summary, //throw FileError
+ const ErrorLog& log,
+ size_t maxBytesToWrite);
@@ -34,114 +42,136 @@ void saveToLastSyncsLog(const Utf8String& logstream); //throw FileError
//####################### implementation #######################
namespace
{
-Utf8String generateLogStream_impl(const ErrorLog& log,
- const std::wstring& jobName, //may be empty
- const std::wstring& finalStatus,
- int itemsSynced, Int64 dataSynced,
- int itemsTotal, Int64 dataTotal,
- long totalTime) //unit: [sec]
+std::wstring generateLogHeader(const SummaryInfo& s)
{
- assert(itemsSynced <= itemsTotal);
- assert(dataSynced <= dataTotal);
+ assert(s.itemsSynced <= s.itemsTotal);
+ assert(s.dataSynced <= s.dataTotal);
- Utf8String output;
+ std::wstring output;
//write header
std::wstring headerLine = formatTime<std::wstring>(FORMAT_DATE);
- if (!jobName.empty())
- headerLine += L" - " + jobName;
- headerLine += L": " + finalStatus;
+ if (!s.jobName.empty())
+ headerLine += L" - " + s.jobName;
+ headerLine += L": " + s.finalStatus;
//assemble results box
std::vector<std::wstring> results;
results.push_back(headerLine);
results.push_back(L"");
- const wchar_t tabSpace[] = L" ";
+ const wchar_t tabSpace[] = L" ";
- std::wstring itemsProc = tabSpace + _("Items processed:") + L" " + toGuiString(itemsSynced); //show always, even if 0!
- if (itemsSynced != 0 || dataSynced != 0) //[!] don't show 0 bytes processed if 0 items were processed
- itemsProc += + L" (" + filesizeToShortString(dataSynced) + L")";
+ std::wstring itemsProc = tabSpace + _("Items processed:") + L" " + toGuiString(s.itemsSynced); //show always, even if 0!
+ if (s.itemsSynced != 0 || s.dataSynced != 0) //[!] don't show 0 bytes processed if 0 items were processed
+ itemsProc += + L" (" + filesizeToShortString(s.dataSynced) + L")";
results.push_back(itemsProc);
- if (itemsTotal != 0 || dataTotal != 0) //=: sync phase was reached and there were actual items to sync
+ if (s.itemsTotal != 0 || s.dataTotal != 0) //=: sync phase was reached and there were actual items to sync
{
- if (itemsSynced != itemsTotal ||
- dataSynced != dataTotal)
- results.push_back(tabSpace + _("Items remaining:") + L" " + toGuiString(itemsTotal - itemsSynced) + L" (" + filesizeToShortString(dataTotal - dataSynced) + L")");
+ if (s.itemsSynced != s.itemsTotal ||
+ s.dataSynced != s.dataTotal)
+ results.push_back(tabSpace + _("Items remaining:") + L" " + toGuiString(s.itemsTotal - s.itemsSynced) + L" (" + filesizeToShortString(s.dataTotal - s.dataSynced) + L")");
}
- results.push_back(tabSpace + _("Total time:") + L" " + copyStringTo<std::wstring>(wxTimeSpan::Seconds(totalTime).Format()));
+ results.push_back(tabSpace + _("Total time:") + L" " + copyStringTo<std::wstring>(wxTimeSpan::Seconds(s.totalTime).Format()));
//calculate max width, this considers UTF-16 only, not true Unicode...
size_t sepLineLen = 0;
std::for_each(results.begin(), results.end(), [&](const std::wstring& str) { sepLineLen = std::max(sepLineLen, str.size()); });
- for (size_t i = 0; i < sepLineLen; ++i) output += '_'; //this considers UTF-16 only, not true Unicode!!!
- output += "\n";
-
- std::for_each(results.begin(), results.end(), [&](const std::wstring& str) { output += utfCvrtTo<Utf8String>(str); output += '\n'; });
+ for (size_t i = 0; i < sepLineLen; ++i) output += L'_'; //this considers UTF-16 only, not true Unicode!!!
+ output += L'\n';
- for (size_t i = 0; i < sepLineLen; ++i) output += '_';
- output += "\n\n";
+ std::for_each(results.begin(), results.end(), [&](const std::wstring& str) { output += str; output += L'\n'; });
- //write log items
- const auto& entries = log.getEntries();
- for (auto iter = entries.begin(); iter != entries.end(); ++iter)
- {
- output += utfCvrtTo<Utf8String>(formatMessage(*iter));
- output += '\n';
- }
+ for (size_t i = 0; i < sepLineLen; ++i) output += L'_';
+ output += L'\n';
- return replaceCpy(output, '\n', LINE_BREAK); //don't replace line break any earlier
+ return output;
}
}
inline
-Utf8String generateLogStream(const ErrorLog& log,
- const std::wstring& jobName, //may be empty
- const std::wstring& finalStatus,
- int itemsSynced, Int64 dataSynced,
- int itemsTotal, Int64 dataTotal,
- long totalTime) //unit: [sec]
+void saveLogToFile(const SummaryInfo& summary, //throw FileError
+ const ErrorLog& log,
+ FileOutput& fileOut)
{
- return generateLogStream_impl(log, jobName, finalStatus, itemsSynced, dataSynced, itemsTotal, dataTotal, totalTime);
+ Utf8String header = utfCvrtTo<Utf8String>(generateLogHeader(summary));
+ replace(header, '\n', LINE_BREAK); //don't replace line break any earlier
+ header += LINE_BREAK; //make sure string is not empty!
+
+ fileOut.write(&*header.begin(), header.size()); //throw FileError
+
+ //write log items one after the other instead of creating one big string: memory allocation might fail; think 1 million entries!
+ for (auto iter = log.begin(); iter != log.end(); ++iter)
+ {
+ Utf8String msg = replaceCpy(utfCvrtTo<Utf8String>(formatMessage<std::wstring>(*iter)), '\n', LINE_BREAK);
+ msg += LINE_BREAK; //make sure string is not empty!
+
+ fileOut.write(&*msg.begin(), msg.size()); //throw FileError
+ }
}
inline
-void saveToLastSyncsLog(const Utf8String& logstream) //throw FileError
+void saveToLastSyncsLog(const SummaryInfo& summary, //throw FileError
+ const ErrorLog& log,
+ size_t maxBytesToWrite) //log may be *huge*, e.g. 1 million items; LastSyncs.log *must not* create performance problems!
+
{
const Zstring filename = getConfigDir() + Zstr("LastSyncs.log");
- Utf8String oldStream;
- try
- {
- oldStream = loadBinStream<Utf8String>(filename); //throw FileError, ErrorNotExisting
- }
- catch (const ErrorNotExisting&) {}
+ Utf8String newStream = utfCvrtTo<Utf8String>(generateLogHeader(summary));
+ replace(newStream, '\n', LINE_BREAK); //don't replace line break any earlier
+ newStream += LINE_BREAK;
- Utf8String newStream = logstream;
- if (!oldStream.empty())
+ //write log items one after the other instead of creating one big string: memory allocation might fail; think 1 million entries!
+ for (auto iter = log.begin(); iter != log.end(); ++iter)
{
+ newStream += replaceCpy(utfCvrtTo<Utf8String>(formatMessage<std::wstring>(*iter)), '\n', LINE_BREAK);
newStream += LINE_BREAK;
- newStream += LINE_BREAK;
- newStream += oldStream;
- }
- //limit file size: 128 kB (but do not truncate new log)
- const size_t newSize = std::min(newStream.size(), std::max<size_t>(logstream.size(), 128 * 1024));
+ if (newStream.size() > maxBytesToWrite)
+ {
+ newStream += "[...]";
+ newStream += LINE_BREAK;
+ break;
+ }
+ }
- //do not cut in the middle of a row
- auto iter = std::search(newStream.cbegin() + newSize, newStream.cend(), std::begin(LINE_BREAK), std::end(LINE_BREAK) - 1);
- if (iter != newStream.cend())
+ //fill up the rest of permitted space by appending old log
+ if (newStream.size() < maxBytesToWrite)
{
- newStream.resize(iter - newStream.cbegin());
-
- newStream += LINE_BREAK;
- newStream += "[...]";
- newStream += LINE_BREAK;
+ Utf8String oldStream;
+ try
+ {
+ oldStream = loadBinStream<Utf8String>(filename); //throw FileError, ErrorNotExisting
+ }
+ catch (const ErrorNotExisting&) {}
+
+ if (!oldStream.empty())
+ {
+ newStream += LINE_BREAK;
+ newStream += LINE_BREAK;
+ newStream += oldStream; //impliticly limited by "maxBytesToWrite"!
+
+ //truncate size if required
+ if (newStream.size() > maxBytesToWrite)
+ {
+ //but do not cut in the middle of a row
+ auto iter = std::search(newStream.cbegin() + maxBytesToWrite, newStream.cend(), std::begin(LINE_BREAK), std::end(LINE_BREAK) - 1);
+ if (iter != newStream.cend())
+ {
+ newStream.resize(iter - newStream.cbegin());
+ newStream += LINE_BREAK;
+
+ newStream += "[...]";
+ newStream += LINE_BREAK;
+ }
+ }
+ }
}
saveBinStream(filename, newStream); //throw FileError
diff --git a/lib/localization.cpp b/lib/localization.cpp
index 64c8b0f3..5792b3d1 100644
--- a/lib/localization.cpp
+++ b/lib/localization.cpp
@@ -123,7 +123,7 @@ public:
virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) { return LINK_SKIP; }
virtual std::shared_ptr<TraverseCallback> onDir(const Zchar* shortName, const Zstring& fullName) { return nullptr; }
- virtual HandleError onError(const std::wstring& msg) { return ON_ERROR_IGNORE; } //errors are not really critical in this context
+ virtual HandleError onError(const std::wstring& msg) { assert(false); return ON_ERROR_IGNORE; } //errors are not really critical in this context
private:
std::vector<Zstring>& lngFiles_;
diff --git a/lib/lock_holder.h b/lib/lock_holder.h
index 6265747b..5ae2f8ae 100644
--- a/lib/lock_holder.h
+++ b/lib/lock_holder.h
@@ -3,6 +3,7 @@
#include <map>
#include <zen/zstring.h>
+#include <zen/stl_tools.h>
#include "dir_lock.h"
#include "status_handler.h"
#include "dir_exist_async.h"
@@ -11,43 +12,49 @@ namespace zen
{
const Zstring LOCK_FILE_ENDING = Zstr(".ffs_lock"); //intermediate locks created by DirLock use this extension, too!
-//convenience class for creating and holding locks for a number of directories
+//hold locks for a number of directories without blocking during lock creation
class LockHolder
{
public:
- LockHolder(bool allowUserInteraction) : allowUserInteraction_(allowUserInteraction) {}
-
- void addDir(const Zstring& dirnameFmt, ProcessCallback& procCallback) //resolved dirname ending with path separator
+ LockHolder(const std::vector<Zstring>& dirnamesFmt, //resolved dirname ending with path separator
+ ProcessCallback& procCallback,
+ bool allowUserInteraction) : allowUserInteraction_(allowUserInteraction)
{
- if (dirnameFmt.empty())
- return;
+ std::vector<Zstring> dirs = dirnamesFmt;
+ vector_remove_if(dirs, [](const Zstring& dir) { return dir.empty(); });
- if (!dirExistsUpdating(dirnameFmt, allowUserInteraction_, procCallback))
- return;
+ for (auto iter = dirs.begin(); iter != dirs.end(); ++iter)
+ {
+ const Zstring& dirnameFmt = *iter;
- if (lockHolder.find(dirnameFmt) != lockHolder.end()) return;
- assert(endsWith(dirnameFmt, FILE_NAME_SEPARATOR)); //this is really the contract, formatting does other things as well, e.g. macro substitution
+ if (!dirExistsUpdating(dirnameFmt, allowUserInteraction_, procCallback))
+ continue;
- class WaitOnLockHandler : public DirLockCallback
- {
- public:
- WaitOnLockHandler(ProcessCallback& pc) : pc_(pc) {}
- virtual void requestUiRefresh() { pc_.requestUiRefresh(); } //allowed to throw exceptions
- virtual void reportInfo(const std::wstring& text) { pc_.reportStatus(text); }
- private:
- ProcessCallback& pc_;
- } callback(procCallback);
-
- try
- {
- //lock file creation is synchronous and may block noticably for very slow devices (usb sticks, mapped cloud storages)
- procCallback.forceUiRefresh(); //=> make sure the right folder name is shown on GUI during this time!
- lockHolder.insert(std::make_pair(dirnameFmt, DirLock(dirnameFmt + Zstr("sync") + LOCK_FILE_ENDING, &callback)));
- }
- catch (const FileError& e)
- {
- bool dummy = false; //this warning shall not be shown but logged only
- procCallback.reportWarning(e.toString(), dummy); //may throw!
+ if (lockHolder.find(dirnameFmt) != lockHolder.end())
+ continue;
+ assert(endsWith(dirnameFmt, FILE_NAME_SEPARATOR)); //this is really the contract, formatting does other things as well, e.g. macro substitution
+
+ class WaitOnLockHandler : public DirLockCallback
+ {
+ public:
+ WaitOnLockHandler(ProcessCallback& pc) : pc_(pc) {}
+ virtual void requestUiRefresh() { pc_.requestUiRefresh(); } //allowed to throw exceptions
+ virtual void reportInfo(const std::wstring& text) { pc_.reportStatus(text); }
+ private:
+ ProcessCallback& pc_;
+ } callback(procCallback);
+
+ try
+ {
+ //lock file creation is synchronous and may block noticably for very slow devices (usb sticks, mapped cloud storages)
+ procCallback.forceUiRefresh(); //=> make sure the right folder name is shown on GUI during this time!
+ lockHolder.insert(std::make_pair(dirnameFmt, DirLock(dirnameFmt + Zstr("sync") + LOCK_FILE_ENDING, &callback)));
+ }
+ catch (const FileError& e)
+ {
+ bool dummy = false; //this warning shall not be shown but logged only
+ procCallback.reportWarning(e.toString(), dummy); //may throw!
+ }
}
}
diff --git a/lib/parallel_scan.cpp b/lib/parallel_scan.cpp
index b9d29699..a31e30ee 100644
--- a/lib/parallel_scan.cpp
+++ b/lib/parallel_scan.cpp
@@ -541,7 +541,7 @@ void zen::fillBuffer(const std::set<DirectoryKey>& keysToRead, //in
std::for_each(worker.begin(), worker.end(), [](boost::thread& wt) { wt.interrupt(); }); //interrupt all at once, then join
std::for_each(worker.begin(), worker.end(), [](boost::thread& wt)
{
- if (wt.joinable()) //this is a precondition of thread::join()!!! Latter will throw an exception if violated!
+ if (wt.joinable()) //= precondition of thread::join(), which throws an exception if violated!
wt.join(); //in this context it is possible a thread is *not* joinable anymore due to the thread::timed_join() below!
});
});
diff --git a/lib/process_xml.cpp b/lib/process_xml.cpp
index 57ba69e6..aed9c35f 100644
--- a/lib/process_xml.cpp
+++ b/lib/process_xml.cpp
@@ -715,10 +715,10 @@ 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.offset_);
- out.attribute("Stretch", value.stretch_);
+ out.attribute("Type", value.type_);
+ out.attribute("Visible", value.visible_);
+ out.attribute("Width", value.offset_);
+ out.attribute("Stretch", value.stretch_);
}
@@ -943,6 +943,8 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config)
//max. allowed file time deviation
inShared["FileTimeTolerance"](config.fileTimeTolerance);
+ inShared["LastSyncsFileSizeMax"](config.lastSyncsLogFileSizeMax);
+
XmlIn inOpt = inShared["OptionalDialogs"];
inOpt["WarnUnresolvedConflicts" ](config.optDialogs.warningUnresolvedConflicts);
inOpt["WarnNotEnoughDiskSpace" ](config.optDialogs.warningNotEnoughDiskSpace);
@@ -1248,6 +1250,8 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out)
//max. allowed file time deviation
outShared["FileTimeTolerance"](config.fileTimeTolerance);
+ outShared["LastSyncsFileSizeMax"](config.lastSyncsLogFileSizeMax);
+
XmlOut outOpt = outShared["OptionalDialogs"];
outOpt["WarnUnresolvedConflicts" ](config.optDialogs.warningUnresolvedConflicts);
outOpt["WarnNotEnoughDiskSpace" ](config.optDialogs.warningNotEnoughDiskSpace);
diff --git a/lib/process_xml.h b/lib/process_xml.h
index d21f7ffc..8d1d4538 100644
--- a/lib/process_xml.h
+++ b/lib/process_xml.h
@@ -124,6 +124,7 @@ struct XmlGlobalSettings
copyFilePermissions(false),
runWithBackgroundPriority(false),
fileTimeTolerance(2), //default 2s: FAT vs NTFS
+ lastSyncsLogFileSizeMax(100000), //maximum size for LastSyncs.log: use a human-readable number
verifyFileCopy(false),
transactionalFileCopy(true),
createLockFile(true) {}
@@ -135,6 +136,7 @@ struct XmlGlobalSettings
bool runWithBackgroundPriority;
size_t fileTimeTolerance; //max. allowed file time deviation
+ size_t lastSyncsLogFileSizeMax;
bool verifyFileCopy; //verify copied files
bool transactionalFileCopy;
bool createLockFile;
diff --git a/lib/resolve_path.cpp b/lib/resolve_path.cpp
index 0b8e80b9..248e3507 100644
--- a/lib/resolve_path.cpp
+++ b/lib/resolve_path.cpp
@@ -300,7 +300,7 @@ public:
devices_.insert(std::make_pair(shortName, fullName));
return nullptr; //DON'T traverse into subdirs
}
- virtual HandleError onError(const std::wstring& msg) { return ON_ERROR_IGNORE; }
+ virtual HandleError onError(const std::wstring& msg) { assert(false); return ON_ERROR_IGNORE; }
private:
DeviceList& devices_;
diff --git a/lib/shadow.cpp b/lib/shadow.cpp
index 5f2225e7..8ef86f30 100644
--- a/lib/shadow.cpp
+++ b/lib/shadow.cpp
@@ -35,7 +35,6 @@ bool runningWOW64() //test if process is running under WOW64 (reference http://m
//#############################################################################################################
-
class ShadowCopy::ShadowVolume
{
public:
@@ -84,8 +83,8 @@ private:
Zstring shadowVolPf;
ShadowHandle backupHandle;
};
-//#############################################################################################################
+//#############################################################################################################
Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile)
{
diff --git a/lib/status_handler.cpp b/lib/status_handler.cpp
index 3188ba0d..c24c6f50 100644
--- a/lib/status_handler.cpp
+++ b/lib/status_handler.cpp
@@ -29,7 +29,7 @@ TickVal lastExec = getTicks();
bool zen::updateUiIsAllowed()
{
const TickVal now = getTicks(); //0 on error
- if (now - lastExec >= TICKS_UPDATE_INTERVAL) //perform ui updates not more often than necessary
+ if (dist(lastExec, now) >= TICKS_UPDATE_INTERVAL) //perform ui updates not more often than necessary
{
lastExec = now;
return true;
diff --git a/synchronization.cpp b/synchronization.cpp
index a70aad30..ecebd8bd 100644
--- a/synchronization.cpp
+++ b/synchronization.cpp
@@ -958,10 +958,10 @@ private:
Int64 spaceNeededLeft;
Int64 spaceNeededRight;
};
-}
+
//----------------------------------------------------------------------------------------
-class zen::SynchronizeFolderPair
+class SynchronizeFolderPair
{
public:
SynchronizeFolderPair(ProcessCallback& procCallback,
@@ -1062,9 +1062,9 @@ private:
const std::wstring txtWritingAttributes;
const std::wstring txtMovingFile;
};
+
//---------------------------------------------------------------------------------------------------------------
-namespace zen
-{
+
template <> inline
DeletionHandling& SynchronizeFolderPair::getDelHandling<LEFT_SIDE>() { return delHandlingLeft_; }
@@ -1072,7 +1072,6 @@ template <> inline
DeletionHandling& SynchronizeFolderPair::getDelHandling<RIGHT_SIDE>() { return delHandlingRight_; }
}
-
/*
__________________________
|Move algorithm, 0th pass|
@@ -1578,8 +1577,7 @@ void SynchronizeFolderPair::synchronizeFileInt(FileMapping& fileObj, SyncOperati
reportInfo(txtOverwritingFile, target);
FileAttrib newAttr;
- copyFileUpdatingTo<sideTrg>(fileObj,
- [&] //delete target at appropriate time
+ copyFileUpdatingTo<sideTrg>(fileObj, [&] //delete target at appropriate time
{
reportStatus(this->getDelHandling<sideTrg>().getTxtRemovingFile(), fileObj.getFullName<sideTrg>());
@@ -1876,6 +1874,51 @@ struct LessDependentDirectory : public std::binary_function<Zstring, Zstring, bo
}
};
*/
+
+template <SelectedSide side> //create base directories first (if not yet existing) -> no symlink or attribute copying!
+bool createBaseDirectory(BaseDirMapping& baseMap, ProcessCallback& callback) //nothrow; return false if fatal error occurred
+{
+ const Zstring dirname = beforeLast(baseMap.getBaseDirPf<side>(), FILE_NAME_SEPARATOR);
+ if (!dirname.empty())
+ {
+ if (baseMap.isExisting<side>()) //atomicity: do NOT check directory existence again!
+ {
+ //just convenience: exit sync right here instead of showing tons of error messages during file copy
+ return tryReportingError([&]
+ {
+ if (!dirExistsUpdating(dirname, false, callback))
+ throw FileError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtFileName(dirname))); //this should really be a "fatal error" if not recoverable
+ }, callback); //may throw in error-callback!
+ }
+ else //create target directory: user presumably ignored error "dir existing" in order to have it created automatically
+ {
+ bool temporaryNetworkDrop = false;
+ bool rv = tryReportingError([&]
+ {
+ try
+ {
+ makeNewDirectory(dirname, Zstring(), false); //FileError, ErrorTargetExisting
+ //a nice race-free check and set operation!
+ baseMap.setExisting<side>(true); //update our model!
+ }
+ catch (const ErrorTargetExisting&)
+ {
+ //TEMPORARY network drop: base directory not found during comparison, but reappears during synchronization
+ //=> sync-directions are based on false assumptions! Abort.
+ callback.reportFatalError(replaceCpy(_("Target folder %x already existing."), L"%x", fmtFileName(dirname)));
+ temporaryNetworkDrop = true;
+
+ //Is it possible we're catching a "false-positive" here, could FFS have created the directory indirectly after comparison?
+ // 1. deletion handling: recycler -> no, temp directory created only at first deletion
+ // 2. deletion handling: versioning -> "
+ // 3. log file creates containing folder -> no, log only created in batch mode, and only *before* comparison
+ }
+ }, callback); //may throw in error-callback!
+ return rv && !temporaryNetworkDrop;
+ }
+ }
+ return true;
+}
}
@@ -2086,8 +2129,8 @@ void zen::synchronize(const TimeComp& timeStamp,
}
return true;
};
- if (!checkSourceMissing(j->getBaseDirPf<LEFT_SIDE >(), j->wasExisting<LEFT_SIDE >()) ||
- !checkSourceMissing(j->getBaseDirPf<RIGHT_SIDE>(), j->wasExisting<RIGHT_SIDE>()))
+ if (!checkSourceMissing(j->getBaseDirPf<LEFT_SIDE >(), j->isExisting<LEFT_SIDE >()) ||
+ !checkSourceMissing(j->getBaseDirPf<RIGHT_SIDE>(), j->isExisting<RIGHT_SIDE>()))
continue;
//check if more than 50% of total number of files/dirs are to be created/overwritten/deleted
@@ -2250,50 +2293,8 @@ void zen::synchronize(const TimeComp& timeStamp,
continue;
//create base directories first (if not yet existing) -> no symlink or attribute copying!
- auto createDir = [&](const Zstring& baseDirPf, bool wasExisting) -> bool
- {
- const Zstring dirname = beforeLast(baseDirPf, FILE_NAME_SEPARATOR);
- if (!dirname.empty())
- {
- if (wasExisting) //atomicity: do NOT check directory existence again!
- {
- //just convenience: exit sync right here instead of showing tons of error messages during file copy
- return tryReportingError([&]
- {
- if (!dirExistsUpdating(dirname, false, callback))
- throw FileError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtFileName(dirname))); //this should really be a "fatal error"
- }, callback); //may throw in error-callback!
- }
- else //create target directory: user presumably ignored error "dir existing" in order to have it created automatically
- {
- bool temporaryNetworkDrop = false;
- bool rv = tryReportingError([&]
- {
- try
- {
- makeNewDirectory(dirname, Zstring(), false); //FileError, ErrorTargetExisting
- //a nice race-free check and set operation!
- }
- catch (const ErrorTargetExisting&)
- {
- //TEMPORARY network drop: base directory not found during comparison, but reappears during synchronization
- //=> sync-directions are based on false assumptions! Abort.
- callback.reportFatalError(replaceCpy(_("Target folder %x already existing."), L"%x", fmtFileName(baseDirPf)));
- temporaryNetworkDrop = true;
-
- //Is it possible we're catching a "false-positive" here, could FFS have created the directory indirectly after comparison?
- // 1. deletion handling: recycler -> no, temp directory created only at first deletion
- // 2. deletion handling: versioning -> "
- // 3. log file creates containing folder -> no, log only created in batch mode, and only *before* comparison
- }
- }, callback); //may throw in error-callback!
- return rv && !temporaryNetworkDrop;
- }
- }
- return true;
- };
- if (!createDir(j->getBaseDirPf<LEFT_SIDE >(), j->wasExisting<LEFT_SIDE >()) ||
- !createDir(j->getBaseDirPf<RIGHT_SIDE>(), j->wasExisting<RIGHT_SIDE>()))
+ if (!createBaseDirectory<LEFT_SIDE >(*j, callback) ||
+ !createBaseDirectory<RIGHT_SIDE>(*j, callback))
continue; //skip this folder pair
//------------------------------------------------------------------------------------------
diff --git a/synchronization.h b/synchronization.h
index 87ad963a..dceac496 100644
--- a/synchronization.h
+++ b/synchronization.h
@@ -78,7 +78,6 @@ struct FolderPairSyncCfg
};
std::vector<FolderPairSyncCfg> extractSyncCfg(const MainConfiguration& mainCfg);
-class SynchronizeFolderPair;
//FFS core routine:
void synchronize(const TimeComp& timeStamp,
diff --git a/ui/IFileDialog_Vista/IFileDialog_Vista.vcxproj b/ui/IFileDialog_Vista/IFileDialog_Vista.vcxproj
index f3dfcd23..1b84eade 100644
--- a/ui/IFileDialog_Vista/IFileDialog_Vista.vcxproj
+++ b/ui/IFileDialog_Vista/IFileDialog_Vista.vcxproj
@@ -19,6 +19,7 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
+ <ClCompile Include="..\..\zen\debug_memory_leaks.cpp" />
<ClCompile Include="dll_main.cpp" />
<ClCompile Include="ifile_dialog.cpp" />
</ItemGroup>
@@ -117,6 +118,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -150,7 +152,9 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
- <AdditionalLibraryDirectories></AdditionalLibraryDirectories>
+ <AdditionalLibraryDirectories>
+ </AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -171,6 +175,7 @@
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -185,6 +190,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -208,6 +214,7 @@
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -221,7 +228,9 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
- <AdditionalLibraryDirectories></AdditionalLibraryDirectories>
+ <AdditionalLibraryDirectories>
+ </AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
diff --git a/ui/Taskbar_Seven/Taskbar_Seven.vcxproj b/ui/Taskbar_Seven/Taskbar_Seven.vcxproj
index 37786768..a979a1a2 100644
--- a/ui/Taskbar_Seven/Taskbar_Seven.vcxproj
+++ b/ui/Taskbar_Seven/Taskbar_Seven.vcxproj
@@ -96,8 +96,9 @@
<WarningLevel>Level4</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
- <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <DisableSpecificWarnings>4100,4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -109,6 +110,7 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -129,8 +131,9 @@
<WarningLevel>Level4</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
- <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <DisableSpecificWarnings>4100,4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -142,6 +145,7 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -159,7 +163,7 @@
<WarningLevel>Level4</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
- <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <DisableSpecificWarnings>4100,4996</DisableSpecificWarnings>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
</ClCompile>
@@ -175,6 +179,7 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -195,7 +200,7 @@
<WarningLevel>Level4</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
- <DisableSpecificWarnings>4100</DisableSpecificWarnings>
+ <DisableSpecificWarnings>4100,4996</DisableSpecificWarnings>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
</ClCompile>
@@ -211,9 +216,11 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
+ <ClCompile Include="..\..\zen\debug_memory_leaks.cpp" />
<ClCompile Include="dll_main.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
diff --git a/ui/batch_config.cpp b/ui/batch_config.cpp
index d5aa7bc2..0463bd80 100644
--- a/ui/batch_config.cpp
+++ b/ui/batch_config.cpp
@@ -570,7 +570,7 @@ void BatchDialog::loadBatchFile(const std::vector<wxString>& filenames)
const wxString activeFile = filenames.size() == 1 ? filenames[0] : wxString();
if (activeFile.empty())
- SetTitle(_("Create a batch job"));
+ SetTitle(_("Save as batch job"));
else
SetTitle(activeFile);
diff --git a/ui/batch_status_handler.cpp b/ui/batch_status_handler.cpp
index b33b0d80..5a32e545 100644
--- a/ui/batch_status_handler.cpp
+++ b/ui/batch_status_handler.cpp
@@ -38,7 +38,7 @@ private:
virtual std::shared_ptr<TraverseCallback>
onDir (const Zchar* shortName, const Zstring& fullName) { return nullptr; } //DON'T traverse into subdirs
virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) { return LINK_SKIP; }
- virtual HandleError onError (const std::wstring& msg) { return ON_ERROR_IGNORE; } //errors are not really critical in this context
+ virtual HandleError onError (const std::wstring& msg) { assert(false); return ON_ERROR_IGNORE; } //errors are not really critical in this context
const Zstring prefix_;
std::vector<Zstring>& logfiles_;
@@ -100,6 +100,7 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress,
const TimeComp& timeStamp,
const Zstring& logfileDirectory, //may be empty
int logfilesCountLimit,
+ size_t lastSyncsLogFileSizeMax,
const xmlAccess::OnError handleError,
const SwitchToGui& switchBatchToGui, //functionality to change from batch mode to GUI mode
FfsReturnCode& returnCode,
@@ -108,6 +109,7 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress,
switchBatchToGui_(switchBatchToGui),
showFinalResults(showProgress), //=> exit immediately or wait when finished
switchToGuiRequested(false),
+ lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax),
handleError_(handleError),
returnCode_(returnCode),
syncStatusFrame(*this, *this, nullptr, showProgress, jobName, execWhenFinished, execFinishedHistory),
@@ -129,7 +131,7 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress,
totalTime.Start(); //measure total time
//if (logFile)
- // ::wxSetEnv(L"logfile", utfCvrtTo<wxString>(logFile->getFilename())); -> requires a command line interpreter to take advantage of
+ // ::wxSetEnv(L"logfile", utfCvrtTo<wxString>(logFile->getFilename()));
}
@@ -168,24 +170,35 @@ BatchStatusHandler::~BatchStatusHandler()
errorLog.logMsg(finalStatus, TYPE_INFO);
}
- const Utf8String logStream = generateLogStream(errorLog, jobName_, finalStatus,
- getObjectsCurrent(PHASE_SYNCHRONIZING), getDataCurrent(PHASE_SYNCHRONIZING),
- getObjectsTotal (PHASE_SYNCHRONIZING), getDataTotal (PHASE_SYNCHRONIZING), totalTime.Time() / 1000);
+ const SummaryInfo summary =
+ {
+ jobName_,
+ finalStatus,
+ getObjectsCurrent(PHASE_SYNCHRONIZING), getDataCurrent(PHASE_SYNCHRONIZING),
+ getObjectsTotal (PHASE_SYNCHRONIZING), getDataTotal (PHASE_SYNCHRONIZING),
+ totalTime.Time() / 1000
+ };
+
//print the results list: logfile
if (logFile.get())
{
+ //saving log file below may take a *long* time, so report (without logging)
try
{
- if (!logStream.empty())
- logFile->write(&*logStream.begin(), logStream.size()); //throw FileError
+ reportStatus(replaceCpy(_("Saving log file %x"), L"%x", fmtFileName(logFile->getFilename()))); //throw?
+ forceUiRefresh(); //
+ }
+ catch (...) {}
+ try
+ {
+ saveLogToFile(summary, errorLog, *logFile); //throw FileError
}
catch (FileError&) {}
-
logFile.reset(); //close file now: user may do something with it in "on completion"
}
try
{
- saveToLastSyncsLog(logStream); //throw FileError
+ saveToLastSyncsLog(summary, errorLog, lastSyncsLogFileSizeMax_); //throw FileError
}
catch (FileError&) {}
diff --git a/ui/batch_status_handler.h b/ui/batch_status_handler.h
index d83f8acc..884f22e5 100644
--- a/ui/batch_status_handler.h
+++ b/ui/batch_status_handler.h
@@ -29,6 +29,7 @@ public:
const zen::TimeComp& timeStamp,
const Zstring& logfileDirectory,
int logfilesCountLimit, //0: logging inactive; < 0: no limit
+ size_t lastSyncsLogFileSizeMax,
const xmlAccess::OnError handleError,
const zen::SwitchToGui& switchBatchToGui, //functionality to change from batch mode to GUI mode
zen::FfsReturnCode& returnCode,
@@ -51,6 +52,7 @@ private:
const zen::SwitchToGui& switchBatchToGui_; //functionality to change from batch mode to GUI mode
bool showFinalResults;
bool switchToGuiRequested;
+ const size_t lastSyncsLogFileSizeMax_;
xmlAccess::OnError handleError_;
zen::ErrorLog errorLog; //list of non-resolved errors and warnings
zen::FfsReturnCode& returnCode_;
diff --git a/ui/column_attr.h b/ui/column_attr.h
index 23cff92c..8152c01c 100644
--- a/ui/column_attr.h
+++ b/ui/column_attr.h
@@ -44,8 +44,8 @@ std::vector<ColumnAttributeRim> getDefaultColumnAttributesLeft()
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_SIZE, 80, 0, true));
attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, 0, false));
return attr;
}
@@ -57,8 +57,8 @@ std::vector<ColumnAttributeRim> getDefaultColumnAttributesRight()
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_SIZE, 80, 0, true));
attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, 0, false));
return attr;
}
@@ -72,7 +72,6 @@ enum ColumnTypeMiddle
COL_TYPE_BORDER
};
-
//------------------------------------------------------------------
enum ColumnTypeNavi
diff --git a/ui/custom_grid.cpp b/ui/custom_grid.cpp
index e7152905..975dca5a 100644
--- a/ui/custom_grid.cpp
+++ b/ui/custom_grid.cpp
@@ -11,6 +11,7 @@
#include <zen/file_error.h>
#include <zen/basic_math.h>
#include <zen/format_unit.h>
+#include <zen/scope_guard.h>
#include <wx+/tooltip.h>
#include <wx+/string_conv.h>
#include <wx+/rtl.h>
@@ -71,7 +72,7 @@ void refreshCell(Grid& grid, size_t row, ColumnType colType)
}
-std::pair<ptrdiff_t, ptrdiff_t> getVisibleRows(Grid& grid) //returns range [from, to)
+std::pair<ptrdiff_t, ptrdiff_t> getVisibleRows(const Grid& grid) //returns range [from, to)
{
const wxSize clientSize = grid.getMainWin().GetClientSize();
if (clientSize.GetHeight() > 0)
@@ -79,15 +80,15 @@ std::pair<ptrdiff_t, ptrdiff_t> getVisibleRows(Grid& grid) //returns range [from
wxPoint topLeft = grid.CalcUnscrolledPosition(wxPoint(0, 0));
wxPoint bottom = grid.CalcUnscrolledPosition(wxPoint(0, clientSize.GetHeight() - 1));
- ptrdiff_t rowFrom = grid.getRowAtPos(topLeft.y); //returns < 0 if column not found; absolute coordinates!
+ const ptrdiff_t rowCount = grid.getRowCount();
+ const ptrdiff_t rowFrom = grid.getRowAtPos(topLeft.y); //return -1 for invalid position, rowCount if out of range
if (rowFrom >= 0)
{
- ptrdiff_t rowEnd = grid.getRowAtPos(bottom.y); //returns < 0 if column not found; absolute coordinates!
- if (rowEnd < 0)
- rowEnd = grid.getRowCount();
+ const ptrdiff_t rowTo = grid.getRowAtPos(bottom.y);
+ if (0 <= rowTo && rowTo < rowCount)
+ return std::make_pair(rowFrom, rowTo + 1);
else
- ++rowEnd;
- return std::make_pair(rowFrom, rowEnd);
+ return std::make_pair(rowFrom, rowCount);
}
}
return std::make_pair(0, 0);
@@ -448,7 +449,6 @@ private:
static const int CELL_BORDER = 2;
-
virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, size_t row, ColumnType colType)
{
wxRect rectTmp = rect;
@@ -784,20 +784,20 @@ public:
if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE &&
row < refGrid().getRowCount())
{
- refGrid().clearSelection();
- dragSelection.reset(new std::pair<size_t, BlockPosition>(row, mousePosToBlock(clientPos, row)));
+ refGrid().clearSelection(false); //don't emit event, prevent recursion!
+ dragSelection = make_unique<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
+ void onSelectEnd(size_t rowFirst, size_t rowLast) //we cannot reuse row from "onSelectBegin": if user is holding shift, this may now be in the middle of the range!
{
- refGrid().clearSelection();
+ refGrid().clearSelection(false); //don't emit event, prevent recursion!
//issue custom event
if (dragSelection)
{
- if (rowFrom < refGrid().getRowCount() &&
- rowTo < refGrid().getRowCount()) //row is -1 on capture lost!
+ if (rowFirst < rowLast && //may be empty? probably not in this context
+ rowLast <= refGrid().getRowCount())
{
if (wxEvtHandler* evtHandler = refGrid().GetEventHandler())
switch (dragSelection->second)
@@ -807,25 +807,25 @@ public:
const FileSystemObject* fsObj = getRawData(dragSelection->first);
const bool setIncluded = fsObj ? !fsObj->isActive() : true;
- CheckRowsEvent evt(rowFrom, rowTo, setIncluded);
+ CheckRowsEvent evt(rowFirst, rowLast, setIncluded);
evtHandler->ProcessEvent(evt);
}
break;
case BLOCKPOS_LEFT:
{
- SyncDirectionEvent evt(rowFrom, rowTo, SYNC_DIR_LEFT);
+ SyncDirectionEvent evt(rowFirst, rowLast, SYNC_DIR_LEFT);
evtHandler->ProcessEvent(evt);
}
break;
case BLOCKPOS_MIDDLE:
{
- SyncDirectionEvent evt(rowFrom, rowTo, SYNC_DIR_NONE);
+ SyncDirectionEvent evt(rowFirst, rowLast, SYNC_DIR_NONE);
evtHandler->ProcessEvent(evt);
}
break;
case BLOCKPOS_RIGHT:
{
- SyncDirectionEvent evt(rowFrom, rowTo, SYNC_DIR_RIGHT);
+ SyncDirectionEvent evt(rowFirst, rowLast, SYNC_DIR_RIGHT);
evtHandler->ProcessEvent(evt);
}
break;
@@ -844,12 +844,13 @@ public:
}
else
{
- if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE)
+ if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE &&
+ row < refGrid().getRowCount())
{
if (highlight) //refresh old highlight
refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE));
- highlight.reset(new std::pair<size_t, BlockPosition>(row, mousePosToBlock(clientPos, row)));
+ highlight = make_unique<std::pair<size_t, BlockPosition>>(row, mousePosToBlock(clientPos, row));
refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE));
//show custom tooltip
@@ -1193,7 +1194,8 @@ public:
GridDataMiddle& provMiddle,
GridDataRight& provRight) :
gridL_(gridL), gridC_(gridC), gridR_(gridR), scrollMaster(nullptr),
- provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight)
+ provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight),
+ scrollbarUpdatePending(false)
{
gridL_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnL), nullptr, this);
gridR_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnR), nullptr, this);
@@ -1238,6 +1240,8 @@ public:
Connect(EVENT_ALIGN_SCROLLBARS, wxEventHandler(GridEventManager::onAlignScrollBars), NULL, this);
}
+ ~GridEventManager() { assert(!scrollbarUpdatePending); }
+
private:
void onCenterSelectBegin(GridClickEvent& event)
{
@@ -1248,15 +1252,14 @@ private:
void onCenterSelectEnd(GridRangeSelectEvent& event)
{
- 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_);
+ provMiddle_.onSelectEnd(event.rowFirst_, event.rowLast_);
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!
+ const ptrdiff_t row = gridC_.getRowAtPos(topLeftAbs.y); //return -1 for invalid position, rowCount if one past the end
if (auto colInfo = gridC_.getColumnAtPos(topLeftAbs.x)) //(column type, component position)
{
//redirect mouse movement to middle grid component
@@ -1277,7 +1280,7 @@ private:
void onGridSelection(const Grid& grid, Grid& other)
{
if (!wxGetKeyState(WXK_CONTROL)) //clear other grid unless user is holding CTRL
- other.clearSelection();
+ other.clearSelection(false); //don't emit event, prevent recursion!
}
void onKeyDownL(wxKeyEvent& event) { onKeyDown(event, gridL_); }
@@ -1397,13 +1400,22 @@ private:
//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!
+
+ //perf note: send one async event at most, else they may accumulate and create perf issues, see grid.cpp
+ if (!scrollbarUpdatePending)
+ {
+ scrollbarUpdatePending = true;
+ wxCommandEvent alignEvent(EVENT_ALIGN_SCROLLBARS);
+ AddPendingEvent(alignEvent); //waits until next idle event - may take up to a second if the app is busy on wxGTK!
+ }
}
void onAlignScrollBars(wxEvent& event)
{
- auto needsHorizontalScrollbars = [](Grid& grid) -> bool
+ ZEN_ON_SCOPE_EXIT(scrollbarUpdatePending = false);
+ assert(scrollbarUpdatePending);
+
+ auto needsHorizontalScrollbars = [](const Grid& grid) -> bool
{
const wxWindow& mainWin = grid.getMainWin();
return mainWin.GetVirtualSize().GetWidth() > mainWin.GetClientSize().GetWidth();
@@ -1433,6 +1445,8 @@ private:
GridDataLeft& provLeft_;
GridDataMiddle& provMiddle_;
GridDataRight& provRight_;
+
+ bool scrollbarUpdatePending;
};
}
@@ -1541,6 +1555,7 @@ private:
};
}
+
void gridview::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, bool show, IconBuffer::IconSize sz)
{
auto* provLeft = dynamic_cast<GridDataLeft*>(gridLeft .getDataProvider());
@@ -1548,7 +1563,7 @@ void gridview::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, boo
if (provLeft && provRight)
{
- int newRowHeight = 0;
+ int iconHeight = 0;
if (show)
{
auto iconMgr = std::make_shared<IconManager>(sz);
@@ -1556,14 +1571,17 @@ void gridview::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, boo
provLeft ->setIconManager(iconMgr);
provRight->setIconManager(iconMgr);
- newRowHeight = iconMgr->iconBuffer.getSize() + 1; //+ 1 for line between rows
+ iconHeight = iconMgr->iconBuffer.getSize();
}
else
{
provLeft ->setIconManager(nullptr);
provRight->setIconManager(nullptr);
- newRowHeight = IconBuffer(IconBuffer::SIZE_SMALL).getSize() + 1; //+ 1 for line between rows
+ iconHeight = IconBuffer(IconBuffer::SIZE_SMALL).getSize();
}
+
+ const int newRowHeight = std::max(iconHeight, gridLeft.getMainWin().GetCharHeight()) + 1; //add some space
+
gridLeft .setRowHeight(newRowHeight);
gridCenter.setRowHeight(newRowHeight);
gridRight .setRowHeight(newRowHeight);
diff --git a/ui/custom_grid.h b/ui/custom_grid.h
index b5a4cce1..6381c8c0 100644
--- a/ui/custom_grid.h
+++ b/ui/custom_grid.h
@@ -48,22 +48,22 @@ extern const wxEventType EVENT_GRID_SYNC_DIRECTION;
struct CheckRowsEvent : public wxCommandEvent
{
- CheckRowsEvent(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool setIncluded) : wxCommandEvent(EVENT_GRID_CHECK_ROWS), rowFrom_(rowFrom), rowTo_(rowTo), setIncluded_(setIncluded) {}
+ CheckRowsEvent(size_t rowFirst, size_t rowLast, bool setIncluded) : wxCommandEvent(EVENT_GRID_CHECK_ROWS), rowFirst_(rowFirst), rowLast_(rowLast), setIncluded_(setIncluded) { assert(rowFirst <= rowLast); }
virtual wxEvent* Clone() const { return new CheckRowsEvent(*this); }
- const ptrdiff_t rowFrom_;
- const ptrdiff_t rowTo_;
+ const size_t rowFirst_; //selected range: [rowFirst_, rowLast_)
+ const size_t rowLast_; //range is empty when clearing selection
const bool setIncluded_;
};
struct SyncDirectionEvent : public wxCommandEvent
{
- SyncDirectionEvent(ptrdiff_t rowFrom, ptrdiff_t rowTo, SyncDirection direction) : wxCommandEvent(EVENT_GRID_SYNC_DIRECTION), rowFrom_(rowFrom), rowTo_(rowTo), direction_(direction) {}
+ SyncDirectionEvent(size_t rowFirst, size_t rowLast, SyncDirection direction) : wxCommandEvent(EVENT_GRID_SYNC_DIRECTION), rowFirst_(rowFirst), rowLast_(rowLast), direction_(direction) { assert(rowFirst <= rowLast); }
virtual wxEvent* Clone() const { return new SyncDirectionEvent(*this); }
- const ptrdiff_t rowFrom_;
- const ptrdiff_t rowTo_;
+ const size_t rowFirst_; //see CheckRowsEvent
+ const size_t rowLast_; //
const SyncDirection direction_;
};
diff --git a/ui/dir_name.cpp b/ui/dir_name.cpp
index 7d4f7a31..a1a00d74 100644
--- a/ui/dir_name.cpp
+++ b/ui/dir_name.cpp
@@ -175,7 +175,7 @@ void DirectoryName<NameControl>::OnSelectDir(wxCommandEvent& event)
}
}
- //wxDirDialog internally uses lame looking SHBrowseForFolder(); Better use IFileDialog() instead! (remembers size and position!)
+ //wxDirDialog internally uses lame-looking SHBrowseForFolder(); we better use IFileDialog() instead! (remembers size and position!)
std::unique_ptr<wxString> newFolder;
#ifdef FFS_WIN
if (vistaOrLater())
diff --git a/ui/gui_generated.cpp b/ui/gui_generated.cpp
index d132d748..6619b646 100644
--- a/ui/gui_generated.cpp
+++ b/ui/gui_generated.cpp
@@ -41,6 +41,9 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_menuItemSaveAs = new wxMenuItem( m_menuFile, wxID_SAVEAS, wxString( _("Save &as...") ) , wxEmptyString, wxITEM_NORMAL );
m_menuFile->Append( m_menuItemSaveAs );
+ m_menuItem7 = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("Save as &batch job...") ) , wxEmptyString, wxITEM_NORMAL );
+ m_menuFile->Append( m_menuItem7 );
+
m_menuFile->AppendSeparator();
m_menuItem10 = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("1. &Compare") ) + wxT('\t') + wxT("F5"), wxEmptyString, wxITEM_NORMAL );
@@ -76,9 +79,6 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_menuItemGlobSett = new wxMenuItem( m_menuAdvanced, wxID_PREFERENCES, wxString( _("&Global settings...") ) , wxEmptyString, wxITEM_NORMAL );
m_menuAdvanced->Append( m_menuItemGlobSett );
- m_menuItem7 = new wxMenuItem( m_menuAdvanced, wxID_ANY, wxString( _("&Create batch job...") ) , wxEmptyString, wxITEM_NORMAL );
- m_menuAdvanced->Append( m_menuItem7 );
-
wxMenuItem* m_menuItem5;
m_menuItem5 = new wxMenuItem( m_menuAdvanced, wxID_ANY, wxString( _("&Export file list...") ) , wxEmptyString, wxITEM_NORMAL );
m_menuAdvanced->Append( m_menuItem5 );
@@ -346,7 +346,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
bSizer1601->Add( bSizer91, 0, wxEXPAND, 5 );
m_scrolledWindowFolderPairs = new wxScrolledWindow( m_panelDirectoryPairs, wxID_ANY, wxDefaultPosition, wxSize( -1,-1 ), wxHSCROLL|wxVSCROLL );
- m_scrolledWindowFolderPairs->SetScrollRate( 5, 5 );
+ m_scrolledWindowFolderPairs->SetScrollRate( 10, 10 );
m_scrolledWindowFolderPairs->SetMinSize( wxSize( -1,0 ) );
bSizerAddFolderPairs = new wxBoxSizer( wxVERTICAL );
@@ -539,7 +539,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
bSizer151 = new wxBoxSizer( wxHORIZONTAL );
m_bpButtonLoad = new wxBitmapButton( m_panelConfig, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW );
- m_bpButtonLoad->SetToolTip( _("Open...") );
+ m_bpButtonLoad->SetToolTip( _("Open") );
bSizer151->Add( m_bpButtonLoad, 0, wxALIGN_CENTER_VERTICAL, 5 );
@@ -548,6 +548,11 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
bSizer151->Add( m_bpButtonSave, 0, wxALIGN_CENTER_VERTICAL, 5 );
+ m_bpButtonBatchJob = new wxBitmapButton( m_panelConfig, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW );
+ m_bpButtonBatchJob->SetToolTip( _("Save as batch job") );
+
+ bSizer151->Add( m_bpButtonBatchJob, 0, wxALIGN_CENTER_VERTICAL, 5 );
+
bSizerConfig->Add( bSizer151, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
@@ -846,11 +851,11 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
this->Connect( m_menuItemLoad->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigLoad ) );
this->Connect( m_menuItemSave->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ) );
this->Connect( m_menuItemSaveAs->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigSaveAs ) );
+ this->Connect( m_menuItem7->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ) );
this->Connect( m_menuItem10->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnCompare ) );
this->Connect( m_menuItem11->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnStartSync ) );
this->Connect( m_menuItem4->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuQuit ) );
this->Connect( m_menuItemGlobSett->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuGlobalSettings ) );
- this->Connect( m_menuItem7->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ) );
this->Connect( m_menuItem5->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuExportFileList ) );
this->Connect( m_menuItemManual->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnShowHelp ) );
this->Connect( m_menuItemCheckVer->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuCheckVersion ) );
@@ -864,6 +869,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_bpButtonSwapSides->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSwapSides ), NULL, this );
m_bpButtonLoad->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigLoad ), NULL, this );
m_bpButtonSave->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), NULL, this );
+ m_bpButtonBatchJob->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ), NULL, this );
m_listBoxHistory->Connect( wxEVT_CHAR, wxKeyEventHandler( MainDialogGenerated::OnCfgHistoryKeyEvent ), NULL, this );
m_listBoxHistory->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistory ), NULL, this );
m_listBoxHistory->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistoryDoubleClick ), NULL, this );
@@ -893,11 +899,11 @@ MainDialogGenerated::~MainDialogGenerated()
this->Disconnect( wxID_OPEN, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigLoad ) );
this->Disconnect( wxID_SAVE, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ) );
this->Disconnect( wxID_SAVEAS, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigSaveAs ) );
+ this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ) );
this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnCompare ) );
this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnStartSync ) );
this->Disconnect( wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuQuit ) );
this->Disconnect( wxID_PREFERENCES, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuGlobalSettings ) );
- this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ) );
this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuExportFileList ) );
this->Disconnect( wxID_HELP, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnShowHelp ) );
this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuCheckVersion ) );
@@ -911,6 +917,7 @@ MainDialogGenerated::~MainDialogGenerated()
m_bpButtonSwapSides->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSwapSides ), NULL, this );
m_bpButtonLoad->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigLoad ), NULL, this );
m_bpButtonSave->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), NULL, this );
+ m_bpButtonBatchJob->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ), NULL, this );
m_listBoxHistory->Disconnect( wxEVT_CHAR, wxKeyEventHandler( MainDialogGenerated::OnCfgHistoryKeyEvent ), NULL, this );
m_listBoxHistory->Disconnect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistory ), NULL, this );
m_listBoxHistory->Disconnect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistoryDoubleClick ), NULL, this );
@@ -1184,31 +1191,31 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS
wxBoxSizer* bSizer87;
bSizer87 = new wxBoxSizer( wxHORIZONTAL );
- m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER|wxTAB_TRAVERSAL );
+ m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panel8->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) );
wxBoxSizer* bSizer72;
bSizer72 = new wxBoxSizer( wxHORIZONTAL );
- m_bitmap27 = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,40 ), 0 );
+ m_bitmap27 = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
bSizer72->Add( m_bitmap27, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
m_staticText56 = new wxStaticText( m_panel8, wxID_ANY, _("Batch job"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText56->Wrap( -1 );
- m_staticText56->SetFont( wxFont( 14, 70, 90, 92, false, wxEmptyString ) );
+ m_staticText56->SetFont( wxFont( 12, 70, 90, 92, false, wxEmptyString ) );
m_staticText56->SetForegroundColour( wxColour( 0, 0, 0 ) );
bSizer72->Add( m_staticText56, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
+ m_staticText44 = new wxStaticText( m_panel8, wxID_ANY, _("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"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText44->Wrap( 480 );
+ bSizer72->Add( m_staticText44, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+
m_panel8->SetSizer( bSizer72 );
m_panel8->Layout();
bSizer72->Fit( m_panel8 );
- bSizer87->Add( m_panel8, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
-
- m_staticText44 = new wxStaticText( this, wxID_ANY, _("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"), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText44->Wrap( 480 );
- bSizer87->Add( m_staticText44, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
+ bSizer87->Add( m_panel8, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 );
m_bpButtonHelp = new wxBitmapButton( this, wxID_HELP, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW );
m_bpButtonHelp->SetToolTip( _("Help") );
@@ -1216,7 +1223,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS
bSizer87->Add( m_bpButtonHelp, 0, wxALIGN_CENTER_VERTICAL, 5 );
- bSizer54->Add( bSizer87, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT, 5 );
+ bSizer54->Add( bSizer87, 0, wxALIGN_CENTER_HORIZONTAL|wxEXPAND, 5 );
m_notebook1 = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
m_panelOverview = new wxPanel( m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
@@ -1305,7 +1312,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS
bSizer120->Add( 0, 5, 0, 0, 5 );
m_scrolledWindow6 = new wxScrolledWindow( m_panelOverview, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL );
- m_scrolledWindow6->SetScrollRate( 5, 5 );
+ m_scrolledWindow6->SetScrollRate( 10, 10 );
wxBoxSizer* bSizer141;
bSizer141 = new wxBoxSizer( wxVERTICAL );
@@ -1533,7 +1540,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS
bSizer117->Fit( m_panelBatchSettings );
m_notebook1->AddPage( m_panelBatchSettings, _("Batch settings"), false );
- bSizer54->Add( m_notebook1, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 );
+ bSizer54->Add( m_notebook1, 1, wxEXPAND|wxRIGHT|wxLEFT, 5 );
wxBoxSizer* bSizer68;
bSizer68 = new wxBoxSizer( wxHORIZONTAL );
@@ -2505,6 +2512,12 @@ LogControlGenerated::LogControlGenerated( wxWindow* parent, wxWindowID id, const
{
this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
+ wxBoxSizer* bSizer179;
+ bSizer179 = new wxBoxSizer( wxVERTICAL );
+
+ m_staticline12 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer179->Add( m_staticline12, 0, wxEXPAND, 5 );
+
wxBoxSizer* bSizer153;
bSizer153 = new wxBoxSizer( wxHORIZONTAL );
@@ -2526,14 +2539,17 @@ LogControlGenerated::LogControlGenerated( wxWindow* parent, wxWindowID id, const
m_staticline13 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL );
bSizer153->Add( m_staticline13, 0, wxEXPAND, 5 );
- m_textCtrlInfo = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1,-1 ), wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER );
- m_textCtrlInfo->SetMaxLength( 0 );
- bSizer153->Add( m_textCtrlInfo, 1, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 );
+ m_gridMessages = new zen::Grid( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL );
+ m_gridMessages->SetScrollRate( 5, 5 );
+ bSizer153->Add( m_gridMessages, 1, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 );
+
+ bSizer179->Add( bSizer153, 1, wxEXPAND, 5 );
- this->SetSizer( bSizer153 );
+
+ this->SetSizer( bSizer179 );
this->Layout();
- bSizer153->Fit( this );
+ bSizer179->Fit( this );
// Connect Events
m_bpButtonErrors->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( LogControlGenerated::OnErrors ), NULL, this );
@@ -2580,7 +2596,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS
m_build = new wxStaticText( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
m_build->Wrap( -1 );
- bSizer53->Add( m_build, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 );
+ bSizer53->Add( m_build, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
bSizer53->Add( 0, 5, 0, 0, 5 );
@@ -2730,7 +2746,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS
bSizer53->Add( m_panel40, 0, wxEXPAND|wxBOTTOM|wxALIGN_CENTER_HORIZONTAL, 5 );
m_scrolledWindowTranslators = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition, wxSize( -1,-1 ), wxDOUBLE_BORDER|wxHSCROLL|wxVSCROLL );
- m_scrolledWindowTranslators->SetScrollRate( 5, 5 );
+ m_scrolledWindowTranslators->SetScrollRate( 10, 10 );
m_scrolledWindowTranslators->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
m_scrolledWindowTranslators->SetMinSize( wxSize( -1,180 ) );
@@ -3076,45 +3092,39 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizer70 = new wxBoxSizer( wxHORIZONTAL );
bSizer70->SetMinSize( wxSize( 550,-1 ) );
-
- bSizer70->Add( 0, 0, 1, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 );
-
- m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER|wxTAB_TRAVERSAL );
+ m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panel8->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) );
wxBoxSizer* bSizer72;
bSizer72 = new wxBoxSizer( wxHORIZONTAL );
- m_bitmap26 = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,40 ), 0 );
+ m_bitmap26 = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
bSizer72->Add( m_bitmap26, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
m_staticTexHeader = new wxStaticText( m_panel8, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTexHeader->Wrap( -1 );
- m_staticTexHeader->SetFont( wxFont( 14, 70, 90, 92, false, wxEmptyString ) );
+ m_staticTexHeader->SetFont( wxFont( 12, 70, 90, 92, false, wxEmptyString ) );
m_staticTexHeader->SetForegroundColour( wxColour( 0, 0, 0 ) );
bSizer72->Add( m_staticTexHeader, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
+ m_staticText44 = new wxStaticText( m_panel8, wxID_ANY, _("Only files that match all filter settings will be synchronized.\nNote: File names must be relative to base directories!"), wxDefaultPosition, wxSize( -1,-1 ), 0 );
+ m_staticText44->Wrap( 480 );
+ bSizer72->Add( m_staticText44, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+
m_panel8->SetSizer( bSizer72 );
m_panel8->Layout();
bSizer72->Fit( m_panel8 );
- bSizer70->Add( m_panel8, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
-
- m_staticText44 = new wxStaticText( this, wxID_ANY, _("Only files that match all filter settings will be synchronized.\nNote: File names must be relative to base directories!"), wxDefaultPosition, wxSize( -1,-1 ), 0 );
- m_staticText44->Wrap( 480 );
- bSizer70->Add( m_staticText44, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+ bSizer70->Add( m_panel8, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 );
m_bpButtonHelp = new wxBitmapButton( this, wxID_HELP, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW );
m_bpButtonHelp->SetToolTip( _("Help") );
- bSizer70->Add( m_bpButtonHelp, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
-
+ bSizer70->Add( m_bpButtonHelp, 0, wxALIGN_CENTER_VERTICAL, 5 );
- bSizer70->Add( 0, 0, 1, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 );
-
- bSizer21->Add( bSizer70, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT|wxEXPAND, 5 );
+ bSizer21->Add( bSizer70, 0, wxALIGN_CENTER_HORIZONTAL|wxEXPAND, 5 );
wxBoxSizer* bSizer159;
bSizer159 = new wxBoxSizer( wxHORIZONTAL );
@@ -3253,7 +3263,7 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizer159->Add( bSizer160, 0, wxEXPAND, 5 );
- bSizer21->Add( bSizer159, 1, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 );
+ bSizer21->Add( bSizer159, 1, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxRIGHT|wxLEFT, 5 );
wxBoxSizer* bSizer22;
bSizer22 = new wxBoxSizer( wxHORIZONTAL );
@@ -3323,33 +3333,27 @@ GlobalSettingsDlgGenerated::GlobalSettingsDlgGenerated( wxWindow* parent, wxWind
wxBoxSizer* bSizer95;
bSizer95 = new wxBoxSizer( wxVERTICAL );
- wxBoxSizer* bSizer86;
- bSizer86 = new wxBoxSizer( wxHORIZONTAL );
-
- m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER|wxTAB_TRAVERSAL );
+ m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panel8->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) );
wxBoxSizer* bSizer72;
bSizer72 = new wxBoxSizer( wxHORIZONTAL );
- m_bitmapSettings = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,40 ), 0 );
+ m_bitmapSettings = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
bSizer72->Add( m_bitmapSettings, 0, wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 );
m_staticText56 = new wxStaticText( m_panel8, wxID_ANY, _("Global settings"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText56->Wrap( -1 );
- m_staticText56->SetFont( wxFont( 14, 70, 90, 92, false, wxEmptyString ) );
+ m_staticText56->SetFont( wxFont( 12, 70, 90, 92, false, wxEmptyString ) );
m_staticText56->SetForegroundColour( wxColour( 0, 0, 0 ) );
- bSizer72->Add( m_staticText56, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
+ bSizer72->Add( m_staticText56, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
m_panel8->SetSizer( bSizer72 );
m_panel8->Layout();
bSizer72->Fit( m_panel8 );
- bSizer86->Add( m_panel8, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
-
-
- bSizer95->Add( bSizer86, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT, 5 );
+ bSizer95->Add( m_panel8, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND|wxALL, 5 );
wxStaticBoxSizer* sbSizer23;
sbSizer23 = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxEmptyString ), wxVERTICAL );
diff --git a/ui/gui_generated.h b/ui/gui_generated.h
index 14e30f75..b14e0034 100644
--- a/ui/gui_generated.h
+++ b/ui/gui_generated.h
@@ -76,12 +76,12 @@ class MainDialogGenerated : public wxFrame
wxMenuItem* m_menuItemLoad;
wxMenuItem* m_menuItemSave;
wxMenuItem* m_menuItemSaveAs;
+ wxMenuItem* m_menuItem7;
wxMenuItem* m_menuItem10;
wxMenuItem* m_menuItem11;
wxMenu* m_menuAdvanced;
wxMenu* m_menuLanguages;
wxMenuItem* m_menuItemGlobSett;
- wxMenuItem* m_menuItem7;
wxMenu* m_menuHelp;
wxMenuItem* m_menuItemManual;
wxMenuItem* m_menuItemCheckVer;
@@ -134,6 +134,7 @@ class MainDialogGenerated : public wxFrame
wxBoxSizer* bSizerConfig;
wxBitmapButton* m_bpButtonLoad;
wxBitmapButton* m_bpButtonSave;
+ wxBitmapButton* m_bpButtonBatchJob;
wxListBox* m_listBoxHistory;
wxPanel* m_panelFilter;
wxBitmapButton* m_bpButtonFilter;
@@ -177,11 +178,11 @@ class MainDialogGenerated : public wxFrame
virtual void OnConfigLoad( wxCommandEvent& event ) { event.Skip(); }
virtual void OnConfigSave( wxCommandEvent& event ) { event.Skip(); }
virtual void OnConfigSaveAs( wxCommandEvent& event ) { event.Skip(); }
+ virtual void OnMenuBatchJob( wxCommandEvent& event ) { event.Skip(); }
virtual void OnCompare( wxCommandEvent& event ) { event.Skip(); }
virtual void OnStartSync( wxCommandEvent& event ) { event.Skip(); }
virtual void OnMenuQuit( wxCommandEvent& event ) { event.Skip(); }
virtual void OnMenuGlobalSettings( wxCommandEvent& event ) { event.Skip(); }
- virtual void OnMenuBatchJob( wxCommandEvent& event ) { event.Skip(); }
virtual void OnMenuExportFileList( wxCommandEvent& event ) { event.Skip(); }
virtual void OnShowHelp( wxCommandEvent& event ) { event.Skip(); }
virtual void OnMenuCheckVersion( wxCommandEvent& event ) { event.Skip(); }
@@ -366,7 +367,7 @@ class BatchDlgGenerated : public wxDialog
wxBitmapButton* m_bpButtonAltSyncCfg;
FolderHistoryBox* m_comboBoxLogfileDir;
- BatchDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Create a batch job"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER );
+ BatchDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Save as batch job"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER );
~BatchDlgGenerated();
};
@@ -593,11 +594,12 @@ class LogControlGenerated : public wxPanel
private:
protected:
+ wxStaticLine* m_staticline12;
ToggleButton* m_bpButtonErrors;
ToggleButton* m_bpButtonWarnings;
ToggleButton* m_bpButtonInfo;
wxStaticLine* m_staticline13;
- wxTextCtrl* m_textCtrlInfo;
+ zen::Grid* m_gridMessages;
// Virtual event handlers, overide them in your derived class
virtual void OnErrors( wxCommandEvent& event ) { event.Skip(); }
diff --git a/ui/gui_status_handler.cpp b/ui/gui_status_handler.cpp
index 120eab39..c8311811 100644
--- a/ui/gui_status_handler.cpp
+++ b/ui/gui_status_handler.cpp
@@ -175,16 +175,18 @@ void CompareStatusHandler::abortThisProcess()
requestAbortion(); //just make sure...
throw GuiAbortProcess();
}
-//########################################################################################################
+//########################################################################################################
SyncStatusHandler::SyncStatusHandler(MainDialog* parentDlg,
+ size_t lastSyncsLogFileSizeMax,
OnGuiError handleError,
const std::wstring& jobName,
const std::wstring& execWhenFinished,
std::vector<std::wstring>& execFinishedHistory) :
parentDlg_(parentDlg),
syncStatusFrame(*this, *this, parentDlg, true, jobName, execWhenFinished, execFinishedHistory),
+ lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax),
handleError_(handleError),
jobName_(jobName)
{
@@ -225,12 +227,17 @@ SyncStatusHandler::~SyncStatusHandler()
errorLog.logMsg(finalStatus, TYPE_INFO);
}
- const Utf8String logStream = generateLogStream(errorLog, jobName_, finalStatus,
- getObjectsCurrent(PHASE_SYNCHRONIZING), getDataCurrent(PHASE_SYNCHRONIZING),
- getObjectsTotal (PHASE_SYNCHRONIZING), getDataTotal (PHASE_SYNCHRONIZING), totalTime.Time() / 1000);
+ const SummaryInfo summary =
+ {
+ jobName_, finalStatus,
+ getObjectsCurrent(PHASE_SYNCHRONIZING), getDataCurrent(PHASE_SYNCHRONIZING),
+ getObjectsTotal (PHASE_SYNCHRONIZING), getDataTotal (PHASE_SYNCHRONIZING),
+ totalTime.Time() / 1000
+ };
+
try
{
- saveToLastSyncsLog(logStream); //throw FileError
+ saveToLastSyncsLog(summary, errorLog, lastSyncsLogFileSizeMax_); //throw FileError
}
catch (FileError&) {}
diff --git a/ui/gui_status_handler.h b/ui/gui_status_handler.h
index e1940a2d..fb0dbf51 100644
--- a/ui/gui_status_handler.h
+++ b/ui/gui_status_handler.h
@@ -49,6 +49,7 @@ class SyncStatusHandler : public zen::StatusHandler
{
public:
SyncStatusHandler(MainDialog* parentDlg,
+ size_t lastSyncsLogFileSizeMax,
xmlAccess::OnGuiError handleError,
const std::wstring& jobName,
const std::wstring& execWhenFinished,
@@ -69,6 +70,7 @@ private:
MainDialog* parentDlg_;
SyncStatus syncStatusFrame; //the window managed by SyncStatus has longer lifetime than this handler!
+ const size_t lastSyncsLogFileSizeMax_;
xmlAccess::OnGuiError handleError_;
zen::ErrorLog errorLog;
const std::wstring jobName_;
diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp
index 0004d2fc..80c5e18f 100644
--- a/ui/main_dlg.cpp
+++ b/ui/main_dlg.cpp
@@ -594,6 +594,8 @@ MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg,
m_bpButtonSyncConfig->SetBitmapLabel(GlobalResources::getImage(L"syncConfig"));
m_bpButtonCmpConfig ->SetBitmapLabel(GlobalResources::getImage(L"cmpConfig"));
m_bpButtonLoad ->SetBitmapLabel(GlobalResources::getImage(L"load"));
+ m_bpButtonBatchJob ->SetBitmapLabel(GlobalResources::getImage(L"batch"));
+
m_bpButtonAddPair ->SetBitmapLabel(GlobalResources::getImage(L"item_add"));
{
IconBuffer tmp(IconBuffer::SIZE_SMALL);
@@ -955,43 +957,50 @@ typedef Zbase<wchar_t> zxString; //guaranteed exponential growth
void MainDialog::copySelectionToClipboard()
{
- zxString clipboardString;
-
- auto addSelection = [&](const Grid& grid)
+ try
{
- if (auto prov = grid.getDataProvider())
+ zxString clipboardString;
+
+ auto addSelection = [&](const Grid& grid)
{
- std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig();
- vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
- if (!colAttr.empty())
+ if (auto prov = grid.getDataProvider())
{
- const std::vector<size_t> selection = grid.getSelectedRows();
- std::for_each(selection.begin(), selection.end(),
- [&](size_t row)
+ std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig();
+ vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
+ if (!colAttr.empty())
{
- std::for_each(colAttr.begin(), colAttr.end() - 1,
- [&](const Grid::ColumnAttribute& ca)
+ const std::vector<size_t> selection = grid.getSelectedRows();
+ std::for_each(selection.begin(), selection.end(),
+ [&](size_t row)
{
- clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_));
- clipboardString += L'\t';
+ std::for_each(colAttr.begin(), colAttr.end() - 1,
+ [&](const Grid::ColumnAttribute& ca)
+ {
+ clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_));
+ clipboardString += L'\t';
+ });
+ clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_));
+ clipboardString += L'\n';
});
- clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_));
- clipboardString += L'\n';
- });
+ }
}
- }
- };
+ };
- addSelection(*m_gridMainL);
- addSelection(*m_gridMainR);
+ addSelection(*m_gridMainL);
+ addSelection(*m_gridMainR);
- //finally write to clipboard
- if (!clipboardString.empty())
- if (wxTheClipboard->Open())
- {
- wxTheClipboard->SetData(new wxTextDataObject(copyStringTo<wxString>(clipboardString))); //ownership passed
- wxTheClipboard->Close();
- }
+ //finally write to clipboard
+ if (!clipboardString.empty())
+ if (wxClipboard::Get()->Open())
+ {
+ ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close());
+ wxClipboard::Get()->SetData(new wxTextDataObject(copyStringTo<wxString>(clipboardString))); //ownership passed
+ }
+ }
+ catch (const std::bad_alloc& e)
+ {
+ wxMessageBox(_("Out of memory!") + L" " + utfCvrtTo<std::wstring>(e.what()), _("Error"), wxOK | wxICON_ERROR);
+ }
}
@@ -1625,7 +1634,7 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide)
case WXK_SPACE:
case WXK_NUMPAD_SPACE:
{
- const std::vector<FileSystemObject*>& selection = getGridSelection();
+ const std::vector<FileSystemObject*>& selection = getGridSelection();
if (!selection.empty())
setFilterManually(selection, !selection[0]->isActive());
}
@@ -1730,7 +1739,7 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou
{
m_gridMainL->SetFocus();
- event.SetEventType(wxEVT_KEY_DOWN); //the grid event handler doesn't expect wxEVT_CHAR_HOOK!
+ event.SetEventType(wxEVT_KEY_DOWN); //the grid event handler doesn't expect wxEVT_CHAR_HOOK!
evtHandler->ProcessEvent(event); //propagating event catched at wxTheApp to child leads to recursion, but we prevented it...
event.Skip(false); //definitively handled now!
return;
@@ -1746,24 +1755,25 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou
void MainDialog::onNaviSelection(GridRangeSelectEvent& event)
{
//scroll m_gridMain to user's new selection on m_gridNavi
- int leadRow = -1;
- if (std::unique_ptr<TreeView::Node> node = treeDataView->getLine(event.rowFrom_))
- {
- if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get()))
- leadRow = gridDataView->findRowFirstChild(&(root->baseMap_));
- else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get()))
+ ptrdiff_t leadRow = -1;
+ if (event.rowFirst_ != event.rowLast_)
+ if (std::unique_ptr<TreeView::Node> node = treeDataView->getLine(event.rowFirst_))
{
- leadRow = gridDataView->findRowDirect(&(dir->dirObj_));
- if (leadRow < 0) //directory was filtered out! still on tree view (but NOT on grid view)
- leadRow = gridDataView->findRowFirstChild(&(dir->dirObj_));
+ if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get()))
+ leadRow = gridDataView->findRowFirstChild(&(root->baseMap_));
+ else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get()))
+ {
+ leadRow = gridDataView->findRowDirect(&(dir->dirObj_));
+ if (leadRow < 0) //directory was filtered out! still on tree view (but NOT on grid view)
+ leadRow = gridDataView->findRowFirstChild(&(dir->dirObj_));
+ }
+ else if (const TreeView::FilesNode* files = dynamic_cast<const TreeView::FilesNode*>(node.get()))
+ leadRow = gridDataView->findRowDirect(files->firstFile_.getId());
}
- else if (const TreeView::FilesNode* files = dynamic_cast<const TreeView::FilesNode*>(node.get()))
- leadRow = gridDataView->findRowDirect(files->firstFile_.getId());
- }
if (leadRow >= 0)
{
- leadRow =std::max(0, leadRow - 1); //scroll one more row
+ leadRow = std::max<ptrdiff_t>(0, leadRow - 1); //scroll one more row
m_gridMainL->scrollTo(leadRow);
m_gridMainC->scrollTo(leadRow);
@@ -1857,7 +1867,7 @@ void MainDialog::onNaviGridContext(GridClickEvent& event)
//----------------------------------------------------------------------------------------------------
//CONTEXT_DELETE_FILES
menu.addSeparator();
- menu.addItem(_("Delete") + L"\tDel", [&] { deleteSelectedFiles(selection, selection); }, nullptr, !selection.empty());
+ menu.addItem(_("Delete") + L"\tDel", [&] { deleteSelectedFiles(selection, selection); }, nullptr, !selection.empty(), wxID_DELETE);
menu.popup(*this);
}
@@ -2454,7 +2464,7 @@ bool MainDialog::trySaveConfig(const wxString* fileName) //return true if saved
try
{
- xmlAccess::writeConfig(guiCfg, toZ(targetFilename)); //write config to XML
+ xmlAccess::writeConfig(guiCfg, toZ(targetFilename)); //throw FfsXmlError
setLastUsedConfig(targetFilename, guiCfg);
flashStatusInformation(_("Configuration saved!"));
@@ -2686,16 +2696,14 @@ void MainDialog::OnClose(wxCloseEvent& event)
void MainDialog::onCheckRows(CheckRowsEvent& event)
{
- const int rowFirst = std::min(event.rowFrom_, event.rowTo_); // [rowFirst, rowLast)
- int rowLast = std::max(event.rowFrom_, event.rowTo_) + 1; //
- rowLast = std::min(rowLast, static_cast<int>(gridDataView->rowsOnView())); //consider dummy rows
+ std::set<size_t> selectedRows;
- if (0 <= rowFirst && rowFirst < rowLast)
- {
- std::set<size_t> selectedRows;
- for (int i = rowFirst; i < rowLast; ++i)
- selectedRows.insert(i);
+ const size_t rowLast = std::min(event.rowLast_, gridDataView->rowsOnView()); //consider dummy rows
+ for (size_t i = event.rowFirst_; i < rowLast; ++i)
+ selectedRows.insert(i);
+ if (!selectedRows.empty())
+ {
std::vector<FileSystemObject*> objects = gridDataView->getAllFileRef(selectedRows);
setFilterManually(objects, event.setIncluded_);
}
@@ -2704,16 +2712,14 @@ void MainDialog::onCheckRows(CheckRowsEvent& event)
void MainDialog::onSetSyncDirection(SyncDirectionEvent& event)
{
- const int rowFirst = std::min(event.rowFrom_, event.rowTo_); // [rowFirst, rowLast)
- int rowLast = std::max(event.rowFrom_, event.rowTo_) + 1; //
- rowLast = std::min(rowLast, static_cast<int>(gridDataView->rowsOnView())); //consider dummy rows
+ std::set<size_t> selectedRows;
- if (0 <= rowFirst && rowFirst < rowLast)
- {
- std::set<size_t> selectedRows;
- for (int i = rowFirst; i < rowLast; ++i)
- selectedRows.insert(i);
+ const size_t rowLast = std::min(event.rowLast_, gridDataView->rowsOnView()); //consider dummy rows
+ for (size_t i = event.rowFirst_; i < rowLast; ++i)
+ selectedRows.insert(i);
+ if (!selectedRows.empty())
+ {
std::vector<FileSystemObject*> objects = gridDataView->getAllFileRef(selectedRows);
setSyncDirManually(objects, event.direction_);
}
@@ -3172,7 +3178,7 @@ void MainDialog::OnCompare(wxCommandEvent& event)
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 scrollPosX = 0;
int scrollPosY = 0;
m_gridMainL->GetViewStart(&scrollPosX, &scrollPosY); //preserve current scroll position
ZEN_ON_SCOPE_EXIT(
@@ -3193,12 +3199,14 @@ void MainDialog::OnCompare(wxCommandEvent& event)
std::unique_ptr<LockHolder> dummy2;
if (globalCfg.createLockFile)
{
- dummy2.reset(new LockHolder(true)); //allow pw prompt
- for (auto iter = cmpConfig.begin(); iter != cmpConfig.end(); ++iter)
+ std::vector<Zstring> dirnames;
+ std::for_each(cmpConfig.begin(), cmpConfig.end(),
+ [&](const FolderPairCfg& fpCfg)
{
- dummy2->addDir(iter->leftDirectoryFmt, statusHandler);
- dummy2->addDir(iter->rightDirectoryFmt, statusHandler);
- }
+ dirnames.push_back(fpCfg.leftDirectoryFmt);
+ dirnames.push_back(fpCfg.rightDirectoryFmt);
+ });
+ dummy2 = make_unique<LockHolder>(dirnames, statusHandler, true); //allow pw prompt
}
//COMPARE DIRECTORIES
@@ -3234,7 +3242,7 @@ void MainDialog::OnCompare(wxCommandEvent& event)
//add to folder history after successful comparison only
folderHistoryLeft ->addItem(toZ(m_directoryLeft->GetValue()));
folderHistoryRight->addItem(toZ(m_directoryRight->GetValue()));
-
+
//prepare status information
if (allElementsEqual(folderCmp))
flashStatusInformation(_("All folders are in sync!"));
@@ -3378,7 +3386,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler())
evtHandler->ProcessEvent(dummy2); //synchronous call
- if (folderCmp.empty()) //check if user aborted or error occured, ect...
+ if (folderCmp.empty()) //check if user aborted or error occurred, ect...
return;
}
@@ -3410,6 +3418,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
//class handling status updates and error messages
SyncStatusHandler statusHandler(this, //throw GuiAbortProcess
+ globalCfg.lastSyncsLogFileSizeMax,
currentCfg.handleError,
xmlAccess::extractJobName(activeFileName),
guiCfg.mainCfg.onCompletion,
@@ -3419,12 +3428,13 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
std::unique_ptr<LockHolder> dummy2;
if (globalCfg.createLockFile)
{
- dummy2.reset(new LockHolder(true)); //allow pw prompt
+ std::vector<Zstring> dirnames;
for (auto iter = begin(folderCmp); iter != end(folderCmp); ++iter)
{
- dummy2->addDir(iter->getBaseDirPf<LEFT_SIDE >(), statusHandler);
- dummy2->addDir(iter->getBaseDirPf<RIGHT_SIDE>(), statusHandler);
+ dirnames.push_back(iter->getBaseDirPf<LEFT_SIDE >());
+ dirnames.push_back(iter->getBaseDirPf<RIGHT_SIDE>());
}
+ dummy2 = make_unique<LockHolder>(dirnames, statusHandler, true); //allow pw prompt
}
//START SYNCHRONIZATION
@@ -3465,7 +3475,7 @@ void MainDialog::onGridDoubleClickR(GridClickEvent& event)
onGridDoubleClickRim(event.row_, false);
}
-void MainDialog::onGridDoubleClickRim(int row, bool leftSide)
+void MainDialog::onGridDoubleClickRim(size_t row, bool leftSide)
{
if (!globalCfg.gui.externelApplications.empty())
openExternalApplication(globalCfg.gui.externelApplications[0].second,
@@ -3966,8 +3976,8 @@ void MainDialog::clearAddFolderPairs()
//m_scrolledWindowFolderPairs->SetMinSize(wxSize(-1, 0));
//bSizer1->Layout();
}
-//########################################################################################################
+//########################################################################################################
//menu events
void MainDialog::OnMenuGlobalSettings(wxCommandEvent& event)
@@ -3992,44 +4002,44 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event)
const Zstring filename = utfCvrtTo<Zstring>(filePicker.GetPath());
- Utf8String buffer; //perf: wxString doesn't model exponential growth and so is out, std::string doesn't give performance guarantee!
- buffer += BYTE_ORDER_MARK_UTF8;
+ Utf8String header; //perf: wxString doesn't model exponential growth and so is out, std::string doesn't give performance guarantee!
+ header += BYTE_ORDER_MARK_UTF8;
//write legend
- buffer += utfCvrtTo<Utf8String>(_("Legend")) + '\n';
+ header += utfCvrtTo<Utf8String>(_("Legend")) + '\n';
if (showSyncAction_)
{
- buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_EQUAL)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_EQUAL)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_CREATE_NEW_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_CREATE_NEW_LEFT)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_CREATE_NEW_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_CREATE_NEW_RIGHT)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_OVERWRITE_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_OVERWRITE_LEFT)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_OVERWRITE_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_OVERWRITE_RIGHT)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DELETE_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DELETE_LEFT)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DELETE_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DELETE_RIGHT)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DO_NOTHING)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DO_NOTHING)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_UNRESOLVED_CONFLICT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_UNRESOLVED_CONFLICT)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_EQUAL)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_EQUAL)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_CREATE_NEW_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_CREATE_NEW_LEFT)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_CREATE_NEW_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_CREATE_NEW_RIGHT)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_OVERWRITE_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_OVERWRITE_LEFT)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_OVERWRITE_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_OVERWRITE_RIGHT)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DELETE_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DELETE_LEFT)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DELETE_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DELETE_RIGHT)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DO_NOTHING)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DO_NOTHING)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_UNRESOLVED_CONFLICT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_UNRESOLVED_CONFLICT)) + '\n';
}
else
{
- buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_EQUAL)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_EQUAL)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_DIFFERENT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_DIFFERENT)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_LEFT_SIDE_ONLY)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_LEFT_SIDE_ONLY)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_RIGHT_SIDE_ONLY)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_RIGHT_SIDE_ONLY)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_LEFT_NEWER)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_LEFT_NEWER)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_RIGHT_NEWER)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_RIGHT_NEWER)) + '\n';
- buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_CONFLICT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_CONFLICT)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_EQUAL)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_EQUAL)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_DIFFERENT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_DIFFERENT)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_LEFT_SIDE_ONLY)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_LEFT_SIDE_ONLY)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_RIGHT_SIDE_ONLY)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_RIGHT_SIDE_ONLY)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_LEFT_NEWER)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_LEFT_NEWER)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_RIGHT_NEWER)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_RIGHT_NEWER)) + '\n';
+ header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_CONFLICT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_CONFLICT)) + '\n';
}
- buffer += '\n';
+ header += '\n';
//base folders
- buffer += utfCvrtTo<Utf8String>(_("Folder pairs")) + '\n' ;
+ header += utfCvrtTo<Utf8String>(_("Folder pairs")) + '\n' ;
std::for_each(begin(folderCmp), end(folderCmp),
[&](BaseDirMapping& baseMap)
{
- buffer += utfCvrtTo<Utf8String>(baseMap.getBaseDirPf<LEFT_SIDE >()) + ';';
- buffer += utfCvrtTo<Utf8String>(baseMap.getBaseDirPf<RIGHT_SIDE>()) + '\n';
+ header += utfCvrtTo<Utf8String>(baseMap.getBaseDirPf<LEFT_SIDE >()) + ';';
+ header += utfCvrtTo<Utf8String>(baseMap.getBaseDirPf<RIGHT_SIDE>()) + '\n';
});
- buffer += '\n';
+ header += '\n';
//write header
auto provLeft = m_gridMainL->getDataProvider();
@@ -4044,12 +4054,12 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event)
vector_remove_if(colAttrMiddle, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
vector_remove_if(colAttrRight , [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
- auto addCellValue = [&](const wxString& val)
+ auto fmtCellValue = [](const wxString& val) -> Utf8String
{
if (val.find(L';') != wxString::npos)
- buffer += '\"' + utfCvrtTo<Utf8String>(val) + '\"';
+ return '\"' + utfCvrtTo<Utf8String>(val) + '\"';
else
- buffer += utfCvrtTo<Utf8String>(val);
+ return utfCvrtTo<Utf8String>(val);
};
if (provLeft && provMiddle && provRight)
@@ -4057,62 +4067,73 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event)
std::for_each(colAttrLeft.begin(), colAttrLeft.end(),
[&](const Grid::ColumnAttribute& ca)
{
- addCellValue(provLeft->getColumnLabel(ca.type_));
- buffer += ';';
+ header += fmtCellValue(provLeft->getColumnLabel(ca.type_));
+ header += ';';
});
std::for_each(colAttrMiddle.begin(), colAttrMiddle.end(),
[&](const Grid::ColumnAttribute& ca)
{
- addCellValue(provMiddle->getColumnLabel(ca.type_));
- buffer += ';';
+ header += fmtCellValue(provMiddle->getColumnLabel(ca.type_));
+ header += ';';
});
if (!colAttrRight.empty())
{
std::for_each(colAttrRight.begin(), colAttrRight.end() - 1,
[&](const Grid::ColumnAttribute& ca)
{
- addCellValue(provRight->getColumnLabel(ca.type_));
- buffer += ';';
+ header += fmtCellValue(provRight->getColumnLabel(ca.type_));
+ header += ';';
});
- addCellValue(provRight->getColumnLabel(colAttrRight.back().type_));
+ header += fmtCellValue(provRight->getColumnLabel(colAttrRight.back().type_));
}
- buffer += '\n';
+ header += '\n';
- //main grid
- const size_t rowCount = m_gridMainL->getRowCount();
- for (size_t row = 0; row < rowCount; ++row)
+ try
{
- std::for_each(colAttrLeft.begin(), colAttrLeft.end(),
- [&](const Grid::ColumnAttribute& ca)
- {
- addCellValue(provLeft->getValue(row, ca.type_));
- buffer += ';';
- });
- std::for_each(colAttrMiddle.begin(), colAttrMiddle.end(),
- [&](const Grid::ColumnAttribute& ca)
- {
- addCellValue(provMiddle->getValue(row, ca.type_));
- buffer += ';';
- });
- if (!colAttrRight.empty())
+ //write file
+ FileOutput fileOut(filename, zen::FileOutput::ACC_OVERWRITE); //throw FileError
+
+ replace(header, '\n', LINE_BREAK);
+ fileOut.write(&*header.begin(), header.size()); //throw FileError
+
+ //main grid: write rows one after the other instead of creating one big string: memory allocation might fail; think 1 million rows!
+ /*
+ performance test case "export 600.000 rows" to CSV:
+ aproach 1. assemble single temporary string, then write file: 4.6s
+ aproach 2. write to buffered file output directly for each row: 6.4s
+ */
+ const size_t rowCount = m_gridMainL->getRowCount();
+ for (size_t row = 0; row < rowCount; ++row)
{
- std::for_each(colAttrRight.begin(), colAttrRight.end() - 1,
+ Utf8String tmp;
+
+ std::for_each(colAttrLeft.begin(), colAttrLeft.end(),
[&](const Grid::ColumnAttribute& ca)
{
- addCellValue(provRight->getValue(row, ca.type_));
- buffer += ';';
+ tmp += fmtCellValue(provLeft->getValue(row, ca.type_));
+ tmp += ';';
});
- addCellValue(provRight->getValue(row, colAttrRight.back().type_));
- }
- buffer += '\n';
- }
-
- //write file
- try
- {
- replace(buffer, '\n', LINE_BREAK);
+ std::for_each(colAttrMiddle.begin(), colAttrMiddle.end(),
+ [&](const Grid::ColumnAttribute& ca)
+ {
+ tmp += fmtCellValue(provMiddle->getValue(row, ca.type_));
+ tmp += ';';
+ });
+ if (!colAttrRight.empty())
+ {
+ std::for_each(colAttrRight.begin(), colAttrRight.end() - 1,
+ [&](const Grid::ColumnAttribute& ca)
+ {
+ tmp += fmtCellValue(provRight->getValue(row, ca.type_));
+ tmp += ';';
+ });
+ tmp += fmtCellValue(provRight->getValue(row, colAttrRight.back().type_));
+ }
+ tmp += '\n';
- saveBinStream(filename, buffer); //throw FileError
+ replace(tmp, '\n', LINE_BREAK);
+ fileOut.write(&*tmp.begin(), tmp.size()); //throw FileError
+ }
flashStatusInformation(_("File list exported!"));
}
diff --git a/ui/main_dlg.h b/ui/main_dlg.h
index f5f07624..07687f88 100644
--- a/ui/main_dlg.h
+++ b/ui/main_dlg.h
@@ -163,7 +163,7 @@ private:
void onGridDoubleClickL(zen::GridClickEvent& event);
void onGridDoubleClickR(zen::GridClickEvent& event);
- void onGridDoubleClickRim(int row, bool leftSide);
+ void onGridDoubleClickRim(size_t row, bool leftSide);
void onGridLabelLeftClickC(zen::GridClickEvent& event);
void onGridLabelLeftClickL(zen::GridClickEvent& event);
diff --git a/ui/progress_indicator.cpp b/ui/progress_indicator.cpp
index ebbc9edd..caf87428 100644
--- a/ui/progress_indicator.cpp
+++ b/ui/progress_indicator.cpp
@@ -10,12 +10,15 @@
#include <wx/stopwatch.h>
#include <wx/wupdlock.h>
#include <wx/sound.h>
+#include <wx/clipbrd.h>
+#include <wx/msgdlg.h>
#include <zen/basic_math.h>
#include <zen/format_unit.h>
#include <wx+/mouse_move_dlg.h>
#include <wx+/toggle_button.h>
#include <wx+/image_tools.h>
#include <wx+/graph.h>
+#include <wx+/context_menu.h>
#include <wx+/no_flicker.h>
#include <zen/file_handling.h>
#include "gui_generated.h"
@@ -272,7 +275,7 @@ namespace
inline
wxBitmap buttonPressed(const std::string& name)
{
- wxBitmap background = GlobalResources::getImage(wxT("log button pressed"));
+ wxBitmap background = GlobalResources::getImage(L"log button pressed");
return layOver(GlobalResources::getImage(utfCvrtTo<wxString>(name)), background);
}
@@ -287,17 +290,283 @@ wxBitmap buttonReleased(const std::string& name)
zen::move(output, 0, -1); //move image right one pixel
return output;
}
+
+
+//a vector-view on ErrorLog considering multi-line messages: prepare consumption by Grid
+class MessageView
+{
+public:
+ MessageView(const ErrorLog& log) : log_(log) {}
+
+ size_t rowsOnView() const { return viewRef.size(); }
+
+ struct LogEntryView
+ {
+ time_t time;
+ MessageType type;
+ MsgString messageLine;
+ bool firstLine; //if LogEntry::message spans multiple rows
+ };
+ bool getEntry(size_t row, LogEntryView& out) const
+ {
+ if (row < viewRef.size())
+ {
+ const Line& line = viewRef[row];
+ out.time = line.entry_->time;
+ out.type = line.entry_->type;
+ out.messageLine = extractLine(line.entry_->message, line.rowNumber_);
+ out.firstLine = line.rowNumber_ == 0; //this is virtually always correct, unless first line of the original message is empty!
+ return true;
+ }
+ return false;
+ }
+
+ void updateView(int includedTypes) //TYPE_INFO | TYPE_WARNING, ect. see error_log.h
+ {
+ viewRef.clear();
+
+ for (auto iter = log_.begin(); iter != log_.end(); ++iter)
+ if (iter->type & includedTypes)
+ {
+ assert_static((IsSameType<GetCharType<MsgString>::Type, wchar_t>::value));
+ assert(!startsWith(iter->message, L'\n'));
+
+ size_t rowNumber = 0;
+ bool lastCharNewline = true;
+ std::for_each(iter->message.begin(), iter->message.end(),
+ [&](wchar_t c)
+ {
+ typedef Line Line; //workaround MSVC compiler bug!
+
+ if (c == L'\n')
+ {
+ if (!lastCharNewline) //do not reference empty lines!
+ viewRef.push_back(Line(&*iter, rowNumber));
+ ++rowNumber;
+ lastCharNewline = true;
+ }
+ else
+ lastCharNewline = false;
+ });
+ if (!lastCharNewline)
+ viewRef.push_back(Line(&*iter, rowNumber));
+ }
+ }
+
+private:
+ static MsgString extractLine(const MsgString& message, size_t textRow)
+ {
+ auto iter1 = message.begin();
+ for (;;)
+ {
+ auto iter2 = std::find_if(iter1, message.end(), [](wchar_t c) { return c == L'\n'; });
+ if (textRow == 0)
+ return iter1 == message.end() ? MsgString() : MsgString(&*iter1, iter2 - iter1); //must not dereference iterator pointing to "end"!
+
+ if (iter2 == message.end())
+ {
+ assert(false);
+ return MsgString();
+ }
+
+ iter1 = iter2 + 1; //skip newline
+ --textRow;
+ }
+ }
+
+ struct Line
+ {
+ Line(const LogEntry* entry, size_t rowNumber) : entry_(entry), rowNumber_(rowNumber) {}
+ const LogEntry* entry_; //always bound!
+ size_t rowNumber_; //LogEntry::message may span multiple rows
+ };
+
+ std::vector<Line> viewRef; //partial view on log_
+ /* /|\
+ | updateView()
+ | */
+ const ErrorLog log_;
+};
+
+//-----------------------------------------------------------------------------
+
+enum ColumnTypeMsg
+{
+ COL_TYPE_MSG_TIME,
+ COL_TYPE_MSG_CATEGORY,
+ COL_TYPE_MSG_TEXT,
+};
+
+//Grid data implementation referencing MessageView
+class GridDataMessages : public GridData
+{
+ static const int COLUMN_BORDER_LEFT = 4; //for left-aligned text
+
+public:
+ GridDataMessages(const std::shared_ptr<MessageView>& msgView) : msgView_(msgView) {}
+
+ virtual size_t getRowCount() const { return msgView_ ? msgView_->rowsOnView() : 0; }
+
+ virtual wxString getValue(size_t row, ColumnType colType) const
+ {
+ MessageView::LogEntryView entry = {};
+ if (msgView_ && msgView_->getEntry(row, entry))
+ switch (static_cast<ColumnTypeMsg>(colType))
+ {
+ case COL_TYPE_MSG_TIME:
+ if (entry.firstLine)
+ return formatTime<wxString>(FORMAT_TIME, localTime(entry.time));
+ break;
+
+ case COL_TYPE_MSG_CATEGORY:
+ if (entry.firstLine)
+ switch (entry.type)
+ {
+ case TYPE_INFO:
+ return _("Info");
+ case TYPE_WARNING:
+ return _("Warning");
+ case TYPE_ERROR:
+ return _("Error");
+ case TYPE_FATAL_ERROR:
+ return _("Fatal Error");
+ }
+ break;
+
+ case COL_TYPE_MSG_TEXT:
+ return copyStringTo<wxString>(entry.messageLine);
+ }
+ return wxEmptyString;
+ }
+
+ virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, size_t row, ColumnType colType)
+ {
+ wxRect rectTmp = rect;
+
+ const wxColor colorGridLine = wxColour(192, 192, 192); //light grey
+
+ wxDCPenChanger dummy2(dc, wxPen(colorGridLine, 1, wxSOLID));
+ const bool drawBottomLine = [&]() -> bool //don't separate multi-line messages
+ {
+ MessageView::LogEntryView nextEntry = {};
+ if (msgView_ && msgView_->getEntry(row + 1, nextEntry))
+ return nextEntry.firstLine;
+ return true;
+ }();
+
+ if (drawBottomLine)
+ {
+ dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0));
+ --rectTmp.height;
+ }
+
+ //--------------------------------------------------------
+
+ MessageView::LogEntryView entry = {};
+ if (msgView_ && msgView_->getEntry(row, entry))
+ switch (static_cast<ColumnTypeMsg>(colType))
+ {
+ case COL_TYPE_MSG_TIME:
+ drawCellText(dc, rectTmp, getValue(row, colType), grid.IsEnabled(), wxALIGN_CENTER);
+ break;
+
+ case COL_TYPE_MSG_CATEGORY:
+ if (entry.firstLine)
+ switch (entry.type)
+ {
+ case TYPE_INFO:
+ dc.DrawLabel(wxString(), GlobalResources::getImage(L"msg_small_info"), rectTmp, wxALIGN_CENTER);
+ break;
+ case TYPE_WARNING:
+ dc.DrawLabel(wxString(), GlobalResources::getImage(L"msg_small_warning"), rectTmp, wxALIGN_CENTER);
+ break;
+ case TYPE_ERROR:
+ case TYPE_FATAL_ERROR:
+ dc.DrawLabel(wxString(), GlobalResources::getImage(L"msg_small_error"), rectTmp, wxALIGN_CENTER);
+ break;
+ }
+ break;
+
+ case COL_TYPE_MSG_TEXT:
+ {
+ rectTmp.x += COLUMN_BORDER_LEFT;
+ rectTmp.width -= COLUMN_BORDER_LEFT;
+ drawCellText(dc, rectTmp, getValue(row, colType), grid.IsEnabled());
+ }
+ break;
+ }
+ }
+
+ virtual size_t getBestSize(wxDC& dc, size_t row, ColumnType colType)
+ {
+ // -> synchronize renderCell() <-> getBestSize()
+
+ MessageView::LogEntryView entry = {};
+ if (msgView_ && msgView_->getEntry(row, entry))
+ switch (static_cast<ColumnTypeMsg>(colType))
+ {
+ case COL_TYPE_MSG_TIME:
+ return 2 * COLUMN_BORDER_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth();
+
+ case COL_TYPE_MSG_CATEGORY:
+ return GlobalResources::getImage(L"msg_small_info").GetWidth();
+
+ case COL_TYPE_MSG_TEXT:
+ return COLUMN_BORDER_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth();
+ }
+ return 0;
+ }
+
+ static int getColumnTimeDefaultWidth(Grid& grid)
+ {
+ wxClientDC dc(&grid.getMainWin());
+ dc.SetFont(grid.getMainWin().GetFont());
+ return 2 * COLUMN_BORDER_LEFT + dc.GetTextExtent(formatTime<wxString>(FORMAT_TIME)).GetWidth();
+ }
+
+ static int getColumnCategoryDefaultWidth()
+ {
+ return GlobalResources::getImage(L"msg_small_info").GetWidth();
+ }
+
+ static int getRowDefaultHeight(const Grid& grid)
+ {
+ return std::max(GlobalResources::getImage(L"msg_small_info").GetHeight(), grid.getMainWin().GetCharHeight() + 2) + 1; //+ some space + bottom border
+ }
+
+ virtual wxString getToolTip(size_t row, ColumnType colType) const
+ {
+ MessageView::LogEntryView entry = {};
+ if (msgView_ && msgView_->getEntry(row, entry))
+ switch (static_cast<ColumnTypeMsg>(colType))
+ {
+ case COL_TYPE_MSG_TIME:
+ case COL_TYPE_MSG_TEXT:
+ break;
+
+ case COL_TYPE_MSG_CATEGORY:
+ return getValue(row, colType);
+ }
+ return wxEmptyString;
+ }
+
+ virtual wxString getColumnLabel(ColumnType colType) const { return wxEmptyString; }
+
+private:
+ const std::shared_ptr<MessageView> msgView_;
+};
}
class LogControl : public LogControlGenerated
{
public:
- LogControl(wxWindow* parent, const ErrorLog& log) : LogControlGenerated(parent), log_(log)
+ LogControl(wxWindow* parent, const ErrorLog& log) : LogControlGenerated(parent),
+ msgView(std::make_shared<MessageView>(log))
{
- const int errorCount = log_.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR);
- const int warningCount = log_.getItemCount(TYPE_WARNING);
- const int infoCount = log_.getItemCount(TYPE_INFO);
+ const int errorCount = log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR);
+ const int warningCount = log.getItemCount(TYPE_WARNING);
+ const int infoCount = log.getItemCount(TYPE_INFO);
m_bpButtonErrors ->init(buttonPressed ("msg_error" ), buttonReleased("msg_error" ), _("Error" ) + wxString::Format(L" (%d)", errorCount ));
m_bpButtonWarnings->init(buttonPressed ("msg_warning"), buttonReleased("msg_warning"), _("Warning") + wxString::Format(L" (%d)", warningCount));
@@ -311,47 +580,49 @@ public:
m_bpButtonWarnings->Show(warningCount != 0);
m_bpButtonInfo ->Show(infoCount != 0);
- m_textCtrlInfo->SetMaxLength(0); //allow large entries!
+ //init grid, determine default sizes
+ const int rowHeight = GridDataMessages::getRowDefaultHeight(*m_gridMessages);
+ const int colMsgTimeWidth = GridDataMessages::getColumnTimeDefaultWidth(*m_gridMessages);
+ const int colMsgCategoryWidth = GridDataMessages::getColumnCategoryDefaultWidth();
+
+ m_gridMessages->setDataProvider(std::make_shared<GridDataMessages>(msgView));
+ m_gridMessages->setColumnLabelHeight(0);
+ m_gridMessages->showRowLabel(false);
+ m_gridMessages->setRowHeight(rowHeight);
+ std::vector<Grid::ColumnAttribute> attr;
+ attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_TIME ), colMsgTimeWidth, 0));
+ attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_CATEGORY), colMsgCategoryWidth, 0));
+ attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_TEXT ), -colMsgTimeWidth - colMsgCategoryWidth, 1));
+ m_gridMessages->setColumnConfig(attr);
+
+ //support for CTRL + C
+ m_gridMessages->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(LogControl::onGridButtonEvent), nullptr, this);
- updateLogText();
+ m_gridMessages->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(LogControl::onMsgGridContext), nullptr, this);
- m_textCtrlInfo->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(LogControl::onKeyEvent), nullptr, this);
+ updateGrid();
}
private:
virtual void OnErrors(wxCommandEvent& event)
{
m_bpButtonErrors->toggle();
- updateLogText();
+ updateGrid();
}
virtual void OnWarnings(wxCommandEvent& event)
{
m_bpButtonWarnings->toggle();
- updateLogText();
+ updateGrid();
}
virtual void OnInfo(wxCommandEvent& event)
{
m_bpButtonInfo->toggle();
- updateLogText();
+ updateGrid();
}
- void onKeyEvent(wxKeyEvent& event)
- {
- const int keyCode = event.GetKeyCode();
-
- if (event.ControlDown())
- switch (keyCode)
- {
- case 'A': //CTRL + A
- m_textCtrlInfo->SetSelection(-1, -1); //select all
- return;
- }
- event.Skip();
- }
-
- void updateLogText()
+ void updateGrid()
{
int includedTypes = 0;
if (m_bpButtonErrors->isActive())
@@ -363,27 +634,83 @@ private:
if (m_bpButtonInfo->isActive())
includedTypes |= TYPE_INFO;
- //fast replacement for wxString modelling exponential growth
- MsgString logText;
+ msgView->updateView(includedTypes); //update MVC "model"
+ m_gridMessages->Refresh(); //update MVC "view"
+ }
+
- const auto& entries = log_.getEntries();
- for (auto iter = entries.begin(); iter != entries.end(); ++iter)
- if (iter->type & includedTypes)
+ void onGridButtonEvent(wxKeyEvent& event)
+ {
+ int keyCode = event.GetKeyCode();
+
+ if (event.ControlDown())
+ switch (keyCode)
{
- logText += formatMessage(*iter);
- logText += L'\n';
+ case 'C':
+ case WXK_INSERT: //CTRL + C || CTRL + INS
+ copySelectionToClipboard();
+ return; // -> swallow event! don't allow default grid commands!
}
- if (logText.empty()) //if no messages match selected view filter, at least show final status message
- if (!entries.empty())
- logText = formatMessage(entries.back());
+ event.Skip(); //unknown keypress: propagate
+ }
+
+ void onMsgGridContext(GridClickEvent& event)
+ {
+ const std::vector<size_t> selection = m_gridMessages->getSelectedRows();
+
+ ContextMenu menu;
+ menu.addItem(_("Copy") + L"\tCtrl+C", [this] { copySelectionToClipboard(); }, nullptr, !selection.empty(), wxID_COPY);
+ menu.popup(*this);
+ }
+
+ void copySelectionToClipboard()
+ {
+ try
+ {
+ typedef Zbase<wchar_t> zxString; //guaranteed exponential growth, unlike wxString
+ zxString clipboardString;
+
+ if (auto prov = m_gridMessages->getDataProvider())
+ {
+ std::vector<Grid::ColumnAttribute> colAttr = m_gridMessages->getColumnConfig();
+ vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
+ if (!colAttr.empty())
+ {
+ const std::vector<size_t> selection = m_gridMessages->getSelectedRows();
+ std::for_each(selection.begin(), selection.end(),
+ [&](size_t row)
+ {
+#ifdef _MSC_VER
+ typedef zxString zxString; //workaround MSVC compiler bug!
+#endif
+ std::for_each(colAttr.begin(), --colAttr.end(),
+ [&](const Grid::ColumnAttribute& ca)
+ {
+ clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_));
+ clipboardString += L'\t';
+ });
+ clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_));
+ clipboardString += L'\n';
+ });
+ }
+ }
- wxWindowUpdateLocker dummy(m_textCtrlInfo);
- m_textCtrlInfo->ChangeValue(copyStringTo<wxString>(logText));
- m_textCtrlInfo->ShowPosition(m_textCtrlInfo->GetLastPosition());
+ //finally write to clipboard
+ if (!clipboardString.empty())
+ if (wxClipboard::Get()->Open())
+ {
+ ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close());
+ wxClipboard::Get()->SetData(new wxTextDataObject(copyStringTo<wxString>(clipboardString))); //ownership passed
+ }
+ }
+ catch (const std::bad_alloc& e)
+ {
+ wxMessageBox(_("Out of memory!") + L" " + utfCvrtTo<std::wstring>(e.what()), _("Error"), wxOK | wxICON_ERROR);
+ }
}
- const ErrorLog log_;
+ std::shared_ptr<MessageView> msgView; //bound!
};
//########################################################################################
@@ -983,12 +1310,12 @@ void SyncStatus::SyncStatusImpl::updateProgress(bool allowYield)
if (paused_)
{
stopTimer();
+ ZEN_ON_SCOPE_EXIT(resumeTimer());
while (paused_)
{
wxMilliSleep(UI_UPDATE_INTERVAL);
updateUiNow(); //receive UI message that ends pause
}
- resumeTimer();
}
/*
/|\
@@ -1242,7 +1569,7 @@ void SyncStatus::SyncStatusImpl::processHasFinished(SyncResult resultId, const E
m_listbookResult->AddPage(logControl, _("Logging"), false);
//bSizerHoldStretch->Insert(0, logControl, 1, wxEXPAND);
- //show log instead of graph if errors occured! (not required for ignored warnings)
+ //show log instead of graph if errors occurred! (not required for ignored warnings)
if (log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR) > 0)
m_listbookResult->ChangeSelection(posLog);
diff --git a/ui/small_dlgs.cpp b/ui/small_dlgs.cpp
index 4b070dce..2b6c9d4a 100644
--- a/ui/small_dlgs.cpp
+++ b/ui/small_dlgs.cpp
@@ -109,14 +109,10 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent)
bmpLogo = wxBitmap(tmp);
}
{
- wxMemoryDC dc;
- dc.SelectObject(bmpLogo);
-
+ wxMemoryDC dc(bmpLogo);
dc.SetTextForeground(*wxBLACK);
dc.SetFont(wxFont(18, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, L"Tahoma"));
dc.DrawLabel(wxString(L"FreeFileSync ") + zen::currentVersion, wxNullBitmap, wxRect(0, 0, bmpLogo.GetWidth(), bmpLogo.GetHeight()), wxALIGN_CENTER);
-
- dc.SelectObject(wxNullBitmap);
}
m_bitmap11->SetBitmap(bmpLogo);
diff --git a/ui/tree_view.cpp b/ui/tree_view.cpp
index a3a6b2aa..119091a0 100644
--- a/ui/tree_view.cpp
+++ b/ui/tree_view.cpp
@@ -939,7 +939,7 @@ private:
{
const int tolerance = 1;
const int xNodeStatusFirst = -tolerance + cellArea.x + static_cast<int>(node->level_) * widthLevelStep + CELL_BORDER + (showPercentBar ? widthPercentBar + 2 * CELL_BORDER : 0);
- const int xNodeStatusLast = xNodeStatusFirst + widthNodeStatus + 2 * tolerance;
+ const int xNodeStatusLast = (xNodeStatusFirst + tolerance) + widthNodeStatus + tolerance;
// -> synchronize renderCell() <-> getBestSize() <-> onMouseLeft()
if (xNodeStatusFirst <= absX && absX < xNodeStatusLast)
@@ -948,7 +948,7 @@ private:
}
//--------------------------------------------------------------------------------------------------
- if (clickOnNodeStatus && event.row_ >= 0)
+ if (clickOnNodeStatus)
switch (treeDataView_->getStatus(event.row_))
{
case TreeView::STATUS_EXPANDED:
@@ -964,7 +964,7 @@ private:
void onMouseLeftDouble(GridClickEvent& event)
{
- if (event.row_ >= 0 && treeDataView_)
+ if (treeDataView_)
switch (treeDataView_->getStatus(event.row_))
{
case TreeView::STATUS_EXPANDED:
@@ -1102,7 +1102,7 @@ private:
void expandNode(size_t row)
{
treeDataView_->expandNode(row);
- grid_.Refresh(); //this one clears selection (changed row count)
+ grid_.Refresh(); //implicitly clears selection (changed row count after expand)
grid_.setGridCursor(row);
//grid_.autoSizeColumns(); -> doesn't look as good as expected
}
@@ -1110,9 +1110,8 @@ private:
void reduceNode(size_t row)
{
treeDataView_->reduceNode(row);
- grid_.Refresh(); //this one clears selection (changed row count)
+ grid_.Refresh();
grid_.setGridCursor(row);
- //grid_.autoSizeColumns(); -> doesn't look as good as expected
}
std::shared_ptr<TreeView> treeDataView_;
@@ -1134,7 +1133,9 @@ void treeview::init(Grid& grid, const std::shared_ptr<TreeView>& treeDataView)
{
grid.setDataProvider(std::make_shared<GridDataNavi>(grid, treeDataView));
grid.showRowLabel(false);
- grid.setRowHeight(IconBuffer(IconBuffer::SIZE_SMALL).getSize() + 2); //add some space
+
+ const int rowHeight = std::max(IconBuffer(IconBuffer::SIZE_SMALL).getSize(), grid.getMainWin().GetCharHeight()) + 1; //add some space
+ grid.setRowHeight(rowHeight);
}
diff --git a/version/version.h b/version/version.h
index 96636e6d..fe73caec 100644
--- a/version/version.h
+++ b/version/version.h
@@ -3,7 +3,7 @@
namespace zen
{
-const wchar_t currentVersion[] = L"5.9"; //internal linkage!
+const wchar_t currentVersion[] = L"5.10"; //internal linkage!
}
#endif
diff --git a/version/version.rc b/version/version.rc
index 9ef55dc3..ea24eb33 100644
--- a/version/version.rc
+++ b/version/version.rc
@@ -1,2 +1,2 @@
-#define FREEFILESYNC_VER 5,9,0,0
-#define FREEFILESYNC_VER_STR "5.9\0"
+#define FREEFILESYNC_VER 5,10,0,0
+#define FREEFILESYNC_VER_STR "5.10\0"
diff --git a/wx+/button.cpp b/wx+/button.cpp
index 8fce99f4..a67624b8 100644
--- a/wx+/button.cpp
+++ b/wx+/button.cpp
@@ -128,8 +128,7 @@ wxBitmap BitmapButton::createBitmapFromText(const wxString& text)
wxBitmap newBitmap(sizeNeeded.GetWidth(), sizeNeeded.GetHeight());
{
- wxMemoryDC dc;
- dc.SelectObject(newBitmap);
+ wxMemoryDC dc(newBitmap);
//set up white background
dc.SetBackground(*wxWHITE_BRUSH);
@@ -150,8 +149,6 @@ wxBitmap BitmapButton::createBitmapFromText(const wxString& text)
dc.SetFont(currentFont);
dc.DrawLabel(textLabelFormatted, wxNullBitmap, wxRect(0, 0, newBitmap.GetWidth(), newBitmap.GetHeight()), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, indexAccel);
-
- dc.SelectObject(wxNullBitmap);
}
//add alpha channel to image
diff --git a/wx+/context_menu.h b/wx+/context_menu.h
index 2557737a..cb6cb86d 100644
--- a/wx+/context_menu.h
+++ b/wx+/context_menu.h
@@ -30,9 +30,9 @@ class ContextMenu : private wxEvtHandler
public:
ContextMenu() : menu(new wxMenu) {}
- void addItem(const wxString& label, const std::function<void()>& command, const wxBitmap* bmp = nullptr, bool enabled = true)
+ void addItem(const wxString& label, const std::function<void()>& command, const wxBitmap* bmp = nullptr, bool enabled = true, int id = wxID_ANY)
{
- wxMenuItem* newItem = new wxMenuItem(menu.get(), wxID_ANY, label); //menu owns item!
+ wxMenuItem* newItem = new wxMenuItem(menu.get(), id, label); //menu owns item!
if (bmp) newItem->SetBitmap(*bmp); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
menu->Append(newItem);
if (!enabled) newItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason
diff --git a/wx+/file_drop.h b/wx+/file_drop.h
index 22a6542c..7b6020ac 100644
--- a/wx+/file_drop.h
+++ b/wx+/file_drop.h
@@ -13,6 +13,8 @@
namespace zen
{
//register simple file drop event (without issue of freezing dialogs and without wxFileDropTarget overdesign)
+//CAVEAT: a drop target window must not be directly or indirectly contained within a wxStaticBoxSizer until the following wxGTK bug
+//is fixed. According to wxWidgets release cycles this is expected to be: never http://trac.wxwidgets.org/ticket/2763
//1. setup a window to emit EVENT_DROP_FILE
void setupFileDrop(wxWindow& wnd);
@@ -39,7 +41,8 @@ void setupFileDrop(wxWindow& wnd);
-
+namespace impl
+{
inline
wxEventType createNewEventType()
{
@@ -47,9 +50,11 @@ wxEventType createNewEventType()
static wxEventType dummy = wxNewEventType();
return dummy;
}
+}
+
//define new event type
-const wxEventType EVENT_DROP_FILE = createNewEventType();
+const wxEventType EVENT_DROP_FILE = impl::createNewEventType();
class FileDropEvent : public wxCommandEvent
{
@@ -78,6 +83,8 @@ typedef void (wxEvtHandler::*FileDropEventFunction)(FileDropEvent&);
(wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FileDropEventFunction, &func)
+namespace impl
+{
class WindowDropTarget : public wxFileDropTarget
{
public:
@@ -99,12 +106,13 @@ private:
wxWindow& dropWindow_;
};
+}
inline
void setupFileDrop(wxWindow& wnd)
{
- wnd.SetDropTarget(new WindowDropTarget(wnd)); //takes ownership
+ wnd.SetDropTarget(new impl::WindowDropTarget(wnd)); //takes ownership
}
}
diff --git a/wx+/grid.cpp b/wx+/grid.cpp
index 22a8bba1..ff45224d 100644
--- a/wx+/grid.cpp
+++ b/wx+/grid.cpp
@@ -41,11 +41,10 @@ namespace
{
//------------ 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 = 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_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]
@@ -200,10 +199,10 @@ void GridData::drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bo
{
if (selected)
{
- if (hasFocus)
- dc.GradientFillLinear(rect, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST);
- else
- dc.GradientFillLinear(rect, COLOR_SELECTION_GRADIENT_NO_FOCUS_FROM, COLOR_SELECTION_GRADIENT_NO_FOCUS_TO, wxEAST);
+ //if (hasFocus)
+ dc.GradientFillLinear(rect, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST);
+ //else -> doesn't look too good...
+ // dc.GradientFillLinear(rect, COLOR_SELECTION_GRADIENT_NO_FOCUS_FROM, COLOR_SELECTION_GRADIENT_NO_FOCUS_TO, wxEAST);
}
else
clearArea(dc, rect, backgroundColor);
@@ -323,7 +322,6 @@ void GridData::drawColumnLabelText(wxDC& dc, const wxRect& rect, const wxString&
}
//----------------------------------------------------------------------------------------------------------------
-
/*
SubWindow
/|\
@@ -495,7 +493,8 @@ class Grid::RowLabelWin : public SubWindow
public:
RowLabelWin(Grid& parent) :
SubWindow(parent),
- rowHeight(DEFAULT_ROW_HEIGHT) {}
+ rowHeight(parent.GetCharHeight() + 2 + 1) {} //default height; don't call any functions on "parent" other than those from wxWindow during construction!
+ //2 for some more space, 1 for bottom border (gives 15 + 2 + 1 on Windows, 17 + 2 + 1 on Ubuntu)
int getBestWidth(ptrdiff_t rowFrom, ptrdiff_t rowTo)
{
@@ -509,13 +508,12 @@ public:
size_t getLogicalHeight() const { return refParent().getRowCount() * rowHeight; }
- ptrdiff_t getRowAtPos(ptrdiff_t posY) const //returns < 0 if row not found
+ ptrdiff_t getRowAtPos(ptrdiff_t posY) const //returns < 0 on invalid input, else row number within: [0, rowCount]; rowCount if out of range
{
if (posY >= 0 && rowHeight > 0)
{
const size_t row = posY / rowHeight;
- if (row < refParent().getRowCount())
- return row;
+ return std::min(row, refParent().getRowCount());
}
return -1;
}
@@ -912,11 +910,14 @@ public:
ColLabelWin& colLabelWin) : SubWindow(parent),
rowLabelWin_(rowLabelWin),
colLabelWin_(colLabelWin),
- selectionAnchor(0)
+ selectionAnchor(0),
+ gridUpdatePending(false)
{
- Connect(EVENT_GRID_HAS_SCROLLED, wxCommandEventHandler(MainWin::updateAfterScroll), nullptr, this);
+ Connect(EVENT_GRID_HAS_SCROLLED, wxEventHandler(MainWin::onRequestWindowUpdate), nullptr, this);
}
+ ~MainWin() { assert(!gridUpdatePending); }
+
void makeRowVisible(size_t row)
{
const wxRect labelRect = rowLabelWin_.getRowLabelArea(row); //returns empty rect if column not found
@@ -978,7 +979,7 @@ private:
const int rowHeight = rowLabelWin_.getRowHeight();
- //why again aren't we using RowLabelWin::getRowsOnClient() here?
+ //why again aren't we using RowLabelWin::getRowsOnClient() here?
const wxPoint topLeft = refParent().CalcUnscrolledPosition(rect.GetTopLeft());
const wxPoint bottomRight = refParent().CalcUnscrolledPosition(rect.GetBottomRight());
@@ -1026,15 +1027,15 @@ private:
}
}
- void drawBackground(GridData& prov, wxDC& dc, const wxRect& rect, int row, size_t compPos)
+ void drawBackground(GridData& prov, wxDC& dc, const wxRect& rect, size_t row, size_t compPos)
{
Grid& grid = refParent();
//check if user is currently selecting with mouse
bool drawSelection = grid.isSelected(row, compPos);
if (activeSelection)
{
- const int rowFrom = std::min(activeSelection->getStartRow(), activeSelection->getCurrentRow());
- const int rowTo = std::max(activeSelection->getStartRow(), activeSelection->getCurrentRow());
+ const size_t rowFrom = std::min(activeSelection->getStartRow(), activeSelection->getCurrentRow());
+ const size_t rowTo = std::max(activeSelection->getStartRow(), activeSelection->getCurrentRow());
if (compPos == activeSelection->getComponentPos() && rowFrom <= row && row <= rowTo)
drawSelection = activeSelection->isPositiveSelect(); //overwrite default
@@ -1051,13 +1052,16 @@ private:
virtual void onMouseLeftDouble(wxMouseEvent& event)
{
const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
- const auto row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 if no row at this position
- const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns (column type, compPos)
+ const auto row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
+ if (row >= 0)
+ {
+ const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns (column type, compPos)
- const ColumnType colType = colInfo ? colInfo->first : DUMMY_COLUMN_TYPE;
- const ptrdiff_t compPos = colInfo ? colInfo->second : -1;
- //client is interested in all double-clicks, even those outside of the grid!
- sendEventNow(GridClickEvent(EVENT_GRID_MOUSE_LEFT_DOUBLE, event, row, colType, compPos));
+ const ColumnType colType = colInfo ? colInfo->first : DUMMY_COLUMN_TYPE;
+ const ptrdiff_t compPos = colInfo ? colInfo->second : -1;
+ //client is interested in all double-clicks, even those outside of the grid!
+ sendEventNow(GridClickEvent(EVENT_GRID_MOUSE_LEFT_DOUBLE, event, row, colType, compPos));
+ }
event.Skip();
}
@@ -1067,76 +1071,83 @@ private:
SetFocus();
const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
-
- const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 if no row at this position
- const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns (column type, compPos)
- const ColumnType colType = colInfo ? colInfo->first : DUMMY_COLUMN_TYPE;
- const ptrdiff_t compPos = colInfo ? colInfo->second : -1;
-
- //notify event
- GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, colType, compPos);
- if (!sendEventNow(mouseEvent)) //if event was not processed externally...
+ const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
+ if (row >= 0)
{
+ const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns (column type, compPos)
+ const ColumnType colType = colInfo ? colInfo->first : DUMMY_COLUMN_TYPE;
+ const ptrdiff_t compPos = colInfo ? colInfo->second : -1;
+
if (!event.RightDown() || !refParent().isSelected(row, compPos)) //do NOT start a new selection if user right-clicks on a selected area!
{
- if (row >= 0 && compPos >= 0)
- cursor = std::make_pair(row, compPos);
-
if (event.ControlDown())
{
- if (row >= 0 && compPos >= 0)
+ if (compPos >= 0)
activeSelection.reset(new MouseSelection(*this, row, compPos, !refParent().isSelected(row, compPos)));
- selectionAnchor = cursor.first; //[!] anchor is coupled with cursor, *not* row
}
else if (event.ShiftDown())
{
- if (row >= 0 && compPos >= 0)
+ if (compPos >= 0)
activeSelection.reset(new MouseSelection(*this, selectionAnchor, compPos, true));
- else
- selectionAnchor = cursor.first;
- refParent().clearSelectionAll();
+ refParent().clearSelectionAllAndNotify();
}
else
{
- if (row >= 0 && compPos >= 0)
+ if (compPos >= 0)
activeSelection.reset(new MouseSelection(*this, row, compPos, true));
- selectionAnchor = cursor.first;
- refParent().clearSelectionAll();
+ refParent().clearSelectionAllAndNotify();
}
}
+
+ //notify event *after* potential "clearSelectionAllAndNotify()" above: a client should first receive a GridRangeSelectEvent for clearing the grid, if necessary,
+ //then GridClickEvent and the associated GridRangeSelectEvent one after the other
+ GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, colType, compPos);
+ sendEventNow(mouseEvent);
+
Refresh();
}
-
event.Skip(); //allow changing focus
}
void onMouseUp(wxMouseEvent& event)
{
- //const int currentRow = clientPosToRow(event.GetPosition()); -> this one may point to row which is not in visible area!
-
if (activeSelection)
{
- const auto rowFrom = activeSelection->getStartRow();
- const auto rowTo = activeSelection->getCurrentRow();
- const auto compPos = activeSelection->getComponentPos();
- const bool positive = activeSelection->isPositiveSelect();
-
- 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);
+ const size_t rowCount = refParent().getRowCount();
+ if (rowCount > 0)
+ {
+ if (activeSelection->getCurrentRow() < rowCount)
+ {
+ cursor.first = activeSelection->getCurrentRow();
+ selectionAnchor = activeSelection->getStartRow(); //allowed to be "out of range"
+ }
+ else if (activeSelection->getStartRow() < rowCount) //don't change cursor if "to" and "from" are out of range
+ {
+ cursor.first = rowCount - 1;
+ selectionAnchor = activeSelection->getStartRow(); //allowed to be "out of range"
+ }
+ else //total selection "out of range"
+ selectionAnchor = cursor.first;
+ }
+ //slight deviation from Explorer: change cursor while dragging mouse! -> unify behavior with shift + direction keys
+ refParent().selectRangeAndNotify(activeSelection->getStartRow (), //from
+ activeSelection->getCurrentRow(), //to
+ activeSelection->getComponentPos(),
+ activeSelection->isPositiveSelect());
activeSelection.reset();
}
//this one may point to row which is not in visible area!
const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
- const auto row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 if no row at this position
+ const auto row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns optional pair (column type, compPos)
const ColumnType colType = colInfo ? colInfo->first : DUMMY_COLUMN_TYPE; //we probably should notify even if colInfo is invalid!
const ptrdiff_t compPos = colInfo ? colInfo->second : -1;
- //notify event
+ //notify click event after the range selection! e.g. this makes sure the selection is applied before showing a context menu
sendEventNow(GridClickEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, event, row, colType, compPos));
Refresh();
@@ -1158,11 +1169,12 @@ private:
//change tooltip
const wxString toolTip = [&]() -> wxString
{
+ const ptrdiff_t rowCount = refParent().getRowCount();
const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
- const auto row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 if no row at this position
+ const auto row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns (column type, compPos)
- if (colInfo && row >= 0)
+ if (colInfo && 0 <= row && row < rowCount)
{
if (auto prov = refParent().getDataProvider(colInfo->second))
return prov->getToolTip(row, colInfo->first);
@@ -1212,7 +1224,7 @@ private:
auto& comp = refParent().comp;
std::for_each(comp.begin(), comp.end(), [](Grid::Component& c) { c.selection.clear(); }); //clear selection, do NOT fire event
- refParent().selectRange(selectionAnchor, row, cursor.second); //set new selection + fire event
+ refParent().selectRangeAndNotify(selectionAnchor, row, cursor.second); //set new selection + fire event
cursor.first = row; //don't call setCursor() since it writes to "selectionAnchor"!
this->makeRowVisible(row);
@@ -1303,7 +1315,7 @@ private:
case 'A': //Ctrl + A - select all
if (event.ControlDown())
- refParent().selectRange(0, rowCount, cursor.second);
+ refParent().selectRangeAndNotify(0, rowCount, cursor.second);
break;
case WXK_NUMPAD_ADD: //CTRL + '+' - auto-size all
@@ -1320,7 +1332,7 @@ private:
class MouseSelection : private wxEvtHandler
{
public:
- MouseSelection(MainWin& wnd, ptrdiff_t rowStart, size_t compPos, bool positiveSelect) :
+ MouseSelection(MainWin& wnd, size_t rowStart, size_t compPos, bool positiveSelect) :
wnd_(wnd), rowStart_(rowStart), compPos_(compPos), rowCurrent_(rowStart), positiveSelect_(positiveSelect), toScrollX(0), toScrollY(0),
tickCountLast(getTicks()),
ticksPerSec_(ticksPerSec())
@@ -1332,10 +1344,10 @@ private:
}
~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); }
- ptrdiff_t getStartRow () const { return rowStart_; }
- size_t getComponentPos () const { return compPos_; }
- ptrdiff_t getCurrentRow () const { return rowCurrent_; }
- bool isPositiveSelect() const { return positiveSelect_; } //are we selecting or unselecting?
+ size_t getStartRow () const { return rowStart_; }
+ size_t getComponentPos () const { return compPos_; }
+ size_t getCurrentRow () const { return rowCurrent_; }
+ bool isPositiveSelect() const { return positiveSelect_; } //are we selecting or unselecting?
void evalMousePos()
{
@@ -1343,7 +1355,7 @@ private:
if (ticksPerSec_ > 0)
{
const TickVal now = getTicks(); //0 on error
- deltaTime = static_cast<double>(now - tickCountLast) / ticksPerSec_; //unit: [sec]
+ deltaTime = static_cast<double>(dist(tickCountLast, now)) / ticksPerSec_; //unit: [sec]
tickCountLast = now;
}
@@ -1389,18 +1401,14 @@ private:
wxPoint clientPosTrimmed = clientPos;
numeric::confine(clientPosTrimmed.y, 0, clientSize.GetHeight() - 1); //do not select row outside client window!
- wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed);
-
- //make sure "current row" is always at a valid position while moving!
- ptrdiff_t currentRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 if no row at this position
- if (currentRow < 0)
- currentRow = wnd_.refParent().getRowCount() - 1; //seems, we hit the empty space at the end: empty size covered!
-
- if (currentRow >= 0 && rowCurrent_ != currentRow)
- {
- rowCurrent_ = currentRow;
- wnd_.Refresh();
- }
+ const wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed);
+ const ptrdiff_t newRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
+ if (newRow >= 0)
+ if (rowCurrent_ != newRow)
+ {
+ rowCurrent_ = newRow;
+ wnd_.Refresh();
+ }
}
}
@@ -1408,7 +1416,7 @@ private:
void onTimer(wxEvent& event) { evalMousePos(); }
MainWin& wnd_;
- const ptrdiff_t rowStart_;
+ const size_t rowStart_;
const size_t compPos_;
ptrdiff_t rowCurrent_;
const bool positiveSelect_;
@@ -1429,12 +1437,22 @@ private:
//which *first* calls us, MainWin::ScrollWindow(), and *then* internally updates m_yScrollPosition
//=> we cannot use CalcUnscrolledPosition() here which gives the wrong/outdated value!!!
//=> we need to update asynchronously:
- wxCommandEvent scrollEvent(EVENT_GRID_HAS_SCROLLED);
- AddPendingEvent(scrollEvent); //asynchronously call updateAfterScroll()
+ //=> don't use plain async event => severe performance issues on wxGTK!
+ //=> can't use idle event neither: too few idle events on Windows, e.g. NO idle events while mouse drag-scrolling!
+ //=> solution: send single async event at most!
+ if (!gridUpdatePending) //without guarding, the number of outstanding async events can get very high during scrolling!! test case: Ubuntu: 170; Windows: 20
+ {
+ gridUpdatePending = true;
+ wxCommandEvent scrollEvent(EVENT_GRID_HAS_SCROLLED);
+ AddPendingEvent(scrollEvent); //asynchronously call updateAfterScroll()
+ }
}
- void updateAfterScroll(wxCommandEvent&)
+ void onRequestWindowUpdate(wxEvent& event)
{
+ ZEN_ON_SCOPE_EXIT(gridUpdatePending = false);
+ assert(gridUpdatePending);
+
refParent().updateWindowSizes(false); //row label width has changed -> do *not* update scrollbars: recursion on wxGTK! -> still a problem, now that we're called async??
rowLabelWin_.Update(); //update while dragging scroll thumb
}
@@ -1446,6 +1464,7 @@ private:
std::pair<ptrdiff_t, ptrdiff_t> cursor; //(row, component position), always valid! still unsigned type to facilitate "onKeyDown()"
size_t selectionAnchor;
+ bool gridUpdatePending;
};
//----------------------------------------------------------------------------------------------------------------
@@ -1642,15 +1661,8 @@ void Grid::scrollDelta(int deltaX, int deltaY)
scrollPosX += deltaX;
scrollPosY += deltaY;
- scrollPosX = std::max(0, scrollPosX); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"!
- scrollPosY = std::max(0, scrollPosY); //
-
- //const int unitsTotalX = GetScrollLines(wxHORIZONTAL);
- //const int unitsTotalY = GetScrollLines(wxVERTICAL);
-
- //if (unitsTotalX <= 0 || unitsTotalY <= 0) return; -> premature
- //numeric::confine(scrollPosX, 0, unitsTotalX - 1); //make sure scroll target is in valid range
- //numeric::confine(scrollPosY, 0, unitsTotalY - 1); //
+ scrollPosX = std::max(0, scrollPosX); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"!
+ scrollPosY = std::max(0, scrollPosY); //
Scroll(scrollPosX, scrollPosY);
updateWindowSizes(); //may show horizontal scroll bar
@@ -1710,7 +1722,7 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr, size_
visibleCols.push_back(Grid::VisibleColumn(ca.type_, ca.offset_, ca.stretch_));
});
- //set ownership of visible columns
+ //"ownership" of visible columns is now within Grid
comp[compPos].visibleCols = visibleCols;
updateWindowSizes();
@@ -1723,34 +1735,30 @@ std::vector<Grid::ColumnAttribute> Grid::getColumnConfig(size_t compPos) const
{
if (compPos < comp.size())
{
- auto iterVcols = comp[compPos].visibleCols.begin();
- auto iterVcolsend = comp[compPos].visibleCols.end();
-
- std::set<ColumnType> visibleTypes;
- std::transform(iterVcols, iterVcolsend, std::inserter(visibleTypes, visibleTypes.begin()),
- [](const VisibleColumn& vc) { return vc.type_; });
-
//get non-visible columns (+ outdated visible ones)
std::vector<ColumnAttribute> output = comp[compPos].oldColAttributes;
+ auto iterVcols = comp[compPos].visibleCols.begin();
+ auto iterVcolsend = comp[compPos].visibleCols.end();
+
//update visible columns but keep order of non-visible ones!
std::for_each(output.begin(), output.end(),
[&](ColumnAttribute& ca)
{
- if (visibleTypes.find(ca.type_) != visibleTypes.end())
+ if (ca.visible_)
{
if (iterVcols != iterVcolsend)
{
- ca.visible_ = true; //paranoia
ca.type_ = iterVcols->type_;
ca.stretch_ = iterVcols->stretch_;
ca.offset_ = iterVcols->offset_;
++iterVcols;
}
+ else
+ assert(false);
}
- else
- ca.visible_ = false; //paranoia
});
+ assert(iterVcols == iterVcolsend);
return output;
}
@@ -1835,13 +1843,16 @@ void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range,
WXLRESULT Grid::MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
- //we land here if wxWindowMSW::MSWWindowProc() couldn't handle the message
- //http://msdn.microsoft.com/en-us/library/windows/desktop/ms645614(v=vs.85).aspx
+ //we land here if wxWindowMSW::MSWWindowProc() couldn't handle the message
+ //http://msdn.microsoft.com/en-us/library/windows/desktop/ms645614(v=vs.85).aspx
if (nMsg == WM_MOUSEHWHEEL) //horizontal wheel
{
const int distance = GET_WHEEL_DELTA_WPARAM(wParam);
const int delta = WHEEL_DELTA;
- const int rotations = distance / delta;
+ int rotations = distance / delta;
+
+ if (GetLayoutDirection() == wxLayout_RightToLeft)
+ rotations = -rotations;
static int linesPerRotation = -1;
if (linesPerRotation < 0)
@@ -1849,7 +1860,7 @@ WXLRESULT Grid::MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
linesPerRotation = 3;
scrollDelta(rotations * linesPerRotation, 0); //in scroll units
- return 0; //"If an application processes this message, it should return zero."
+ return 0; //"If an application processes this message, it should return zero."
}
return wxScrolledWindow::MSWDefWindowProc(nMsg, wParam, lParam);
@@ -1861,6 +1872,7 @@ wxWindow& Grid::getCornerWin () { return *cornerWin_; }
wxWindow& Grid::getRowLabelWin() { return *rowLabelWin_; }
wxWindow& Grid::getColLabelWin() { return *colLabelWin_; }
wxWindow& Grid::getMainWin () { return *mainWin_; }
+const wxWindow& Grid::getMainWin() const { return *mainWin_; }
wxRect Grid::getColumnLabelArea(ColumnType colType, size_t compPos) const
@@ -2018,16 +2030,6 @@ 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())
@@ -2036,7 +2038,7 @@ void Grid::setGridCursor(size_t row, size_t 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
+ selectRangeAndNotify(row, row, compPos); //set new selection + fire event
mainWin_->Refresh();
rowLabelWin_->Refresh(); //row labels! (Kubuntu)
@@ -2044,14 +2046,22 @@ void Grid::setGridCursor(size_t row, size_t compPos)
}
-void Grid::selectRange(ptrdiff_t rowFrom, ptrdiff_t rowTo, size_t compPos, bool positive)
+void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, size_t compPos, bool positive)
{
if (compPos < comp.size())
{
- comp[compPos].selection.selectRange(rowFrom, rowTo, positive);
+ //sort + convert to half-open range
+ auto rowFirst = std::min(rowFrom, rowTo);
+ auto rowLast = std::max(rowFrom, rowTo) + 1;
+
+ const size_t rowCount = getRowCount();
+ numeric::confine<ptrdiff_t>(rowFirst, 0, rowCount);
+ numeric::confine<ptrdiff_t>(rowLast, 0, rowCount);
+
+ comp[compPos].selection.selectRange(rowFirst, rowLast, positive);
//notify event
- GridRangeSelectEvent selectionEvent(rowFrom, rowTo, compPos, positive);
+ GridRangeSelectEvent selectionEvent(rowFirst, rowLast, compPos, positive);
if (wxEvtHandler* evtHandler = GetEventHandler())
evtHandler->ProcessEvent(selectionEvent);
@@ -2060,22 +2070,31 @@ void Grid::selectRange(ptrdiff_t rowFrom, ptrdiff_t rowTo, size_t compPos, bool
}
-void Grid::clearSelectionAll()
+void Grid::clearSelection(bool emitSelectRangeEvent, size_t compPos)
{
- for (auto iter = comp.begin(); iter != comp.end(); ++iter)
+ if (compPos < comp.size())
{
- Grid::Component& c = *iter;
- c.selection.clear();
+ comp[compPos].selection.clear();
+ mainWin_->Refresh();
- //notify event
- const size_t compPos = iter - comp.begin();
- GridRangeSelectEvent unselectionEvent(-1, -1, compPos, false);
- if (wxEvtHandler* evtHandler = GetEventHandler())
- evtHandler->ProcessEvent(unselectionEvent);
+ if (emitSelectRangeEvent)
+ {
+ //notify event, even if we're not triggered by user interaction
+ GridRangeSelectEvent unselectionEvent(0, 0, compPos, false);
+ if (wxEvtHandler* evtHandler = GetEventHandler())
+ evtHandler->ProcessEvent(unselectionEvent);
+ }
}
}
+void Grid::clearSelectionAllAndNotify()
+{
+ for (size_t compPos = 0; compPos < comp.size(); ++compPos)
+ clearSelection(true, compPos);
+}
+
+
void Grid::scrollTo(size_t row)
{
const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if column not found
@@ -2111,7 +2130,7 @@ ptrdiff_t Grid::getBestColumnSize(size_t col, size_t compPos) const
{
if (compPos < comp.size())
{
- auto& visibleCols = comp[compPos].visibleCols;
+ const auto& visibleCols = comp[compPos].visibleCols;
auto dataView = comp[compPos].dataView_;
if (dataView && col < visibleCols.size())
{
@@ -2140,26 +2159,35 @@ void Grid::setColWidthAndNotify(ptrdiff_t width, size_t col, size_t compPos, boo
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;
-
- //CAVEAT:
- //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
+ const ptrdiff_t stretchedWithCol = getColStretchedWidth(vcRs.stretch_, stretchTotal, mainWinWidth);
+
+ vcRs.offset_ = width - stretchedWithCol; //width := stretchedWidth + offset
+
+ //CAVEATS:
+ //I. fixed-size columns: normalize offset so that resulting width is at least COLUMN_MIN_WIDTH: this is NOT enforced by getColWidths()!
+ //II. stretched columns: do not allow user to set offsets so small that they result in negative (non-normalized) widths: this gives an
+ //unusual delay when enlarging the column again later
+ vcRs.offset_ = std::max(vcRs.offset_, COLUMN_MIN_WIDTH - stretchedWithCol);
+
+ //III. resizing any column should normalize *all* other stretched columns' offsets considering current mainWinWidth!
+ // test case:
+ //1. have columns, both fixed-size and stretched, fit whole window width
+ //2. shrink main window width so that horizontal scrollbars are shown despite the streched column
+ //3. shrink a fixed-size column so that the scrollbars vanish and columns cover full width again
+ //4. now verify that the stretched column is resizing immediately if main window is enlarged again
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
+ if (vc.stretch_ > 0) //normalize stretched columns only
+ {
+ const ptrdiff_t stretchedWidth = Grid::getColStretchedWidth(vc.stretch_, stretchTotal, mainWinWidth);
+ vc.offset_ = std::max(vc.offset_, COLUMN_MIN_WIDTH - stretchedWidth);
+ }
});
});
- GridColumnResizeEvent sizeEvent(offset, vcRs.type_, compPos);
+ GridColumnResizeEvent sizeEvent(vcRs.offset_, vcRs.type_, compPos);
if (wxEvtHandler* evtHandler = GetEventHandler())
{
if (notifyAsync)
@@ -2168,6 +2196,8 @@ void Grid::setColWidthAndNotify(ptrdiff_t width, size_t col, size_t compPos, boo
evtHandler->ProcessEvent(sizeEvent);
}
}
+ else
+ assert(false);
}
@@ -2200,7 +2230,7 @@ ptrdiff_t Grid::getStretchTotal() const //sum of all stretch factors
}
-ptrdiff_t Grid::getColStretchedWidth(ptrdiff_t stretch, ptrdiff_t stretchTotal, int mainWinWidth) //final width = stretchedWidth + (normalized) offset
+ptrdiff_t Grid::getColStretchedWidth(ptrdiff_t stretch, ptrdiff_t stretchTotal, int mainWinWidth) //final width := stretchedWidth + (normalized) offset
{
return stretchTotal > 0 ? mainWinWidth * stretch / stretchTotal : 0; //rounds down! => not all of clientWidth is correctly distributed according to stretch factors
}
@@ -2225,8 +2255,12 @@ std::vector<std::vector<Grid::ColumnWidth>> Grid::getColWidths(int mainWinWidth)
std::for_each(c.visibleCols.begin(), c.visibleCols.end(), [&](const VisibleColumn& vc)
{
- 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));
+ ptrdiff_t widthNormalized = Grid::getColStretchedWidth(vc.stretch_, stretchTotal, mainWinWidth) + vc.offset_;
+
+ if (vc.stretch_ > 0)
+ widthNormalized = std::max(widthNormalized, static_cast<ptrdiff_t>(COLUMN_MIN_WIDTH)); //normalization really needed here: e.g. smaller main window would result in negative width
+ else
+ widthNormalized = std::max(widthNormalized, static_cast<ptrdiff_t>(0)); //support smaller width than COLUMN_MIN_WIDTH if set via configuration
compWidths.push_back(Grid::ColumnWidth(vc.type_, widthNormalized));
});
@@ -2245,3 +2279,4 @@ ptrdiff_t Grid::getColWidthsSum(int mainWinWidth) const
[](ptrdiff_t val2, const Grid::ColumnWidth& cw) { return val2 + cw.width_; });
});
};
+
diff --git a/wx+/grid.h b/wx+/grid.h
index 89926e00..11317fd8 100644
--- a/wx+/grid.h
+++ b/wx+/grid.h
@@ -20,7 +20,7 @@ namespace zen
{
typedef enum { DUMMY_COLUMN_TYPE = static_cast<unsigned int>(-1) } ColumnType;
-//----- Events -----------------------------------------------------------------------------------------------
+//----- events ------------------------------------------------------------------------
extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridClickEvent
extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_RIGHT; //
extern const wxEventType EVENT_GRID_COL_RESIZE; //generates: GridColumnResizeEvent
@@ -33,17 +33,14 @@ extern const wxEventType EVENT_GRID_MOUSE_RIGHT_UP; //
extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridRangeSelectEvent
//NOTE: neither first nor second row need to match EVENT_GRID_MOUSE_LEFT_DOWN/EVENT_GRID_MOUSE_LEFT_UP: user holding SHIFT; moving out of window...
-//=> range always specifies *valid* rows
//example: wnd.Connect(EVENT_GRID_COL_LABEL_LEFT_CLICK, GridClickEventHandler(MyDlg::OnLeftClick), nullptr, this);
-
struct GridClickEvent : public wxMouseEvent
{
- GridClickEvent(wxEventType et, const wxMouseEvent& me, int row, ColumnType colType, size_t compPos) : wxMouseEvent(me), row_(row), colType_(colType), compPos_(compPos) { SetEventType(et); }
+ GridClickEvent(wxEventType et, const wxMouseEvent& me, ptrdiff_t row, ColumnType colType, size_t compPos) : wxMouseEvent(me), row_(row), colType_(colType), compPos_(compPos) { SetEventType(et); }
virtual wxEvent* Clone() const { return new GridClickEvent(*this); }
-
- const int row_;
+ const ptrdiff_t row_; //-1 for invalid position, >= rowCount if out of range
const ColumnType colType_;
const size_t compPos_;
};
@@ -60,11 +57,11 @@ struct GridColumnResizeEvent : public wxCommandEvent
struct GridRangeSelectEvent : public wxCommandEvent
{
- GridRangeSelectEvent(int rowFrom, int rowTo, size_t compPos, bool positive) : wxCommandEvent(EVENT_GRID_SELECT_RANGE), rowFrom_(rowFrom), rowTo_(rowTo), compPos_(compPos), positive_(positive) {}
+ GridRangeSelectEvent(size_t rowFirst, size_t rowLast, size_t compPos, bool positive) : wxCommandEvent(EVENT_GRID_SELECT_RANGE), rowFirst_(rowFirst), rowLast_(rowLast), compPos_(compPos), positive_(positive) { assert(rowFirst <= rowLast); }
virtual wxEvent* Clone() const { return new GridRangeSelectEvent(*this); }
- const int rowFrom_;
- const int rowTo_;
+ const size_t rowFirst_; //selected range: [rowFirst_, rowLast_)
+ const size_t rowLast_; //range is empty when clearing selection
const size_t compPos_;
const bool positive_;
};
@@ -168,7 +165,7 @@ public:
void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical);
std::vector<size_t> getSelectedRows(size_t compPos = 0) const;
- void clearSelection(size_t compPos = 0);
+ void clearSelection(bool emitSelectRangeEvent = true, size_t compPos = 0); //turn off range selection event when calling this function in an event handler to avoid recursion!
void scrollDelta(int deltaX, int deltaY); //in scroll units
@@ -176,8 +173,9 @@ public:
wxWindow& getRowLabelWin();
wxWindow& getColLabelWin();
wxWindow& getMainWin ();
+ const wxWindow& getMainWin() const;
- ptrdiff_t getRowAtPos(int posY) const; //returns < 0 if column not found; absolute coordinates!
+ ptrdiff_t getRowAtPos(int posY) const; //return -1 for invalid position, >= rowCount if out of range; absolute coordinates!
Opt<std::pair<ColumnType, size_t>> getColumnAtPos(int posX) const; //returns (column type, component pos)
wxRect getCellArea(size_t row, ColumnType colType, size_t compPos = 0) const; //returns empty rect if column not found; absolute coordinates!
@@ -185,7 +183,7 @@ public:
void enableColumnMove (bool value, size_t compPos = 0) { if (compPos < comp.size()) comp[compPos].allowColumnMove = value; }
void enableColumnResize(bool value, size_t compPos = 0) { if (compPos < comp.size()) comp[compPos].allowColumnResize = value; }
- void setGridCursor(size_t row, size_t compPos = 0); //set + show + select cursor
+ void setGridCursor(size_t row, size_t compPos = 0); //set + show + select cursor (+ emit range selection event)
std::pair<size_t, size_t> getGridCursor() const; //(row, component pos)
void scrollTo(size_t row);
@@ -193,6 +191,7 @@ public:
virtual void Refresh(bool eraseBackground = true, const wxRect* rect = nullptr);
virtual bool Enable( bool enable = true) { Refresh(); return wxScrolledWindow::Enable(enable); }
void autoSizeColumns(size_t compPos = 0);
+ //############################################################################################################
private:
void onPaintEvent(wxPaintEvent& event);
@@ -235,15 +234,16 @@ private:
bool isSelected(size_t row) const { return row < rowSelectionValue.size() ? rowSelectionValue[row] != 0 : false; }
- void selectRange(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive = true) //select [rowFrom, rowTo], very tolerant: trims and swaps if required!
+ void selectRange(size_t rowFirst, size_t rowLast, bool positive = true) //select [rowFirst, rowLast), trims if required!
{
- auto rowFirst = std::min(rowFrom, rowTo);
- auto rowLast = std::max(rowFrom, rowTo) + 1;
-
- numeric::confine<ptrdiff_t>(rowFirst, 0, rowSelectionValue.size());
- numeric::confine<ptrdiff_t>(rowLast, 0, rowSelectionValue.size());
-
- std::fill(rowSelectionValue.begin() + rowFirst, rowSelectionValue.begin() + rowLast, positive);
+ if (rowFirst <= rowLast)
+ {
+ numeric::confine<size_t>(rowFirst, 0, rowSelectionValue.size());
+ numeric::confine<size_t>(rowLast, 0, rowSelectionValue.size());
+
+ std::fill(rowSelectionValue.begin() + rowFirst, rowSelectionValue.begin() + rowLast, positive);
+ }
+ else assert(false);
}
private:
@@ -294,50 +294,11 @@ private:
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
- void selectRange(ptrdiff_t rowFrom, ptrdiff_t rowTo, size_t compPos, bool positive = true); //select range + notify event!
+ void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, size_t compPos, bool positive = true); //select inclusive range [rowFrom, rowTo] + notify event!
- void clearSelectionAll(); //clear selection + notify event
+ void clearSelectionAllAndNotify(); //clear selection + notify event
bool isSelected(size_t row, size_t compPos) const { return compPos < comp.size() ? comp[compPos].selection.isSelected(row) : false; }
@@ -359,9 +320,9 @@ private:
/*
Visual layout:
------------------------------------------------
- |CornerWin | RowLabelWin: |
+ |CornerWin | ColLabelWin: |
|-------------------------- Comp1 | Comp2 ... | row label and main window are vertically tiled into one or more "components"
- |ColLabelWin | MainWin: |
+ |RowLabelWin | MainWin: |
------------------------------------------------
*/
CornerWin* cornerWin_;
diff --git a/wx+/zlib_wrap.h b/wx+/zlib_wrap.h
index a5ad2cb1..c6545c9d 100644
--- a/wx+/zlib_wrap.h
+++ b/wx+/zlib_wrap.h
@@ -111,7 +111,7 @@ BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError
const size_t bytesWritten = impl::zlib_decompress(&*stream.begin() + sizeof(uncompressedSize),
stream.size() - sizeof(uncompressedSize),
&*contOut.begin(),
- uncompressedSize); //throw ZlibInternalError
+ static_cast<size_t>(uncompressedSize)); //throw ZlibInternalError
if (bytesWritten != static_cast<size_t>(uncompressedSize))
throw ZlibInternalError();
}
diff --git a/zen/FindFilePlus/FindFilePlus.vcxproj b/zen/FindFilePlus/FindFilePlus.vcxproj
index a50239ab..b94174ac 100644
--- a/zen/FindFilePlus/FindFilePlus.vcxproj
+++ b/zen/FindFilePlus/FindFilePlus.vcxproj
@@ -102,6 +102,7 @@
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -113,6 +114,7 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -135,6 +137,7 @@
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -146,6 +149,7 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -179,6 +183,7 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -215,9 +220,11 @@
</ProfileGuidedDatabase>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
+ <ClCompile Include="..\debug_memory_leaks.cpp" />
<ClCompile Include="dll_main.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp
index 5fc1a538..ec56b0bc 100644
--- a/zen/FindFilePlus/find_file_plus.cpp
+++ b/zen/FindFilePlus/find_file_plus.cpp
@@ -321,7 +321,7 @@ void FileSearcher::readDirImpl(FileInformation& output) //throw NtFileError
nextEntryOffset += dirInfo.NextEntryOffset;
- auto toFileTime = [](const LARGE_INTEGER & rawTime) -> FILETIME
+ auto toFileTime = [](const LARGE_INTEGER& rawTime) -> FILETIME
{
FILETIME tmp = { rawTime.LowPart, rawTime.HighPart };
return tmp;
diff --git a/zen/IFileOperation/FileOperation_Vista.vcxproj b/zen/IFileOperation/FileOperation_Vista.vcxproj
index 4bd5b509..73bfd56a 100644
--- a/zen/IFileOperation/FileOperation_Vista.vcxproj
+++ b/zen/IFileOperation/FileOperation_Vista.vcxproj
@@ -99,6 +99,7 @@
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -111,6 +112,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -133,6 +135,7 @@
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DisableSpecificWarnings>4100;4996</DisableSpecificWarnings>
<AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories>
+ <SmallerTypeCheck>true</SmallerTypeCheck>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
@@ -145,6 +148,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -179,6 +183,7 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -216,9 +221,11 @@
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
+ <ClCompile Include="..\debug_memory_leaks.cpp" />
<ClCompile Include="dll_main.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp
index 3591de12..5d4cfdc9 100644
--- a/zen/IFileOperation/file_op.cpp
+++ b/zen/IFileOperation/file_op.cpp
@@ -173,16 +173,16 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError
// Set the operation flags. Turn off all UI from being shown to the user during the
// operation. This includes error, confirmation and progress dialogs.
- ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_ALLOWUNDO |
+ ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_ALLOWUNDO |
FOF_NOCONFIRMATION |
FOF_SILENT | //no progress dialog box
FOF_NOERRORUI |
- FOFX_EARLYFAILURE |
- //without FOFX_EARLYFAILURE, IFileOperationProgressSink::PostDeleteItem() will always report success, even if deletion failed!!? WTF!?
- //PerformOperations() will still succeed but set the uselessly generic GetAnyOperationsAborted() instead :(((
- //=> always set FOFX_EARLYFAILURE since we prefer good error messages over "doing as much as possible"
- //luckily for FreeFileSync we don't expect failures on individual files anyway: FreeFileSync moves files to be
- //deleted to a temporary folder first, so there is no reason why a second move (the recycling itself) should fail
+ FOFX_EARLYFAILURE |
+ //without FOFX_EARLYFAILURE, IFileOperationProgressSink::PostDeleteItem() will always report success, even if deletion failed!!? WTF!?
+ //PerformOperations() will still succeed but set the uselessly generic GetAnyOperationsAborted() instead :(((
+ //=> always set FOFX_EARLYFAILURE since we prefer good error messages over "doing as much as possible"
+ //luckily for FreeFileSync we don't expect failures on individual files anyway: FreeFileSync moves files to be
+ //deleted to a temporary folder first, so there is no reason why a second move (the recycling itself) should fail
FOF_NO_CONNECTED_ELEMENTS));
//use FOFX_RECYCLEONDELETE when Windows 8 is available!?
diff --git a/zen/basic_math.h b/zen/basic_math.h
index 7923dc5d..bd416d19 100644
--- a/zen/basic_math.h
+++ b/zen/basic_math.h
@@ -32,6 +32,8 @@ const T& max(const T& a, const T& b, const T& c);
template <class T>
void confine(T& val, const T& minVal, const T& maxVal); //make sure minVal <= val && val <= maxVal
+template <class T>
+T confineCpy(const T& val, const T& minVal, const T& maxVal);
template <class InputIterator>
std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last);
@@ -97,6 +99,7 @@ const double ln2 = 0.693147180559945309417;
template <class T> inline
T abs(T value)
{
+ //static_assert(std::is_signed<T>::value, ""); might not compile for non-built-in arithmetic types; anyway "-value" should emit compiler error or warning for unsigned types
if (value < 0)
return -value; // operator "?:" caveat: may be different type than "value"
else
@@ -132,6 +135,17 @@ const T& max(const T& a, const T& b, const T& c)
template <class T> inline
+T confineCpy(const T& val, const T& minVal, const T& maxVal)
+{
+ assert(minVal <= maxVal);
+ if (val < minVal)
+ return minVal;
+ else if (val > maxVal)
+ return maxVal;
+ return val;
+}
+
+template <class T> inline
void confine(T& val, const T& minVal, const T& maxVal) //name trim?
{
assert(minVal <= maxVal);
diff --git a/zen/debug_memory_leaks.cpp b/zen/debug_memory_leaks.cpp
new file mode 100644
index 00000000..8774d16f
--- /dev/null
+++ b/zen/debug_memory_leaks.cpp
@@ -0,0 +1,31 @@
+// **************************************************************************
+// * 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 (zenju AT gmx DOT de) - All Rights Reserved *
+// **************************************************************************
+
+//-----------Memory Leak Detection--------------------------
+//Usage: just include this file into a Visual Studio project
+
+
+#ifndef NDEBUG
+#define _CRTDBG_MAP_ALLOC //
+#include <stdlib.h> //keep this order: "The #include statements must be in the order shown here. If you change the order, the functions you use may not work properly."
+#include <crtdbg.h> //overwrites "operator new" ect; no need to include this in every compilation unit!
+//http://msdn.microsoft.com/en-us/library/e5ewb1h3(v=vs.80).aspx
+
+namespace
+{
+struct OnStartup
+{
+ OnStartup()
+ {
+ //note: wxWidgets also "activates" leak detection in the usual buggy way: it sets incomplete flags and incorrectly overwrites them rather than appending -> luckily it still seems to work!
+ int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
+ flags |= _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF;
+ _CrtSetDbgFlag(flags);
+ }
+
+} dummy;
+}
+#endif \ No newline at end of file
diff --git a/zen/debug_new.cpp b/zen/debug_minidump.cpp
index 40fb88c7..9429819f 100644
--- a/zen/debug_new.cpp
+++ b/zen/debug_minidump.cpp
@@ -4,9 +4,10 @@
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
// **************************************************************************
-#include "debug_new.h"
+#include "debug_minidump.h"
#include <string>
#include <sstream>
+#include <cassert>
#include <cstdlib> //malloc(), free()
#include "win.h" //includes "windows.h"
#include "DbgHelp.h" //available for MSC only
@@ -28,7 +29,7 @@ LONG WINAPI writeDumpOnException(EXCEPTION_POINTERS* pExceptionInfo)
MINIDUMP_EXCEPTION_INFORMATION* exceptParam = pExceptionInfo ? &exInfo : nullptr;
/*bool rv = */
- ::MiniDumpWriteDump(::GetCurrentProcess(), //__in HANDLE hProcess,
+ ::MiniDumpWriteDump(::GetCurrentProcess (), //__in HANDLE hProcess,
::GetCurrentProcessId(), //__in DWORD ProcessId,
hFile, //__in HANDLE hFile,
MiniDumpWithDataSegs, //__in MINIDUMP_TYPE DumpType, ->Standard: MiniDumpNormal, Medium: MiniDumpWithDataSegs, Full: MiniDumpWithFullMemory
@@ -38,11 +39,12 @@ LONG WINAPI writeDumpOnException(EXCEPTION_POINTERS* pExceptionInfo)
::CloseHandle(hFile);
}
+ assert(false);
return EXCEPTION_EXECUTE_HANDLER;
}
//ensure that a dump-file is written for uncaught exceptions
-struct Dummy { Dummy() { ::SetUnhandledExceptionFilter(writeDumpOnException); }} dummy;
+struct OnStartup { OnStartup() { ::SetUnhandledExceptionFilter(writeDumpOnException); }} dummy;
}
@@ -105,7 +107,7 @@ void* operator new(size_t size)
return ptr;
debug_tools::writeMinidump();
- throw debug_tools::BadAllocDetailed(size);
+ throw ::BadAllocDetailed(size);
}
void operator delete(void* ptr) { ::free(ptr); }
diff --git a/zen/debug_new.h b/zen/debug_minidump.h
index 4ef0106e..4ef0106e 100644
--- a/zen/debug_new.h
+++ b/zen/debug_minidump.h
diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp
index c02453c6..2d249af8 100644
--- a/zen/dir_watcher.cpp
+++ b/zen/dir_watcher.cpp
@@ -97,7 +97,7 @@ public:
{
boost::lock_guard<boost::mutex> dummy(lockAccess);
- //first check whether errors occured in thread
+ //first check whether errors occurred in thread
if (!errorMsg.first.empty())
{
const std::wstring msg = errorMsg.first.c_str();
@@ -125,7 +125,7 @@ private:
boost::mutex lockAccess;
std::vector<DirWatcher::Entry> changedFiles;
- std::pair<BasicWString, DWORD> errorMsg; //non-empty if errors occured in thread
+ std::pair<BasicWString, DWORD> errorMsg; //non-empty if errors occurred in thread
};
@@ -279,9 +279,12 @@ private:
virtual void onRequestRemoval(HANDLE hnd)
{
//must release hDir immediately => stop monitoring!
- worker_.interrupt();
- worker_.join(); //we assume precondition "worker.joinable()"!!!
- //now hDir should have been released
+ if (worker_.joinable()) //= join() precondition: play safe; can't trust Windows to only call-back once
+ {
+ worker_.interrupt();
+ worker_.join(); //we assume precondition "worker.joinable()"!!!
+ //now hDir should have been released
+ }
removalRequested = true;
} //don't throw!
@@ -319,8 +322,13 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError
DirWatcher::~DirWatcher()
{
- pimpl_->worker.interrupt();
- //pimpl_->worker.join(); -> we don't have time to wait... will take ~50ms anyway
+ if (pimpl_->worker.joinable()) //= thread::detach() precondition! -> may already be joined by HandleVolumeRemoval::onRequestRemoval()
+ {
+ pimpl_->worker.interrupt();
+ //if (pimpl_->worker.joinable()) pimpl_->worker.join(); -> we don't have time to wait... will take ~50ms anyway
+ pimpl_->worker.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!!
+ }
+
//caveat: exitting the app may simply kill this thread!
}
@@ -409,10 +417,11 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError
//set non-blocking mode
bool initSuccess = false;
- int flags = ::fcntl(pimpl_->notifDescr, F_GETFL);
- if (flags != -1)
- initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1;
-
+ {
+ int flags = ::fcntl(pimpl_->notifDescr, F_GETFL);
+ if (flags != -1)
+ initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1;
+ }
if (!initSuccess)
throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted());
@@ -455,13 +464,18 @@ DirWatcher::~DirWatcher()
std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError
{
//non-blocking call, see O_NONBLOCK
- std::vector<char> buffer(1024 * (sizeof(struct inotify_event) + 16));
- ssize_t bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size());
+ std::vector<char> buffer(1024 * (sizeof(struct ::inotify_event) + 16));
+
+ ssize_t bytesRead = 0;
+ do
+ {
+ bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size());
+ }
+ while (bytesRead < 0 && errno == EINTR); //"Interrupted function call; When this happens, you should try the call again."
- if (bytesRead == -1)
+ if (bytesRead < 0)
{
- if (errno == EINTR || //Interrupted function call; When this happens, you should try the call again.
- errno == EAGAIN) //Non-blocking I/O has been selected using O_NONBLOCK and no data was immediately available for reading
+ if (errno == EAGAIN) //this error is ignored in all inotify wrappers I found
return std::vector<Entry>();
throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->dirname)) + L"\n\n" + getLastErrorFormatted());
@@ -472,7 +486,7 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()
ssize_t bytePos = 0;
while (bytePos < bytesRead)
{
- struct inotify_event& evt = reinterpret_cast<struct inotify_event&>(buffer[bytePos]);
+ struct ::inotify_event& evt = reinterpret_cast<struct ::inotify_event&>(buffer[bytePos]);
if (evt.len != 0) //exclude case: deletion of "self", already reported by parent directory watch
{
@@ -497,7 +511,7 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()
}
}
- bytePos += sizeof(struct inotify_event) + evt.len;
+ bytePos += sizeof(struct ::inotify_event) + evt.len;
}
return output;
diff --git a/zen/error_log.h b/zen/error_log.h
index 401581d7..490bb1f4 100644
--- a/zen/error_log.h
+++ b/zen/error_log.h
@@ -25,30 +25,35 @@ enum MessageType
TYPE_FATAL_ERROR = 0x8,
};
-typedef Zbase<wchar_t> MsgString; //std::wstring may employ small string optimization: we cannot accept bloating the "logEntries" memory block below (think 1 million entries)
+typedef Zbase<wchar_t> MsgString; //std::wstring may employ small string optimization: we cannot accept bloating the "ErrorLog::entries" memory block below (think 1 million items)
struct LogEntry
{
time_t time;
MessageType type;
- MsgString message;
+ MsgString message;
};
-MsgString formatMessage(const LogEntry& msg);
+template <class String>
+String formatMessage(const LogEntry& entry);
class ErrorLog
{
public:
- template <class String>
- void logMsg(const String& message, MessageType type);
+ template <class String> //a wchar_t-based string!
+ void logMsg(const String& text, MessageType type);
int getItemCount(int typeFilter = TYPE_INFO | TYPE_WARNING | TYPE_ERROR | TYPE_FATAL_ERROR) const;
- const std::vector<LogEntry>& getEntries() const { return logEntries; }
+ //subset of std::vector<> interface:
+ typedef std::vector<LogEntry>::const_iterator const_iterator;
+ const_iterator begin() const { return entries.begin(); }
+ const_iterator end () const { return entries.end (); }
+ bool empty() const { return entries.empty(); }
private:
- std::vector<LogEntry> logEntries; //list of non-resolved errors and warnings
+ std::vector<LogEntry> entries; //list of non-resolved errors and warnings
};
@@ -59,29 +64,26 @@ private:
-
-
-
-
//######################## implementation ##########################
template <class String> inline
-void ErrorLog::logMsg(const String& message, zen::MessageType type)
+void ErrorLog::logMsg(const String& text, zen::MessageType type)
{
- const LogEntry newEntry = { std::time(nullptr), type, copyStringTo<MsgString>(message) };
- logEntries.push_back(newEntry);
+ const LogEntry newEntry = { std::time(nullptr), type, copyStringTo<MsgString>(text) };
+ entries.push_back(newEntry);
}
inline
int ErrorLog::getItemCount(int typeFilter) const
{
- return static_cast<int>(std::count_if(logEntries.begin(), logEntries.end(), [&](const LogEntry& e) { return e.type & typeFilter; }));
+ return static_cast<int>(std::count_if(entries.begin(), entries.end(), [&](const LogEntry& e) { return e.type & typeFilter; }));
}
namespace
{
-MsgString formatMessageImpl(const LogEntry& entry) //internal linkage
+template <class String>
+String formatMessageImpl(const LogEntry& entry) //internal linkage
{
auto getTypeName = [&]() -> std::wstring
{
@@ -96,11 +98,11 @@ MsgString formatMessageImpl(const LogEntry& entry) //internal linkage
case TYPE_FATAL_ERROR:
return _("Fatal Error");
}
- assert(false);
+ assert(false);
return std::wstring();
};
- MsgString formattedText = L"[" + formatTime<MsgString>(FORMAT_TIME, localTime(entry.time)) + L"] " + copyStringTo<MsgString>(getTypeName()) + L": ";
+ String formattedText = L"[" + formatTime<String>(FORMAT_TIME, localTime(entry.time)) + L"] " + copyStringTo<String>(getTypeName()) + L": ";
const size_t prefixLen = formattedText.size();
for (auto iter = entry.message.begin(); iter != entry.message.end(); )
@@ -108,7 +110,7 @@ MsgString formatMessageImpl(const LogEntry& entry) //internal linkage
{
formattedText += L'\n';
- MsgString blanks;
+ String blanks;
blanks.resize(prefixLen, L' ');
formattedText += blanks;
@@ -125,8 +127,8 @@ MsgString formatMessageImpl(const LogEntry& entry) //internal linkage
}
}
-inline
-MsgString formatMessage(const LogEntry& entry) { return formatMessageImpl(entry); }
+template <class String> inline
+String formatMessage(const LogEntry& entry) { return formatMessageImpl<String>(entry); }
}
#endif //ERRORLOGGING_H_INCLUDED
diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp
index 589057ad..fce85bcd 100644
--- a/zen/file_handling.cpp
+++ b/zen/file_handling.cpp
@@ -28,9 +28,8 @@
#elif defined FFS_LINUX
#include <sys/stat.h>
-#include <time.h>
#include <utime.h>
-#include <sys/time.h>
+#include <sys/time.h> //futimes
#include <sys/vfs.h>
#ifdef HAVE_SELINUX
@@ -926,7 +925,7 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
#elif defined FFS_LINUX
if (procSl == SYMLINK_FOLLOW)
{
- struct utimbuf newTimes = {};
+ struct ::utimbuf newTimes = {};
newTimes.actime = ::time(nullptr);
newTimes.modtime = to<time_t>(modificationTime);
@@ -936,12 +935,9 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
}
else
{
- struct timeval newTimes[2] = {};
- newTimes[0].tv_sec = ::time(nullptr); //seconds
- newTimes[0].tv_usec = 0; //microseconds
-
- newTimes[1].tv_sec = to<time_t>(modificationTime);
- newTimes[1].tv_usec = 0;
+ struct ::timeval newTimes[2] = {};
+ newTimes[0].tv_sec = ::time(nullptr); //access time (seconds)
+ newTimes[1].tv_sec = to<time_t>(modificationTime); //modification time (seconds)
if (::lutimes(filename.c_str(), newTimes) != 0)
throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted());
@@ -1315,8 +1311,8 @@ void createDirectoryStraight(const Zstring& directory, //throw FileError, ErrorT
::SetFileAttributes(applyLongPathPrefix(directory).c_str(), sourceAttr);
//copy "read-only and system attributes": http://blogs.msdn.com/b/oldnewthing/archive/2003/09/30/55100.aspx
- const bool isCompressed = (sourceAttr & FILE_ATTRIBUTE_COMPRESSED) != 0;
- const bool isEncrypted = (sourceAttr & FILE_ATTRIBUTE_ENCRYPTED) != 0;
+ const bool isCompressed = (sourceAttr & FILE_ATTRIBUTE_COMPRESSED) != 0;
+ const bool isEncrypted = (sourceAttr & FILE_ATTRIBUTE_ENCRYPTED) != 0;
if (isEncrypted)
::EncryptFile(directory.c_str()); //seems no long path is required (check passed!)
@@ -1492,7 +1488,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
catch (...) {}
});
- //file times: essential for a symlink: enforce this! (don't just try!)
+ //file times: essential for sync'ing a symlink: enforce this! (don't just try!)
{
const Int64 modTime = getFileTime(sourceLink, SYMLINK_DIRECT); //throw FileError
setFileTime(targetLink, modTime, SYMLINK_DIRECT); //throw FileError
@@ -1741,11 +1737,8 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
}
//----------------------------------------------------------------------
- const DWORD BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size - must be greater than sizeof(WIN32_STREAM_ID)
- static boost::thread_specific_ptr<std::vector<BYTE>> cpyBuf;
- if (!cpyBuf.get())
- cpyBuf.reset(new std::vector<BYTE>(BUFFER_SIZE));
- std::vector<BYTE>& buffer = *cpyBuf;
+ const DWORD BUFFER_SIZE = 128 * 1024; //must be greater than sizeof(WIN32_STREAM_ID)
+ std::vector<BYTE> buffer(BUFFER_SIZE);
LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite()
LPVOID contextWrite = nullptr; //
@@ -2172,23 +2165,21 @@ void copyFileLinux(const Zstring& sourceFile,
FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
{
zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (...) {} }); //transactional behavior: place guard before lifetime of FileOutput
+
+ //open sourceFile for reading
+ FileInputUnbuffered fileIn(sourceFile); //throw FileError, ErrorNotExisting
+
+ struct ::stat sourceInfo = {};
+ if (::fstat(fileIn.getDescriptor(), &sourceInfo) != 0) //read file attributes from source
+ throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + getLastErrorFormatted());
+
try
{
- //open sourceFile for reading
- FileInput fileIn(sourceFile); //throw FileError
-
//create targetFile and open it for writing
- FileOutput fileOut(targetFile, FileOutput::ACC_CREATE_NEW); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
-
- std::vector<char>& buffer = []() -> std::vector<char>&
- {
- static boost::thread_specific_ptr<std::vector<char>> cpyBuf;
- if (!cpyBuf.get())
- cpyBuf.reset(new std::vector<char>(512 * 1024)); //512 kb seems to be a reasonable buffer size
- return *cpyBuf;
- }();
+ FileOutputUnbuffered fileOut(targetFile, sourceInfo.st_mode); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
//copy contents of sourceFile to targetFile
+ std::vector<char> buffer(128 * 1024); //see comment in FileInputUnbuffered::read
do
{
const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError
@@ -2200,39 +2191,34 @@ void copyFileLinux(const Zstring& sourceFile,
callback->updateCopyStatus(Int64(bytesRead)); //throw X!
}
while (!fileIn.eof());
- }
- catch (ErrorTargetExisting&)
- {
- guardTarget.dismiss(); //don't delete file that existed previously!
- throw;
- }
-
- //adapt file modification time:
- {
- struct ::stat srcInfo = {};
- if (::stat(sourceFile.c_str(), &srcInfo) != 0) //read file attributes from source directory
- throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + getLastErrorFormatted());
- struct ::utimbuf newTimes = {};
- newTimes.actime = srcInfo.st_atime;
- newTimes.modtime = srcInfo.st_mtime;
-
- //set new "last write time"
- if (::utime(targetFile.c_str(), &newTimes) != 0)
- throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted());
-
- if (newAttrib)
+ //adapt target file modification time:
{
- struct ::stat trgInfo = {};
- if (::stat(targetFile.c_str(), &trgInfo) != 0) //read file attributes from source directory
+ struct ::timeval newTimes[2] = {};
+ newTimes[0].tv_sec = sourceInfo.st_atime;
+ newTimes[1].tv_sec = sourceInfo.st_mtime;
+ if (::futimes(fileOut.getDescriptor(), newTimes) != 0) //by using the already open file handle, we avoid issues like: https://sourceforge.net/p/freefilesync/bugs/230/
+ throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted());
+
+ //read and return file statistics
+ struct ::stat targetInfo = {};
+ if (::fstat(fileOut.getDescriptor(), &targetInfo) != 0)
throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted());
- newAttrib->fileSize = UInt64(srcInfo.st_size);
- newAttrib->modificationTime = srcInfo.st_mtime;
- newAttrib->sourceFileId = extractFileID(srcInfo);
- newAttrib->targetFileId = extractFileID(trgInfo);
+ if (newAttrib)
+ {
+ newAttrib->fileSize = UInt64(sourceInfo.st_size);
+ newAttrib->modificationTime = sourceInfo.st_mtime;
+ newAttrib->sourceFileId = extractFileID(sourceInfo);
+ newAttrib->targetFileId = extractFileID(targetInfo);
+ }
}
}
+ catch (ErrorTargetExisting&)
+ {
+ guardTarget.dismiss(); //don't delete file that existed previously!
+ throw;
+ }
guardTarget.dismiss(); //target has been created successfully!
}
diff --git a/zen/file_io.cpp b/zen/file_io.cpp
index 0b5586b0..4880f6cc 100644
--- a/zen/file_io.cpp
+++ b/zen/file_io.cpp
@@ -8,20 +8,46 @@
#ifdef FFS_WIN
#include "long_path_prefix.h"
+#elif defined FFS_LINUX
+#include <fcntl.h> //open, close
+#include <unistd.h> //read, write
#endif
using namespace zen;
-FileInput::FileInput(FileHandle handle, const Zstring& filename) :
- eofReached(false),
- fileHandle(handle),
- filename_(filename) {}
+FileInput::FileInput(FileHandle handle, const Zstring& filename) : FileInputBase(filename), fileHandle(handle) {}
+
+#ifdef FFS_LINUX
+//"filename" could be a named pipe which *blocks* forever during "open()"! https://sourceforge.net/p/freefilesync/bugs/221/
+void checkForUnsupportedType(const Zstring& filename) //throw FileError
+{
+ struct ::stat fileInfo = {};
+ if (::stat(filename.c_str(), &fileInfo) != 0) //follows symlinks
+ return; //let the caller handle errors like "not existing"
+
+ if (!S_ISREG(fileInfo.st_mode) &&
+ !S_ISLNK(fileInfo.st_mode) &&
+ !S_ISDIR(fileInfo.st_mode))
+ {
+ auto getTypeName = [](mode_t m) -> std::wstring
+ {
+ const wchar_t* name =
+ S_ISCHR (m) ? L"character device":
+ S_ISBLK (m) ? L"block device" :
+ S_ISFIFO(m) ? L"FIFO, named pipe" :
+ S_ISSOCK(m) ? L"socket" : nullptr;
+ const std::wstring numFmt = printNumber<std::wstring>(L"0%06o", m & __S_IFMT);
+ return name ? numFmt + L", " + name : numFmt;
+ };
+ throw FileError(replaceCpy(_("Type of item %x is not supported:"), L"%x", fmtFileName(filename)) + L" " + getTypeName(fileInfo.st_mode));
+ }
+}
+#endif
FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExisting
- eofReached(false),
- filename_(filename)
+ FileInputBase(filename)
{
#ifdef FFS_WIN
fileHandle = ::CreateFile(applyLongPathPrefix(filename).c_str(),
@@ -57,6 +83,7 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExis
nullptr);
if (fileHandle == INVALID_HANDLE_VALUE)
#elif defined FFS_LINUX
+ checkForUnsupportedType(filename); //throw FileError; reading a named pipe would block forever!
fileHandle = ::fopen(filename.c_str(), "r,type=record,noseek"); //utilize UTF-8 filename
if (!fileHandle)
#endif
@@ -64,9 +91,9 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExis
const ErrorCode lastError = getLastError();
if (errorCodeForNotExisting(lastError))
- throw ErrorNotExisting(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted(lastError));
+ throw ErrorNotExisting(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted(lastError));
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (open)");
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted(lastError) + L" (open)");
}
}
@@ -83,6 +110,8 @@ FileInput::~FileInput()
size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number of bytes read; throw FileError
{
+ assert(!eof());
+ if (bytesToRead == 0) return 0;
#ifdef FFS_WIN
DWORD bytesRead = 0;
if (!::ReadFile(fileHandle, //__in HANDLE hFile,
@@ -94,33 +123,33 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number
const size_t bytesRead = ::fread(buffer, 1, bytesToRead, fileHandle);
if (::ferror(fileHandle) != 0) //checks status of stream, not fread()!
#endif
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted() + L" (read)");
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (read)");
#ifdef FFS_WIN
if (bytesRead < bytesToRead) //verify only!
- eofReached = true;
+ setEof();
#elif defined FFS_LINUX
if (::feof(fileHandle) != 0)
- eofReached = true;
+ setEof();
if (bytesRead < bytesToRead)
- if (!eofReached) //pathologic!?
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + L"Incomplete read!");
+ if (!eof()) //pathologic!?
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"Incomplete read!");
#endif
if (bytesRead > bytesToRead)
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + L"buffer overflow");
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"buffer overflow");
return bytesRead;
}
-FileOutput::FileOutput(FileHandle handle, const Zstring& filename) : fileHandle(handle), filename_(filename) {}
+FileOutput::FileOutput(FileHandle handle, const Zstring& filename) : FileOutputBase(filename), fileHandle(handle) {}
FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
- filename_(filename)
+ FileOutputBase(filename)
{
#ifdef FFS_WIN
const DWORD dwCreationDisposition = access == FileOutput::ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW;
@@ -160,7 +189,7 @@ FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw Fil
//"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);
+ const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + 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
@@ -174,13 +203,14 @@ FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw Fil
}
#elif defined FFS_LINUX
+ checkForUnsupportedType(filename); //throw FileError; writing a named pipe would block forever!
fileHandle = ::fopen(filename.c_str(),
//GNU extension: https://www.securecoding.cert.org/confluence/display/cplusplus/FIO03-CPP.+Do+not+make+assumptions+about+fopen()+and+file+creation
access == ACC_OVERWRITE ? "w,type=record,noseek" : "wx,type=record,noseek");
if (!fileHandle)
{
const int lastError = errno;
- const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + zen::getLastErrorFormatted(lastError);
+ const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + zen::getLastErrorFormatted(lastError);
if (lastError == EEXIST)
throw ErrorTargetExisting(errorMessage);
@@ -216,8 +246,108 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro
const size_t bytesWritten = ::fwrite(buffer, 1, bytesToWrite, fileHandle);
if (::ferror(fileHandle) != 0) //checks status of stream, not fwrite()!
#endif
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted() + L" (w)"); //w -> distinguish from fopen error message!
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (w)"); //w -> distinguish from fopen error message!
if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes!
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + L"Incomplete write!");
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"Incomplete write!");
+}
+
+
+#ifdef FFS_LINUX
+//Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-5.0.tar.gz
+
+FileInputUnbuffered::FileInputUnbuffered(const Zstring& filename) : FileInputBase(filename) //throw FileError, ErrorNotExisting
+{
+ checkForUnsupportedType(filename); //throw FileError; reading a named pipe would block forever!
+
+ fdFile = ::open(filename.c_str(), O_RDONLY);
+ if (fdFile < 0)
+ {
+ const ErrorCode lastError = getLastError();
+
+ if (errorCodeForNotExisting(lastError))
+ throw ErrorNotExisting(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastError));
+
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (open)");
+ }
+}
+
+
+FileInputUnbuffered::~FileInputUnbuffered() { ::close(fdFile); }
+
+
+size_t FileInputUnbuffered::read(void* buffer, size_t bytesToRead) //throw FileError; returns actual number of bytes read
+{
+ assert(!eof());
+ if (bytesToRead == 0) return 0; //[!]
+
+ ssize_t bytesRead = 0;
+ do
+ {
+ bytesRead = ::read(fdFile, buffer, bytesToRead);
+ }
+ while (bytesRead < 0 && errno == EINTR);
+
+ if (bytesRead < 0)
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (read)");
+ else if (bytesRead == 0) //"zero indicates end of file"
+ setEof();
+ else if (bytesRead > static_cast<ssize_t>(bytesToRead)) //better safe than sorry
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"buffer overflow");
+ //if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead"!
+
+ return bytesRead;
+}
+
+
+FileOutputUnbuffered::FileOutputUnbuffered(const Zstring& filename, mode_t mode) : FileOutputBase(filename) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
+{
+ //checkForUnsupportedType(filename); -> not needed, open() + O_EXCL shoul fail fast
+
+ //overwrite is: O_CREAT | O_WRONLY | O_TRUNC
+ fdFile = ::open(filename.c_str(), O_CREAT | O_WRONLY | O_EXCL, mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+ if (fdFile < 0)
+ {
+ const int lastError = errno;
+ const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename)) + L"\n\n" + zen::getLastErrorFormatted(lastError);
+ if (lastError == EEXIST)
+ throw ErrorTargetExisting(errorMessage);
+
+ if (lastError == ENOENT)
+ throw ErrorTargetPathMissing(errorMessage);
+
+ throw FileError(errorMessage);
+ }
}
+
+
+FileOutputUnbuffered::~FileOutputUnbuffered() { ::close(fdFile); }
+
+
+void FileOutputUnbuffered::write(const void* buffer, size_t bytesToWrite) //throw FileError
+{
+ while (bytesToWrite > 0)
+ {
+ ssize_t bytesWritten = 0;
+ do
+ {
+ bytesWritten = ::write(fdFile, buffer, bytesToWrite);
+ }
+ while (bytesWritten < 0 && errno == EINTR);
+
+ if (bytesWritten <= 0)
+ {
+ if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers
+ errno = ENOSPC;
+
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (w)");
+ }
+ if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"buffer overflow");
+
+ //if ::write is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"!
+ buffer = static_cast<const char*>(buffer) + bytesWritten; //suppress warning about pointer arithmetics on void*
+ bytesToWrite -= bytesWritten;
+ }
+}
+#endif
diff --git a/zen/file_io.h b/zen/file_io.h
index b4d58ea4..31373857 100644
--- a/zen/file_io.h
+++ b/zen/file_io.h
@@ -7,15 +7,16 @@
#ifndef FILEIO_H_INCLUDED
#define FILEIO_H_INCLUDED
+#include "file_io_base.h"
+#include "file_error.h"
+
#ifdef FFS_WIN
#include "win.h" //includes "windows.h"
-
#elif defined FFS_LINUX
#include <cstdio>
+#include <sys/stat.h>
#endif
-#include "zstring.h"
-#include "file_error.h"
namespace zen
{
@@ -25,7 +26,7 @@ static const char LINE_BREAK[] = "\r\n";
static const char LINE_BREAK[] = "\n";
#endif
-//file IO optimized for sequential read/write accesses + better error reporting + long path support (following symlinks)
+//buffered file IO optimized for sequential read/write accesses + better error reporting + long path support (following symlinks)
#ifdef FFS_WIN
typedef HANDLE FileHandle;
@@ -33,52 +34,66 @@ typedef HANDLE FileHandle;
typedef FILE* FileHandle;
#endif
-class FileInput
+class FileInput : public FileInputBase
{
public:
FileInput(const Zstring& filename); //throw FileError, ErrorNotExisting
FileInput(FileHandle handle, const Zstring& filename); //takes ownership!
~FileInput();
- size_t read(void* buffer, size_t bytesToRead); //throw FileError; returns actual number of bytes read
- bool eof() { return eofReached; } //end of file reached
-
- const Zstring& getFilename() const { return filename_; }
+ virtual size_t read(void* buffer, size_t bytesToRead); //throw FileError; returns actual number of bytes read
+ //expected to fill buffer completely unless "end of file"
private:
- FileInput(const FileInput&);
- FileInput& operator=(const FileInput&);
-
- bool eofReached;
FileHandle fileHandle;
- const Zstring filename_;
};
-class FileOutput
+class FileOutput : public FileOutputBase
{
public:
- enum AccessFlag
- {
- ACC_OVERWRITE,
- ACC_CREATE_NEW
- };
FileOutput(const Zstring& filename, AccessFlag access); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
FileOutput(FileHandle handle, const Zstring& filename); //takes ownership!
~FileOutput();
- void write(const void* buffer, size_t bytesToWrite); //throw FileError
-
- const Zstring& getFilename() const { return filename_; }
+ virtual void write(const void* buffer, size_t bytesToWrite); //throw FileError
private:
- FileOutput(const FileOutput&);
- FileOutput& operator=(const FileOutput&);
-
FileHandle fileHandle;
- const Zstring filename_;
};
+
+#ifdef FFS_LINUX
+class FileInputUnbuffered : public FileInputBase
+{
+public:
+ FileInputUnbuffered(const Zstring& filename); //throw FileError, ErrorNotExisting
+ ~FileInputUnbuffered();
+
+ //considering safe-read.c it seems buffer size should be a multiple of 8192
+ virtual size_t read(void* buffer, size_t bytesToRead); //throw FileError; returns actual number of bytes read
+ //we should not rely on buffer being filled completely!
+
+ int getDescriptor() { return fdFile;}
+
+private:
+ int fdFile;
+};
+
+class FileOutputUnbuffered : public FileOutputBase
+{
+public:
+ //creates a new file (no overwrite allowed!)
+ FileOutputUnbuffered(const Zstring& filename, mode_t mode); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
+ ~FileOutputUnbuffered();
+
+ virtual void write(const void* buffer, size_t bytesToWrite); //throw FileError
+ int getDescriptor() { return fdFile;}
+
+private:
+ int fdFile;
+};
+#endif
}
#endif // FILEIO_H_INCLUDED
diff --git a/zen/file_io_base.h b/zen/file_io_base.h
new file mode 100644
index 00000000..f26cd8c2
--- /dev/null
+++ b/zen/file_io_base.h
@@ -0,0 +1,64 @@
+// **************************************************************************
+// * 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 (zenju AT gmx DOT de) - All Rights Reserved *
+// **************************************************************************
+
+#ifndef FILEIO_BASE_H_INCLUDED_23432431789615314
+#define FILEIO_BASE_H_INCLUDED_23432431789615314
+
+#include "zstring.h"
+
+namespace zen
+{
+class FileBase
+{
+public:
+ const Zstring& getFilename() const { return filename_; }
+
+protected:
+ FileBase(const Zstring& filename) : filename_(filename) {}
+ ~FileBase() {}
+
+private:
+ FileBase(const FileBase&);
+ FileBase& operator=(const FileBase&);
+
+ const Zstring filename_;
+};
+
+
+class FileInputBase : public FileBase
+{
+public:
+ virtual size_t read(void* buffer, size_t bytesToRead) = 0; //throw FileError; returns actual number of bytes read
+ bool eof() const { return eofReached; } //end of file reached
+
+protected:
+ FileInputBase(const Zstring& filename) : FileBase(filename), eofReached(false) {}
+ ~FileInputBase() {}
+ void setEof() { eofReached = true; }
+
+private:
+ bool eofReached;
+};
+
+
+class FileOutputBase : public FileBase
+{
+public:
+ enum AccessFlag
+ {
+ ACC_OVERWRITE,
+ ACC_CREATE_NEW
+ };
+ virtual void write(const void* buffer, size_t bytesToWrite) = 0; //throw FileError
+
+protected:
+ FileOutputBase(const Zstring& filename) : FileBase(filename) {}
+ ~FileOutputBase() {}
+};
+
+}
+
+#endif //FILEIO_BASE_H_INCLUDED_23432431789615314
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index a95f5dee..2cea74d8 100644
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -547,7 +547,6 @@ private:
if (!dirEntry) //no more items or ignored error
return;
-
//don't return "." and ".."
const char* const shortName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"!
if (shortName[0] == '.' &&
@@ -597,12 +596,12 @@ private:
if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName))
traverse(fullName, *rv, level + 1);
}
- else //a file
+ else //a file or named pipe, ect.
{
TraverseCallback::FileInfo fileInfo;
fileInfo.fileSize = zen::UInt64(statDataTrg.st_size);
fileInfo.lastWriteTimeRaw = statDataTrg.st_mtime; //UTC time (time_t format); unit: 1 second
- //fileInfo.id = extractFileID(statDataTrg); -> id from dereferenced symlink is problematic, since renaming will consider the link, not the target!
+ //fileInfo.id = extractFileID(statDataTrg); -> id from dereferenced symlink is problematic, since renaming would consider the link, not the target!
sink.onFile(shortName, fullName, fileInfo);
}
}
@@ -619,7 +618,7 @@ private:
if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName))
traverse(fullName, *rv, level + 1);
}
- else //a file
+ else //a file or named pipe, ect.
{
TraverseCallback::FileInfo fileInfo;
fileInfo.fileSize = zen::UInt64(statData.st_size);
@@ -628,6 +627,13 @@ private:
sink.onFile(shortName, fullName, fileInfo);
}
+ /*
+ It may be a good idea to not check "S_ISREG(statData.st_mode)" explicitly and to not issue an error message on other types to support these scenarios:
+ - RTS setup watch (essentially wants to read directories only)
+ - removeDirectory (wants to delete everything; pipes can be deleted just like files via "unlink")
+
+ However an "open" on a pipe will block (https://sourceforge.net/p/freefilesync/bugs/221/), so the copy routines need to be smarter!!
+ */
}
}
diff --git a/zen/perf.h b/zen/perf.h
index c8a14950..92350602 100644
--- a/zen/perf.h
+++ b/zen/perf.h
@@ -48,7 +48,7 @@ public:
if (!now.isValid())
throw TimerError();
- const auto delta = static_cast<long>(1000.0 * (now - startTime) / ticksPerSec_);
+ const auto delta = static_cast<long>(1000.0 * dist(startTime, now) / ticksPerSec_);
#ifdef FFS_WIN
std::ostringstream ss;
ss << delta << " ms";
diff --git a/zen/scroll_window_under_cursor.cpp b/zen/scroll_window_under_cursor.cpp
index 6031cd88..f201bb04 100644
--- a/zen/scroll_window_under_cursor.cpp
+++ b/zen/scroll_window_under_cursor.cpp
@@ -4,10 +4,6 @@
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
// **************************************************************************
-#include <cassert>
-#include "win.h" //includes "windows.h"
-#include "Windowsx.h" //WM_MOUSEWHEEL
-
//redirect mouse wheel events directly to window under cursor rather than window having input focus
//implementing new Windows Vista UI guidelines: http://msdn.microsoft.com/en-us/library/bb545459.aspx#wheel
//this is confirmed to be required for at least Windows 2000 to Windows 8
@@ -15,6 +11,11 @@
//Usage: just include this file into a Windows project
+#include <cassert>
+#include "win.h" //includes "windows.h"
+#include "Windowsx.h" //WM_MOUSEWHEEL
+
+
namespace
{
#ifndef WM_MOUSEHWHEEL //MinGW is clueless...
@@ -25,7 +26,7 @@ LRESULT CALLBACK mouseInputHook(int nCode, WPARAM wParam, LPARAM lParam)
{
//"if nCode is less than zero, the hook procedure must pass the message to the CallNextHookEx function
//without further processing and should return the value returned by CallNextHookEx"
- if (nCode >= 0)
+ if (nCode == HC_ACTION) //the only valid value for this hook type
{
MSG& msgInfo = *reinterpret_cast<MSG*>(lParam);
@@ -37,8 +38,7 @@ LRESULT CALLBACK mouseInputHook(int nCode, WPARAM wParam, LPARAM lParam)
pt.y = GET_Y_LPARAM(msgInfo.lParam); //
//visible child window directly under cursor; attention: not necessarily from our process!
- //http://blogs.msdn.com/b/oldnewthing/archive/2010/12/30/10110077.aspx
- if (HWND hWin = ::WindowFromPoint(pt))
+ if (HWND hWin = ::WindowFromPoint(pt)) //http://blogs.msdn.com/b/oldnewthing/archive/2010/12/30/10110077.aspx
if (msgInfo.hwnd != hWin && ::GetCapture() == nullptr)
{
DWORD winProcessId = 0;
@@ -50,7 +50,6 @@ LRESULT CALLBACK mouseInputHook(int nCode, WPARAM wParam, LPARAM lParam)
}
}
}
-
return ::CallNextHookEx(nullptr, nCode, wParam, lParam);
}
diff --git a/zen/serialize.h b/zen/serialize.h
index d22e3cea..a9238359 100644
--- a/zen/serialize.h
+++ b/zen/serialize.h
@@ -162,7 +162,7 @@ BinContainer loadBinStream(const Zstring& filename) //throw FileError, ErrorNotE
FileInput fileIn(filename); //throw FileError, ErrorNotExisting
BinContainer contOut;
- const size_t blockSize = 64 * 1024;
+ const size_t blockSize = 128 * 1024;
do
{
contOut.resize(contOut.size() + blockSize);
diff --git a/zen/string_base.h b/zen/string_base.h
index c3ddde36..05e5935e 100644
--- a/zen/string_base.h
+++ b/zen/string_base.h
@@ -142,7 +142,8 @@ protected:
static void destroy(Char* ptr)
{
- if (--descr(ptr)->refCount == 0)
+ assert(descr(ptr)->refCount > 0);
+ if (--descr(ptr)->refCount == 0) //operator--() is overloaded to decrement and evaluate in a single atomic operation!
{
descr(ptr)->~Descriptor();
AP::deallocate(descr(ptr));
@@ -213,8 +214,8 @@ public:
Char* end ();
const Char* begin() const;
const Char* end () const;
- const Char* cbegin() const { return begin(); }
- const Char* cend () const { return end(); }
+ const Char* cbegin() const { return begin(); }
+ const Char* cend () const { return end(); }
//std::string functions
size_t length() const;
@@ -268,10 +269,15 @@ template <class Char, template <class, class> class SP, class AP> bool operator<
template <class Char, template <class, class> class SP, class AP> bool operator<(const Zbase<Char, SP, AP>& lhs, const Char* rhs);
template <class Char, template <class, class> class SP, class AP> bool operator<(const Char* lhs, const Zbase<Char, SP, AP>& rhs);
-//rvalue references: unified first argument!
-template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP> lhs, const Zbase<Char, SP, AP>& rhs) { return lhs += rhs; }
-template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP> lhs, const Char* rhs) { return lhs += rhs; }
-template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP> lhs, Char rhs) { return lhs += rhs; }
+template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Zbase<Char, SP, AP>& lhs, const Zbase<Char, SP, AP>& rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; }
+template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Zbase<Char, SP, AP>& lhs, const Char* rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; }
+template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Zbase<Char, SP, AP>& lhs, Char rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; }
+
+//don't use unified first argument but save one move-construction in the r-value case instead!
+template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, const Zbase<Char, SP, AP>& rhs) { return std::move(lhs += rhs); } //is the move really needed?
+template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, const Char* rhs) { return std::move(lhs += rhs); } //lhs, is an l-vlaue in the function body...
+template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, Char rhs) { return std::move(lhs += rhs); } //and not a local variable => no copy elision
+
template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+( Char lhs, const Zbase<Char, SP, AP>& rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; }
template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Char* lhs, const Zbase<Char, SP, AP>& rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; }
diff --git a/zen/string_tools.h b/zen/string_tools.h
index 32d12119..c0bb1039 100644
--- a/zen/string_tools.h
+++ b/zen/string_tools.h
@@ -286,34 +286,45 @@ template <class S, class T, class U> inline
S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll)
{
assert_static(IsStringLike<T>::value && IsStringLike<U>::value);
-
typedef typename GetCharType<S>::Type CharType;
const size_t oldLen = strLength(oldTerm);
- const size_t newLen = strLength(newTerm);
-
- S output;
+ if (oldLen == 0)
+ {
+ assert(false);
+ return str;
+ }
const CharType* strPos = strBegin(str);
const CharType* const strEnd = strPos + strLength(str);
const CharType* const oldBegin = strBegin(oldTerm);
+ const CharType* const oldEnd = oldBegin + oldLen;
+
+ //optimize "oldTerm not found"
+ const CharType* strMatch = std::search(strPos, strEnd,
+ oldBegin, oldEnd);
+ if (strMatch == strEnd)
+ return str;
+
+ const size_t newLen = strLength(newTerm);
const CharType* const newBegin = strBegin(newTerm);
+ S output;
for (;;)
{
- const CharType* ptr = std::search(strPos, strEnd,
- oldBegin, oldBegin + oldLen);
- if (ptr == strEnd)
- break;
-
- implementation::stringAppend(output, strPos, ptr - strPos);
+ implementation::stringAppend(output, strPos, strMatch - strPos);
implementation::stringAppend(output, newBegin, newLen);
- strPos = ptr + oldLen;
+ strPos = strMatch + oldLen;
if (!replaceAll)
break;
+
+ strMatch = std::search(strPos, strEnd,
+ oldBegin, oldEnd);
+ if (strMatch == strEnd)
+ break;
}
implementation::stringAppend(output, strPos, strEnd - strPos);
diff --git a/zen/thread.h b/zen/thread.h
index 432a521e..f00c0298 100644
--- a/zen/thread.h
+++ b/zen/thread.h
@@ -9,7 +9,6 @@
//temporary solution until C++11 thread becomes fully available
#include <memory>
-#include "fixed_list.h"
//fix this pathetic boost thread warning mess
#ifdef __MINGW32__
@@ -64,12 +63,12 @@ public:
bool timedWait(const Duration& duration) const; //true: "get()" is ready, false: time elapsed
//return first value or none if all jobs failed; blocks until result is ready!
- std::unique_ptr<T> get() const; //must be called only once!
+ std::unique_ptr<T> get() const; //may be called only once!
private:
class AsyncResult;
- FixedList<boost::thread> workload; //note: we cannot use std::vector<boost::thread>: compiler error on GCC 4.7, probably a boost screw-up
std::shared_ptr<AsyncResult> result;
+ size_t jobsTotal;
};
@@ -93,11 +92,12 @@ private:
#endif
template <class T, class Function> inline
-auto async2(Function fun) -> boost::unique_future<T> //workaround VS2010 bug: bool (*fun)(); decltype(fun()) == int!
+auto async2(Function fun) -> boost::unique_future<T> //support for workaround of VS2010 bug: bool (*fun)(); decltype(fun()) == int!
{
- boost::packaged_task<T> pt([=] { return fun(); });
+ boost::packaged_task<T> pt(fun);
auto fut = pt.get_future();
- boost::thread(std::move(pt));
+ boost::thread t(std::move(pt));
+ t.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!!
return std::move(fut); //compiler error without "move", why needed???
}
@@ -181,28 +181,27 @@ private:
template <class T> inline
-RunUntilFirstHit<T>::RunUntilFirstHit() : result(std::make_shared<AsyncResult>()) {}
+RunUntilFirstHit<T>::RunUntilFirstHit() : result(std::make_shared<AsyncResult>()), jobsTotal(0) {}
template <class T>
template <class Fun> inline
void RunUntilFirstHit<T>::addJob(Fun f) //f must return a std::unique_ptr<T> containing a value on success
{
- auto result2 = result; //VC11: this is ridiculous!!!
- workload.emplace_back([result2, f]
- {
- result2->reportFinished(f());
- });
+ auto result2 = result; //MSVC2010: this is ridiculous!!!
+ boost::thread t([result2, f] { result2->reportFinished(f()); });
+ ++jobsTotal;
+ t.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!!
}
template <class T>
template <class Duration> inline
-bool RunUntilFirstHit<T>::timedWait(const Duration& duration) const { return result->waitForResult(workload.size(), duration); }
+bool RunUntilFirstHit<T>::timedWait(const Duration& duration) const { return result->waitForResult(jobsTotal, duration); }
template <class T> inline
-std::unique_ptr<T> RunUntilFirstHit<T>::get() const { return result->getResult(workload.size()); }
+std::unique_ptr<T> RunUntilFirstHit<T>::get() const { return result->getResult(jobsTotal); }
}
#endif //BOOST_THREAD_WRAP_H
diff --git a/zen/tick_count.h b/zen/tick_count.h
index 4f1a047e..98e59ae5 100644
--- a/zen/tick_count.h
+++ b/zen/tick_count.h
@@ -8,8 +8,10 @@
#define ZEN_TICK_COUNT_HEADER_3807326
#include <cstdint>
+#include <algorithm>
#include "type_traits.h"
#include "assert_static.h"
+#include <cmath>
#ifdef FFS_WIN
#include "win.h" //includes "windows.h"
@@ -21,7 +23,7 @@ namespace zen
{
//a portable "GetTickCount()" using "wall time equivalent" - e.g. no jumps due to ntp time corrections
class TickVal;
-std::int64_t operator-(const TickVal& lhs, const TickVal& rhs);
+std::int64_t dist(const TickVal& lhs, const TickVal& rhs); //use absolute difference for paranoid security: even QueryPerformanceCounter "wraps-around" at *some* time
std::int64_t ticksPerSec(); //return 0 on error
TickVal getTicks(); //return invalid value on error: !TickVal::isValid()
@@ -40,10 +42,6 @@ TickVal getTicks(); //return invalid value on error: !TickVal::isValid()
-
-
-
-
//############################ implementation ##############################
class TickVal
{
@@ -58,19 +56,31 @@ public:
explicit TickVal(const NativeVal& val) : val_(val) {}
inline friend
- std::int64_t operator-(const TickVal& lhs, const TickVal& rhs)
+ std::int64_t dist(const TickVal& lhs, const TickVal& rhs)
{
#ifdef FFS_WIN
assert_static(IsSignedInt<decltype(lhs.val_.QuadPart)>::value);
- return lhs.val_.QuadPart - rhs.val_.QuadPart;
+ return std::abs(lhs.val_.QuadPart - rhs.val_.QuadPart);
#elif defined FFS_LINUX
assert_static(IsSignedInt<decltype(lhs.val_.tv_sec)>::value);
assert_static(IsSignedInt<decltype(lhs.val_.tv_nsec)>::value);
- return static_cast<std::int64_t>(lhs.val_.tv_sec - rhs.val_.tv_sec) * 1000000000.0 + lhs.val_.tv_nsec - rhs.val_.tv_nsec;
+ return std::abs(static_cast<std::int64_t>(lhs.val_.tv_sec - rhs.val_.tv_sec) * 1000000000.0 + (lhs.val_.tv_nsec - rhs.val_.tv_nsec));
+#endif
+ }
+
+ inline friend
+ bool operator<(const TickVal& lhs, const TickVal& rhs) //evaluate directly rather than reuse operator-
+ {
+#ifdef FFS_WIN
+ return lhs.val_.QuadPart < rhs.val_.QuadPart;
+#elif defined FFS_LINUX
+ if (lhs.val_.tv_sec != rhs.val_.tv_sec)
+ return lhs.val_.tv_sec < rhs.val_.tv_sec;
+ return lhs.val_.tv_nsec < rhs.val_.tv_nsec;
#endif
}
- bool isValid() const { return *this - TickVal() != 0; }
+ bool isValid() const { return dist(*this, TickVal()) != 0; }
private:
NativeVal val_;
@@ -82,7 +92,7 @@ std::int64_t ticksPerSec() //return 0 on error
{
#ifdef FFS_WIN
LARGE_INTEGER frequency = {};
- if (!::QueryPerformanceFrequency(&frequency))
+ if (!::QueryPerformanceFrequency(&frequency)) //MSDN promises: "The frequency cannot change while the system is running."
return 0;
assert_static(sizeof(std::int64_t) >= sizeof(frequency.QuadPart));
return frequency.QuadPart;
diff --git a/zen/time.h b/zen/time.h
index aa1613cc..474c48c5 100644
--- a/zen/time.h
+++ b/zen/time.h
@@ -69,15 +69,6 @@ bool parseTime(const String& format, const String& str, TimeComp& comp); //simil
-
-
-
-
-
-
-
-
-
//############################ implementation ##############################
namespace implementation
{
diff --git a/zen/zstring.h b/zen/zstring.h
index 9d93d2d3..df96df7b 100644
--- a/zen/zstring.h
+++ b/zen/zstring.h
@@ -7,8 +7,8 @@
#ifndef ZSTRING_H_INCLUDED
#define ZSTRING_H_INCLUDED
-#include "string_base.h"
#include <cstring> //strcmp()
+#include "string_base.h"
#ifndef NDEBUG
#include "thread.h" //includes <boost/thread.hpp>
bgstack15