diff options
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 Binary files differindex e4eba632..07e52459 100644 --- a/BUILD/FreeFileSync.chm +++ b/BUILD/FreeFileSync.chm 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 Binary files differindex 1f602205..6ce935e9 100644 --- a/BUILD/Resources.zip +++ b/BUILD/Resources.zip 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 Binary files differindex 14e2bb10..1856b1fb 100644 --- a/lib/Batch.ico +++ b/lib/Batch.ico 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_; }); }); }; + @@ -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!! + */ } } @@ -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; @@ -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> |