diff options
127 files changed, 4655 insertions, 3259 deletions
diff --git a/Application.cpp b/Application.cpp index 7f89efcc..54258cd9 100644 --- a/Application.cpp +++ b/Application.cpp @@ -41,18 +41,21 @@ IMPLEMENT_APP(Application) namespace { +/* boost::thread::id mainThreadId = boost::this_thread::get_id(); void onTerminationRequested() { - std::wstring msg = boost::this_thread::get_id() == mainThreadId ? - L"Termination requested in main thread!\n\n" : - L"Termination requested in worker thread!\n\n"; - msg += L"Please file a bug report at: http://sourceforge.net/projects/freefilesync"; +std::wstring msg = boost::this_thread::get_id() == mainThreadId ? + L"Termination requested in main thread!\n\n" : + L"Termination requested in worker thread!\n\n"; +msg += L"Please file a bug report at: http://sourceforge.net/projects/freefilesync"; - wxSafeShowMessage(_("An exception occurred"), msg); - std::abort(); +wxSafeShowMessage(_("An exception occurred"), msg); +std::abort(); } +*/ + #ifdef _MSC_VER void crtInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) { assert(false); } #endif @@ -118,7 +121,8 @@ const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); bool Application::OnInit() { - std::set_terminate(onTerminationRequested); //unlike wxWidgets uncaught exception handling, this works for all worker threads + //-> this seems rather useless: + //std::set_terminate(onTerminationRequested); //unlike wxWidgets uncaught exception handling, this works for all worker threads #ifdef ZEN_WIN #ifdef _MSC_VER @@ -262,9 +266,10 @@ void Application::onQueryEndSession(wxEvent& event) } -void runGuiMode(const xmlAccess::XmlGuiConfig& guiCfg); -void runGuiMode(const std::vector<Zstring>& cfgFileName); -void runBatchMode(const Zstring& filename, FfsReturnCode& returnCode); +void runGuiMode(); +void runGuiMode(const XmlGuiConfig& guiCfg, const std::vector<Zstring>& referenceFiles); +void runBatchMode(const XmlBatchConfig& batchCfg, const Zstring& referenceFile, FfsReturnCode& returnCode); +void showSyntaxHelp(); void Application::launch(const std::vector<Zstring>& commandArgs) @@ -279,131 +284,247 @@ void Application::launch(const std::vector<Zstring>& commandArgs) //tentatively set program language to OS default until GlobalSettings.xml is read later setLanguage(xmlAccess::XmlGlobalSettings().programLanguage); //throw FileError } - catch (const FileError&) {} //no messagebox: consider batch job! + catch (const FileError&) { assert(false); } //no messagebox: consider batch job! - if (commandArgs.empty()) - runGuiMode(commandArgs); - else + + //parse command line arguments + std::vector<Zstring> leftDirs; + std::vector<Zstring> rightDirs; + std::vector<std::pair<Zstring, XmlType>> configFiles; //XmlType: batch or GUI files only { - const bool gotDirNames = std::any_of(commandArgs.begin(), commandArgs.end(), [](const Zstring& dirname) { return dirExists(dirname); }); - if (gotDirNames) //mode 1: create temp configuration based on directory names passed - { - XmlGuiConfig guiCfg; - guiCfg.mainCfg.syncCfg.directionCfg.var = DirectionConfig::MIRROR; + const Zchar optionLeftDir [] = Zstr("-leftdir"); + const Zchar optionRightDir[] = Zstr("-rightdir"); - for (auto it = commandArgs.begin(); it != commandArgs.end(); ++it) + auto syntaxHelpRequested = [](const Zstring& arg) + { + auto it = std::find_if(arg.begin(), arg.end(), [](Zchar c) { return c != Zchar('/') && c != Zchar('-'); }); + const Zstring argTmp(it, arg.end()); + return argTmp == Zstr("help") || + argTmp == Zstr("h") || + argTmp == Zstr("?"); + }; + + for (auto it = commandArgs.begin(); it != commandArgs.end(); ++it) + if (syntaxHelpRequested(*it)) + return showSyntaxHelp(); + else if (*it == optionLeftDir) { - size_t index = it - commandArgs.begin(); - - FolderPairEnh* fp = nullptr; - if (index < 2) - fp = &guiCfg.mainCfg.firstPair; - else + if (++it == commandArgs.end()) { - guiCfg.mainCfg.additionalPairs.resize((index - 2) / 2 + 1); - fp = &guiCfg.mainCfg.additionalPairs.back(); + wxMessageBox(replaceCpy(_("A directory path is expected after %x."), L"%x", utfCvrtTo<std::wstring>(optionLeftDir)), L"FreeFileSync - " + _("Syntax error"), wxOK | wxICON_ERROR); + return; } - - if (index % 2 == 0) - fp->leftDirectory = *it; - else - fp->rightDirectory = *it; + leftDirs.push_back(*it); } - - runGuiMode(guiCfg); - } - else //mode 2: try to set config/batch-filename set by %1 parameter - { - std::vector<Zstring> argsTmp = commandArgs; - - for (auto it = argsTmp.begin(); it != argsTmp.end(); ++it) + else if (*it == optionRightDir) { - const Zstring& filename = *it; - + if (++it == commandArgs.end()) + { + wxMessageBox(replaceCpy(_("A directory path is expected after %x."), L"%x", utfCvrtTo<std::wstring>(optionRightDir)), L"FreeFileSync - " + _("Syntax error"), wxOK | wxICON_ERROR); + return; + } + rightDirs.push_back(*it); + } + else + { + Zstring filename = *it; if (!fileExists(filename)) //...be a little tolerant { if (fileExists(filename + Zstr(".ffs_batch"))) - *it += Zstr(".ffs_batch"); + filename += Zstr(".ffs_batch"); else if (fileExists(filename + Zstr(".ffs_gui"))) - *it += Zstr(".ffs_gui"); + filename += Zstr(".ffs_gui"); else { - wxMessageBox(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filename)), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filename)), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR); return; } } + + switch (getXmlType(filename)) //throw() + { + case XML_TYPE_GLOBAL: + case XML_TYPE_OTHER: + wxMessageBox(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtFileName(filename)), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR); + return; + + case XML_TYPE_GUI: + configFiles.push_back(std::make_pair(filename, XML_TYPE_GUI)); + break; + case XML_TYPE_BATCH: + configFiles.push_back(std::make_pair(filename, XML_TYPE_BATCH)); + break; + } } + } + + if (leftDirs.size() != rightDirs.size()) + { + wxMessageBox(_("Unequal number of left and right directories specified."), L"FreeFileSync - " + _("Syntax error"), wxOK | wxICON_ERROR); + return; + } + + auto hasNonDefaultConfig = [](const FolderPairEnh& fp) + { + return !(fp == FolderPairEnh(fp.leftDirectory, + fp.rightDirectory, + nullptr, nullptr, FilterConfig())); + }; - switch (getMergeType(argsTmp)) //throw () + auto replaceDirectories = [&](MainConfiguration& mainCfg) + { + if (!leftDirs.empty()) + { + //check if config at folder-pair level is present: this probably doesn't make sense when replacing/adding the user-specified directories + if (hasNonDefaultConfig(mainCfg.firstPair) || std::any_of(mainCfg.additionalPairs.begin(), mainCfg.additionalPairs.end(), hasNonDefaultConfig)) { - case MERGE_BATCH: //pure batch config files - if (argsTmp.size() == 1) - runBatchMode(argsTmp[0], returnCode); - else - runGuiMode(argsTmp); - break; + wxMessageBox(_("The config file must not contain settings at directory pair level when directories are set via command line."), L"FreeFileSync - " + _("Syntax error"), wxOK | wxICON_ERROR); + return false; + } - case MERGE_GUI: //pure gui config files - case MERGE_GUI_BATCH: //gui and batch files - runGuiMode(argsTmp); - break; + mainCfg.additionalPairs.clear(); + for (size_t i = 0; i < leftDirs.size(); ++i) + if (i == 0) + { + mainCfg.firstPair.leftDirectory = leftDirs [0]; + mainCfg.firstPair.rightDirectory = rightDirs[0]; + } + else + mainCfg.additionalPairs.push_back(FolderPairEnh(leftDirs [i], + rightDirs[i], + nullptr, nullptr, FilterConfig())); + } + return true; + }; - case MERGE_OTHER: //= none or unknown; - //argsTmp are not empty and contain at least one non-gui/non-batch config file: find it! - std::find_if(argsTmp.begin(), argsTmp.end(), - [](const Zstring& filename) -> bool - { - switch (getXmlType(filename)) //throw() - { - case XML_TYPE_GLOBAL: - case XML_TYPE_OTHER: - wxMessageBox(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtFileName(filename)), _("Error"), wxOK | wxICON_ERROR); - return true; - - case XML_TYPE_GUI: - case XML_TYPE_BATCH: - break; - } - return false; - }); - break; + //distinguish sync scenarios: + //--------------------------- + if (configFiles.empty()) + { + //gui mode: default startup + if (leftDirs.empty()) + runGuiMode(); + //gui mode: default config with given directories + else + { + XmlGuiConfig guiCfg; + guiCfg.mainCfg.syncCfg.directionCfg.var = DirectionConfig::MIRROR; + + if (!replaceDirectories(guiCfg.mainCfg)) return; + runGuiMode(guiCfg, std::vector<Zstring>()); + } + } + else if (configFiles.size() == 1) + { + const Zstring filename = configFiles[0].first; + + //batch mode + if (configFiles[0].second == XML_TYPE_BATCH) + { + XmlBatchConfig batchCfg; + try + { + readConfig(filename, batchCfg); + } + catch (const xmlAccess::FfsXmlError& e) + { + wxMessageBox(e.toString(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR); //batch mode: break on errors AND even warnings! + raiseReturnCode(returnCode, FFS_RC_ABORTED); + return; + } + if (!replaceDirectories(batchCfg.mainCfg)) return; + runBatchMode(batchCfg, filename, returnCode); + } + //GUI mode: single config + else + { + XmlGuiConfig guiCfg; + try + { + readConfig(filename, guiCfg); + } + catch (const xmlAccess::FfsXmlError& e) + { + if (e.getSeverity() == FfsXmlError::WARNING) + wxMessageBox(e.toString(), L"FreeFileSync - " + _("Warning"), wxOK | wxICON_WARNING); + //what about simulating changed config on parsing errors???? + else + { + wxMessageBox(e.toString(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR); + return; + } + } + if (!replaceDirectories(guiCfg.mainCfg)) return; + //what about simulating changed config due to directory replacement? + //-> propably fine to not show as changed on GUI and not ask user to save on exit! + + runGuiMode(guiCfg, { filename }); //caveat: guiCfg and filename do not match if directories were set/replaced via command line! + } + } + //gui mode: merged configs + else + { + if (!leftDirs.empty()) + { + wxMessageBox(_("Directories cannot be set for more than one configuration file."), L"FreeFileSync - " + _("Syntax error"), wxOK | wxICON_ERROR); + return; + } + + std::vector<Zstring> filenames; + for (const auto& item : configFiles) + filenames.push_back(item.first); + + XmlGuiConfig guiCfg; //structure to receive gui settings with default values + try + { + readAnyConfig(filenames, guiCfg); //throw FfsXmlError + } + catch (const FfsXmlError& e) + { + if (e.getSeverity() == FfsXmlError::WARNING) + wxMessageBox(e.toString(), L"FreeFileSync - " + _("Warning"), wxOK | wxICON_WARNING); + //what about simulating changed config on parsing errors???? + else + { + wxMessageBox(e.toString(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR); + return; } } + runGuiMode(guiCfg, filenames); } } -void runGuiMode(const xmlAccess::XmlGuiConfig& guiCfg) +void runGuiMode() { MainDialog::create(); } + + +void runGuiMode(const xmlAccess::XmlGuiConfig& guiCfg, + const std::vector<Zstring>& referenceFiles) { - MainDialog::create(guiCfg, true); + MainDialog::create(guiCfg, referenceFiles, nullptr, true); //startComparison == true! } -void runGuiMode(const std::vector<Zstring>& cfgFileNames) +void showSyntaxHelp() { - MainDialog::create(cfgFileNames); + wxMessageBox(_("Syntax:") + L"\n" + + L"FreeFileSync [" + _("config files") + L"]\n[-leftdir " + _("directory") + L"] [-rightdir " + _("directory") + L"]" + L"\n" + + L"\n" + + _("config files") + L"\n" + + _("Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.") + L"\n\n" + + L"-leftdir " + _("directory") + L" -rightdir " + _("directory") + L"\n" + + _("Any number of alternative directories for at most one config file."), + L"FreeFileSync - " + _("Command line"), wxOK | wxICON_INFORMATION); } -void runBatchMode(const Zstring& filename, FfsReturnCode& returnCode) +void runBatchMode(const XmlBatchConfig& batchCfg, const Zstring& referenceFile, FfsReturnCode& returnCode) { - //load XML settings - XmlBatchConfig batchCfg; - try - { - readConfig(filename, batchCfg); - } - catch (const xmlAccess::FfsXmlError& e) - { - wxMessageBox(e.toString(), _("Error"), wxOK | wxICON_ERROR); //batch mode: break on errors AND even warnings! - raiseReturnCode(returnCode, FFS_RC_ABORTED); - return; - } - auto notifyError = [&](const std::wstring& msg) { if (batchCfg.handleError == ON_ERROR_POPUP) - wxMessageBox(msg.c_str(), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(msg.c_str(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR); else //"exit" or "ignore" logError(utfCvrtTo<std::string>(msg)); @@ -445,11 +566,11 @@ void runBatchMode(const Zstring& filename, FfsReturnCode& returnCode) const TimeComp timeStamp = localTime(); - const SwitchToGui switchBatchToGui(filename, batchCfg, globalCfg); //prepare potential operational switch + const SwitchToGui switchBatchToGui(referenceFile, batchCfg, globalCfg); //prepare potential operational switch //class handling status updates and error messages BatchStatusHandler statusHandler(batchCfg.showProgress, //throw BatchAbortProcess - extractJobName(filename), + extractJobName(referenceFile), timeStamp, batchCfg.logFileDirectory, batchCfg.logfilesCountLimit, diff --git a/BUILD/Changelog.txt b/BUILD/Changelog.txt index b3ffe191..d8e3ad35 100644 --- a/BUILD/Changelog.txt +++ b/BUILD/Changelog.txt @@ -1,6 +1,29 @@ +FreeFileSync 5.19 +----------------- +Redesigned progress dialog including new items graph +New command line syntax: set directory names of a .ffs_gui/.ffs_batch externally +Explicit button on progress dialog to minimize to systray +Fixed progress graph labels being truncated (Debian, Ubuntu, openSUSE) +Resolved main dialog z-order issues during sync (OS X) +Reduced progress dialog layout twitching +Further improved comparison speed by 10% +Use proper config file path in file picker dialog (OS X) +Never interrupt when updating a file with fail-safe file copy after target was deleted +Prevent crash when closing progress dialog while paused (OS X) +Support external command lines starting with whitespace (Windows) +Show warning before starting external applications for more than 10 items +Start external applications synchronously if needed to avoid running out of system resources +Don't show hidden progress dialog when showing an error message in silent batch mode (OS X) +Correctly show file names containing ampersand characters in progress dialog +Adapt size of results dialog to fit contents +Correctly execute file move before parent directory will be deleted +Show a blinking system tray icon on errors instead of a modal dialog in RealtimeSync +Added installation size for Windows' Add/Remove Programs + + FreeFileSync 5.18 ----------------- -Workaround boost 1.54 bug "Procedure Entry Point Not Found in Kernel32.dll" (Windows XP) +Work around boost 1.54 bug "The procedure entry point GetTickCount64 could not be located in the dynamic link library KERNEL32.dll" (Windows XP) FreeFileSync 5.17 @@ -30,6 +53,7 @@ Improved sync progress dialog layout Suppress dubious wxWidgets error message "locale 'es_AR' can not be set". (OS X) Don't show busy cursor on synchronization results dialog Log error message upon retry as type info only +Updated translation files FreeFileSync 5.16 diff --git a/BUILD/FreeFileSync.chm b/BUILD/FreeFileSync.chm Binary files differindex ba059751..d6009619 100644 --- a/BUILD/FreeFileSync.chm +++ b/BUILD/FreeFileSync.chm diff --git a/BUILD/Help/FreeFileSync.hhc b/BUILD/Help/FreeFileSync.hhc index bfcd35f9..d3c318d1 100644 --- a/BUILD/Help/FreeFileSync.hhc +++ b/BUILD/Help/FreeFileSync.hhc @@ -20,8 +20,8 @@ <param name="Local" value="html\FreeFileSync.html"> </OBJECT> <LI> <OBJECT type="text/sitemap"> - <param name="Name" value="Batch scripting"> - <param name="Local" value="html\Batch scripting.html"> + <param name="Name" value="Command line"> + <param name="Local" value="html\Command line.html"> </OBJECT> <LI> <OBJECT type="text/sitemap"> <param name="Name" value="Comparison settings"> diff --git a/BUILD/Help/FreeFileSync.hhp b/BUILD/Help/FreeFileSync.hhp index 38c789d9..713fab68 100644 --- a/BUILD/Help/FreeFileSync.hhp +++ b/BUILD/Help/FreeFileSync.hhp @@ -12,7 +12,7 @@ Title=FreeFileSync - Help [FILES] html\FreeFileSync.html html\Versioning.html -html\Batch scripting.html +html\Command line.html html\Expert settings.html html\Comparison settings.html html\Daylight Saving time.html diff --git a/BUILD/Help/html/Command line.html b/BUILD/Help/html/Command line.html new file mode 100644 index 00000000..3768d036 --- /dev/null +++ b/BUILD/Help/html/Command line.html @@ -0,0 +1,138 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> + <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=windows-1252"> + <TITLE></TITLE> + <META NAME="GENERATOR" CONTENT="LibreOffice 4.0.4.2 (Windows)"> + <META NAME="CREATED" CONTENT="20091206;16574000"> + <META NAME="CHANGED" CONTENT="20130722;18174509"> + <META NAME="Info 1" CONTENT=""> + <META NAME="Info 2" CONTENT=""> + <META NAME="Info 3" CONTENT=""> + <META NAME="Info 4" CONTENT=""> + <STYLE TYPE="text/css"> + <!-- + @page { margin: 2cm } + P { margin-bottom: 0.21cm } + H2.cjk { font-family: "SimSun" } + H2.ctl { font-family: "Mangal" } + --> + </STYLE> +</HEAD> +<BODY LANG="en-US" DIR="LTR"> +<H2 CLASS="western"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=4 STYLE="font-size: 15pt">Command +line</FONT></FONT></H2> +<P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">FreeFileSync +enables additional synchronization scenarios via a command line interface. To get a syntax overview, open the console, go to the directory where FreeFileSync is installed and type:</FONT></P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen4" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: none; padding: 0cm; background: #e6e6e6"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><FONT FACE="Courier New, monospace">FreeFileSync + -h</FONT></FONT></P> +</SPAN><BR CLEAR=LEFT><BR> +</P> +<P STYLE="margin-bottom: 0cm"><BR> +</P> +<P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>1. +Run a FreeFileSync batch job</B></FONT></P> +<P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">In order to start synchronization in batch mode, supply the path of a +<FONT FACE="Courier New, monospace">*.ffs_batch</FONT> +configuration as the first argument for FreeFileSync.exe:</FONT></P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen1" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: none; padding: 0cm; background: #e6e6e6"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><FONT FACE="Courier New, monospace">FreeFileSync + "D:\Backup </FONT><FONT FACE="Courier New, monospace">Projects</FONT><FONT FACE="Courier New, monospace">.ffs_batch"</FONT></FONT></P> +</SPAN><BR CLEAR=LEFT><BR> +</P> +<P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">After +synchronization one of the following status codes is returned:</FONT></P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen6" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: none; padding: 0cm; background: #e6e6e6"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><SPAN STYLE="font-variant: normal"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>Return + Codes</B></SPAN></FONT></SPAN><SPAN STYLE="font-variant: normal"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><BR>0 + - Synchronization completed successfully<BR>1 - Synchronization + completed with warnings<BR>2 - Synchronization completed with + errors<BR>3 - Synchronization was aborted</SPAN></FONT></SPAN> + </P> +</SPAN><BR CLEAR=LEFT><BR> +</P> +<P STYLE="margin-bottom: 0cm; font-weight: normal"><FONT FACE="Tahoma, sans-serif">You +can evaluate these codes from a script (e.g. a <FONT FACE="Courier New, monospace">*.cmd</FONT> +or <FONT FACE="Courier New, monospace">*.bat</FONT> file on Windows) +and check if synchronization completed successfully:</FONT></P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen6" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: none; padding: 0cm; background: #e6e6e6"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><FONT FACE="Courier New, monospace">"C:\Program + Files\FreeFileSync\FreeFileSync.exe" "D:\Backup + </FONT><FONT FACE="Courier New, monospace">Projects</FONT><FONT FACE="Courier New, monospace">.ffs_batch"<BR>if + errorlevel 1 (<BR> </FONT><FONT COLOR="#808080"><FONT FACE="Courier New, monospace"><I><B>::if + return code is 1 or greater, something went wrong, add special + treatment here<BR> </B></I></FONT></FONT><SPAN STYLE="font-variant: normal"><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><SPAN STYLE="font-style: normal"><SPAN STYLE="font-weight: normal">echo + Errors occurred during synchronization...</SPAN></SPAN></FONT></FONT></SPAN><FONT COLOR="#808080"><FONT FACE="Courier New, monospace"><I><B><BR> </B></I></FONT></FONT><FONT FACE="Courier New, monospace">pause<BR>)</FONT></FONT></P> +</SPAN><BR CLEAR=LEFT><BR> +</P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Instead +of displaying "An error occurred!" you can also send an +email notification (using a third party tool).</FONT> +</P> +<P STYLE="margin-bottom: 0cm"><BR> +</P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen3" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> +<P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"> + <FONT FACE="Tahoma, sans-serif"><B>Attention<BR></B>Make + sure your script is not blocked by a popup dialog. Consider the + following options when setting up a FreeFileSync batch job:</FONT> + </P> + + <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"> + <FONT FACE="Tahoma, sans-serif">Disable + checkbox <B>Show + progress dialog</B> or have <B>On completion</B> + automatically close the results dialog after synchronization.</FONT> + </P> + + <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"> + <FONT FACE="Tahoma, sans-serif">Set error handling to <B>Exit instantly</B> or <B>Ignore errors</B>.</FONT> + </P> + +</SPAN><BR CLEAR=LEFT><BR> +</P> +<P STYLE="margin-bottom: 0cm"><BR> +</P> +<P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>2. +Start a FreeFileSync GUI configuration</B></FONT></P> +<P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">If you +pass a <FONT FACE="Courier New, monospace">*.ffs_gui</FONT> +file, FreeFileSync will start in GUI mode and immediately start +comparison (but only if all directories exist):</FONT></P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen2" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: none; padding: 0cm; background: #e6e6e6"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Courier New, monospace">FreeFileSync + "D:\Manual Backup.ffs_gui"</FONT></P> +</SPAN><BR CLEAR=LEFT><BR> +</P> +<P STYLE="margin-bottom: 0cm"><BR> +</P> +<P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>3. +Customize an existing configuration</B></FONT></P> +<P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">You can +replace the directories of a given <FONT FACE="Courier New, monospace">*.ffs_gui +</FONT>or <FONT FACE="Courier New, monospace">*.ffs_batch +</FONT>configuration by using the <FONT FACE="Courier New, monospace">-leftdir</FONT> +and <FONT FACE="Courier New, monospace">-rightdir</FONT> parameters:</FONT></P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen5" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: none; padding: 0cm; background: #e6e6e6"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"> + <FONT FACE="Courier New, monospace">FreeFileSync "D:\Manual Backup.ffs_gui" -leftdir C:\NewSource -rightdir D:\NewTarget</FONT></P> +</SPAN><BR CLEAR=LEFT><BR> +</P> +<P STYLE="margin-bottom: 0cm"><BR> +</P> +<P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>4. +Merge multiple configurations</B></FONT></P> +<P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">When +more than one configuration file is provided, FreeFileSync will merge +everything into a single configuration with multiple folder pairs and +start in GUI mode:</FONT></P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen7" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: none; padding: 0cm; background: #e6e6e6"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"> + <FONT FACE="Courier New, monospace">FreeFileSync "D:\Manual Backup.ffs_gui" "D:\Backup Projects.ffs_batch"</FONT></P> +</SPAN><BR CLEAR=LEFT><BR> +</P> +<P STYLE="margin-bottom: 0cm"><BR> +</P> +</BODY> +</HTML>
\ No newline at end of file diff --git a/BUILD/Help/html/Links.html b/BUILD/Help/html/Links.html index 125a8f2b..38c46bf3 100644 --- a/BUILD/Help/html/Links.html +++ b/BUILD/Help/html/Links.html @@ -26,8 +26,8 @@ Links</FONT></FONT></H2> feedback, suggestions and bug-reports</SPAN></FONT><FONT FACE="Tahoma, sans-serif"><B><BR></B></FONT><A HREF="http://sourceforge.net/projects/freefilesync"><FONT FACE="Tahoma, sans-serif">http://sourceforge.net/projects/freefilesync</FONT></A></P> <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"> - <FONT FACE="Tahoma, sans-serif"><B>If you like FreeFileSync consider - supporting the project by a donation:<BR></B></FONT><A HREF="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=zenju@gmx.de&no_shipping=1&lc=US&currency_code=EUR"><FONT FACE="Tahoma, sans-serif">Donate + <FONT FACE="Tahoma, sans-serif"><B>If you like FreeFileSync:</B> consider + supporting the project by a donation<BR></FONT><A HREF="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=zenju@gmx.de&no_shipping=1&lc=US&currency_code=EUR"><FONT FACE="Tahoma, sans-serif">Donate via PayPal</FONT></A></P> </SPAN><BR CLEAR=LEFT><BR> </P> diff --git a/BUILD/Help/html/RealtimeSync.html b/BUILD/Help/html/RealtimeSync.html index c147a60e..d765c818 100644 --- a/BUILD/Help/html/RealtimeSync.html +++ b/BUILD/Help/html/RealtimeSync.html @@ -3,9 +3,9 @@ <HEAD> <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=windows-1252"> <TITLE></TITLE> - <META NAME="GENERATOR" CONTENT="OpenOffice.org 3.4.1 (Win32)"> + <META NAME="GENERATOR" CONTENT="LibreOffice 4.0.4.2 (Windows)"> <META NAME="CREATED" CONTENT="20091206;16574000"> - <META NAME="CHANGED" CONTENT="20130206;20451343"> + <META NAME="CHANGED" CONTENT="20130722;18255897"> <META NAME="Info 1" CONTENT=""> <META NAME="Info 2" CONTENT=""> <META NAME="Info 3" CONTENT=""> @@ -26,7 +26,7 @@ Synchronization</SPAN></I></FONT></H3> <P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">RealtimeSync is a small tool which executes a command line each time it detects -changes in one of the monitored directories </FONT><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>or</B></SPAN></FONT> +changes in one of the monitored directories </FONT><SPAN STYLE="font-variant: normal"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>or</B></SPAN></FONT></SPAN> <FONT FACE="Tahoma, sans-serif">a directory becomes available (e. g. insert of a USB-stick). Usually this command line will simply trigger a FreeFileSync batch job.</FONT></P> @@ -39,42 +39,46 @@ combination with FreeFileSync</FONT></P> RealtimeSync.exe located in FreeFileSync's installation directory. Then specify all folders that shall be monitored. Instead of doing this manually you can import a </FONT><FONT FACE="Courier New, monospace">*.ffs_batch</FONT> -<FONT FACE="Tahoma, sans-serif">file via </FONT><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>Menu -→ Program → Open</B></SPAN></FONT><FONT FACE="Tahoma, sans-serif">. +<FONT FACE="Tahoma, sans-serif">file via </FONT><SPAN STYLE="font-variant: normal"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>Menu +→ Program → Open</B></SPAN></FONT></SPAN><FONT FACE="Tahoma, sans-serif">. This not only extracts all directories relevant for synchronization but also sets up the command line to execute the </FONT><FONT FACE="Courier New, monospace">*.ffs_batch</FONT> <FONT FACE="Tahoma, sans-serif">file each time changes are detected. -Now press </FONT><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>Start</B></SPAN></FONT> +Now press </FONT><SPAN STYLE="font-variant: normal"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>Start</B></SPAN></FONT></SPAN> <FONT FACE="Tahoma, sans-serif">to begin monitoring.</FONT></P> <UL> <P STYLE="margin-bottom: 0cm"><IMG SRC="../img/RealtimeSync.png" NAME="Grafik3" ALIGN=MIDDLE WIDTH=461 HEIGHT=388 BORDER=0></P> </UL> <P STYLE="margin-bottom: 0cm"><BR> </P> -<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen1" DIR="LTR" STYLE="float: left; width: 80%; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> - <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note</B></FONT></P> - <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">The +<SPAN ID="Rahmen1" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note</B></FONT></P> + + <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">The command should </FONT><FONT FACE="Tahoma, sans-serif"><B>not</B></FONT> <FONT FACE="Tahoma, sans-serif"><B>block</B></FONT> <FONT FACE="Tahoma, sans-serif">progress. Make sure the FreeFileSync batch job does not show any popup - dialogs. See notes in <A HREF="Batch%20Scripting.html">Batch + dialogs. See notes in <A HREF="Batch%20scripting.html">Batch Scripting</A>.</FONT><SPAN STYLE="text-decoration: none"><FONT FACE="Tahoma, sans-serif"><BR> </FONT></SPAN></P> - <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">The + + <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">The settings dialog can be skipped by passing a RealtimeSync configuration file (<FONT FACE="Courier New, monospace">*.ffs_real</FONT>) <B>or</B> a FreeFileSync batch file (<FONT FACE="Courier New, monospace">*.ffs_batch</FONT>) as first command line argument to RealtimeSync.exe. This can be integrated into the operating system's auto start facility:<BR> <FONT FACE="Courier New, monospace">"C:\Program - Files\FreeFileSync\RealtimeSync.exe" - "C:\SyncJob.ffs_real"<BR></FONT> <FONT FACE="Courier New, monospace">"C:\Program - Files\FreeFileSync\RealtimeSync.exe" "C:\SyncJob.ffs_batch"</FONT><BR> </FONT></P> - <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Using + Files\FreeFileSync\RealtimeSync.exe" "</FONT><FONT FACE="Courier New, monospace">D</FONT><FONT FACE="Courier New, monospace">:\</FONT><FONT FACE="Courier New, monospace">Backup + Projects</FONT><FONT FACE="Courier New, monospace">.ffs_real"<BR></FONT> <FONT FACE="Courier New, monospace">"C:\Program + Files\FreeFileSync\RealtimeSync.exe" "</FONT><FONT FACE="Courier New, monospace">D</FONT><FONT FACE="Courier New, monospace">:\</FONT><FONT FACE="Courier New, monospace">Backup + Projects</FONT><FONT FACE="Courier New, monospace">.ffs_batch"</FONT><BR> </FONT></P> + + <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Using RealtimeSync is not restricted to starting FreeFileSync. It can also be used in other scenarios, like sending an email whenever a certain directory is modified.</FONT></P> </SPAN><BR CLEAR=LEFT><BR> -</P> + <P STYLE="margin-bottom: 0cm"><BR> </P> <P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Example:</B></FONT> @@ -98,41 +102,37 @@ files are modified in "</FONT><FONT FACE="Courier New, monospace">H:\Data</ </P> <P STYLE="margin-bottom: 0cm"><BR> </P> -<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen2" DIR="LTR" STYLE="float: left; width: 80%; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> - <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note<BR></B></FONT><FONT FACE="Tahoma, sans-serif">The - full path of the last changed file and the action that triggered - the change notification (create, update or delete) are </FONT><SPAN STYLE="text-decoration: none"><FONT FACE="Tahoma, sans-serif">written - to the environment variables </FONT></SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Courier New, monospace"><B>%change_path%</B></FONT></SPAN><SPAN STYLE="text-decoration: none"> - </SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Tahoma, sans-serif">and - </FONT></SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Courier New, monospace"><B>%change_action%</B></FONT></SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Courier New, monospace">.</FONT></SPAN></P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen2" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note<BR></B></FONT><FONT FACE="Tahoma, sans-serif">The + full path of the last changed file and the action that triggered the + change notification (create, update or delete) are </FONT><SPAN STYLE="text-decoration: none"><FONT FACE="Tahoma, sans-serif">written + to the environment variables </FONT></SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Courier New, monospace"><B>%change_path%</B></FONT></SPAN><SPAN STYLE="text-decoration: none"> + </SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Tahoma, sans-serif">and + </FONT></SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Courier New, monospace"><B>%change_action%</B></FONT></SPAN><SPAN STYLE="text-decoration: none"><FONT FACE="Courier New, monospace">.</FONT></SPAN></P> </SPAN><BR CLEAR=LEFT><BR> </P> <P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Example:</B></FONT> <FONT FACE="Tahoma, sans-serif">Show names of changed files or directories. (Windows)</FONT></P> - - <P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen4" DIR="LTR" STYLE="float: left; width: 80%; border: none; padding: 0cm; background: #e6e6e6"> - <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Courier New, monospace"><FONT FACE="Tahoma, sans-serif">Show - which file or directory has triggered a change. Enter command - line:</FONT><BR> cmd /c echo - %change_action% "%change_path%" & pause<BR><BR><FONT FACE="Tahoma, sans-serif">Write - a list of all changes to a logfile:</FONT><BR> cmd - /c echo %change_action% "%change_path%" >> - c:\log.txt</FONT></P> - </SPAN><BR CLEAR=LEFT> - </P> - -<P STYLE="margin-bottom: 0cm"><BR> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen4" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: none; padding: 0cm; background: #e6e6e6"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Courier New, monospace"><FONT FACE="Tahoma, sans-serif">Show + which file or directory has triggered a change. Enter command + line:</FONT><BR> cmd /c echo %change_action% + "%change_path%" & pause<BR><BR><FONT FACE="Tahoma, sans-serif">Write + a list of all changes to a logfile:</FONT><BR> cmd + /c echo %change_action% "%change_path%" >> + c:\log.txt</FONT></P> +</SPAN><BR CLEAR=LEFT><BR> </P> -<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen5" DIR="LTR" STYLE="float: left; width: 80%; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> - <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note<BR></B></FONT><FONT FACE="Tahoma, sans-serif">During - execution of a Windows Batch file (*.bat/*.cmd) a black console - window is shown. You can hide it using the Visual Basic script - "HideConsole.vbs" located in FreeFileSync's installation - directory:</FONT></P> - <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Courier New, monospace"><B>wscript - "C:\Program files\FreeFileSync\HideConsole.vbs" - C:\MyBatchFile.cmd</B></FONT></P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen5" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note<BR></B></FONT><FONT FACE="Tahoma, sans-serif">During + execution of a Windows Batch file (*.bat/*.cmd) a black console + window is shown. You can hide it using the Visual Basic script + "HideConsole.vbs" located in FreeFileSync's installation + directory:</FONT></P> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Courier New, monospace"><B>wscript + "C:\Program files\FreeFileSync\HideConsole.vbs" + C:\MyBatchFile.cmd</B></FONT></P> </SPAN><BR CLEAR=LEFT><BR> </P> <P STYLE="margin-bottom: 0cm"><BR> diff --git a/BUILD/Help/html/Run as Service.html b/BUILD/Help/html/Run as Service.html index 2a853717..d04d8115 100644 --- a/BUILD/Help/html/Run as Service.html +++ b/BUILD/Help/html/Run as Service.html @@ -3,9 +3,9 @@ <HEAD> <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=windows-1252"> <TITLE></TITLE> - <META NAME="GENERATOR" CONTENT="OpenOffice.org 3.4.1 (Win32)"> + <META NAME="GENERATOR" CONTENT="LibreOffice 4.0.4.2 (Windows)"> <META NAME="CREATED" CONTENT="20091206;16574000"> - <META NAME="CHANGED" CONTENT="20130206;19210588"> + <META NAME="CHANGED" CONTENT="20130722;18264912"> <META NAME="Info 1" CONTENT=""> <META NAME="Info 2" CONTENT=""> <META NAME="Info 3" CONTENT=""> @@ -27,40 +27,40 @@ is designed as a background process which does not need further attention once it is running. Depending on your requirements there are a number of ways to start it together with the operating system. Generally the goal is to execute a command line like the following: -</FONT><FONT FACE="Courier New, monospace"><SPAN STYLE="font-style: normal"><SPAN STYLE="font-weight: normal"><installation +</FONT><SPAN STYLE="font-variant: normal"><FONT FACE="Courier New, monospace"><SPAN STYLE="font-style: normal"><SPAN STYLE="font-weight: normal"><installation folder>\RealtimeSync.exe <path to *.ffs_real or *.ffs_batch -file></SPAN></SPAN></FONT></P> +file></SPAN></SPAN></FONT></SPAN></P> <P STYLE="margin-bottom: 0cm"><BR> </P> <P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Example:</B></FONT></P> <UL> - <P ><SPAN ID="Rahmen2" DIR="LTR" STYLE="float: left; width: 80%; border: none; padding: 0cm; background: #e6e6e6"> - <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"> - <FONT FACE="Courier New, monospace">"C:\Program - Files\FreeFileSync\RealtimeSync.exe" "C:\some - folder\SyncJob.ffs_real"</FONT></P> + <P><SPAN ID="Rahmen2" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: none; padding: 0cm; background: #e6e6e6"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Courier New, monospace">"C:\Program + Files\FreeFileSync\RealtimeSync.exe" "<FONT COLOR="#000000"><FONT SIZE=3><SPAN STYLE="font-weight: normal">D</SPAN></FONT></FONT><FONT COLOR="#000000"><FONT SIZE=3><SPAN STYLE="font-weight: normal">:\</SPAN></FONT></FONT><FONT COLOR="#000000"><FONT SIZE=3><SPAN STYLE="font-weight: normal">Backup + Projects</SPAN></FONT></FONT>.ffs_real"</FONT></P> </SPAN><BR CLEAR=LEFT> </P> </UL> <P><BR><BR> </P> <OL> - <LI><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><SPAN STYLE="font-weight: normal">RealtimeSync - should be monitoring only while a specific user is logged in: </SPAN></SPAN>Create + <LI><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-variant: normal"><SPAN STYLE="font-style: normal"><SPAN STYLE="font-weight: normal">RealtimeSync + should be monitoring only while a specific user is logged in: </SPAN></SPAN></SPAN>Create a new shortcut, enter the command line from above as target and place it into the user's autostart folder.</FONT></P> - <P STYLE="margin-bottom: 0cm"><IMG SRC="../img/create_shortcut.png" NAME="Grafik3" ALIGN=BOTTOM WIDTH=626 HEIGHT=338 BORDER=0></P> - <P STYLE="margin-bottom: 0cm"><IMG SRC="../img/shortcut_properties.png" NAME="Grafik4" ALIGN=BOTTOM WIDTH=377 HEIGHT=451 BORDER=0></P> + <P STYLE="margin-bottom: 0cm"><IMG SRC="../img/create_shortcut.png" NAME="Grafik3" ALIGN=BOTTOM BORDER=0></P> + <P STYLE="margin-bottom: 0cm"><IMG SRC="../img/shortcut_properties.png" NAME="Grafik4" ALIGN=BOTTOM BORDER=0></P> <P STYLE="margin-bottom: 0cm"> </P> <LI><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">RealtimeSync should be monitoring while Windows is online irrespective of currently logged in users: Create a new task in your operating systems's task scheduler and have it execute the command line above - when the system starts. See <A HREF="Schedule%20a%20Batch%20Job.html">Schedule + when the system starts. See <A HREF="Schedule%20a%20batch%20job.html">Schedule a Batch Job</A> for an example how to add a new task. Then change - the user to run the task to </FONT><FONT FACE="Tahoma, sans-serif"><B>SYSTEM</B></FONT><FONT FACE="Tahoma, sans-serif"> - - a special user account always running in the background.</FONT></P> - <P STYLE="margin-bottom: 0cm"><IMG SRC="../img/schedule_realtimesync.png" NAME="Grafik1" ALIGN=BOTTOM WIDTH=708 HEIGHT=284 BORDER=0></P> + the user to run the task to </FONT><FONT FACE="Tahoma, sans-serif"><B>SYSTEM</B></FONT> + <FONT FACE="Tahoma, sans-serif">- a special user account always + running in the background.</FONT></P> + <P STYLE="margin-bottom: 0cm"><IMG SRC="../img/schedule_realtimesync.png" NAME="Grafik1" ALIGN=BOTTOM BORDER=0></P> </OL> <P STYLE="margin-bottom: 0cm"><BR> </P> diff --git a/BUILD/Help/html/Schedule a Batch Job.html b/BUILD/Help/html/Schedule a Batch Job.html index c77dbaad..76393187 100644 --- a/BUILD/Help/html/Schedule a Batch Job.html +++ b/BUILD/Help/html/Schedule a Batch Job.html @@ -3,9 +3,9 @@ <HEAD> <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=windows-1252"> <TITLE></TITLE> - <META NAME="GENERATOR" CONTENT="OpenOffice.org 3.4.1 (Win32)"> + <META NAME="GENERATOR" CONTENT="LibreOffice 4.0.4.2 (Windows)"> <META NAME="CREATED" CONTENT="20091206;16574000"> - <META NAME="CHANGED" CONTENT="20130207;1441045"> + <META NAME="CHANGED" CONTENT="20130722;18221462"> <STYLE TYPE="text/css"> <!-- @page { margin: 2cm } @@ -16,28 +16,29 @@ </STYLE> </HEAD> <BODY LANG="de-DE" DIR="LTR"> -<H2 CLASS="western"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=4 STYLE="font-size: 15pt">Schedule a batch job</FONT></FONT></H2> +<H2 CLASS="western"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=4 STYLE="font-size: 15pt">Schedule +a batch job</FONT></FONT></H2> <OL> <LI><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Create - a new batch job via FreeFileSync's main dialog: </FONT><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>Menu - → Program → Save as batch job...</B></SPAN></FONT><FONT FACE="Tahoma, sans-serif"><BR> </FONT></P> + a new batch job via FreeFileSync's main dialog: </FONT><SPAN STYLE="font-variant: normal"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>Menu + → Program → Save as batch job...</B></SPAN></FONT></SPAN><FONT FACE="Tahoma, sans-serif"><BR> </FONT></P> <LI><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">If the batch job shall run without user interaction or as part of an unattended batch script, make sure that no popup dialog stops the - progress:<BR>Disable checkbox <SPAN STYLE="font-style: normal"><B>Show - progress dialog</B></SPAN> to avoid blocking while showing the - result after synchronization. Alternatively you can select the "On - completion" action <B>Close progress dialog</B> located in - synchronization settings.<BR><B>Note:</B> Even if the progress - dialog is not shown at the beginning, a user can make it visible - <B>during</B> synchronization by double-clicking the FreeFileSync - systray icon.<BR><IMG SRC="../img/SetupBatch.png" NAME="Grafik3" ALIGN=BOTTOM WIDTH=660 HEIGHT=263 BORDER=0></FONT></P> + progress:<BR>Disable checkbox <SPAN STYLE="font-variant: normal"><SPAN STYLE="font-style: normal"><B>Show + progress dialog</B></SPAN></SPAN> to avoid blocking while showing + the result after synchronization. Alternatively you can select the + "On completion" action <B>Close progress dialog</B> + located in synchronization settings.<BR><B>Note:</B> Even if the + progress dialog is not shown at the beginning, a user can make it + visible <B>during</B> synchronization by double-clicking the + FreeFileSync systray icon.<BR><IMG SRC="../img/SetupBatch.png" NAME="Grafik3" ALIGN=BOTTOM WIDTH=660 HEIGHT=263 BORDER=0></FONT></P> <P STYLE="margin-bottom: 0cm"> </P> <LI><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">In order to prevent error or warning popup messages from stopping - progress, set <SPAN STYLE="font-style: normal"><B>Error handling</B></SPAN> - to either <SPAN STYLE="font-style: normal"><B>Ignore</B></SPAN> or - <SPAN STYLE="font-style: normal"><B>Exit</B></SPAN>.</FONT></P> + progress, set <SPAN STYLE="font-variant: normal"><SPAN STYLE="font-style: normal"><B>Error + handling</B></SPAN></SPAN> to either <SPAN STYLE="font-variant: normal"><SPAN STYLE="font-style: normal"><B>Ignore</B></SPAN></SPAN> + or <SPAN STYLE="font-variant: normal"><SPAN STYLE="font-style: normal"><B>Exit</B></SPAN></SPAN>.</FONT></P> <P STYLE="margin-bottom: 0cm"> </P> <LI><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Setup your operating system's scheduler</FONT></P> @@ -56,13 +57,12 @@ and insert the <FONT FACE="Courier New, monospace">*.ffs_batch</FONT> file into <B>Add arguments</B>.</FONT></FONT></FONT></P> <LI><P STYLE="margin-bottom: 0cm; font-weight: normal"><FONT FACE="Tahoma, sans-serif"><FONT COLOR="#000000"><FONT SIZE=3>Use - quotation marks to protect spaces in path names, e.g.: </FONT></FONT><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><FONT SIZE=3>"C:\some - folder\SyncJob.ffs_batch"</FONT></FONT></FONT><FONT COLOR="#000000"><FONT SIZE=3><BR></FONT></FONT><IMG SRC="../img/win7scheduler.png" NAME="Grafik1" ALIGN=BOTTOM WIDTH=708 HEIGHT=284 BORDER=0></FONT></P> + quotation marks to protect spaces in path names, e.g.: </FONT></FONT><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><FONT SIZE=3>"</FONT></FONT></FONT><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><FONT SIZE=3>D</FONT></FONT></FONT><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><FONT SIZE=3>:\</FONT></FONT></FONT><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><FONT SIZE=3>Backup + Projects</FONT></FONT></FONT><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><FONT SIZE=3>.ffs_batch"</FONT></FONT></FONT><FONT COLOR="#000000"><FONT SIZE=3><BR></FONT></FONT><IMG SRC="../img/win7scheduler.png" NAME="Grafik1" ALIGN=BOTTOM WIDTH=708 HEIGHT=284 BORDER=0></FONT></P> </UL> </OL> - <P STYLE="margin-bottom: 0cm"><SPAN ID="Rahmen2" DIR="LTR" STYLE="float: left; width: 80%; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> - <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"> - <FONT FACE="Tahoma, sans-serif"><B>Note</B></FONT><FONT FACE="Tahoma, sans-serif"><BR>Beginning + <P STYLE="margin-bottom: 0cm"><SPAN ID="Rahmen2" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note</B></FONT><FONT FACE="Tahoma, sans-serif"><BR>Beginning with Windows Vista the "Program/script" always needs point to an executable file like "FreeFileSync.exe" even if ffs_batch file associations have been set!</FONT> I<FONT FACE="Tahoma, sans-serif">f @@ -77,9 +77,10 @@ XP Scheduled Tasks:</B></FONT></P> <UL> <LI VALUE=1><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Go - to <SPAN STYLE="font-style: normal"><B>Start → Control Panel - → Scheduled Tasks</B></SPAN> and select <SPAN STYLE="font-style: normal"><B>Add - Scheduled Task</B></SPAN>.</FONT></P> + to <SPAN STYLE="font-variant: normal"><SPAN STYLE="font-style: normal"><B>Start + → Control Panel → Scheduled Tasks</B></SPAN></SPAN> and + select <SPAN STYLE="font-variant: normal"><SPAN STYLE="font-style: normal"><B>Add + Scheduled Task</B></SPAN></SPAN>.</FONT></P> <LI><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Follow the wizard and select FreeFileSync.exe as program to run.</FONT></P> <LI><P STYLE="margin-bottom: 0cm; font-weight: normal"><FONT FACE="Tahoma, sans-serif">Fill @@ -94,12 +95,12 @@ Gnome-schedule, if necessary: <FONT FACE="Courier New, monospace">sudo apt-get install gnome-schedule</FONT></FONT></P> <LI><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-weight: normal">Go - to </SPAN></FONT><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>System - → Preferences → Scheduled tasks</B></SPAN></FONT></P> - <LI><P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><SPAN STYLE="font-weight: normal">Enter - the command </SPAN></SPAN></FONT><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><SPAN STYLE="font-weight: normal">as:</SPAN></SPAN></FONT><SPAN STYLE="font-style: normal"> - </SPAN><FONT FACE="Courier New, monospace"><SPAN STYLE="font-style: normal"><SPAN STYLE="font-weight: normal"><FreeFileSync - installation folder>/FreeFileSync <job name>.ffs_batch</SPAN></SPAN></FONT><FONT FACE="Courier New, monospace"><I><SPAN STYLE="font-weight: normal"><BR></SPAN></I></FONT><IMG SRC="../img/ubuntuScheduler.png" NAME="Grafik5" ALIGN=BOTTOM WIDTH=629 HEIGHT=560 BORDER=0></P> + to </SPAN></FONT><SPAN STYLE="font-variant: normal"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><B>System + → Preferences → Scheduled tasks</B></SPAN></FONT></SPAN></P> + <LI><P STYLE="margin-bottom: 0cm"><SPAN STYLE="font-variant: normal"><FONT FACE="Tahoma, sans-serif"><SPAN STYLE="font-style: normal"><SPAN STYLE="font-weight: normal">Enter + the command as:</SPAN></SPAN></FONT></SPAN><SPAN STYLE="font-variant: normal"> + </SPAN><SPAN STYLE="font-variant: normal"><FONT FACE="Courier New, monospace"><SPAN STYLE="font-style: normal"><SPAN STYLE="font-weight: normal"><FreeFileSync + installation folder>/FreeFileSync <job name>.ffs_batch</SPAN></SPAN></FONT></SPAN><FONT FACE="Courier New, monospace"><I><SPAN STYLE="font-weight: normal"><BR></SPAN></I></FONT><IMG SRC="../img/ubuntuScheduler.png" NAME="Grafik5" ALIGN=BOTTOM WIDTH=629 HEIGHT=560 BORDER=0></P> </UL> </OL> </OL> diff --git a/BUILD/Help/html/Variable Drive Letters.html b/BUILD/Help/html/Variable Drive Letters.html index 5d41c4ca..ee95e4a3 100644 --- a/BUILD/Help/html/Variable Drive Letters.html +++ b/BUILD/Help/html/Variable Drive Letters.html @@ -3,9 +3,9 @@ <HEAD> <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=windows-1252"> <TITLE></TITLE> - <META NAME="GENERATOR" CONTENT="OpenOffice.org 3.4.1 (Win32)"> + <META NAME="GENERATOR" CONTENT="LibreOffice 4.0.4.2 (Windows)"> <META NAME="CREATED" CONTENT="20091206;16574000"> - <META NAME="CHANGED" CONTENT="20130206;18481035"> + <META NAME="CHANGED" CONTENT="20130722;18235553"> <STYLE TYPE="text/css"> <!-- @page { margin: 2cm } @@ -19,27 +19,27 @@ <H2 CLASS="western"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=4 STYLE="font-size: 15pt">Variable drive letters</FONT></FONT></H2> <P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">USB -memory sticks or external hard disks are often assigned different drive letters when plugged into -distinct computers. FreeFileSync offers two solutions to handle this -problem:</FONT></P> +memory sticks or external hard disks often get different +drive letters assigned when plugged into distinct computers. FreeFileSync +offers two solutions to handle this problem:</FONT></P> <P STYLE="margin-bottom: 0cm"><BR> </P> <P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Option 1: </B>Specify a folder path by using the volume name:</FONT></P> -<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen5" DIR="LTR" STYLE="float: left; width: 80%; border: none; padding: 0cm; background: #e6e6e6"> - <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"> - <FONT FACE="Tahoma, sans-serif">Use "<FONT FACE="Courier New, monospace">[ZENJU-USB]\folder</FONT>" +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen5" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: none; padding: 0cm; background: #e6e6e6"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Use + "<FONT FACE="Courier New, monospace">[ZENJU-USB]\folder</FONT>" instead of "<FONT FACE="Courier New, monospace">G:\folder</FONT>" where "<FONT FACE="Courier New, monospace">ZENJU-USB</FONT>" - is the volume name of the USB stick which is currently mounted in drive - G:\.</FONT></P> + is the volume name of the USB stick which is currently mounted in + drive G:\.</FONT></P> </SPAN><BR CLEAR=LEFT><BR> </P> -<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen4" DIR="LTR" STYLE="float: left; width: 80%; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> - <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note<BR></B>It - is not required to look up and enter the volume name manually! Just - select the corresponding entry in the drop down menu.</FONT></P> - <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><IMG SRC="../img/VolumeName.png" NAME="Grafik1" ALIGN=BOTTOM WIDTH=424 HEIGHT=86 BORDER=0></P> +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen4" DIR="LTR" STYLE="float: left; width: 80%; height: 0.04cm; border: 1px solid #000080; padding: 0.05cm; background: #ccccff"> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Note<BR></B>It + is not required to look up and enter the volume name manually. Just + select the corresponding entry in the drop down menu.</FONT></P> + <P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><IMG SRC="../img/VolumeName.png" NAME="Grafik1" ALIGN=BOTTOM WIDTH=424 HEIGHT=86 BORDER=0></P> </SPAN><BR CLEAR=LEFT><BR> </P> <P STYLE="margin-bottom: 0cm"><BR> @@ -47,20 +47,32 @@ problem:</FONT></P> <P STYLE="margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif"><B>Option 2: </B></FONT><FONT FACE="Tahoma, sans-serif">Use a relative directory name:</FONT></P> -<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen1" DIR="LTR" STYLE="float: left; width: 80%; border: none; padding: 0cm; background: #e6e6e6"> - <LI><P STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Use - "</FONT><FONT FACE="Courier New, monospace">\folder</FONT><FONT FACE="Tahoma, sans-serif">" - instead of "</FONT><FONT FACE="Courier New, monospace">G:\folder</FONT><FONT FACE="Tahoma, sans-serif">"<BR> </FONT></P> - <LI><P STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Save - and copy synchronization settings to the USB stick: - "</FONT><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><FONT SIZE=3>G:\settings.ffs_gui"<BR> </FONT></FONT></FONT></P> - <LI><P STYLE="margin-left: 0.79cm; margin-bottom: 0cm"><FONT FACE="Tahoma, sans-serif">Start - FreeFileSync by double-clicking on "</FONT><FONT FACE="Courier New, monospace">G:\settings.ffs_gui</FONT><FONT FACE="Tahoma, sans-serif">"<BR>→ - Working directory is automatically set to "</FONT><FONT FACE="Courier New, monospace">G:\</FONT><FONT FACE="Tahoma, sans-serif">" - by the operating system so that "</FONT><FONT FACE="Courier New, monospace">\folder</FONT><FONT FACE="Tahoma, sans-serif">" - will be resolved as "</FONT><FONT FACE="Courier New, monospace">G:\folder</FONT><FONT FACE="Tahoma, sans-serif">" - during synchronization.</FONT></P> + + +<P STYLE="margin-left: 1.32cm; margin-bottom: 0cm"><SPAN ID="Rahmen3" DIR="LTR" STYLE="float: left; width: 80%; border: none; padding: 0.05cm; background: #e6e6e6"> + <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm"> + <FONT FACE="Tahoma, sans-serif">Use + "</FONT><FONT FACE="Courier New, monospace">\folder</FONT><FONT FACE="Tahoma, sans-serif">" + instead of "</FONT><FONT FACE="Courier New, monospace">G:\folder</FONT><FONT FACE="Tahoma, sans-serif">"</FONT> + </P><BR> + + <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm; font-style: normal"> + <FONT FACE="Tahoma, sans-serif">Save + and copy synchronization settings to the USB stick: + "</FONT><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><FONT SIZE=3>G:\</FONT></FONT></FONT><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><FONT SIZE=3>Backup</FONT></FONT></FONT><FONT COLOR="#000000"><FONT FACE="Courier New, monospace"><FONT SIZE=3>.ffs_gui"</FONT></FONT></FONT> + </P><BR> + + <LI><P ALIGN=LEFT STYLE="margin-left: 0.79cm; margin-bottom: 0cm; font-style: normal"> + <FONT FACE="Tahoma, sans-serif">Start + FreeFileSync by double-clicking on "</FONT><FONT FACE="Courier New, monospace">G:\</FONT><FONT FACE="Courier New, monospace">Backup</FONT><FONT FACE="Courier New, monospace">.ffs_gui</FONT><FONT FACE="Tahoma, sans-serif">"<BR>→ + The working directory is automatically set to "</FONT><FONT FACE="Courier New, monospace">G:\</FONT><FONT FACE="Tahoma, sans-serif">" + by the operating system so that </FONT><FONT FACE="Tahoma, sans-serif">the + relative path </FONT><FONT FACE="Tahoma, sans-serif">"</FONT><FONT FACE="Courier New, monospace">\folder</FONT><FONT FACE="Tahoma, sans-serif">" + will be resolved as "</FONT><FONT FACE="Courier New, monospace">G:\folder</FONT><FONT FACE="Tahoma, sans-serif">" + during synchronization.</FONT> + </P> </SPAN><BR CLEAR=LEFT><BR> </P> + </BODY> </HTML>
\ No newline at end of file diff --git a/BUILD/Help/img/RealtimeSync.png b/BUILD/Help/img/RealtimeSync.png Binary files differindex 32706c6f..59b4fa01 100644 --- a/BUILD/Help/img/RealtimeSync.png +++ b/BUILD/Help/img/RealtimeSync.png diff --git a/BUILD/Help/img/ScheduleBatch.png b/BUILD/Help/img/ScheduleBatch.png Binary files differindex c991d1fd..214a2870 100644 --- a/BUILD/Help/img/ScheduleBatch.png +++ b/BUILD/Help/img/ScheduleBatch.png diff --git a/BUILD/Help/img/WatchUsbInsert.png b/BUILD/Help/img/WatchUsbInsert.png Binary files differindex b057d6b0..35d0b45d 100644 --- a/BUILD/Help/img/WatchUsbInsert.png +++ b/BUILD/Help/img/WatchUsbInsert.png diff --git a/BUILD/Help/img/create_shortcut.png b/BUILD/Help/img/create_shortcut.png Binary files differindex 2c42f3a1..7b0a5e2c 100644 --- a/BUILD/Help/img/create_shortcut.png +++ b/BUILD/Help/img/create_shortcut.png diff --git a/BUILD/Help/img/schedule_realtimesync.png b/BUILD/Help/img/schedule_realtimesync.png Binary files differindex ac1e2a80..ce52fd28 100644 --- a/BUILD/Help/img/schedule_realtimesync.png +++ b/BUILD/Help/img/schedule_realtimesync.png diff --git a/BUILD/Help/img/shortcut_properties.png b/BUILD/Help/img/shortcut_properties.png Binary files differindex 1e06b617..77e9a773 100644 --- a/BUILD/Help/img/shortcut_properties.png +++ b/BUILD/Help/img/shortcut_properties.png diff --git a/BUILD/Help/img/win7scheduler.png b/BUILD/Help/img/win7scheduler.png Binary files differindex 93f1e020..eabf331f 100644 --- a/BUILD/Help/img/win7scheduler.png +++ b/BUILD/Help/img/win7scheduler.png diff --git a/BUILD/Languages/arabic.lng b/BUILD/Languages/arabic.lng index 8d84a0b1..4d5bf07b 100644 --- a/BUILD/Languages/arabic.lng +++ b/BUILD/Languages/arabic.lng @@ -250,8 +250,8 @@ <target>الفحص:</target> <source> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> </source> <target> <pluralform>0 بند</pluralform> diff --git a/BUILD/Languages/croatian.lng b/BUILD/Languages/croatian.lng index b87ebc76..8407fe7f 100644 --- a/BUILD/Languages/croatian.lng +++ b/BUILD/Languages/croatian.lng @@ -1,84 +1,12 @@ <header> <language>Hrvatski</language> - <translator>Slavko Blažević</translator> + <translator>Miroslav Vranić</translator> <locale>hr_HR</locale> <flag_image>flag_croatia.png</flag_image> <plural_form_count>3</plural_form_count> <plural_definition>n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2</plural_definition> </header> -<source>Cannot determine final path for %x.</source> -<target></target> - -<source>Failed to register to receive system messages.</source> -<target></target> - -<source>Cannot create symbolic link %x.</source> -<target></target> - -<source> -<pluralform>Do you really want to delete the following item?</pluralform> -<pluralform>Do you really want to delete the following %x items?</pluralform> -</source> -<target></target> - -<source> -<pluralform>Do you really want to move the following item to the Recycle Bin?</pluralform> -<pluralform>Do you really want to move the following %x items to the Recycle Bin?</pluralform> -</source> -<target></target> - -<source>Do&n't save</source> -<target></target> - -<source> -<pluralform>%y of 1 row in view</pluralform> -<pluralform>%y of %x rows in view</pluralform> -</source> -<target></target> - -<source>Select a variant</source> -<target></target> - -<source>Check for new &version</source> -<target></target> - -<source>&Tools</source> -<target></target> - -<source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source> -<target></target> - -<source>&Download</source> -<target></target> - -<source>New version found</source> -<target></target> - -<source>Switching to FreeFileSync main dialog</source> -<target></target> - -<source>Data verification error: %x and %y have different content.</source> -<target></target> - -<source>Cannot determine volume name for %x.</source> -<target></target> - -<source>Starting comparison</source> -<target></target> - -<source>Resolving symbolic link %x</source> -<target></target> - -<source>The following folders have dependent paths. Be careful when setting up synchronization rules:</source> -<target></target> - -<source>The Recycle Bin is not available for the following folders. Files will be deleted permanently instead:</source> -<target></target> - -<source>The database entry is not in sync considering current settings.</source> -<target></target> - <source>Both sides have changed since last synchronization.</source> <target>Obje su strane promjenjene od posljednje sinkronizacije.</target> @@ -88,6 +16,9 @@ <source>No change since last synchronization.</source> <target>Nema promjena od zadnje sinkronizacije.</target> +<source>The database entry is not in sync considering current settings.</source> +<target>Unos podataka nije u sinkronizaciji uzevši u obzir trenutne postavke.</target> + <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> <target>Postavljam zadani sinkronizacijski smijer: Stare datoteke će biti prepisane novim datotekama.</target> @@ -112,11 +43,20 @@ <source>Deleting symbolic link %x</source> <target>Brisanje simboličnih poveznica %x</target> +<source>The Recycle Bin is not available for the following folders. Files will be deleted permanently instead:</source> +<target>Koš nije dostupan za sljedeće mape. Datoteke će biti nepovratno izbrisane:</target> + <source>An exception occurred</source> <target>Dogodilo se izuzeće</target> -<source>Cannot find file %x.</source> -<target>Ne mogu pronaći datoteku %x.</target> +<source>A directory path is expected after %x.</source> +<target>Putanja direktorija se očekuje nakon %x.</target> + +<source>Syntax error</source> +<target>Greška sintakse</target> + +<source>Cannot open file %x.</source> +<target>Ne mogu otvoriti datoteku %x.</target> <source>Error</source> <target>Greška</target> @@ -124,6 +64,36 @@ <source>File %x does not contain a valid configuration.</source> <target>Datoteka %x ne sadrži valjanu konfiguraciju</target> +<source>Unequal number of left and right directories specified.</source> +<target>Neravnomjeran broj lijevih i desnih direkotrija specificiran.</target> + +<source>The config file must not contain settings at directory pair level when directories are set via command line.</source> +<target>Config datoteka ne smije sadržavati postavke na direktorij par razini ukoliko su direktoriji upisani preko naredbene linije.</target> + +<source>Warning</source> +<target>Oprez</target> + +<source>Directories cannot be set for more than one configuration file.</source> +<target>Direkotrij nemože biti postavljen za više od jedne konfigracijske datoteke.</target> + +<source>Syntax:</source> +<target>Sintaksa:</target> + +<source>config files</source> +<target>config datoteke</target> + +<source>directory</source> +<target>direktorij</target> + +<source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> +<target>Bilo koji broj FreeFileSync . ffs_gui i/ili .ffs_batch konfiguracijskih datoteka.</target> + +<source>Any number of alternative directories for at most one config file.</source> +<target>Bilo koji broj alternativnih mapa za najviše jedanu config datoteku.</target> + +<source>Command line</source> +<target>Naredbena linija</target> + <source>A folder input field is empty.</source> <target>Polje za odabir foldera je prazno.</target> @@ -136,6 +106,9 @@ <source>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.</source> <target>Možete ignorirati ovu pogrešku te uzeti u obzir svaku mapu praznom. Mape se tada stvaraju automatski tokom sinkronizacije.</target> +<source>The following folders have dependent paths. Be careful when setting up synchronization rules:</source> +<target>Sljedeće mape imaju zavisnu putanju. Budite pažljivi prilikom postavljanja pravila za sinkronizaciju:</target> + <source>File %x has an invalid date.</source> <target>Datoteka %x ima nevaljan datum.</target> @@ -151,12 +124,18 @@ <source>Items differ in attributes only</source> <target>Stavke se razlikuju samo u atributima</target> +<source>Resolving symbolic link %x</source> +<target>Rješavam simboličku vezu %x</target> + <source>Comparing content of files %x</source> <target>Uspoređujem sadržaj datoteka %x</target> <source>Generating file list...</source> <target>Generiram listu datoteka...</target> +<source>Starting comparison</source> +<target>Počinjem usporedbu</target> + <source>Calculating sync directions...</source> <target>Izračunavam smjerove sinkronizacije...</target> @@ -236,7 +215,7 @@ <target>Ne mogu čitati datoteku %x.</target> <source>Database files do not share a common session.</source> -<target>Datoteke baze ne dijele zajednički protokol</target> +<target>Datoteke baze ne dijele zajednički protokol.</target> <source>Searching for folder %x...</source> <target>Tražim mapu %x...</target> @@ -245,7 +224,7 @@ <target>Ne mogu pročitati osobine od %x.</target> <source>Cannot get process information.</source> -<target>Ne mogu dobit iniformacije o procesu</target> +<target>Ne mogu dobit informacije o procesu</target> <source>Waiting while directory is locked (%x)...</source> <target>Čekam dok se direktorij zaključava (%x)...</target> @@ -273,13 +252,13 @@ <target>Ukupno vrijeme:</target> <source> -<pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> +<pluralform>1 byte</pluralform> +<pluralform>%x bytes</pluralform> </source> <target> -<pluralform>%x Bajt</pluralform> -<pluralform>%x Bajta</pluralform> -<pluralform>%x Bajtova</pluralform> +<pluralform>%x bajt</pluralform> +<pluralform>%x bajta</pluralform> +<pluralform>%x bajtova</pluralform> </target> <source>%x MB</source> @@ -301,13 +280,13 @@ <target>Pretražujem:</target> <source> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> </source> <target> -<pluralform>%x Stavka</pluralform> -<pluralform>%x Stavke</pluralform> -<pluralform>%x Stavki</pluralform> +<pluralform>%x nit</pluralform> +<pluralform>%x niti</pluralform> +<pluralform>%x niti</pluralform> </target> <source>Encoding extended time information: %x</source> @@ -316,6 +295,9 @@ <source>/sec</source> <target>/sek</target> +<source>%x items</source> +<target>%x stavke</target> + <source>Configuration file %x loaded partially only.</source> <target>Datoteka postavki %x učitana samo djelomično</target> @@ -337,8 +319,11 @@ <source>Cannot load file %x.</source> <target>Ne mogu učitati datoteku %x.</target> -<source>Volume name %x not part of file name %y.</source> -<target>Naziv particije %x dio naziva datoteke %y.</target> +<source>Cannot determine volume name for %x.</source> +<target>Ne mogu ustanoviti ime volumen for %x.</target> + +<source>Volume name %x is not part of file path %y.</source> +<target>Ime voumena %x nije dio puta datotke %y.</target> <source>Abort requested: Waiting for current operation to finish...</source> <target>Prekid zahtjevan: čekam da se trenutna akcija završi...</target> @@ -406,9 +391,6 @@ <source>Idle time between last detected change and execution of command</source> <target>Vrijeme čekanja između zadnje prepoznate promjene i izvršenja naredbe</target> -<source>Command line</source> -<target>Naredbena linija</target> - <source> The command is triggered if: - files or subfolders change @@ -432,9 +414,6 @@ Naredba će biti pokrenuta ako se: <source>RealtimeSync - Automated Synchronization</source> <target>RealtimeSync - Automatska Sinkronizacija</target> -<source>Warning</source> -<target>Oprez</target> - <source>Build: %x</source> <target>Inačnica: %x</target> @@ -444,15 +423,21 @@ Naredba će biti pokrenuta ako se: <source>All files</source> <target>Sve datoteke</target> +<source>Directory monitoring active</source> +<target>Nadzor direktorija je aktivan</target> + +<source>Waiting until all directories are available...</source> +<target>Čekanje na dostupnost svih direktorija...</target> + <source>&Restore</source> <target>&Vrati</target> +<source>&Show error</source> +<target>&Prikaži grešku</target> + <source>&Exit</source> <target>&Izlaz</target> -<source>Waiting for missing directories...</source> -<target>Čekam nedostajuće direktorije...</target> - <source>Invalid command line:</source> <target>Netočna naredba:</target> @@ -462,14 +447,14 @@ Naredba će biti pokrenuta ako se: <source>File time and size</source> <target>Vrijeme i veličina datoteke</target> -<source> Two way </source> +<source>Two way</source> <target>Dvosmjerno</target> <source>Mirror</source> -<target>Zrcalno </target> +<target>Zrcalno</target> <source>Update</source> -<target>Ažuriraj </target> +<target>Ažuriraj</target> <source>Custom</source> <target>Uobičajeno</target> @@ -514,22 +499,19 @@ Naredba će biti pokrenuta ako se: <target>Odredišna mapa %x već postoji.</target> <source>Target folder input field must not be empty.</source> -<target>Odredišna mapa ne može biti prazna</target> +<target>Odredišna mapa ne može biti prazna.</target> <source>Folder input field for versioning must not be empty.</source> -<target>Mapa unosa ne smije biti prazan</target> +<target>Mapa unosa ne smije biti prazna.</target> <source>Source folder %x not found.</source> -<target>Izvorna mapa %x nije pronađena</target> +<target>Izvorna mapa %x nije pronađena.</target> <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>Slijedeće stavke imaju nedefiniranih sukoba te neće biti sinkronizirane:</target> -<source>Significant difference detected:</source> -<target>Značajna razlika opažena:</target> - -<source>More than 50% of the total number of files will be copied or deleted.</source> -<target>Više od 50% od ukupnog broja datoteka će biti kopirano ili izbrisano.</target> +<source>The following folders are significantly different. Make sure you are matching the correct folders for synchronization.</source> +<target>Navedene mape se značajno razlikuju. Prvojerite dali uspoređujete ispravne mape za sinkronizaciju.</target> <source>Not enough free disk space available in:</source> <target>Nedovoljno prostora na disku:</target> @@ -552,6 +534,9 @@ Naredba će biti pokrenuta ako se: <source>Creating Volume Shadow Copy for %x...</source> <target>Izrađujem Volume Shadow Kopiju za %x...</target> +<source>Data verification error: %x and %y have different content.</source> +<target>Greška pri provjeri podataka: %x i %y imaju različit sadržaj.</target> + <source>Synchronization aborted</source> <target>Sinkronizacija prekinuta</target> @@ -573,12 +558,24 @@ Naredba će biti pokrenuta ako se: <source>Press "Switch" to resolve issues in FreeFileSync main dialog.</source> <target>Preisni "Zamjeni" za riješavanje problema u FreeFileSync glavnom dijaloškom okviru</target> +<source>Switching to FreeFileSync main dialog</source> +<target>Prebacujem na FreeFileSync glavni dijaloški prozor</target> + +<source>Retrying operation after error:</source> +<target>Ponavljam operaciju nakon greške:</target> + <source>A new version of FreeFileSync is available:</source> <target>Nova verzija FreeFileSync je dostupna:</target> <source>Download now?</source> <target>Preuzeti sada?</target> +<source>New version found</source> +<target>Nova verzija je nađena</target> + +<source>&Download</source> +<target>&Preuzmi</target> + <source>FreeFileSync is up to date.</source> <target>FreeFileSync je ažuriran.</target> @@ -588,6 +585,9 @@ Naredba će biti pokrenuta ako se: <source>Unable to connect to sourceforge.net.</source> <target>Ne mogu se povezati na sourceforge.net.</target> +<source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source> +<target>Ne mogu pronaći trenutnu verziju FreeFileSync-a online. Dali želite ručno potražiti?</target> + <source>Symlink</source> <target>Poveznica simbola</target> @@ -690,12 +690,18 @@ Naredba će biti pokrenuta ako se: <source>&Global settings...</source> <target>&Globalne postavke...</target> +<source>&Tools</source> +<target>&Alati</target> + <source>&Check now</source> <target>&Provjeri sada</target> <source>Check &automatically once a week</source> <target>Provjeri &automatski jednom tjedno</target> +<source>Check for new &version</source> +<target>Prvojeri dostupnost nove &verzije</target> + <source>Compare</source> <target>Usporedi</target> @@ -753,6 +759,9 @@ Naredba će biti pokrenuta ako se: <source>Synchronizing...</source> <target>Sinkroniziram...</target> +<source>Minimize to notification area</source> +<target>Spusti u notifikacije</target> + <source>On completion</source> <target>Pri završetku</target> @@ -762,6 +771,18 @@ Naredba će biti pokrenuta ako se: <source>&Pause</source> <target>&Pauziraj</target> +<source>Variant</source> +<target>Način</target> + +<source>Statistics</source> +<target>Statistika</target> + +<source>Don't show this dialog again</source> +<target>Ne prikazuj ovaj prozor ponovno</target> + +<source>Select a variant</source> +<target>Odaberite varijantu</target> + <source> Files are found equal if - last write time and date @@ -952,7 +973,7 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <target>Kopiraj datotečna dopuštenja</target> <source>Transfer file and folder permissions (Requires Administrator rights)</source> -<target>Premjesti datotečna dopuštenja (Zahtjeva Administratorska prava)</target> +<target>Premjesti datotečna dopuštenja (Zahtjeva administratorska prava)</target> <source>Restore hidden dialogs</source> <target>Prikaži skrivene prozore</target> @@ -966,17 +987,8 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <source>&Default</source> <target>&Zadano</target> -<source>Variant</source> -<target>Način</target> - -<source>Statistics</source> -<target>Statistika</target> - -<source>Don't show this dialog again</source> -<target>Ne prikazuj ovaj prozor ponovno</target> - <source>Find what:</source> -<target>Nađi što</target> +<target>Nađi što:</target> <source>Match case</source> <target>Poklopi se</target> @@ -984,15 +996,15 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <source>&Find next</source> <target>&Nađi slijedeće</target> +<source>Start synchronization</source> +<target>Započni sinkronizaciju</target> + <source>Delete</source> <target>Izbriši</target> <source>Configure filter</source> <target>Konfiguriraj filter</target> -<source>Start synchronization</source> -<target>Započni sinkronizaciju</target> - <source>Find</source> <target>Pronađi</target> @@ -1027,6 +1039,25 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <target>Usporedi obje strane</target> <source> +<pluralform>Do you really want to execute the command %y for 1 item?</pluralform> +<pluralform>Do you really want to execute the command %y for %x items?</pluralform> +</source> +<target> +<pluralform>Dali stvarno želite izvršiti nardebu %y za %x stavku?</pluralform> +<pluralform>Dali stvarno želite izvršiti nardebu %y za %x stavke?</pluralform> +<pluralform>Dali stvarno želite izvršiti nardebu %y za %x stavki?</pluralform> +</target> + +<source>Confirm</source> +<target>Potvrdi</target> + +<source>&Execute</source> +<target>&Izvrši</target> + +<source>&Don't show this dialog again</source> +<target>&Nemoj više prikazivati ovaj dijaloški prozor</target> + +<source> <pluralform>1 directory</pluralform> <pluralform>%x directories</pluralform> </source> @@ -1046,11 +1077,21 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <pluralform>%x datoteka</pluralform> </target> +<source> +<pluralform>%y of 1 row in view</pluralform> +<pluralform>%y of %x rows in view</pluralform> +</source> +<target> +<pluralform>%y od %x reda u prikazu</pluralform> +<pluralform>%y od %x reda u prikazu</pluralform> +<pluralform>%y od %x reda u prikazu</pluralform> +</target> + <source>Set direction:</source> <target>Odaberi smijer:</target> <source>Exclude temporarily</source> -<target>Trenutno izključi</target> +<target>Izuzmi privremeno</target> <source>Include temporarily</source> <target>Trenutno uključi</target> @@ -1103,6 +1144,9 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <source>Do you want to save changes to %x?</source> <target>Da li želite spremiti izmjene za %x?</target> +<source>Do&n't save</source> +<target>&Nemoj spremiti</target> + <source>Never save changes</source> <target>Nikad ne spremaj promjene</target> @@ -1157,8 +1201,8 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <source>All folders are in sync</source> <target>Sve mape su u sinkronizaciji</target> -<source>Comma separated list</source> -<target>Zarezom odvojene liste</target> +<source>Comma-separated values</source> +<target>Zarezom odvojene vrijednosti</target> <source>File list exported</source> <target>Datotečna lista izvezena</target> @@ -1211,17 +1255,14 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <source>Completed</source> <target>Završeno</target> -<source>Continue</source> -<target>Nastavi</target> - -<source>Pause</source> -<target>Pauziraj</target> +<source>&Continue</source> +<target>&Nastavi</target> -<source>Logging</source> -<target>Zapisivanje</target> +<source>Log</source> +<target>Dnevnik</target> <source>Cannot find %x</source> -<target>Nemogu pronać %x</target> +<target>Nemogu pronaći %x</target> <source>Inactive</source> <target>Neaktivno</target> @@ -1253,6 +1294,26 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <source>Filter</source> <target>Filtriranje</target> +<source> +<pluralform>Do you really want to move the following item to the Recycle Bin?</pluralform> +<pluralform>Do you really want to move the following %x items to the Recycle Bin?</pluralform> +</source> +<target> +<pluralform>Dali stvarno želite premjestiti sljedeću %x stavku u koš?</pluralform> +<pluralform>Dali stvarno želite premjestiti sljedeće %x stavke u koš?</pluralform> +<pluralform>Dali stvarno želite premjestiti sljedećih %x stavki u koš?</pluralform> +</target> + +<source> +<pluralform>Do you really want to delete the following item?</pluralform> +<pluralform>Do you really want to delete the following %x items?</pluralform> +</source> +<target> +<pluralform>Dali stvarno želite obrisati sljedeću %x stavku?</pluralform> +<pluralform>Dali stvarno želite obristi sljedeće %x stavke?</pluralform> +<pluralform>Dali stvarno želite obristi sljedećih %x stavki?</pluralform> +</target> + <source>Direct</source> <target>Neposredno</target> @@ -1295,9 +1356,6 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <source>Append a timestamp to each file name</source> <target>Dodaj vremensku oznaku svakom imenu datoteke</target> -<source>Folder</source> -<target>Mapa</target> - <source>File</source> <target>Datoteka</target> @@ -1352,6 +1410,9 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <source>Cannot create directory %x.</source> <target>Ne mogu izraditi mapu %x.</target> +<source>Cannot create symbolic link %x.</source> +<target>Ne mogu stvoriti simboličku vezu %x.</target> + <source>Cannot find system function %x.</source> <target>Ne mogu pronaći sistemsku funkciju %x.</target> @@ -1406,6 +1467,9 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <pluralform>%x dana</pluralform> </target> +<source>Failed to register to receive system messages.</source> +<target>Neuspijela registracija za primanje sistemskih poruka.</target> + <source>Cannot set privilege %x.</source> <target>Ne mogu postaviti prava za %x.</target> @@ -1418,6 +1482,9 @@ Napomena: Imena datoteka moraju biti srodni s glavnim mapama. <source>Unable to move %x to the Recycle Bin.</source> <target>Nije moguće premjestiti %x u koš za smeće.</target> +<source>Cannot determine final path for %x.</source> +<target>Ne mogu odrediti završnu putanju za %x.</target> + <source>Error Code %x:</source> <target>Pogreška broj %x:</target> diff --git a/BUILD/Languages/dutch.lng b/BUILD/Languages/dutch.lng index eee7d1fb..911893b9 100644 --- a/BUILD/Languages/dutch.lng +++ b/BUILD/Languages/dutch.lng @@ -7,57 +7,6 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> -<source>Cannot determine final path for %x.</source> -<target></target> - -<source>Failed to register to receive system messages.</source> -<target></target> - -<source>Cannot create symbolic link %x.</source> -<target></target> - -<source>Select a variant</source> -<target></target> - -<source>Check for new &version</source> -<target></target> - -<source>&Tools</source> -<target></target> - -<source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source> -<target></target> - -<source>&Download</source> -<target></target> - -<source>New version found</source> -<target></target> - -<source>Switching to FreeFileSync main dialog</source> -<target></target> - -<source>Data verification error: %x and %y have different content.</source> -<target></target> - -<source>Cannot determine volume name for %x.</source> -<target></target> - -<source>Starting comparison</source> -<target></target> - -<source>Resolving symbolic link %x</source> -<target></target> - -<source>The following folders have dependent paths. Be careful when setting up synchronization rules:</source> -<target></target> - -<source>The Recycle Bin is not available for the following folders. Files will be deleted permanently instead:</source> -<target></target> - -<source>The database entry is not in sync considering current settings.</source> -<target></target> - <source>Both sides have changed since last synchronization.</source> <target>Beide zijdes zijn veranderd sinds de laatste synchronisatie.</target> @@ -67,6 +16,9 @@ <source>No change since last synchronization.</source> <target>Geen veranderingen sinds de laatste synchronisatie.</target> +<source>The database entry is not in sync considering current settings.</source> +<target>De database is niet gesynchroniseerd volgens de huidige instellingen.</target> + <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> <target>Stel standaard synchronisatie richtingen in: Oude bestanden worden door nieuwere bestanden overschreven.</target> @@ -91,11 +43,20 @@ <source>Deleting symbolic link %x</source> <target>Verwijderen van snelkoppeling %x</target> +<source>The Recycle Bin is not available for the following folders. Files will be deleted permanently instead:</source> +<target>De Prullenbak is niet beschikbaar voor de volgende mappen. De bestanden zullen permanent worden verwijderd:</target> + <source>An exception occurred</source> <target>Er heeft een uitzondering plaatsgevonden</target> -<source>Cannot find file %x.</source> -<target>Kan bestand %x niet vinden.</target> +<source>A directory path is expected after %x.</source> +<target>Een bestandslocatie pad is verwacht na %x.</target> + +<source>Syntax error</source> +<target>Syntax fout</target> + +<source>Cannot open file %x.</source> +<target>Kan bestand %x niet openen.</target> <source>Error</source> <target>Fout</target> @@ -103,6 +64,36 @@ <source>File %x does not contain a valid configuration.</source> <target>Bestand %x bevat geen valide configuratie.</target> +<source>Unequal number of left and right directories specified.</source> +<target>Oneven nummer van linker en rechter bestandslocaties gespecificeerd.</target> + +<source>The config file must not contain settings at directory pair level when directories are set via command line.</source> +<target>Het configuratiebestand mag geen instellingen bevatten voor een bestandslocatie level als bestandslocaties in de command line ingesteld zijn.</target> + +<source>Warning</source> +<target>Waarschuwing</target> + +<source>Directories cannot be set for more than one configuration file.</source> +<target>Bestandslocaties kunnen niet voor meer dan een configuratiebestand gemaakt worden.</target> + +<source>Syntax:</source> +<target>Syntax:</target> + +<source>config files</source> +<target>configuratiebestanden</target> + +<source>directory</source> +<target>bestandslocatie</target> + +<source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> +<target>Elk aantal FreeFileSync .ffs_gui en/of .ffs_batch configuratiebestanden.</target> + +<source>Any number of alternative directories for at most one config file.</source> +<target>Elk aantal alternatieve bestandslocaties voor een configuratiebestand.</target> + +<source>Command line</source> +<target>Opdrachtregel</target> + <source>A folder input field is empty.</source> <target>Een map invoerveld is leeg.</target> @@ -115,6 +106,9 @@ <source>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.</source> <target>U kunt deze foutmelding negeren om elke map als leeg te markeren. De mappen worden dan automatisch aangemaakt tijdens de synchronisatie.</target> +<source>The following folders have dependent paths. Be careful when setting up synchronization rules:</source> +<target>De volgende mappen hebben afhankelijke paden. Wees voorzichtig bij het opzetten van synchronisatieregels:</target> + <source>File %x has an invalid date.</source> <target>Bestand %x heeft een ongeldige datum.</target> @@ -130,12 +124,18 @@ <source>Items differ in attributes only</source> <target>Items verschillen alleen in attributen</target> +<source>Resolving symbolic link %x</source> +<target>Bezig met vinden van snelkoppeling %x</target> + <source>Comparing content of files %x</source> <target>De inhoud van %x bestanden wordt vergeleken</target> <source>Generating file list...</source> <target>Genereren van bestandslijst...</target> +<source>Starting comparison</source> +<target>Vergelijking starten</target> + <source>Calculating sync directions...</source> <target>Synchronisatie richtingen calculeren...</target> @@ -251,12 +251,12 @@ <target>Totale tijd:</target> <source> -<pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> +<pluralform>1 byte</pluralform> +<pluralform>%x bytes</pluralform> </source> <target> -<pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> +<pluralform>1 byte</pluralform> +<pluralform>%x bytes</pluralform> </target> <source>%x MB</source> @@ -278,12 +278,12 @@ <target>Doorzoekt:</target> <source> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> </source> <target> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> </target> <source>Encoding extended time information: %x</source> @@ -292,6 +292,9 @@ <source>/sec</source> <target>/sec</target> +<source>%x items</source> +<target>%x items</target> + <source>Configuration file %x loaded partially only.</source> <target>Configuratiebestand %x alleen deels geladen.</target> @@ -313,8 +316,11 @@ <source>Cannot load file %x.</source> <target>Kan bestand %x niet laden.</target> -<source>Volume name %x not part of file name %y.</source> -<target>Schijfnaam %x maakt geen deel uit van bestandsnaam %y.</target> +<source>Cannot determine volume name for %x.</source> +<target>Kan volumenaam voor %x niet bepalen.</target> + +<source>Volume name %x is not part of file path %y.</source> +<target>Schijfnaam %x is geen deel van bestandspad %y.</target> <source>Abort requested: Waiting for current operation to finish...</source> <target>Bezig met afbreken: Wacht op beëindiging huidige bewerking...</target> @@ -382,9 +388,6 @@ <source>Idle time between last detected change and execution of command</source> <target>Tijd tussen de laatste gedetecteerde verandering en de uitvoering van het commando</target> -<source>Command line</source> -<target>Opdrachtregel</target> - <source> The command is triggered if: - files or subfolders change @@ -408,9 +411,6 @@ De opdracht word geactiveerd als: <source>RealtimeSync - Automated Synchronization</source> <target>RealtimeSync - Geautomatiseerde Synchronisatie</target> -<source>Warning</source> -<target>Waarschuwing</target> - <source>Build: %x</source> <target>Build: %x</target> @@ -420,15 +420,21 @@ De opdracht word geactiveerd als: <source>All files</source> <target>Alle bestanden</target> +<source>Directory monitoring active</source> +<target>Bestandslocatie controle actief</target> + +<source>Waiting until all directories are available...</source> +<target>Wachten tot alle bestandslocaties beschikbaar zijn...</target> + <source>&Restore</source> <target>&Herstellen</target> +<source>&Show error</source> +<target>Laat &fouten zien</target> + <source>&Exit</source> <target>&Afsluiten</target> -<source>Waiting for missing directories...</source> -<target>Wacht op ontbrekende mappen...</target> - <source>Invalid command line:</source> <target>Ongeldige opdrachtregel:</target> @@ -438,14 +444,14 @@ De opdracht word geactiveerd als: <source>File time and size</source> <target>Bestandstijd- en grootte</target> -<source> Two way </source> -<target> Twee kanten op </target> +<source>Two way</source> +<target>Twee kanten op</target> <source>Mirror</source> -<target>Spiegelen </target> +<target>Spiegelen</target> <source>Update</source> -<target>Bijwerken </target> +<target>Bijwerken</target> <source>Custom</source> <target>Aangepast</target> @@ -501,11 +507,8 @@ De opdracht word geactiveerd als: <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>De volgende items hebben onopgeloste conflicten en zullen niet worden gesynchroniseerd:</target> -<source>Significant difference detected:</source> -<target>Significant verschil gedetecteerd:</target> - -<source>More than 50% of the total number of files will be copied or deleted.</source> -<target>Meer dan 50% van alle bestanden zal gekopiëerd of verwijderd worden.</target> +<source>The following folders are significantly different. Make sure you are matching the correct folders for synchronization.</source> +<target>De volgende mappen zijn heel verschillend. Wees er zeker van dat dit de goede mappen zijn voor synchronisatie.</target> <source>Not enough free disk space available in:</source> <target>Niet genoeg vrije schijfruimte beschikbaar op:</target> @@ -528,6 +531,9 @@ De opdracht word geactiveerd als: <source>Creating Volume Shadow Copy for %x...</source> <target>Bezig met Volume Schaduwkopie te maken voor %x...</target> +<source>Data verification error: %x and %y have different content.</source> +<target>Data verificatie fout: %x en %y hebben een verschillende inhoud.</target> + <source>Synchronization aborted</source> <target>Synchronisatie afgebroken</target> @@ -549,12 +555,24 @@ De opdracht word geactiveerd als: <source>Press "Switch" to resolve issues in FreeFileSync main dialog.</source> <target>Druk op "Omschakelen" om problemen op te lossen in het hoofdscherm van FreeFileSync.</target> +<source>Switching to FreeFileSync main dialog</source> +<target>Wisselen naar FreeFileSync hoofdscherm</target> + +<source>Retrying operation after error:</source> +<target>Bewerking opnieuw proberen na fout:</target> + <source>A new version of FreeFileSync is available:</source> <target>Er is een nieuwe versie van FreeFileSync beschikbaar:</target> <source>Download now?</source> <target>Nu downloaden?</target> +<source>New version found</source> +<target>Nieuwe versie gevonden</target> + +<source>&Download</source> +<target>&Download</target> + <source>FreeFileSync is up to date.</source> <target>U gebruikt de nieuwste versie van FreeFileSync.</target> @@ -564,6 +582,9 @@ De opdracht word geactiveerd als: <source>Unable to connect to sourceforge.net.</source> <target>Kan geen verbinding maken met sourceforge.net.</target> +<source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source> +<target>Kan huidige FreeFileSync versienummer niet online vinden. Wilt u handmatig controleren?</target> + <source>Symlink</source> <target>Symlink</target> @@ -666,12 +687,18 @@ De opdracht word geactiveerd als: <source>&Global settings...</source> <target>&Algemene instellingen...</target> +<source>&Tools</source> +<target>&Gereedschap</target> + <source>&Check now</source> <target>&Controleer nu</target> <source>Check &automatically once a week</source> <target>Controleer &automatisch eens per week</target> +<source>Check for new &version</source> +<target>Kijk voor een nieuwe &versie</target> + <source>Compare</source> <target>Vergelijk</target> @@ -729,6 +756,9 @@ De opdracht word geactiveerd als: <source>Synchronizing...</source> <target>Synchroniseert...</target> +<source>Minimize to notification area</source> +<target>Minimaliseer naar systeemvak</target> + <source>On completion</source> <target>Na voltooiing</target> @@ -738,6 +768,18 @@ De opdracht word geactiveerd als: <source>&Pause</source> <target>&Pauze</target> +<source>Variant</source> +<target>Variant</target> + +<source>Statistics</source> +<target>Statistieken</target> + +<source>Don't show this dialog again</source> +<target>Laat deze dialoog niet meer zien</target> + +<source>Select a variant</source> +<target>Selecteer een variant</target> + <source> Files are found equal if - last write time and date @@ -942,15 +984,6 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen. <source>&Default</source> <target>&Standaard</target> -<source>Variant</source> -<target>Variant</target> - -<source>Statistics</source> -<target>Statistieken</target> - -<source>Don't show this dialog again</source> -<target>Laat deze dialoog niet meer zien</target> - <source>Find what:</source> <target>Vind wat:</target> @@ -960,15 +993,15 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen. <source>&Find next</source> <target>&Vind volgende</target> +<source>Start synchronization</source> +<target>Start synchronisatie</target> + <source>Delete</source> <target>Verwijderen</target> <source>Configure filter</source> <target>Filterconfiguratie</target> -<source>Start synchronization</source> -<target>Start synchronisatie</target> - <source>Find</source> <target>Vind</target> @@ -1003,6 +1036,24 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen. <target>Vergelijk beide zijdes</target> <source> +<pluralform>Do you really want to execute the command %y for 1 item?</pluralform> +<pluralform>Do you really want to execute the command %y for %x items?</pluralform> +</source> +<target> +<pluralform>Weet u zeker dat u commando %y wilt uitvoeren voor 1 item?</pluralform> +<pluralform>Weet u zeker dat u commando %y wilt uitvoeren voor %x items?</pluralform> +</target> + +<source>Confirm</source> +<target>Bevestigen</target> + +<source>&Execute</source> +<target>&Uitvoeren</target> + +<source>&Don't show this dialog again</source> +<target>Laat deze dialoog &niet meer zien</target> + +<source> <pluralform>1 directory</pluralform> <pluralform>%x directories</pluralform> </source> @@ -1143,8 +1194,8 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen. <source>All folders are in sync</source> <target>Alle mappen zijn gesynchroniseerd</target> -<source>Comma separated list</source> -<target>Kommagescheiden bestand</target> +<source>Comma-separated values</source> +<target>Door komma gescheiden waarden</target> <source>File list exported</source> <target>Bestandslijst geëxporteerd</target> @@ -1197,14 +1248,11 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen. <source>Completed</source> <target>Voltooid</target> -<source>Continue</source> -<target>Doorgaan</target> +<source>&Continue</source> +<target>&Doorgaan</target> -<source>Pause</source> -<target>Pauze</target> - -<source>Logging</source> -<target>Loggen</target> +<source>Log</source> +<target>Log</target> <source>Cannot find %x</source> <target>Kan %x niet vinden</target> @@ -1299,9 +1347,6 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen. <source>Append a timestamp to each file name</source> <target>Voeg een timestamp aan elke bestandsnaam toe</target> -<source>Folder</source> -<target>Map</target> - <source>File</source> <target>Bestand</target> @@ -1356,6 +1401,9 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen. <source>Cannot create directory %x.</source> <target>Kan map %x niet aanmaken.</target> +<source>Cannot create symbolic link %x.</source> +<target>Kan snelkoppeling %x niet aanmaken.</target> + <source>Cannot find system function %x.</source> <target>Kan systeemfunctie %x niet vinden.</target> @@ -1407,6 +1455,9 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen. <pluralform>%x dagen</pluralform> </target> +<source>Failed to register to receive system messages.</source> +<target>Registreren voor het ontvangen van systeemmeldingen is mislukt.</target> + <source>Cannot set privilege %x.</source> <target>Kan privilege %x niet instellen.</target> @@ -1419,6 +1470,9 @@ Opmerking: Bestandsnamen moeten relatief zijn aan de basis mappen. <source>Unable to move %x to the Recycle Bin.</source> <target>Niet mogelijk om %x naar de Prullenbak te verplaatsen.</target> +<source>Cannot determine final path for %x.</source> +<target>Kan pad voor %x niet vaststellen.</target> + <source>Error Code %x:</source> <target>Foutcode %x:</target> diff --git a/BUILD/Languages/english_uk.lng b/BUILD/Languages/english_uk.lng index d1fcab3d..753ccfd0 100644 --- a/BUILD/Languages/english_uk.lng +++ b/BUILD/Languages/english_uk.lng @@ -49,8 +49,14 @@ <source>An exception occurred</source> <target>An exception occurred</target> -<source>Cannot find file %x.</source> -<target>Cannot find file %x.</target> +<source>A directory path is expected after %x.</source> +<target>A directory path is expected after %x.</target> + +<source>Syntax error</source> +<target>Syntax error</target> + +<source>Cannot open file %x.</source> +<target>Cannot open file %x.</target> <source>Error</source> <target>Error</target> @@ -58,6 +64,36 @@ <source>File %x does not contain a valid configuration.</source> <target>File %x does not contain a valid configuration.</target> +<source>Unequal number of left and right directories specified.</source> +<target>Unequal number of left and right directories specified.</target> + +<source>The config file must not contain settings at directory pair level when directories are set via command line.</source> +<target>The config file must not contain settings at directory pair level when directories are set via command line.</target> + +<source>Warning</source> +<target>Warning</target> + +<source>Directories cannot be set for more than one configuration file.</source> +<target>Directories cannot be set for more than one configuration file.</target> + +<source>Syntax:</source> +<target>Syntax:</target> + +<source>config files</source> +<target>config files</target> + +<source>directory</source> +<target>directory</target> + +<source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> +<target>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</target> + +<source>Any number of alternative directories for at most one config file.</source> +<target>Any number of alternative directories for at most one config file.</target> + +<source>Command line</source> +<target>Command line</target> + <source>A folder input field is empty.</source> <target>A folder input field is empty.</target> @@ -215,12 +251,12 @@ <target>Total time:</target> <source> -<pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> +<pluralform>1 byte</pluralform> +<pluralform>%x bytes</pluralform> </source> <target> -<pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> +<pluralform>1 byte</pluralform> +<pluralform>%x bytes</pluralform> </target> <source>%x MB</source> @@ -242,12 +278,12 @@ <target>Scanning:</target> <source> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> </source> <target> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> </target> <source>Encoding extended time information: %x</source> @@ -256,6 +292,9 @@ <source>/sec</source> <target>/sec</target> +<source>%x items</source> +<target>%x items</target> + <source>Configuration file %x loaded partially only.</source> <target>Configuration file %x loaded partially only.</target> @@ -280,8 +319,8 @@ <source>Cannot determine volume name for %x.</source> <target>Cannot determine volume name for %x.</target> -<source>Volume name %x not part of file name %y.</source> -<target>Volume name %x not part of file name %y.</target> +<source>Volume name %x is not part of file path %y.</source> +<target>Volume name %x is not part of file path %y.</target> <source>Abort requested: Waiting for current operation to finish...</source> <target>Abort requested: Waiting for current operation to finish...</target> @@ -349,9 +388,6 @@ <source>Idle time between last detected change and execution of command</source> <target>Idle time between last detected change and execution of command</target> -<source>Command line</source> -<target>Command line</target> - <source> The command is triggered if: - files or subfolders change @@ -375,9 +411,6 @@ The command is triggered if: <source>RealtimeSync - Automated Synchronization</source> <target>RealtimeSync - Automated Synchronisation</target> -<source>Warning</source> -<target>Warning</target> - <source>Build: %x</source> <target>Build: %x</target> @@ -387,15 +420,21 @@ The command is triggered if: <source>All files</source> <target>All files</target> +<source>Directory monitoring active</source> +<target>Directory monitoring active</target> + +<source>Waiting until all directories are available...</source> +<target>Waiting until all directories are available...</target> + <source>&Restore</source> <target>&Restore</target> +<source>&Show error</source> +<target>&Show error</target> + <source>&Exit</source> <target>&Exit</target> -<source>Waiting for missing directories...</source> -<target>Waiting for missing directories...</target> - <source>Invalid command line:</source> <target>Invalid command line:</target> @@ -405,14 +444,14 @@ The command is triggered if: <source>File time and size</source> <target>File time and size</target> -<source> Two way </source> -<target> Two way </target> +<source>Two way</source> +<target>Two way</target> <source>Mirror</source> -<target>Mirror </target> +<target>Mirror</target> <source>Update</source> -<target>Update </target> +<target>Update</target> <source>Custom</source> <target>Custom</target> @@ -468,11 +507,8 @@ The command is triggered if: <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>The following items have unresolved conflicts and will not be synchronised:</target> -<source>Significant difference detected:</source> -<target>Significant difference detected:</target> - -<source>More than 50% of the total number of files will be copied or deleted.</source> -<target>More than 50% of the total number of files will be copied or deleted.</target> +<source>The following folders are significantly different. Make sure you are matching the correct folders for synchronization.</source> +<target>The following folders are significantly different. Make sure you are matching the correct folders for synchronisation.</target> <source>Not enough free disk space available in:</source> <target>Not enough free disk space available in:</target> @@ -520,7 +556,10 @@ The command is triggered if: <target>Press "Switch" to resolve issues in FreeFileSync main dialogue.</target> <source>Switching to FreeFileSync main dialog</source> -<target>Switching to FreeFileSync main dialog</target> +<target>Switching to FreeFileSync main dialogue</target> + +<source>Retrying operation after error:</source> +<target>Retrying operation after error:</target> <source>A new version of FreeFileSync is available:</source> <target>A new version of FreeFileSync is available:</target> @@ -717,6 +756,9 @@ The command is triggered if: <source>Synchronizing...</source> <target>Synchronising...</target> +<source>Minimize to notification area</source> +<target>Minimize to notification area</target> + <source>On completion</source> <target>On completion</target> @@ -726,6 +768,15 @@ The command is triggered if: <source>&Pause</source> <target>&Pause</target> +<source>Variant</source> +<target>Variant</target> + +<source>Statistics</source> +<target>Statistics</target> + +<source>Don't show this dialog again</source> +<target>Don't show this dialogue again</target> + <source>Select a variant</source> <target>Select a variant</target> @@ -933,15 +984,6 @@ Note: File names must be relative to base directories. <source>&Default</source> <target>&Default</target> -<source>Variant</source> -<target>Variant</target> - -<source>Statistics</source> -<target>Statistics</target> - -<source>Don't show this dialog again</source> -<target>Don't show this dialogue again</target> - <source>Find what:</source> <target>Find what:</target> @@ -951,15 +993,15 @@ Note: File names must be relative to base directories. <source>&Find next</source> <target>&Find next</target> +<source>Start synchronization</source> +<target>Start synchronisation</target> + <source>Delete</source> <target>Delete</target> <source>Configure filter</source> <target>Configure filter</target> -<source>Start synchronization</source> -<target>Start synchronisation</target> - <source>Find</source> <target>Find</target> @@ -994,6 +1036,24 @@ Note: File names must be relative to base directories. <target>Compare both sides</target> <source> +<pluralform>Do you really want to execute the command %y for 1 item?</pluralform> +<pluralform>Do you really want to execute the command %y for %x items?</pluralform> +</source> +<target> +<pluralform>Do you really want to execute the command %y for 1 item?</pluralform> +<pluralform>Do you really want to execute the command %y for %x items?</pluralform> +</target> + +<source>Confirm</source> +<target>Confirm</target> + +<source>&Execute</source> +<target>&Execute</target> + +<source>&Don't show this dialog again</source> +<target>&Don't show this dialogue again</target> + +<source> <pluralform>1 directory</pluralform> <pluralform>%x directories</pluralform> </source> @@ -1134,8 +1194,8 @@ Note: File names must be relative to base directories. <source>All folders are in sync</source> <target>All folders are in sync</target> -<source>Comma separated list</source> -<target>Comma separated list</target> +<source>Comma-separated values</source> +<target>Comma-separated values</target> <source>File list exported</source> <target>File list exported</target> @@ -1188,14 +1248,11 @@ Note: File names must be relative to base directories. <source>Completed</source> <target>Completed</target> -<source>Continue</source> -<target>Continue</target> +<source>&Continue</source> +<target>&Continue</target> -<source>Pause</source> -<target>Pause</target> - -<source>Logging</source> -<target>Logging</target> +<source>Log</source> +<target>Log</target> <source>Cannot find %x</source> <target>Cannot find %x</target> @@ -1290,9 +1347,6 @@ Note: File names must be relative to base directories. <source>Append a timestamp to each file name</source> <target>Append a timestamp to each file name</target> -<source>Folder</source> -<target>Folder</target> - <source>File</source> <target>File</target> diff --git a/BUILD/Languages/german.lng b/BUILD/Languages/german.lng index 94c955a4..35c30a34 100644 --- a/BUILD/Languages/german.lng +++ b/BUILD/Languages/german.lng @@ -7,9 +7,6 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> -<source>Retrying operation after error:</source> -<target>Wiederhole Operation nach Fehler:</target> - <source>Both sides have changed since last synchronization.</source> <target>Beide Seiten wurden seit der letzten Synchronisation verändert.</target> @@ -52,8 +49,14 @@ <source>An exception occurred</source> <target>Eine Ausnahme ist aufgetreten</target> -<source>Cannot find file %x.</source> -<target>Die Datei %x wurde nicht gefunden.</target> +<source>A directory path is expected after %x.</source> +<target>Ein Verzeichnispfad wird nach %x erwartet.</target> + +<source>Syntax error</source> +<target>Syntaxfehler</target> + +<source>Cannot open file %x.</source> +<target>Die Datei %x kann nicht geöffnet werden.</target> <source>Error</source> <target>Fehler</target> @@ -61,6 +64,36 @@ <source>File %x does not contain a valid configuration.</source> <target>Die Datei %x enthält keine gültige Konfiguration.</target> +<source>Unequal number of left and right directories specified.</source> +<target>Ungleiche Anzahl linker und rechter Verzeichnisse angegeben.</target> + +<source>The config file must not contain settings at directory pair level when directories are set via command line.</source> +<target>Die Konfigurationsdatei darf keine Einstellungen auf Verzeichnispaarebene enthalten, wenn Verzeichnisse über die Befehlszeile gesetzt werden.</target> + +<source>Warning</source> +<target>Warnung</target> + +<source>Directories cannot be set for more than one configuration file.</source> +<target>Verzeichnisse können nicht für mehr als eine Konfigurationsdatei gesetzt werden.</target> + +<source>Syntax:</source> +<target>Syntax:</target> + +<source>config files</source> +<target>Konfigurationsdateien</target> + +<source>directory</source> +<target>Verzeichnis</target> + +<source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> +<target>Beliebige Anzahl von FreeFileSync .ffs_gui und/oder .ffs_batch Konfigurationsdateien.</target> + +<source>Any number of alternative directories for at most one config file.</source> +<target>Beliebige Anzahl von alternativen Verzeichnissen für höchstens eine Konfigurationsdatei.</target> + +<source>Command line</source> +<target>Befehlszeile</target> + <source>A folder input field is empty.</source> <target>Ein Ordnereingabefeld ist leer.</target> @@ -71,7 +104,7 @@ <target>Die folgenden Ordner wurden nicht gefunden:</target> <source>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.</source> -<target>Dieser Fehler kann ignoriert werden, um die Ordner als leer anzusehen. Die Ordner werden dann beim Synchronisieren automatisch erstellt.</target> +<target>Dieser Fehler kann ignoriert werden, um die Ordner als leer anzusehen und beim Synchronisieren automatisch zu erstellen.</target> <source>The following folders have dependent paths. Be careful when setting up synchronization rules:</source> <target>Die folgenden Ordner haben abhängige Pfade. Achtung beim Festlegen der Synchronisationsregeln:</target> @@ -218,8 +251,8 @@ <target>Gesamtzeit:</target> <source> -<pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> +<pluralform>1 byte</pluralform> +<pluralform>%x bytes</pluralform> </source> <target> <pluralform>1 Byte</pluralform> @@ -245,12 +278,12 @@ <target>Suche Dateien:</target> <source> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> </source> <target> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> +<pluralform>1 Thread</pluralform> +<pluralform>%x Threads</pluralform> </target> <source>Encoding extended time information: %x</source> @@ -259,6 +292,9 @@ <source>/sec</source> <target>/s</target> +<source>%x items</source> +<target>%x Elemente</target> + <source>Configuration file %x loaded partially only.</source> <target>Die Konfigurationsdatei %x wurde nur teilweise geladen.</target> @@ -283,8 +319,8 @@ <source>Cannot determine volume name for %x.</source> <target>Der Laufwerksname für %x konnte nicht bestimmt werden.</target> -<source>Volume name %x not part of file name %y.</source> -<target>Laufwerksname %x ist kein Teil des Dateinamens %y.</target> +<source>Volume name %x is not part of file path %y.</source> +<target>Laufwerksname %x ist kein Teil des Dateipfades %y.</target> <source>Abort requested: Waiting for current operation to finish...</source> <target>Abbruch initiiert: Warte bis die aktuelle Operation beendet ist...</target> @@ -352,16 +388,13 @@ <source>Idle time between last detected change and execution of command</source> <target>Ruhezeit zwischen der letzten erkannten Änderung und dem Aufruf der Befehlszeile</target> -<source>Command line</source> -<target>Befehlszeile</target> - <source> The command is triggered if: - files or subfolders change - new folders arrive (e.g. USB stick insert) </source> <target> -Die Befehlszeile wird ausgelöst wenn: +Die Befehlszeile wird ausgelöst, wenn: - Dateien oder Unterordner sich ändern - neue Ordner erscheinen (z.B. Anschluss eines USB Sticks) </target> @@ -378,9 +411,6 @@ Die Befehlszeile wird ausgelöst wenn: <source>RealtimeSync - Automated Synchronization</source> <target>RealtimeSync - Automatisierte Synchronisation</target> -<source>Warning</source> -<target>Warnung</target> - <source>Build: %x</source> <target>Build: %x</target> @@ -390,15 +420,21 @@ Die Befehlszeile wird ausgelöst wenn: <source>All files</source> <target>Alle Dateien</target> +<source>Directory monitoring active</source> +<target>Verzeichnisüberwachung ist aktiv</target> + +<source>Waiting until all directories are available...</source> +<target>Warte bis alle Verzeichnisse verfügbar sind...</target> + <source>&Restore</source> <target>&Wiederherstellen</target> +<source>&Show error</source> +<target>&Zeige Fehler</target> + <source>&Exit</source> <target>&Beenden</target> -<source>Waiting for missing directories...</source> -<target>Warte auf fehlende Verzeichnisse...</target> - <source>Invalid command line:</source> <target>Ungültige Befehlszeile:</target> @@ -408,14 +444,14 @@ Die Befehlszeile wird ausgelöst wenn: <source>File time and size</source> <target>Datum und Größe</target> -<source> Two way </source> -<target> Zwei Wege </target> +<source>Two way</source> +<target>Zwei Wege</target> <source>Mirror</source> -<target>Spiegeln </target> +<target>Spiegeln</target> <source>Update</source> -<target>Aktualisieren </target> +<target>Aktualisieren</target> <source>Custom</source> <target>Eigene</target> @@ -471,11 +507,8 @@ Die Befehlszeile wird ausgelöst wenn: <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>Die folgenden Elemente haben ungelöste Konflikte und werden nicht synchronisiert werden:</target> -<source>Significant difference detected:</source> -<target>Ein erheblicher Unterschied wurde festgestellt:</target> - -<source>More than 50% of the total number of files will be copied or deleted.</source> -<target>Mehr als 50% aller Dateien werden kopiert oder gelöscht.</target> +<source>The following folders are significantly different. Make sure you are matching the correct folders for synchronization.</source> +<target>Die folgenden Ordner unterscheiden sich erheblich. Stellen Sie sicher, dass die richtigen Ordner für die Synchronisation ausgewählt sind.</target> <source>Not enough free disk space available in:</source> <target>Nicht genügend freier Speicher verfügbar unter:</target> @@ -525,6 +558,9 @@ Die Befehlszeile wird ausgelöst wenn: <source>Switching to FreeFileSync main dialog</source> <target>Wechsle in FreeFileSyncs Hauptdialog</target> +<source>Retrying operation after error:</source> +<target>Wiederhole Operation nach Fehler:</target> + <source>A new version of FreeFileSync is available:</source> <target>Eine neue Version von FreeFileSync ist verfügbar:</target> @@ -720,6 +756,9 @@ Die Befehlszeile wird ausgelöst wenn: <source>Synchronizing...</source> <target>Synchronisiere...</target> +<source>Minimize to notification area</source> +<target>In das Benachrichtigungsfeld minimieren</target> + <source>On completion</source> <target>Nach Abschluss</target> @@ -729,6 +768,15 @@ Die Befehlszeile wird ausgelöst wenn: <source>&Pause</source> <target>&Pause</target> +<source>Variant</source> +<target>Variante</target> + +<source>Statistics</source> +<target>Statistiken</target> + +<source>Don't show this dialog again</source> +<target>Diesen Dialog nicht mehr anzeigen</target> + <source>Select a variant</source> <target>Variante auswählen</target> @@ -936,15 +984,6 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein. <source>&Default</source> <target>&Standard</target> -<source>Variant</source> -<target>Variante</target> - -<source>Statistics</source> -<target>Statistiken</target> - -<source>Don't show this dialog again</source> -<target>Diesen Dialog nicht mehr anzeigen</target> - <source>Find what:</source> <target>Suchen nach:</target> @@ -954,15 +993,15 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein. <source>&Find next</source> <target>&Weitersuchen</target> +<source>Start synchronization</source> +<target>Synchronisation starten</target> + <source>Delete</source> <target>Löschen</target> <source>Configure filter</source> <target>Konfiguriere Filter</target> -<source>Start synchronization</source> -<target>Synchronisation starten</target> - <source>Find</source> <target>Suchen</target> @@ -997,6 +1036,24 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein. <target>Beide Seiten vergleichen</target> <source> +<pluralform>Do you really want to execute the command %y for 1 item?</pluralform> +<pluralform>Do you really want to execute the command %y for %x items?</pluralform> +</source> +<target> +<pluralform>Soll der Befehl %y wirklich für 1 Element ausgeführt werden?</pluralform> +<pluralform>Soll der Befehl %y wirklich für %x Elemente ausgeführt werden?</pluralform> +</target> + +<source>Confirm</source> +<target>Bestätigen</target> + +<source>&Execute</source> +<target>&Ausführen</target> + +<source>&Don't show this dialog again</source> +<target>&Diesen Dialog nicht mehr anzeigen</target> + +<source> <pluralform>1 directory</pluralform> <pluralform>%x directories</pluralform> </source> @@ -1137,8 +1194,8 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein. <source>All folders are in sync</source> <target>Alle Ordner sind synchron</target> -<source>Comma separated list</source> -<target>Kommagetrennte Liste</target> +<source>Comma-separated values</source> +<target>Kommagetrennte Werte</target> <source>File list exported</source> <target>Dateiliste exportiert</target> @@ -1191,13 +1248,10 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein. <source>Completed</source> <target>Fertig</target> -<source>Continue</source> -<target>Fortfahren</target> +<source>&Continue</source> +<target>&Fortfahren</target> -<source>Pause</source> -<target>Pause</target> - -<source>Logging</source> +<source>Log</source> <target>Protokoll</target> <source>Cannot find %x</source> @@ -1293,9 +1347,6 @@ Achtung: Dateinamen müssen relativ zu den Basisverzeichnissen sein. <source>Append a timestamp to each file name</source> <target>Einen Zeitstempel an jeden Dateinamen anhängen</target> -<source>Folder</source> -<target>Ordner</target> - <source>File</source> <target>Datei</target> diff --git a/BUILD/Languages/italian.lng b/BUILD/Languages/italian.lng index e1bfa558..3dcac298 100644 --- a/BUILD/Languages/italian.lng +++ b/BUILD/Languages/italian.lng @@ -1,73 +1,82 @@ <header> <language>Italiano</language> - <translator>Emmo</translator> + <translator>Luciano Paravella</translator> <locale>it_IT</locale> <flag_image>flag_italy.png</flag_image> <plural_form_count>2</plural_form_count> <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> -<source>Cannot determine final path for %x.</source> +<source>Log</source> <target></target> -<source>Failed to register to receive system messages.</source> +<source>Comma-separated values</source> <target></target> -<source>Cannot create symbolic link %x.</source> +<source>Minimize to notification area</source> <target></target> -<source>Items</source> +<source>The following folders are significantly different. Make sure you are matching the correct folders for synchronization.</source> <target></target> -<source> -<pluralform>%y of 1 row in view</pluralform> -<pluralform>%y of %x rows in view</pluralform> -</source> +<source>&Show error</source> <target></target> -<source>Select a variant</source> +<source>Waiting until all directories are available...</source> <target></target> -<source>Check for new &version</source> +<source>Directory monitoring active</source> <target></target> -<source>&Tools</source> +<source>Volume name %x is not part of file path %y.</source> <target></target> -<source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source> +<source>%x items</source> <target></target> -<source>&Download</source> +<source> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> +</source> <target></target> -<source>New version found</source> +<source> +<pluralform>1 byte</pluralform> +<pluralform>%x bytes</pluralform> +</source> <target></target> -<source>Switching to FreeFileSync main dialog</source> +<source>Any number of alternative directories for at most one config file.</source> <target></target> -<source>Data verification error: %x and %y have different content.</source> +<source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> <target></target> -<source>Cannot determine volume name for %x.</source> +<source>directory</source> <target></target> -<source>Starting comparison</source> +<source>config files</source> <target></target> -<source>Resolving symbolic link %x</source> +<source>Syntax:</source> <target></target> -<source>The following folders have dependent paths. Be careful when setting up synchronization rules:</source> +<source>Directories cannot be set for more than one configuration file.</source> <target></target> -<source>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.</source> +<source>The config file must not contain settings at directory pair level when directories are set via command line.</source> <target></target> -<source>The Recycle Bin is not available for the following folders. Files will be deleted permanently instead:</source> +<source>Unequal number of left and right directories specified.</source> <target></target> -<source>The database entry is not in sync considering current settings.</source> +<source>Cannot open file %x.</source> +<target></target> + +<source>Syntax error</source> +<target></target> + +<source>A directory path is expected after %x.</source> <target></target> <source>Both sides have changed since last synchronization.</source> @@ -79,8 +88,11 @@ <source>No change since last synchronization.</source> <target>Nessun cambiamento dall'ultima sincronizzazione.</target> +<source>The database entry is not in sync considering current settings.</source> +<target>La voce di database non è in sincronia con le impostazioni correnti.</target> + <source>Setting default synchronization directions: Old files will be overwritten with newer files.</source> -<target>Imposta direzioni di sincronizzazione dpredefinite: i vecchi file saranno sovrascritti dai nuovi.</target> +<target>Imposta le direzioni di sincronizzazione predefinite: i vecchi file saranno sovrascritti dai nuovi.</target> <source>Checking recycle bin availability for folder %x...</source> <target>Controllo di disponibilità Cestino per la cartella %x...</target> @@ -103,20 +115,26 @@ <source>Deleting symbolic link %x</source> <target>Eliminazione collegamento %x</target> +<source>The Recycle Bin is not available for the following folders. Files will be deleted permanently instead:</source> +<target>Il Cestino non è disponibile per le seguenti cartelle. I file verranno quindi eliminati in modo permanente:</target> + <source>An exception occurred</source> <target>Si è verificata una eccezione</target> -<source>Cannot find file %x.</source> -<target>Impossibile trovare il file %x.</target> - <source>Error</source> <target>Errore</target> <source>File %x does not contain a valid configuration.</source> <target>Il file %x non contiene una configurazione valida.</target> +<source>Warning</source> +<target>Attenzione</target> + +<source>Command line</source> +<target>Linea di comando</target> + <source>A folder input field is empty.</source> -<target>Un campo cartella input è vuoto.</target> +<target>Un campo di input cartella è vuoto.</target> <source>The corresponding folder will be considered as empty.</source> <target>La cartella corrispondente sarà considerata come vuota.</target> @@ -124,6 +142,12 @@ <source>Cannot find the following folders:</source> <target>Impossibile trovare le seguenti cartelle:</target> +<source>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.</source> +<target>È possibile ignorare questo errore che considera ogni cartella come vuota. Le cartelle poi verranno create automaticamente durante la sincronizzazione.</target> + +<source>The following folders have dependent paths. Be careful when setting up synchronization rules:</source> +<target>Le seguenti cartelle hanno percorsi dipendenti. Fare attenzione quando si imposta le regole di sincronizzazione:</target> + <source>File %x has an invalid date.</source> <target>Il file %x ha una data non valida.</target> @@ -139,12 +163,18 @@ <source>Items differ in attributes only</source> <target>Gli oggetti differiscono solo negli attributi</target> +<source>Resolving symbolic link %x</source> +<target>Risoluzione collegamento %x</target> + <source>Comparing content of files %x</source> -<target>Comparazione contenuto del file %x</target> +<target>Confronto contenuto del file %x</target> <source>Generating file list...</source> <target>Generazione elenco file...</target> +<source>Starting comparison</source> +<target>Inizio confronto</target> + <source>Calculating sync directions...</source> <target>Calcolo della direzione di sincronizzazione...</target> @@ -206,16 +236,16 @@ <target>Aggiorna attributi a destra</target> <source>Database file %x is incompatible.</source> -<target>Il file database %x non è compatibile.</target> +<target>Il file del database %x non è compatibile.</target> <source>Initial synchronization:</source> <target>Prima sincronizzazione:</target> <source>Database file %x does not yet exist.</source> -<target>Il file database %x non è ancora stato creato.</target> +<target>Il file del database %x non è ancora stato creato.</target> <source>Database file is corrupt:</source> -<target>Il file database è corrotto:</target> +<target>Il file del database è corrotto:</target> <source>Cannot write file %x.</source> <target>Impossibile scrivere il file %x.</target> @@ -224,7 +254,7 @@ <target>Impossibile leggere il file %x.</target> <source>Database files do not share a common session.</source> -<target>I file database non condividono una sessione comune.</target> +<target>I file del database non condividono una sessione comune.</target> <source>Searching for folder %x...</source> <target>Ricerca della cartella %x...</target> @@ -259,15 +289,6 @@ <source>Total time:</source> <target>Tempo totale:</target> -<source> -<pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> -</source> -<target> -<pluralform>1 Byte</pluralform> -<pluralform>%x Byte</pluralform> -</target> - <source>%x MB</source> <target>%x MB</target> @@ -284,16 +305,7 @@ <target>Impossibile impostare blocco cartella per %x.</target> <source>Scanning:</source> -<target>Analisi di:</target> - -<source> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> -</source> -<target> -<pluralform>[1 thread]</pluralform> -<pluralform>[%x threads]</pluralform> -</target> +<target>Scansione di:</target> <source>Encoding extended time information: %x</source> <target>Codifica estesa informazioni orario: %x</target> @@ -322,11 +334,11 @@ <source>Cannot load file %x.</source> <target>Impossibile caricare il file %x.</target> -<source>Volume name %x not part of file name %y.</source> -<target>Il nome volume %x non è parte del nome file %y.</target> +<source>Cannot determine volume name for %x.</source> +<target>Impossibile determinare nome del volume per %x.</target> <source>Abort requested: Waiting for current operation to finish...</source> -<target>Selezionata interruazione: conclusione dell'operazione...</target> +<target>Richiesta interruazione: attendere conclusione dell'operazione...</target> <source>Failure to create timestamp for versioning:</source> <target>Errore nella creazione di data e ora per il controllo delle versioni:</target> @@ -353,7 +365,7 @@ <target>&Informazioni</target> <source>&Help</source> -<target>&?</target> +<target>&Aiuto</target> <source>Usage:</source> <target>Uso:</target> @@ -365,7 +377,7 @@ <target>2. Inserisci linea di comando.</target> <source>3. Press 'Start'.</source> -<target>3. Premi 'Start'.</target> +<target>3. Premi 'Avvio'.</target> <source>To get started just import a .ffs_batch file.</source> <target>Per iniziare è sufficiente importare un file con estensione .ffs_batch</target> @@ -389,10 +401,7 @@ <target>Tempo di attesa [secondi]</target> <source>Idle time between last detected change and execution of command</source> -<target>Tempo di attesa tra ultimo cambiamento rilevato ed esecuzione dei comandi</target> - -<source>Command line</source> -<target>Linea di comando</target> +<target>Tempo di attesa tra ultimo cambiamento rilevato ed l'esecuzione del comando</target> <source> The command is triggered if: @@ -415,10 +424,7 @@ Il comando è attivato se: <target>Annulla</target> <source>RealtimeSync - Automated Synchronization</source> -<target>RealtimeSync - Sincronizzazione automatizzata</target> - -<source>Warning</source> -<target>Attenzione</target> +<target>SyncTempoReale - Sincronizzazione automatizzata</target> <source>Build: %x</source> <target>Versione: %x</target> @@ -435,9 +441,6 @@ Il comando è attivato se: <source>&Exit</source> <target>&Esci</target> -<source>Waiting for missing directories...</source> -<target>In attesa delle cartelle mancanti...</target> - <source>Invalid command line:</source> <target>Linea di comando non valida:</target> @@ -447,14 +450,14 @@ Il comando è attivato se: <source>File time and size</source> <target>Ora e dimensione file</target> -<source> Two way </source> -<target> Due vie </target> +<source>Two way</source> +<target>Due vie</target> <source>Mirror</source> -<target>Mirror </target> +<target>Specchio</target> <source>Update</source> -<target>Aggiorna </target> +<target>Aggiorna</target> <source>Custom</source> <target>Personalizza</target> @@ -510,12 +513,6 @@ Il comando è attivato se: <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>I seguenti oggetti hanno conflitti irrisolti e non saranno sincronizzati:</target> -<source>Significant difference detected:</source> -<target>Riscontrate differenze significative:</target> - -<source>More than 50% of the total number of files will be copied or deleted.</source> -<target>Piu' del 50% del totale dei file saranno copiati o cancellati.</target> - <source>Not enough free disk space available in:</source> <target>Spazio libero su disco insufficiente in:</target> @@ -523,10 +520,10 @@ Il comando è attivato se: <target>Richiesto:</target> <source>Available:</source> -<target>Dispobilile:</target> +<target>Disponibile:</target> <source>A folder will be modified which is part of multiple folder pairs. Please review synchronization settings.</source> -<target>Verrà modificata una cartella che è parte di molteplici coppie di cartelle. Controlla le impostazioni di sincronizzazione.</target> +<target>Verrà modificata una cartella che è parte di molte coppie di cartelle. Controlla le impostazioni di sincronizzazione.</target> <source>Synchronizing folder pair:</source> <target>Sincronizzazione della coppia di cartelle:</target> @@ -537,6 +534,9 @@ Il comando è attivato se: <source>Creating Volume Shadow Copy for %x...</source> <target>Creazione di Volume Shadow Copy per %x...</target> +<source>Data verification error: %x and %y have different content.</source> +<target>Dati di verifica errore: %x e %y hanno un contenuto diverso.</target> + <source>Synchronization aborted</source> <target>Sincronizzazione abortita</target> @@ -558,12 +558,24 @@ Il comando è attivato se: <source>Press "Switch" to resolve issues in FreeFileSync main dialog.</source> <target>Premere "Passa" per risolvere i problemi nella finestra principale di FreeFileSync.</target> +<source>Switching to FreeFileSync main dialog</source> +<target>Passaggio alla finestra di dialogo principale FreeFileSync</target> + +<source>Retrying operation after error:</source> +<target>Ripetere l'operazione dopo l'errore:</target> + <source>A new version of FreeFileSync is available:</source> <target>E' disponibile una nuova versione di FreeFileSync:</target> <source>Download now?</source> <target>Scaricarla ora?</target> +<source>New version found</source> +<target>Nuova versione trovata</target> + +<source>&Download</source> +<target>&Scaricare</target> + <source>FreeFileSync is up to date.</source> <target>FreeFileSync è aggiornato.</target> @@ -573,6 +585,9 @@ Il comando è attivato se: <source>Unable to connect to sourceforge.net.</source> <target>Impossibile collegarsi a sourceforge.net.</target> +<source>Cannot find current FreeFileSync version number online. Do you want to check manually?</source> +<target>Non riesci a trovare l'attuale numero di versione di FreeFileSync on-line . Vuoi controllare manualmente?</target> + <source>Symlink</source> <target>Symlink</target> @@ -675,12 +690,18 @@ Il comando è attivato se: <source>&Global settings...</source> <target>&Preferenze...</target> +<source>&Tools</source> +<target>&Strumenti</target> + <source>&Check now</source> <target>&Controlla ora</target> <source>Check &automatically once a week</source> <target>Controlla &automaticamente una volta a settimana</target> +<source>Check for new &version</source> +<target>Verificare presenza nuove &versione</target> + <source>Compare</source> <target>Compara</target> @@ -747,6 +768,18 @@ Il comando è attivato se: <source>&Pause</source> <target>&Pausa</target> +<source>Variant</source> +<target>Variante</target> + +<source>Statistics</source> +<target>Statistiche</target> + +<source>Don't show this dialog again</source> +<target>Non mostrare più questo messaggio</target> + +<source>Select a variant</source> +<target>Seleziona una variante</target> + <source> Files are found equal if - last write time and date @@ -775,7 +808,7 @@ I file sono considerati identici se <target>Gestione Collegamenti</target> <source>Help</source> -<target>?</target> +<target>Aiuto</target> <source>OK</source> <target>OK</target> @@ -784,7 +817,7 @@ I file sono considerati identici se <target>Identifica e propaga cambiamenti su entrambi i lati. Cancellazioni, spostamenti e conflitti sono identificati automaticamente usando un database.</target> <source>Mirror backup of left folder. Right folder is modified to exactly match left folder after synchronization.</source> -<target>Backup mirror della cartella di sinistra. La cartella destra verrà modificata per corrispondere esattamente alla cartella di sinistra dopo la sincronizzazione.</target> +<target>Backup a Specchio della cartella di sinistra. La cartella di destra verrà modificata per corrispondere esattamente alla cartella di sinistra dopo la sincronizzazione.</target> <source>Copy new or updated files to right folder.</source> <target>Copia file nuovi o aggiornati nella cartella di destra.</target> @@ -814,7 +847,7 @@ I file sono considerati identici se <target>Permanente</target> <source>Delete or overwrite files permanently</source> -<target>Elimina o sovrascrivi file definitivamente</target> +<target>Elimina o sovrascrivi i file definitivamente</target> <source>Recycle Bin</source> <target>Cestino</target> @@ -893,7 +926,7 @@ Only files that match all filter settings will be synchronized. Note: File names must be relative to base directories. </source> <target> -Solo i file che corrispondono esattamente a tutti i filtri impostati verrranno sincronizzati. +Solo i file che corrispondono esattamente a tutti i parametri dei filtri verrranno sincronizzati. Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. </target> @@ -922,7 +955,7 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <target>Preferenze</target> <source>Fail-safe file copy</source> -<target>Copia file sicura</target> +<target>Copia file di fail-safe</target> <source>Write to a temporary file (*.ffs_tmp) first then rename it. This guarantees a consistent state even in case of fatal error.</source> <target>Scrivi in un file temporaneo (*.ffs_tmp) prima di rinominarlo. Ciò garantisce uno stato consistente anche in caso di errore fatale.</target> @@ -931,7 +964,7 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <target>Copia file bloccati</target> <source>Copy shared or locked files using Volume Shadow Copy Service (Requires Administrator rights)</source> -<target>Copia file condivisi o bloccati usando il Servizio Volume Shadow Copy (richiede diritti di amministarzione)</target> +<target>Copia file condivisi o bloccati usando il Servizio Volume Shadow Copy (richiede diritti di amministratore)</target> <source>Copy file access permissions</source> <target>Copia permessi di accesso file</target> @@ -951,15 +984,6 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <source>&Default</source> <target>&Predefinito</target> -<source>Variant</source> -<target>Variante</target> - -<source>Statistics</source> -<target>Statistiche</target> - -<source>Don't show this dialog again</source> -<target>Non mostrare più questo messaggio</target> - <source>Find what:</source> <target>Trova questo:</target> @@ -969,15 +993,15 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <source>&Find next</source> <target>&Trova successivo</target> +<source>Start synchronization</source> +<target>Avvia sincronizzazione</target> + <source>Delete</source> <target>Elimina</target> <source>Configure filter</source> <target>Configurazione dei filtri</target> -<source>Start synchronization</source> -<target>Avvia sincronizzazione</target> - <source>Find</source> <target>Trova</target> @@ -1009,7 +1033,25 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <target>Salva</target> <source>Compare both sides</source> -<target>Compara le due liste</target> +<target>Confronta entrambi i lati</target> + +<source> +<pluralform>Do you really want to execute the command %y for 1 item?</pluralform> +<pluralform>Do you really want to execute the command %y for %x items?</pluralform> +</source> +<target> +<pluralform>Vuoi veramente eseguire il comando %y per 1 articolo?</pluralform> +<pluralform>Vuoi veramente eseguire %y comando per %x articoli?</pluralform> +</target> + +<source>Confirm</source> +<target>Confermare</target> + +<source>&Execute</source> +<target>&Esecuzione</target> + +<source>&Don't show this dialog again</source> +<target>&Non mostrare più questo avviso</target> <source> <pluralform>1 directory</pluralform> @@ -1029,6 +1071,15 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <pluralform>%x file</pluralform> </target> +<source> +<pluralform>%y of 1 row in view</pluralform> +<pluralform>%y of %x rows in view</pluralform> +</source> +<target> +<pluralform>%y di 1 riga in vista</pluralform> +<pluralform>%y di %x righe in vista</pluralform> +</target> + <source>Set direction:</source> <target>Imposta direzione:</target> @@ -1099,10 +1150,10 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <target>Mostra file esistenti solo a destra</target> <source>Show files that are newer on left</source> -<target>Mostra file di sinistra più recenti che a destra</target> +<target>Mostra i file che sono più recenti sulla sinistra</target> <source>Show files that are newer on right</source> -<target>Mostra file di destra più recenti che a sinistra</target> +<target>Mostra i file che sono più recenti sulla destra</target> <source>Show files that are equal</source> <target>Mostra file identici</target> @@ -1143,9 +1194,6 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <source>All folders are in sync</source> <target>Tutte le cartelle sono sincronizzate</target> -<source>Comma separated list</source> -<target>Elenco elementi separati da virgola</target> - <source>File list exported</source> <target>Elenco file esportato</target> @@ -1197,14 +1245,8 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <source>Completed</source> <target>Completato</target> -<source>Continue</source> -<target>Continua</target> - -<source>Pause</source> -<target>Pausa</target> - -<source>Logging</source> -<target>Registo attività</target> +<source>&Continue</source> +<target>&Continuare</target> <source>Cannot find %x</source> <target>Impossibile trovare %x</target> @@ -1276,13 +1318,13 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <target>- solo porzione cartella</target> <source>- Other side's counterpart to %item_path%</source> -<target>- Controparte sull'altro lato a %item_path%</target> +<target>- Omologo dell'altro lato a %item_path%</target> <source>- Other side's counterpart to %item_folder%</source> -<target>- Controparte sull'altro lato a %item_folder%</target> +<target>- Omologo dell'altro lato a %item_folder%</target> <source>Make hidden warnings and dialogs visible again?</source> -<target>Vuoi visualizzare nuovamente i dialoghi e avvisi nascosti?</target> +<target>Vuoi visualizzare nuovamente i dialoghi e gli avvisi nascosti?</target> <source>Leave as unresolved conflict</source> <target>Lascia come conflitti irrisolti</target> @@ -1299,9 +1341,6 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <source>Append a timestamp to each file name</source> <target>Accoda data e ora ad ogni nome di file</target> -<source>Folder</source> -<target>Cartella</target> - <source>File</source> <target>File</target> @@ -1311,6 +1350,9 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <source>Files</source> <target>File</target> +<source>Items</source> +<target>Articoli</target> + <source>Percentage</source> <target>Percentuale</target> @@ -1353,8 +1395,11 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <source>Cannot create directory %x.</source> <target>Impossibile creare cartella %x.</target> +<source>Cannot create symbolic link %x.</source> +<target>Impossibile creare il collegamento %x.</target> + <source>Cannot find system function %x.</source> -<target>Impossibile trovare funzione di sistema %x.</target> +<target>Impossibile trovare la funzione di sistema %x.</target> <source>Cannot copy file %x to %y.</source> <target>Impossibile copiare file %x in %y.</target> @@ -1404,6 +1449,9 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <pluralform>%x giorni</pluralform> </target> +<source>Failed to register to receive system messages.</source> +<target>Impossibile registrarsi per ricevere i messaggi di sistema.</target> + <source>Cannot set privilege %x.</source> <target>Impossibile impostare privilegi %x.</target> @@ -1416,6 +1464,9 @@ Nota: I nomi dei file devono essere relativi alle cartelle di appartenenza. <source>Unable to move %x to the Recycle Bin.</source> <target>Impossibile spostare %x nel Cestino.</target> +<source>Cannot determine final path for %x.</source> +<target>Impossibile determinare il percorso finale per %x.</target> + <source>Error Code %x:</source> <target>Errore Codice %x:</target> diff --git a/BUILD/Languages/lithuanian.lng b/BUILD/Languages/lithuanian.lng index 0c4cc9b9..e3bfed10 100644 --- a/BUILD/Languages/lithuanian.lng +++ b/BUILD/Languages/lithuanian.lng @@ -22,15 +22,42 @@ <source>Items</source> <target></target> +<source>Log</source> +<target></target> + +<source>&Continue</source> +<target></target> + +<source>Comma-separated values</source> +<target></target> + <source> <pluralform>%y of 1 row in view</pluralform> <pluralform>%y of %x rows in view</pluralform> </source> <target></target> +<source>&Don't show this dialog again</source> +<target></target> + +<source>&Execute</source> +<target></target> + +<source>Confirm</source> +<target></target> + +<source> +<pluralform>Do you really want to execute the command %y for 1 item?</pluralform> +<pluralform>Do you really want to execute the command %y for %x items?</pluralform> +</source> +<target></target> + <source>Select a variant</source> <target></target> +<source>Minimize to notification area</source> +<target></target> + <source>Check for new &version</source> <target></target> @@ -46,15 +73,48 @@ <source>New version found</source> <target></target> +<source>Retrying operation after error:</source> +<target></target> + <source>Switching to FreeFileSync main dialog</source> <target></target> <source>Data verification error: %x and %y have different content.</source> <target></target> +<source>The following folders are significantly different. Make sure you are matching the correct folders for synchronization.</source> +<target></target> + +<source>&Show error</source> +<target></target> + +<source>Waiting until all directories are available...</source> +<target></target> + +<source>Directory monitoring active</source> +<target></target> + +<source>Volume name %x is not part of file path %y.</source> +<target></target> + <source>Cannot determine volume name for %x.</source> <target></target> +<source>%x items</source> +<target></target> + +<source> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> +</source> +<target></target> + +<source> +<pluralform>1 byte</pluralform> +<pluralform>%x bytes</pluralform> +</source> +<target></target> + <source>Cannot read file attributes of %x.</source> <target></target> @@ -70,6 +130,39 @@ <source>You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.</source> <target></target> +<source>Any number of alternative directories for at most one config file.</source> +<target></target> + +<source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> +<target></target> + +<source>directory</source> +<target></target> + +<source>config files</source> +<target></target> + +<source>Syntax:</source> +<target></target> + +<source>Directories cannot be set for more than one configuration file.</source> +<target></target> + +<source>The config file must not contain settings at folder pair level when directories are set via command line.</source> +<target></target> + +<source>Unequal number of left and right directories specified.</source> +<target></target> + +<source>Cannot open file %x.</source> +<target></target> + +<source>Syntax error</source> +<target></target> + +<source>A directory path is expected after %x.</source> +<target></target> + <source>The Recycle Bin is not available for the following folders. Files will be deleted permanently instead:</source> <target></target> @@ -112,15 +205,18 @@ <source>An exception occurred</source> <target>Atsirado išimtis</target> -<source>Cannot find file %x.</source> -<target>Nepavyksta rasti failo %x.</target> - <source>Error</source> <target>Klaida</target> <source>File %x does not contain a valid configuration.</source> <target>Failas %x neturi tinkamų nustatymų.</target> +<source>Warning</source> +<target>Perspėjimas</target> + +<source>Command line</source> +<target>Komandinė eilutė</target> + <source>A folder input field is empty.</source> <target>Aplanko įvesties laukas yra tuščias.</target> @@ -264,17 +360,6 @@ <source>Total time:</source> <target>Visas laikas:</target> -<source> -<pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> -</source> -<target> -<pluralform>1 Baitas</pluralform> -<pluralform>%x Baitai</pluralform> -<pluralform>%x Baitų</pluralform> -<pluralform>%x Baitas</pluralform> -</target> - <source>%x MB</source> <target>%x MB</target> @@ -293,17 +378,6 @@ <source>Scanning:</source> <target>Skenuojama:</target> -<source> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> -</source> -<target> -<pluralform>[1 Grėsmė]</pluralform> -<pluralform>[%x Grėsmės]</pluralform> -<pluralform>[%x Grėsmių]</pluralform> -<pluralform>[%x Grėsmė]</pluralform> -</target> - <source>Encoding extended time information: %x</source> <target>Koduojama išplėstinė laiko informacija: %x</target> @@ -331,9 +405,6 @@ <source>Cannot load file %x.</source> <target>Nepavyksta įkelti failo %x.</target> -<source>Volume name %x not part of file name %y.</source> -<target>Tomo pavadinimas %x nėra dalis failo pavadinimo %y.</target> - <source>Abort requested: Waiting for current operation to finish...</source> <target>Nutraukti: laukiama kol baigsis esama operacija...</target> @@ -400,9 +471,6 @@ <source>Idle time between last detected change and execution of command</source> <target>Neveiklus laikas tarp paskutinio aptikto pokyčio ir komandos įvykdymo</target> -<source>Command line</source> -<target>Komandinė eilutė</target> - <source> The command is triggered if: - files or subfolders change @@ -426,9 +494,6 @@ Komanda inicijuojama jei: <source>RealtimeSync - Automated Synchronization</source> <target>RealtimeSync - Automatizuotas sinchronizavimas</target> -<source>Warning</source> -<target>Perspėjimas</target> - <source>Build: %x</source> <target>Versija: %x</target> @@ -444,9 +509,6 @@ Komanda inicijuojama jei: <source>&Exit</source> <target>&Išeiti</target> -<source>Waiting for missing directories...</source> -<target>Laikiama trūkstamų katalogų...</target> - <source>Invalid command line:</source> <target>Netinkama komandinė eilutė:</target> @@ -456,14 +518,14 @@ Komanda inicijuojama jei: <source>File time and size</source> <target>Failo laiką ir dydį</target> -<source> Two way </source> -<target> Dvipusis </target> +<source>Two way</source> +<target>Dvipusis</target> <source>Mirror</source> -<target>Veidrodis </target> +<target>Veidrodis</target> <source>Update</source> -<target>Atnaujinti </target> +<target>Atnaujinti</target> <source>Custom</source> <target>Savitas</target> @@ -519,12 +581,6 @@ Komanda inicijuojama jei: <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>Šie elementai turi neišspręstų konfliktų ir nebus sinchronizuoti:</target> -<source>Significant difference detected:</source> -<target>Žymus skirtumas nustatytas:</target> - -<source>More than 50% of the total number of files will be copied or deleted.</source> -<target>Daugiau nei 50% failų bus nukopijuota arba ištrinta.</target> - <source>Not enough free disk space available in:</source> <target>Nepakanka laisvos disko vietos:</target> @@ -756,6 +812,15 @@ Komanda inicijuojama jei: <source>&Pause</source> <target>&Pauzė</target> +<source>Variant</source> +<target>Variantas</target> + +<source>Statistics</source> +<target>Statistika</target> + +<source>Don't show this dialog again</source> +<target>Daugiau nerodyti šio dialogo</target> + <source> Files are found equal if - last write time and date @@ -960,15 +1025,6 @@ Pastaba: Failų pavadinimai privalo atitikti bazinius katalogus. <source>&Default</source> <target>&Numatyta</target> -<source>Variant</source> -<target>Variantas</target> - -<source>Statistics</source> -<target>Statistika</target> - -<source>Don't show this dialog again</source> -<target>Daugiau nerodyti šio dialogo</target> - <source>Find what:</source> <target>Rasti kas:</target> @@ -978,15 +1034,15 @@ Pastaba: Failų pavadinimai privalo atitikti bazinius katalogus. <source>&Find next</source> <target>&Rasti kitą</target> +<source>Start synchronization</source> +<target>Pradėti sinchronizavimą</target> + <source>Delete</source> <target>Trinti</target> <source>Configure filter</source> <target>Nustatyti filtrą</target> -<source>Start synchronization</source> -<target>Pradėti sinchronizavimą</target> - <source>Find</source> <target>Rasti</target> @@ -1156,9 +1212,6 @@ Pastaba: Failų pavadinimai privalo atitikti bazinius katalogus. <source>All folders are in sync</source> <target>Visi aplankai susinchronizuoti</target> -<source>Comma separated list</source> -<target>Kableliais atksirtas sąrašas</target> - <source>File list exported</source> <target>Failų sąrašas eksportuotas</target> @@ -1210,15 +1263,6 @@ Pastaba: Failų pavadinimai privalo atitikti bazinius katalogus. <source>Completed</source> <target>Baigta</target> -<source>Continue</source> -<target>Tęsti</target> - -<source>Pause</source> -<target>Pristabdyti</target> - -<source>Logging</source> -<target>Užduoties duomenys</target> - <source>Cannot find %x</source> <target>Nepavyksta rasti %x</target> @@ -1316,9 +1360,6 @@ Pastaba: Failų pavadinimai privalo atitikti bazinius katalogus. <source>Append a timestamp to each file name</source> <target>Pridėti laiko žymę kiekvieno failo pavadinime</target> -<source>Folder</source> -<target>Aplankas</target> - <source>File</source> <target>Failas</target> diff --git a/BUILD/Languages/slovenian.lng b/BUILD/Languages/slovenian.lng index e368737c..a5bed20e 100644 --- a/BUILD/Languages/slovenian.lng +++ b/BUILD/Languages/slovenian.lng @@ -7,9 +7,99 @@ <plural_definition>n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3</plural_definition> </header> +<source>Log</source> +<target></target> + +<source>&Continue</source> +<target></target> + +<source>Comma-separated values</source> +<target></target> + +<source>&Don't show this dialog again</source> +<target></target> + +<source>&Execute</source> +<target></target> + +<source>Confirm</source> +<target></target> + +<source> +<pluralform>Do you really want to execute the command %y for 1 item?</pluralform> +<pluralform>Do you really want to execute the command %y for %x items?</pluralform> +</source> +<target></target> + +<source>Minimize to notification area</source> +<target></target> + <source>Retrying operation after error:</source> <target></target> +<source>The following folders are significantly different. Make sure you are matching the correct folders for synchronization.</source> +<target></target> + +<source>&Show error</source> +<target></target> + +<source>Waiting until all directories are available...</source> +<target></target> + +<source>Directory monitoring active</source> +<target></target> + +<source>Volume name %x is not part of file path %y.</source> +<target></target> + +<source>%x items</source> +<target></target> + +<source> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> +</source> +<target></target> + +<source> +<pluralform>1 byte</pluralform> +<pluralform>%x bytes</pluralform> +</source> +<target></target> + +<source>Any number of alternative directories for at most one config file.</source> +<target></target> + +<source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> +<target></target> + +<source>directory</source> +<target></target> + +<source>config files</source> +<target></target> + +<source>Syntax:</source> +<target></target> + +<source>Directories cannot be set for more than one configuration file.</source> +<target></target> + +<source>The config file must not contain settings at directory pair level when directories are set via command line.</source> +<target></target> + +<source>Unequal number of left and right directories specified.</source> +<target></target> + +<source>Cannot open file %x.</source> +<target></target> + +<source>Syntax error</source> +<target></target> + +<source>A directory path is expected after %x.</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>Obe strani sta se spremenili od zadnje sinhronizacije.</target> @@ -52,15 +142,18 @@ <source>An exception occurred</source> <target>Zgodila se je napaka</target> -<source>Cannot find file %x.</source> -<target>Ne morem najti datoteke %x.</target> - <source>Error</source> <target>Napaka</target> <source>File %x does not contain a valid configuration.</source> <target>Datoteka %x ne vsebuje veljavnih nastavitev</target> +<source>Warning</source> +<target>Pozor</target> + +<source>Command line</source> +<target>Ukazna vrstica</target> + <source>A folder input field is empty.</source> <target>Vnosno polje za mapo je prazno.</target> @@ -219,17 +312,6 @@ <source>Total time:</source> <target>Celoten čas:</target> -<source> -<pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> -</source> -<target> -<pluralform>%x Bajt</pluralform> -<pluralform>%x Bajta</pluralform> -<pluralform>%x Bajti</pluralform> -<pluralform>%x Bajtov</pluralform> -</target> - <source>%x MB</source> <target>%x MB</target> @@ -248,17 +330,6 @@ <source>Scanning:</source> <target>Pregledujem:</target> -<source> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> -</source> -<target> -<pluralform>[%x nit]</pluralform> -<pluralform>[%x niti]</pluralform> -<pluralform>[%x niti]</pluralform> -<pluralform>[%x niti]</pluralform> -</target> - <source>Encoding extended time information: %x</source> <target>Podrobne informacije o času enkodiranja: %x</target> @@ -289,9 +360,6 @@ <source>Cannot determine volume name for %x.</source> <target>Ne morem določiti ime nosilca za %x.</target> -<source>Volume name %x not part of file name %y.</source> -<target>Ime nosilca %x ni del imena datoteke %y.</target> - <source>Abort requested: Waiting for current operation to finish...</source> <target>Zahtevana je bila prekinitev: čakam, da se zaključi trenutna operacija...</target> @@ -358,9 +426,6 @@ <source>Idle time between last detected change and execution of command</source> <target>Čas nedejavnosti med zadnjo zaznano spremembo in izvršitvijo ukaza</target> -<source>Command line</source> -<target>Ukazna vrstica</target> - <source> The command is triggered if: - files or subfolders change @@ -384,9 +449,6 @@ Ukaz se sproži če: <source>RealtimeSync - Automated Synchronization</source> <target>RealtimeSync - Avtomatizirana sinhronizacija</target> -<source>Warning</source> -<target>Pozor</target> - <source>Build: %x</source> <target>Izgradnja: %x</target> @@ -402,9 +464,6 @@ Ukaz se sproži če: <source>&Exit</source> <target>&Izhod</target> -<source>Waiting for missing directories...</source> -<target>Čakam na manjkajoče imenike...</target> - <source>Invalid command line:</source> <target>Napačna ukazna vrstica:</target> @@ -414,14 +473,14 @@ Ukaz se sproži če: <source>File time and size</source> <target>Čas in velikost datoteke</target> -<source> Two way </source> -<target> Obojesmerno </target> +<source>Two way</source> +<target>Obojesmerno</target> <source>Mirror</source> -<target>Zrcalno </target> +<target>Zrcalno</target> <source>Update</source> -<target>Posodobi </target> +<target>Posodobi</target> <source>Custom</source> <target>Po meri</target> @@ -477,12 +536,6 @@ Ukaz se sproži če: <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>Naslednji elementi imajo nerešene konflikte in ne bodo sinhronizirani:</target> -<source>Significant difference detected:</source> -<target>Zaznana je pomembna razlika:</target> - -<source>More than 50% of the total number of files will be copied or deleted.</source> -<target>Več kot 50% od celotnega števila datotek bo kopiranih ali izbrisanih.</target> - <source>Not enough free disk space available in:</source> <target>Na voljo ni dovolj prostega prostora na disku v:</target> @@ -735,6 +788,15 @@ Ukaz se sproži če: <source>&Pause</source> <target>&Premor</target> +<source>Variant</source> +<target>Različica</target> + +<source>Statistics</source> +<target>Statistika</target> + +<source>Don't show this dialog again</source> +<target>Ne prikazuj več tega pogovora</target> + <source>Select a variant</source> <target>Izberite spremenljivko</target> @@ -942,15 +1004,6 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom. <source>&Default</source> <target>&Privzeto</target> -<source>Variant</source> -<target>Različica</target> - -<source>Statistics</source> -<target>Statistika</target> - -<source>Don't show this dialog again</source> -<target>Ne prikazuj več tega pogovora</target> - <source>Find what:</source> <target>Najdi kaj</target> @@ -960,15 +1013,15 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom. <source>&Find next</source> <target>&Najdi naslednje</target> +<source>Start synchronization</source> +<target>Začni sinhronizacijo</target> + <source>Delete</source> <target>Izbriši</target> <source>Configure filter</source> <target>Konfiguriraj filter</target> -<source>Start synchronization</source> -<target>Začni sinhronizacijo</target> - <source>Find</source> <target>Najdi</target> @@ -1149,9 +1202,6 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom. <source>All folders are in sync</source> <target>Vse mape so sinhronizirane</target> -<source>Comma separated list</source> -<target>Seznam ločen z vejico</target> - <source>File list exported</source> <target>Seznam datotek je bil izvožen</target> @@ -1203,15 +1253,6 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom. <source>Completed</source> <target>Zaključeno</target> -<source>Continue</source> -<target>Nadaljuj</target> - -<source>Pause</source> -<target>Premor</target> - -<source>Logging</source> -<target>Beleženje</target> - <source>Cannot find %x</source> <target>Ne najdem %x</target> @@ -1309,9 +1350,6 @@ Opomba: Imena datoteka morajo biti relativna osnovnim imenikom. <source>Append a timestamp to each file name</source> <target>Dodaj časovno oznako k vsakemu imenu datoteke</target> -<source>Folder</source> -<target>Mapa</target> - <source>File</source> <target>Datoteka</target> diff --git a/BUILD/Languages/spanish.lng b/BUILD/Languages/spanish.lng index 6f48fdd6..e1726b2b 100644 --- a/BUILD/Languages/spanish.lng +++ b/BUILD/Languages/spanish.lng @@ -7,6 +7,99 @@ <plural_definition>n == 1 ? 0 : 1</plural_definition> </header> +<source>Log</source> +<target></target> + +<source>&Continue</source> +<target></target> + +<source>Comma-separated values</source> +<target></target> + +<source>&Don't show this dialog again</source> +<target></target> + +<source>&Execute</source> +<target></target> + +<source>Confirm</source> +<target></target> + +<source> +<pluralform>Do you really want to execute the command %y for 1 item?</pluralform> +<pluralform>Do you really want to execute the command %y for %x items?</pluralform> +</source> +<target></target> + +<source>Minimize to notification area</source> +<target></target> + +<source>Retrying operation after error:</source> +<target></target> + +<source>The following folders are significantly different. Make sure you are matching the correct folders for synchronization.</source> +<target></target> + +<source>&Show error</source> +<target></target> + +<source>Waiting until all directories are available...</source> +<target></target> + +<source>Directory monitoring active</source> +<target></target> + +<source>Volume name %x is not part of file path %y.</source> +<target></target> + +<source>%x items</source> +<target></target> + +<source> +<pluralform>1 thread</pluralform> +<pluralform>%x threads</pluralform> +</source> +<target></target> + +<source> +<pluralform>1 byte</pluralform> +<pluralform>%x bytes</pluralform> +</source> +<target></target> + +<source>Any number of alternative directories for at most one config file.</source> +<target></target> + +<source>Any number of FreeFileSync .ffs_gui and/or .ffs_batch configuration files.</source> +<target></target> + +<source>directory</source> +<target></target> + +<source>config files</source> +<target></target> + +<source>Syntax:</source> +<target></target> + +<source>Directories cannot be set for more than one configuration file.</source> +<target></target> + +<source>The config file must not contain settings at directory pair level when directories are set via command line.</source> +<target></target> + +<source>Unequal number of left and right directories specified.</source> +<target></target> + +<source>Cannot open file %x.</source> +<target></target> + +<source>Syntax error</source> +<target></target> + +<source>A directory path is expected after %x.</source> +<target></target> + <source>Both sides have changed since last synchronization.</source> <target>Ambos lados han cambiado desde la última sincronización.</target> @@ -49,15 +142,18 @@ <source>An exception occurred</source> <target>Ha ocurrido una excepción</target> -<source>Cannot find file %x.</source> -<target>No se puede encontrar el archivo %x.</target> - <source>Error</source> <target>Error</target> <source>File %x does not contain a valid configuration.</source> <target>El archivo %x no contiene una configuración válida.</target> +<source>Warning</source> +<target>Atención</target> + +<source>Command line</source> +<target>Línea de comandos</target> + <source>A folder input field is empty.</source> <target>Un campo de entrada de la carpeta está vacío.</target> @@ -214,15 +310,6 @@ <source>Total time:</source> <target>Tiempo total:</target> -<source> -<pluralform>1 Byte</pluralform> -<pluralform>%x Bytes</pluralform> -</source> -<target> -<pluralform>1 byte</pluralform> -<pluralform>%x bytes</pluralform> -</target> - <source>%x MB</source> <target>%x MB</target> @@ -241,15 +328,6 @@ <source>Scanning:</source> <target>Escanear:</target> -<source> -<pluralform>[1 Thread]</pluralform> -<pluralform>[%x Threads]</pluralform> -</source> -<target> -<pluralform>[1 hilo]</pluralform> -<pluralform>[%x hilos]</pluralform> -</target> - <source>Encoding extended time information: %x</source> <target>Condificando información de hora extendida: %x</target> @@ -280,9 +358,6 @@ <source>Cannot determine volume name for %x.</source> <target>No se puede determinar nombre del volumen de %x.</target> -<source>Volume name %x not part of file name %y.</source> -<target>El nombre de volumen %x no es parte del nombre de archivo %y.</target> - <source>Abort requested: Waiting for current operation to finish...</source> <target>Anulación solicitada: esperando a que la operación actual finalice…</target> @@ -349,9 +424,6 @@ <source>Idle time between last detected change and execution of command</source> <target>Tiempo ocioso entre el último cambio detectado y la ejecución del comando</target> -<source>Command line</source> -<target>Línea de comandos</target> - <source> The command is triggered if: - files or subfolders change @@ -375,9 +447,6 @@ El comando es disparado si:: <source>RealtimeSync - Automated Synchronization</source> <target>RealtimeSync - Sincronización Automática</target> -<source>Warning</source> -<target>Atención</target> - <source>Build: %x</source> <target>Completado: %x</target> @@ -393,9 +462,6 @@ El comando es disparado si:: <source>&Exit</source> <target>&Salir</target> -<source>Waiting for missing directories...</source> -<target>Esperando directorios faltantes…</target> - <source>Invalid command line:</source> <target>Línea de comandos errónea:</target> @@ -405,14 +471,14 @@ El comando es disparado si:: <source>File time and size</source> <target>Fecha y tamaño del archivo</target> -<source> Two way </source> -<target> Dos formas </target> +<source>Two way</source> +<target>Dos formas</target> <source>Mirror</source> -<target>Espejo </target> +<target>Espejo</target> <source>Update</source> -<target>Actualizar </target> +<target>Actualizar</target> <source>Custom</source> <target>Personalizado</target> @@ -468,12 +534,6 @@ El comando es disparado si:: <source>The following items have unresolved conflicts and will not be synchronized:</source> <target>Los siguientes elementos tienen conflictos sin resolver y no serán sincronizados:</target> -<source>Significant difference detected:</source> -<target>Diferencia significante detectada:</target> - -<source>More than 50% of the total number of files will be copied or deleted.</source> -<target>Más del 50% del número total de archivos serán copiados o eliminados.</target> - <source>Not enough free disk space available in:</source> <target>Espacio en disco insuficiente en:</target> @@ -726,6 +786,15 @@ El comando es disparado si:: <source>&Pause</source> <target>&Pausa</target> +<source>Variant</source> +<target>Tipo</target> + +<source>Statistics</source> +<target>Estadísticas</target> + +<source>Don't show this dialog again</source> +<target>No volver a mostrar este diálogo</target> + <source>Select a variant</source> <target>Seleccione una variante</target> @@ -933,15 +1002,6 @@ Aviso: Los nombres de archivos deben ser relativos a los directorios base. <source>&Default</source> <target>&Configuración predeterminada</target> -<source>Variant</source> -<target>Tipo</target> - -<source>Statistics</source> -<target>Estadísticas</target> - -<source>Don't show this dialog again</source> -<target>No volver a mostrar este diálogo</target> - <source>Find what:</source> <target>Buscar:</target> @@ -951,15 +1011,15 @@ Aviso: Los nombres de archivos deben ser relativos a los directorios base. <source>&Find next</source> <target>&Buscar siguiente</target> +<source>Start synchronization</source> +<target>Iniciar sincronización</target> + <source>Delete</source> <target>Eliminar</target> <source>Configure filter</source> <target>Configurar filtro</target> -<source>Start synchronization</source> -<target>Iniciar sincronización</target> - <source>Find</source> <target>Buscar</target> @@ -1134,9 +1194,6 @@ Aviso: Los nombres de archivos deben ser relativos a los directorios base. <source>All folders are in sync</source> <target>Todas las carpetas están sincronizadas</target> -<source>Comma separated list</source> -<target>Lista separada por comas</target> - <source>File list exported</source> <target>Lista de archivos exportada</target> @@ -1188,15 +1245,6 @@ Aviso: Los nombres de archivos deben ser relativos a los directorios base. <source>Completed</source> <target>Terminado</target> -<source>Continue</source> -<target>Continuar</target> - -<source>Pause</source> -<target>Pausa</target> - -<source>Logging</source> -<target>Registro</target> - <source>Cannot find %x</source> <target>No se puede encontrar %x</target> @@ -1290,9 +1338,6 @@ Aviso: Los nombres de archivos deben ser relativos a los directorios base. <source>Append a timestamp to each file name</source> <target>Incluir fecha y hora a cada nombre de archivo</target> -<source>Folder</source> -<target>Carpeta</target> - <source>File</source> <target>Archivo</target> diff --git a/BUILD/Resources.zip b/BUILD/Resources.zip Binary files differindex 864883e8..4b79261a 100644 --- a/BUILD/Resources.zip +++ b/BUILD/Resources.zip diff --git a/FreeFileSync.vcxproj b/FreeFileSync.vcxproj index 0bf7114b..fd2c8db9 100644 --- a/FreeFileSync.vcxproj +++ b/FreeFileSync.vcxproj @@ -29,27 +29,27 @@ <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>true</UseDebugLibraries> <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>Windows7.1SDK</PlatformToolset> + <PlatformToolset>v120_xp</PlatformToolset> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>true</UseDebugLibraries> <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>Windows7.1SDK</PlatformToolset> + <PlatformToolset>v120_xp</PlatformToolset> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> <WholeProgramOptimization>true</WholeProgramOptimization> <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>Windows7.1SDK</PlatformToolset> + <PlatformToolset>v120_xp</PlatformToolset> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> <WholeProgramOptimization>true</WholeProgramOptimization> <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>Windows7.1SDK</PlatformToolset> + <PlatformToolset>v120_xp</PlatformToolset> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <ImportGroup Label="ExtensionSettings"> @@ -92,6 +92,7 @@ <IntDir>OBJ\$(ProjectName)_VCPP_$(PlatformName)_$(Configuration)\</IntDir> <GenerateManifest>false</GenerateManifest> <TargetName>$(ProjectName)_$(PlatformName)</TargetName> + <RunCodeAnalysis>false</RunCodeAnalysis> </PropertyGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ClCompile> @@ -99,7 +100,7 @@ <WarningLevel>Level4</WarningLevel> <Optimization>Disabled</Optimization> <PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;__WXDEBUG__;WXUSINGDLL;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x86_debug_dll\mswud;.</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x86_debug_dll\mswud;.</AdditionalIncludeDirectories> <PrecompiledHeaderFile>wx+/pch.h</PrecompiledHeaderFile> <DisableSpecificWarnings>4100;4512</DisableSpecificWarnings> <MultiProcessorCompilation>true</MultiProcessorCompilation> @@ -114,7 +115,7 @@ <GenerateDebugInformation>true</GenerateDebugInformation> <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> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x86_debug_dll</AdditionalLibraryDirectories> <LinkStatus> </LinkStatus> </Link> @@ -129,7 +130,7 @@ <WarningLevel>Level4</WarningLevel> <Optimization>Disabled</Optimization> <PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;__WXDEBUG__;WXUSINGDLL;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x64_debug_dll\mswud;.</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x64_debug_dll\mswud;.</AdditionalIncludeDirectories> <PrecompiledHeaderFile>wx+/pch.h</PrecompiledHeaderFile> <DisableSpecificWarnings>4100;4512</DisableSpecificWarnings> <MultiProcessorCompilation>true</MultiProcessorCompilation> @@ -146,7 +147,7 @@ <GenerateDebugInformation>true</GenerateDebugInformation> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <AdditionalDependencies>wxbase28ud.lib;wxmsw28ud_core.lib;wxmsw28ud_adv.lib;wxmsw28ud_aui.lib;wxbase28ud_net.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_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x64_debug_dll</AdditionalLibraryDirectories> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x64_debug_dll</AdditionalLibraryDirectories> <LinkStatus> </LinkStatus> <ManifestFile> @@ -164,14 +165,12 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x86_release_lib\mswu;.</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x86_release_lib\mswu;.</AdditionalIncludeDirectories> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <DisableSpecificWarnings>4100;4512</DisableSpecificWarnings> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <MultiProcessorCompilation>true</MultiProcessorCompilation> <ForcedIncludeFiles>zen/warn_static.h</ForcedIncludeFiles> - <AdditionalOptions> - </AdditionalOptions> </ClCompile> <Link> <SubSystem>Windows</SubSystem> @@ -180,7 +179,7 @@ <OptimizeReferences>true</OptimizeReferences> <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> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x86_release_lib</AdditionalLibraryDirectories> <LinkStatus> </LinkStatus> </Link> @@ -199,23 +198,22 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x64_release_lib\mswu;.</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x64_release_lib\mswu;.</AdditionalIncludeDirectories> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <DisableSpecificWarnings>4100;4512</DisableSpecificWarnings> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <MultiProcessorCompilation>true</MultiProcessorCompilation> <ForcedIncludeFiles>zen/warn_static.h</ForcedIncludeFiles> - <AdditionalOptions> - </AdditionalOptions> + <EnablePREfast>false</EnablePREfast> </ClCompile> <Link> <SubSystem>Windows</SubSystem> - <GenerateDebugInformation>false</GenerateDebugInformation> + <GenerateDebugInformation>true</GenerateDebugInformation> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> <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> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x64_release_lib</AdditionalLibraryDirectories> <LinkStatus> </LinkStatus> </Link> diff --git a/RealtimeSync/RealtimeSync.vcxproj b/RealtimeSync/RealtimeSync.vcxproj index 487f2cce..08abd76f 100644 --- a/RealtimeSync/RealtimeSync.vcxproj +++ b/RealtimeSync/RealtimeSync.vcxproj @@ -29,27 +29,27 @@ <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>true</UseDebugLibraries> <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>Windows7.1SDK</PlatformToolset> + <PlatformToolset>v120_xp</PlatformToolset> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>true</UseDebugLibraries> <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>Windows7.1SDK</PlatformToolset> + <PlatformToolset>v120_xp</PlatformToolset> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> <WholeProgramOptimization>true</WholeProgramOptimization> <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>Windows7.1SDK</PlatformToolset> + <PlatformToolset>v120_xp</PlatformToolset> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> <WholeProgramOptimization>true</WholeProgramOptimization> <CharacterSet>Unicode</CharacterSet> - <PlatformToolset>Windows7.1SDK</PlatformToolset> + <PlatformToolset>v120_xp</PlatformToolset> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <ImportGroup Label="ExtensionSettings"> @@ -99,7 +99,7 @@ <WarningLevel>Level4</WarningLevel> <Optimization>Disabled</Optimization> <PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;__WXDEBUG__;WXUSINGDLL;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x86_debug_dll\mswud;..</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x86_debug_dll\mswud;..</AdditionalIncludeDirectories> <PrecompiledHeaderFile>wx+/pch.h</PrecompiledHeaderFile> <DisableSpecificWarnings>4100;4512</DisableSpecificWarnings> <MultiProcessorCompilation>true</MultiProcessorCompilation> @@ -114,7 +114,7 @@ <GenerateDebugInformation>true</GenerateDebugInformation> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <AdditionalDependencies>wxmsw28ud_adv.lib;wxmsw28ud_core.lib;wxbase28ud.lib;wxpngd.lib;wxzlibd.lib;wxbase28ud_net.lib;comctl32.lib;ws2_32.lib;Rpcrt4.lib;winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> - <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x86_debug_dll</AdditionalLibraryDirectories> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x86_debug_dll</AdditionalLibraryDirectories> </Link> <ResourceCompile> <AdditionalIncludeDirectories>C:\Program Files\C++\wxWidgets\include</AdditionalIncludeDirectories> @@ -127,7 +127,7 @@ <WarningLevel>Level4</WarningLevel> <Optimization>Disabled</Optimization> <PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;__WXDEBUG__;WXUSINGDLL;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x64_debug_dll\mswud;..</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x64_debug_dll\mswud;..</AdditionalIncludeDirectories> <PrecompiledHeaderFile>wx+/pch.h</PrecompiledHeaderFile> <DisableSpecificWarnings>4100;4512</DisableSpecificWarnings> <MultiProcessorCompilation>true</MultiProcessorCompilation> @@ -144,7 +144,7 @@ <GenerateDebugInformation>true</GenerateDebugInformation> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <AdditionalDependencies>wxmsw28ud_adv.lib;wxmsw28ud_core.lib;wxbase28ud.lib;wxpngd.lib;wxzlibd.lib;wxbase28ud_net.lib;comctl32.lib;ws2_32.lib;Rpcrt4.lib;winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> - <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x64_debug_dll</AdditionalLibraryDirectories> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x64_debug_dll</AdditionalLibraryDirectories> <LinkStatus> </LinkStatus> </Link> @@ -160,7 +160,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x86_release_lib\mswu;..</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x86_release_lib\mswu;..</AdditionalIncludeDirectories> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <DisableSpecificWarnings>4100;4512</DisableSpecificWarnings> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> @@ -174,7 +174,7 @@ <OptimizeReferences>true</OptimizeReferences> <AdditionalDependencies>wxbase28u.lib;wxmsw28u_adv.lib;wxmsw28u_core.lib;wxpng.lib;wxzlib.lib;wxbase28u_net.lib;comctl32.lib;ws2_32.lib;winmm.lib;Rpcrt4.lib;%(AdditionalDependencies)</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> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x86_release_lib</AdditionalLibraryDirectories> </Link> <ResourceCompile> <AdditionalIncludeDirectories>C:\Program Files\C++\wxWidgets\include</AdditionalIncludeDirectories> @@ -191,7 +191,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x64_release_lib\mswu;..</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x64_release_lib\mswu;..</AdditionalIncludeDirectories> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <DisableSpecificWarnings>4100;4512</DisableSpecificWarnings> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> @@ -205,7 +205,7 @@ <OptimizeReferences>true</OptimizeReferences> <AdditionalDependencies>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)</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> + <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x64_release_lib</AdditionalLibraryDirectories> </Link> <ResourceCompile> <AdditionalIncludeDirectories>C:\Program Files\C++\wxWidgets\include</AdditionalIncludeDirectories> @@ -246,9 +246,9 @@ </ClCompile> <ClCompile Include="gui_generated.cpp" /> <ClCompile Include="main_dlg.cpp" /> + <ClCompile Include="monitor.cpp" /> <ClCompile Include="resources.cpp" /> <ClCompile Include="tray_menu.cpp" /> - <ClCompile Include="watcher.cpp" /> <ClCompile Include="xml_ffs.cpp" /> <ClCompile Include="xml_proc.cpp" /> </ItemGroup> diff --git a/RealtimeSync/application.cpp b/RealtimeSync/application.cpp index a7aff305..af8a40a9 100644 --- a/RealtimeSync/application.cpp +++ b/RealtimeSync/application.cpp @@ -36,19 +36,20 @@ IMPLEMENT_APP(Application); namespace { +/* boost::thread::id mainThreadId = boost::this_thread::get_id(); void onTerminationRequested() { - std::wstring msg = boost::this_thread::get_id() == mainThreadId ? - L"Termination requested in main thread!\n\n" : - L"Termination requested in worker thread!\n\n"; - msg += L"Please file a bug report at: http://sourceforge.net/projects/freefilesync"; +std::wstring msg = boost::this_thread::get_id() == mainThreadId ? + L"Termination requested in main thread!\n\n" : + L"Termination requested in worker thread!\n\n"; +msg += L"Please file a bug report at: http://sourceforge.net/projects/freefilesync"; - wxSafeShowMessage(_("An exception occurred"), msg); - std::abort(); +wxSafeShowMessage(_("An exception occurred"), msg); +std::abort(); } - +*/ #ifdef _MSC_VER void crtInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) { assert(false); } #endif @@ -59,7 +60,7 @@ const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); bool Application::OnInit() { - std::set_terminate(onTerminationRequested); //unlike wxWidgets uncaught exception handling, this works for all worker threads + //std::set_terminate(onTerminationRequested); //unlike wxWidgets uncaught exception handling, this works for all worker threads #ifdef ZEN_WIN #ifdef _MSC_VER @@ -107,12 +108,12 @@ void Application::onEnterEventLoop(wxEvent& event) } catch (const FileError& e) { - wxMessageBox(e.toString(), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(e.toString(), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR); //continue! } //try to set config/batch-filename set by %1 parameter - std::vector<wxString> commandArgs; + std::vector<Zstring> commandArgs; for (int i = 1; i < argc; ++i) { Zstring filename = toZ(argv[i]); @@ -125,14 +126,14 @@ void Application::onEnterEventLoop(wxEvent& event) filename += Zstr(".ffs_batch"); else { - wxMessageBox(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filename)), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filename)), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR); return; } } - commandArgs.push_back(toWx(filename)); + commandArgs.push_back(filename); } - wxString cfgFilename; + Zstring cfgFilename; if (!commandArgs.empty()) cfgFilename = commandArgs[0]; diff --git a/RealtimeSync/gui_generated.cpp b/RealtimeSync/gui_generated.cpp index cf072240..659f7ea4 100644 --- a/RealtimeSync/gui_generated.cpp +++ b/RealtimeSync/gui_generated.cpp @@ -100,6 +100,8 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr bSizer1->Add( m_staticText7, 0, wxALL, 5 ); m_panelMainFolder = new wxPanel( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panelMainFolder->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxFlexGridSizer* fgSizer1; fgSizer1 = new wxFlexGridSizer( 0, 2, 0, 0 ); fgSizer1->AddGrowableCol( 1 ); @@ -152,6 +154,8 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr m_scrolledWinFolders = new wxScrolledWindow( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); m_scrolledWinFolders->SetScrollRate( 10, 10 ); + m_scrolledWinFolders->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + bSizerFolders = new wxBoxSizer( wxVERTICAL ); @@ -243,6 +247,8 @@ MainDlgGenerated::~MainDlgGenerated() FolderGenerated::FolderGenerated( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style ) : wxPanel( parent, id, pos, size, style ) { + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxBoxSizer* bSizer114; bSizer114 = new wxBoxSizer( wxHORIZONTAL ); diff --git a/RealtimeSync/gui_generated.h b/RealtimeSync/gui_generated.h index 2b42b271..aabb23fc 100644 --- a/RealtimeSync/gui_generated.h +++ b/RealtimeSync/gui_generated.h @@ -140,7 +140,7 @@ protected: public: - ErrorDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Error"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER ); + ErrorDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Error"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); ~ErrorDlgGenerated(); }; diff --git a/RealtimeSync/main_dlg.cpp b/RealtimeSync/main_dlg.cpp index b750b110..b7a0aee1 100644 --- a/RealtimeSync/main_dlg.cpp +++ b/RealtimeSync/main_dlg.cpp @@ -9,8 +9,7 @@ #include <wx/msgdlg.h> #include <wx/wupdlock.h> #include <wx/filedlg.h> -#include <wx/utils.h> -#include <wx/filedlg.h> +//#include <wx/utils.h> #include <wx+/button.h> #include <wx+/string_conv.h> #include <wx+/mouse_move_dlg.h> @@ -18,7 +17,6 @@ #include <zen/assert_static.h> #include <zen/file_handling.h> #include <zen/build_info.h> -#include "watcher.h" #include "xml_proc.h" #include "tray_menu.h" #include "xml_ffs.h" @@ -44,7 +42,7 @@ private: }; -MainDialog::MainDialog(wxDialog* dlg, const wxString& cfgFileName) +MainDialog::MainDialog(wxDialog* dlg, const Zstring& cfgFileName) : MainDlgGenerated(dlg) { #ifdef ZEN_WIN @@ -73,20 +71,20 @@ MainDialog::MainDialog(wxDialog* dlg, const wxString& cfgFileName) //--------------------------- load config values ------------------------------------ xmlAccess::XmlRealConfig newConfig; - const wxString currentConfigFile = cfgFileName.empty() ? lastConfigFileName() : cfgFileName; + const Zstring currentConfigFile = cfgFileName.empty() ? lastConfigFileName() : cfgFileName; bool loadCfgSuccess = false; - if (!cfgFileName.empty() || wxFileExists(lastConfigFileName())) + if (!cfgFileName.empty() || fileExists(lastConfigFileName())) try { - rts::readRealOrBatchConfig(toZ(currentConfigFile), newConfig); //throw FfsXmlError + rts::readRealOrBatchConfig(currentConfigFile, newConfig); //throw FfsXmlError loadCfgSuccess = true; } catch (const xmlAccess::FfsXmlError& error) { if (error.getSeverity() == xmlAccess::FfsXmlError::WARNING) - wxMessageBox(error.toString(), _("Warning"), wxOK | wxICON_WARNING, this); + wxMessageBox(error.toString(),L"RealtimeSync" + _("Warning"), wxOK | wxICON_WARNING, this); else - wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(error.toString(), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR, this); } const bool startWatchingImmediately = loadCfgSuccess && !cfgFileName.empty(); @@ -127,11 +125,11 @@ MainDialog::~MainDialog() try //write config to XML { - writeRealConfig(currentCfg, toZ(lastConfigFileName())); //throw FfsXmlError + writeRealConfig(currentCfg, lastConfigFileName()); //throw FfsXmlError } catch (const xmlAccess::FfsXmlError& error) { - wxMessageBox(error.toString().c_str(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(error.toString().c_str(), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR, this); } } @@ -145,9 +143,9 @@ void MainDialog::onProcessAsyncTasks(wxEvent& event) } -const wxString& MainDialog::lastConfigFileName() +const Zstring& MainDialog::lastConfigFileName() { - static wxString instance = toWx(zen::getConfigDir()) + L"LastRun.ffs_real"; + static Zstring instance = zen::getConfigDir() + Zstr("LastRun.ffs_real"); return instance; } @@ -206,54 +204,59 @@ void MainDialog::OnStart(wxCommandEvent& event) break; } Show(); //don't show for EXIT_APP + Raise(); } void MainDialog::OnConfigSave(wxCommandEvent& event) { - wxString defaultFileName = currentConfigFileName.empty() ? L"Realtime.ffs_real" : currentConfigFileName; + Zstring defaultFileName = currentConfigFileName.empty() ? Zstr("Realtime.ffs_real") : currentConfigFileName; //attention: currentConfigFileName may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config! - if (endsWith(defaultFileName, L".ffs_batch")) - replace(defaultFileName, L".ffs_batch", L".ffs_real", false); + if (endsWith(defaultFileName, Zstr(".ffs_batch"))) + replace(defaultFileName, Zstr(".ffs_batch"), Zstr(".ffs_real"), false); - wxFileDialog filePicker(this, wxEmptyString, wxEmptyString, defaultFileName, + wxFileDialog filePicker(this, + wxEmptyString, + //OS X really needs dir/file separated like this: + utfCvrtTo<wxString>(beforeLast(defaultFileName, FILE_NAME_SEPARATOR)), //default dir; empty string if / not found + utfCvrtTo<wxString>(afterLast (defaultFileName, FILE_NAME_SEPARATOR)), //default file; whole string if / not found wxString(L"RealtimeSync (*.ffs_real)|*.ffs_real") + L"|" +_("All files") + L" (*.*)|*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (filePicker.ShowModal() != wxID_OK) return; - const wxString newFileName = filePicker.GetPath(); + const Zstring newFileName = utfCvrtTo<Zstring>(filePicker.GetPath()); //write config to XML const xmlAccess::XmlRealConfig currentCfg = getConfiguration(); try { - writeRealConfig(currentCfg, toZ(newFileName)); //throw FfsXmlError + writeRealConfig(currentCfg, newFileName); //throw FfsXmlError setLastUsedConfig(newFileName); } catch (const xmlAccess::FfsXmlError& e) { - wxMessageBox(e.toString().c_str(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(e.toString().c_str(), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR, this); } } -void MainDialog::loadConfig(const wxString& filename) +void MainDialog::loadConfig(const Zstring& filename) { xmlAccess::XmlRealConfig newConfig; try { - rts::readRealOrBatchConfig(toZ(filename), newConfig); + rts::readRealOrBatchConfig(filename, newConfig); } catch (const xmlAccess::FfsXmlError& error) { if (error.getSeverity() == xmlAccess::FfsXmlError::WARNING) - wxMessageBox(error.toString(), _("Warning"), wxOK | wxICON_WARNING, this); + wxMessageBox(error.toString(), L"RealtimeSync" + _("Warning"), wxOK | wxICON_WARNING, this); else { - wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(error.toString(), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR, this); return; } } @@ -263,7 +266,7 @@ void MainDialog::loadConfig(const wxString& filename) } -void MainDialog::setLastUsedConfig(const wxString& filename) +void MainDialog::setLastUsedConfig(const Zstring& filename) { //set title if (filename == lastConfigFileName()) @@ -273,7 +276,7 @@ void MainDialog::setLastUsedConfig(const wxString& filename) } else { - SetTitle(filename); + SetTitle(utfCvrtTo<wxString>(filename)); currentConfigFileName = filename; } } @@ -281,11 +284,14 @@ void MainDialog::setLastUsedConfig(const wxString& filename) void MainDialog::OnConfigLoad(wxCommandEvent& event) { - wxFileDialog filePicker(this, wxEmptyString, wxEmptyString, wxEmptyString, + wxFileDialog filePicker(this, + wxEmptyString, + utfCvrtTo<wxString>(beforeLast(currentConfigFileName, FILE_NAME_SEPARATOR)), //default dir; empty string if / not found + wxEmptyString, wxString(L"RealtimeSync (*.ffs_real;*.ffs_batch)|*.ffs_real;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", wxFD_OPEN); if (filePicker.ShowModal() == wxID_OK) - loadConfig(filePicker.GetPath()); + loadConfig(utfCvrtTo<Zstring>(filePicker.GetPath())); } @@ -293,7 +299,7 @@ void MainDialog::onFilesDropped(FileDropEvent& event) { const auto& files = event.getFiles(); if (!files.empty()) - loadConfig(files[0]); + loadConfig(utfCvrtTo<Zstring>(files[0])); } @@ -307,14 +313,14 @@ void MainDialog::setConfiguration(const xmlAccess::XmlRealConfig& cfg) if (!cfg.directories.empty()) { //fill top folder - dirNameFirst->setName(*cfg.directories.begin()); + dirNameFirst->setName(utfCvrtTo<wxString>(*cfg.directories.begin())); //fill additional folders - addFolder(std::vector<wxString>(cfg.directories.begin() + 1, cfg.directories.end())); + addFolder(std::vector<Zstring>(cfg.directories.begin() + 1, cfg.directories.end())); } //fill commandline - m_textCtrlCommand->SetValue(cfg.commandline); + m_textCtrlCommand->SetValue(utfCvrtTo<wxString>(cfg.commandline)); //set delay m_spinCtrlDelay->SetValue(static_cast<int>(cfg.delay)); @@ -325,11 +331,11 @@ xmlAccess::XmlRealConfig MainDialog::getConfiguration() { xmlAccess::XmlRealConfig output; - output.directories.push_back(dirNameFirst->getName()); - for (std::vector<DirectoryPanel*>::const_iterator i = dirNamesExtra.begin(); i != dirNamesExtra.end(); ++i) - output.directories.push_back((*i)->getName()); + output.directories.push_back(utfCvrtTo<Zstring>(dirNameFirst->getName())); + for (auto it = dirNamesExtra.begin(); it != dirNamesExtra.end(); ++it) + output.directories.push_back(utfCvrtTo<Zstring>((*it)->getName())); - output.commandline = m_textCtrlCommand->GetValue(); + output.commandline = utfCvrtTo<Zstring>(m_textCtrlCommand->GetValue()); output.delay = m_spinCtrlDelay->GetValue(); return output; @@ -338,12 +344,12 @@ xmlAccess::XmlRealConfig MainDialog::getConfiguration() void MainDialog::OnAddFolder(wxCommandEvent& event) { - const wxString topFolder = dirNameFirst->getName(); + const Zstring topFolder = utfCvrtTo<Zstring>(dirNameFirst->getName()); //clear existing top folder first dirNameFirst->setName(wxString()); - std::vector<wxString> newFolders; + std::vector<Zstring> newFolders; newFolders.push_back(topFolder); addFolder(newFolders, true); //add pair in front of additonal pairs @@ -383,7 +389,7 @@ static const size_t MAX_ADD_FOLDERS = 6; #endif -void MainDialog::addFolder(const std::vector<wxString>& newFolders, bool addFront) +void MainDialog::addFolder(const std::vector<Zstring>& newFolders, bool addFront) { if (newFolders.size() == 0) return; @@ -391,7 +397,7 @@ void MainDialog::addFolder(const std::vector<wxString>& newFolders, bool addFron wxWindowUpdateLocker dummy(this); //avoid display distortion int folderHeight = 0; - for (std::vector<wxString>::const_iterator i = newFolders.begin(); i != newFolders.end(); ++i) + for (auto it = newFolders.begin(); it != newFolders.end(); ++it) { //add new folder pair DirectoryPanel* newFolder = new DirectoryPanel(m_scrolledWinFolders); @@ -415,7 +421,7 @@ void MainDialog::addFolder(const std::vector<wxString>& newFolders, bool addFron newFolder->m_bpButtonRemoveFolder->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolder), nullptr, this ); //insert directory name - newFolder->setName(*i); + newFolder->setName(utfCvrtTo<wxString>(*it)); } //set size of scrolled window diff --git a/RealtimeSync/main_dlg.h b/RealtimeSync/main_dlg.h index 5a028f3a..2a1fb03e 100644 --- a/RealtimeSync/main_dlg.h +++ b/RealtimeSync/main_dlg.h @@ -10,9 +10,10 @@ #include "gui_generated.h" #include <vector> #include <memory> +#include <zen/zstring.h> +#include <zen/async_task.h> #include <wx+/file_drop.h> #include "../ui/dir_name.h" -#include <zen/async_task.h> namespace xmlAccess { @@ -24,11 +25,11 @@ class DirectoryPanel; class MainDialog: public MainDlgGenerated { public: - MainDialog(wxDialog* dlg, const wxString& cfgFileName); + MainDialog(wxDialog* dlg, const Zstring& cfgFileName); ~MainDialog(); private: - void loadConfig(const wxString& filename); + void loadConfig(const Zstring& filename); virtual void OnClose (wxCloseEvent& event) { Destroy(); } virtual void OnShowHelp (wxCommandEvent& event); @@ -45,21 +46,21 @@ private: void setConfiguration(const xmlAccess::XmlRealConfig& cfg); xmlAccess::XmlRealConfig getConfiguration(); - void setLastUsedConfig(const wxString& filename); + void setLastUsedConfig(const Zstring& filename); void layoutAsync(); //call Layout() asynchronously - void addFolder(const wxString& dirname, bool addFront = false); - void addFolder(const std::vector<wxString>& newFolders, bool addFront = false); + //void addFolder(const Zstring& dirname, bool addFront = false); + void addFolder(const std::vector<Zstring>& newFolders, bool addFront = false); void removeAddFolder(size_t pos); void clearAddFolders(); - static const wxString& lastConfigFileName(); + static const Zstring& lastConfigFileName(); std::unique_ptr<zen::DirectoryName<wxTextCtrl>> dirNameFirst; std::vector<DirectoryPanel*> dirNamesExtra; //additional pairs to the standard pair - wxString currentConfigFileName; + Zstring currentConfigFileName; void onProcessAsyncTasks(wxEvent& event); diff --git a/RealtimeSync/makefile b/RealtimeSync/makefile index 865d73a1..d84c425f 100644 --- a/RealtimeSync/makefile +++ b/RealtimeSync/makefile @@ -58,7 +58,7 @@ CPP_LIST+=gui_generated.cpp CPP_LIST+=main_dlg.cpp CPP_LIST+=resources.cpp CPP_LIST+=tray_menu.cpp -CPP_LIST+=watcher.cpp +CPP_LIST+=monitor.cpp CPP_LIST+=xml_ffs.cpp CPP_LIST+=xml_proc.cpp CPP_LIST+=../structures.cpp diff --git a/RealtimeSync/watcher.cpp b/RealtimeSync/monitor.cpp index 6ef5e924..88536281 100644 --- a/RealtimeSync/watcher.cpp +++ b/RealtimeSync/monitor.cpp @@ -4,16 +4,14 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#include "watcher.h" +#include "monitor.h" +#include <ctime> #include <set> -#include <zen/tick_count.h> #include <zen/file_handling.h> -#include <zen/stl_tools.h> #include <zen/dir_watcher.h> #include <zen/thread.h> -#include <zen/assert_static.h> #include <zen/tick_count.h> -#include <wx+/string_conv.h> +#include <wx/utils.h> #include "../lib/resolve_path.h" //#include "../library/db_file.h" //SYNC_DB_FILE_ENDING -> complete file too much of a dependency; file ending too little to decouple into single header //#include "../library/lock_holder.h" //LOCK_FILE_ENDING @@ -23,24 +21,6 @@ using namespace zen; namespace { -const std::int64_t TICKS_UPDATE_INTERVAL = rts::UI_UPDATE_INTERVAL* ticksPerSec() / 1000; -TickVal lastExec = getTicks(); -}; - -bool rts::updateUiIsAllowed() -{ - const TickVal now = getTicks(); //0 on error - if (dist(lastExec, now) >= TICKS_UPDATE_INTERVAL) //perform ui updates not more often than necessary - { - lastExec = now; - return true; - } - return false; -} - - -namespace -{ const int CHECK_DIR_INTERVAL = 1; //unit: [s] @@ -53,10 +33,28 @@ std::vector<Zstring> getFormattedDirs(const std::vector<Zstring>& dirs) //throw return std::vector<Zstring>(tmp.begin(), tmp.end()); } -} -rts::WaitResult rts::waitForChanges(const std::vector<Zstring>& dirNamesNonFmt, WaitCallback& statusHandler) //throw FileError +//wait until changes are detected or if a directory is not available (anymore) +struct WaitResult +{ + enum ChangeType + { + CHANGE_DETECTED, + CHANGE_DIR_MISSING + }; + + WaitResult(const zen::DirWatcher::Entry& changedItem) : type(CHANGE_DETECTED), changedItem_(changedItem) {} + WaitResult(const Zstring& dirname) : type(CHANGE_DIR_MISSING), dirname_(dirname) {} + + ChangeType type; + zen::DirWatcher::Entry changedItem_; //for type == CHANGE_DETECTED: file or directory + Zstring dirname_; //for type == CHANGE_DIR_MISSING +}; + + +WaitResult waitForChanges(const std::vector<Zstring>& dirNamesNonFmt, //throw FileError + const std::function<void(bool)>& onRefreshGui) //bool: readyForSync { const std::vector<Zstring> dirNamesFmt = getFormattedDirs(dirNamesNonFmt); //throw FileError if (dirNamesFmt.empty()) //pathological case, but we have to check else this function will wait endlessly @@ -72,20 +70,17 @@ rts::WaitResult rts::waitForChanges(const std::vector<Zstring>& dirNamesNonFmt, { //a non-existent network path may block, so check existence asynchronously! auto ftDirExists = async([=] { return zen::dirExists(dirnameFmt); }); - while (!ftDirExists.timed_wait(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL / 2))) - statusHandler.requestUiRefresh(); //may throw! + //we need to check dirExists(), not somethingExists(): it's not clear if DirWatcher detects a type clash (file instead of directory!) + while (!ftDirExists.timed_wait(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2))) + onRefreshGui(false); //may throw! if (!ftDirExists.get()) return WaitResult(dirnameFmt); - watches.push_back(std::make_pair(dirnameFmt, std::make_shared<DirWatcher>(dirnameFmt))); //throw FileError, ErrorNotExisting + watches.push_back(std::make_pair(dirnameFmt, std::make_shared<DirWatcher>(dirnameFmt))); //throw FileError } - catch (ErrorNotExisting&) //nice atomic behavior: *no* second directory existence check!!! + catch (FileError&) { - return WaitResult(dirnameFmt); - } - catch (FileError&) //play safe: remedy potential FileErrors that should have been ErrorNotExisting (e.g. Linux: errors during directory traversing) - { - if (!dirExists(dirnameFmt)) //file system race condition!! + if (!somethingExists(dirnameFmt)) //a benign(?) race condition with FileError return WaitResult(dirnameFmt); throw; } @@ -114,12 +109,11 @@ rts::WaitResult rts::waitForChanges(const std::vector<Zstring>& dirNamesNonFmt, //IMPORTANT CHECK: dirwatcher has problems detecting removal of top watched directories! if (checkDirExistNow) - if (!dirExists(dirname)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED + if (!dirExists(dirname)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED -> somethingExists() is NOT sufficient here! return WaitResult(dirname); - try { - std::vector<DirWatcher::Entry> changedItems = watcher.getChanges([&] { statusHandler.requestUiRefresh(); }); //throw FileError, ErrorNotExisting + std::vector<DirWatcher::Entry> changedItems = watcher.getChanges([&] { onRefreshGui(false); /*may throw!*/ }); //throw FileError //remove to be ignored changes vector_remove_if(changedItems, [](const DirWatcher::Entry& e) @@ -139,26 +133,23 @@ rts::WaitResult rts::waitForChanges(const std::vector<Zstring>& dirNamesNonFmt, } } - catch (ErrorNotExisting&) //nice atomic behavior: *no* second directory existence check!!! + catch (FileError&) { - return WaitResult(dirname); - } - catch (FileError&) //play safe: remedy potential FileErrors that should have been ErrorNotExisting (e.g. Linux: errors during directory traversing) - { - if (!dirExists(dirname)) //file system race condition!! + if (!somethingExists(dirname)) //a benign(?) race condition with FileError return WaitResult(dirname); throw; } } boost::this_thread::sleep(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2)); - statusHandler.requestUiRefresh(true); //throw ?: may start sync at this presumably idle time + onRefreshGui(true); //throw ?: may start sync at this presumably idle time } } -//support for monitoring newly connected directories volumes (e.g.: USB-sticks) -void rts::waitForMissingDirs(const std::vector<Zstring>& dirNamesNonFmt, WaitCallback& statusHandler) //throw FileError +//wait until all directories become available (again) + logs in network share +void waitForMissingDirs(const std::vector<Zstring>& dirNamesNonFmt, //throw FileError + const std::function<void(const Zstring&)>& onRefreshGui) //Zstring: the directory that is currently being waited for { while (true) { @@ -179,24 +170,112 @@ void rts::waitForMissingDirs(const std::vector<Zstring>& dirNamesNonFmt, WaitCal return zen::dirExists(dirnameFmt); }); while (!ftDirExisting.timed_wait(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2))) - statusHandler.requestUiRefresh(); //may throw! + onRefreshGui(dirnameFmt); //may throw! if (!ftDirExisting.get()) { allExisting = false; + //wait some time... + const int refreshInterval = rts::UI_UPDATE_INTERVAL / 2; + assert_static(CHECK_DIR_INTERVAL * 1000 % refreshInterval == 0); + for (int i = 0; i < CHECK_DIR_INTERVAL * 1000 / refreshInterval; ++i) + { + onRefreshGui(dirnameFmt); //may throw! + boost::this_thread::sleep(boost::posix_time::milliseconds(refreshInterval)); + } break; } } if (allExisting) return; + } +} + + +inline +wxString toString(DirWatcher::ActionType type) +{ + switch (type) + { + case DirWatcher::ACTION_CREATE: + return L"CREATE"; + case DirWatcher::ACTION_UPDATE: + return L"UPDATE"; + case DirWatcher::ACTION_DELETE: + return L"DELETE"; + } + return L"ERROR"; +} + +struct ExecCommandNowException {}; +} + + +void rts::monitorDirectories(const std::vector<Zstring>& dirNamesNonFmt, unsigned int delay, rts::MonitorCallback& callback) +{ + if (dirNamesNonFmt.empty()) + { + assert(false); + return; + } + + auto execMonitoring = [&] //throw FileError + { + callback.setPhase(MonitorCallback::MONITOR_PHASE_WAITING); + waitForMissingDirs(dirNamesNonFmt, [&](const Zstring& dirname) { callback.requestUiRefresh(); }); //throw FileError + callback.setPhase(MonitorCallback::MONITOR_PHASE_ACTIVE); + + //schedule initial execution (*after* all directories have arrived, which could take some time which we don't want to include) + time_t nextExecDate = std::time(nullptr) + delay; + + while (true) //loop over command invocations + { + DirWatcher::Entry lastChangeDetected; + try + { + while (true) //loop over detected changes + { + //wait for changes (and for all directories to become available) + WaitResult res = waitForChanges(dirNamesNonFmt, [&](bool readyForSync) //throw FileError, ExecCommandNowException + { + if (readyForSync) + if (nextExecDate <= std::time(nullptr)) + throw ExecCommandNowException(); //abort wait and start sync + callback.requestUiRefresh(); + }); + switch (res.type) + { + case WaitResult::CHANGE_DIR_MISSING: //don't execute the command before all directories are available! + callback.setPhase(MonitorCallback::MONITOR_PHASE_WAITING); + waitForMissingDirs(dirNamesNonFmt, [&](const Zstring& dirname) { callback.requestUiRefresh(); }); //throw FileError + callback.setPhase(MonitorCallback::MONITOR_PHASE_ACTIVE); + break; + + case WaitResult::CHANGE_DETECTED: + lastChangeDetected = res.changedItem_; + break; + } + nextExecDate = std::time(nullptr) + delay; + } + } + catch (ExecCommandNowException&) {} + + ::wxSetEnv(L"change_path", utfCvrtTo<wxString>(lastChangeDetected.filename_)); //some way to output what file changed to the user + ::wxSetEnv(L"change_action", toString(lastChangeDetected.action_)); // - //wait some time... - const int refreshInterval = UI_UPDATE_INTERVAL / 2; - assert_static(1000 * CHECK_DIR_INTERVAL % refreshInterval == 0); - for (int i = 0; i < 1000 * CHECK_DIR_INTERVAL / refreshInterval; ++i) + //execute command + callback.executeExternalCommand(); + nextExecDate = std::numeric_limits<time_t>::max(); + } + }; + + while (true) + try { - boost::this_thread::sleep(boost::posix_time::milliseconds(refreshInterval)); - statusHandler.requestUiRefresh(); + execMonitoring(); //throw FileError + } + catch (const zen::FileError& e) + { + callback.reportError(e.toString()); } - } } diff --git a/RealtimeSync/monitor.h b/RealtimeSync/monitor.h new file mode 100644 index 00000000..0b9dfbc0 --- /dev/null +++ b/RealtimeSync/monitor.h @@ -0,0 +1,38 @@ +// ************************************************************************** +// * 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 MONITOR_HEADER_345087425834253425 +#define MONITOR_HEADER_345087425834253425 + +#include <functional> +#include <zen/zstring.h> + +namespace rts +{ +const int UI_UPDATE_INTERVAL = 100; //unit: [ms]; perform ui updates not more often than necessary, 100 seems to be a good value with only a minimal performance loss + + +struct MonitorCallback +{ + virtual ~MonitorCallback() {} + + enum WatchPhase + { + MONITOR_PHASE_ACTIVE, + MONITOR_PHASE_WAITING, + }; + virtual void setPhase(WatchPhase mode) = 0; + virtual void executeExternalCommand() = 0; + virtual void requestUiRefresh() = 0; + virtual void reportError(const std::wstring& msg) = 0; //automatically retries after return! +}; +void monitorDirectories(const std::vector<Zstring>& dirNamesNonFmt, + //non-formatted dirnames that yet require call to getFormattedDirectoryName(); empty directories must be checked by caller! + unsigned int delay, + MonitorCallback& callback); +} + +#endif //MONITOR_HEADER_345087425834253425 diff --git a/RealtimeSync/tray_menu.cpp b/RealtimeSync/tray_menu.cpp index 676904f1..33758ad2 100644 --- a/RealtimeSync/tray_menu.cpp +++ b/RealtimeSync/tray_menu.cpp @@ -5,28 +5,20 @@ // ************************************************************************** #include "tray_menu.h" -#include <algorithm> -#include <iterator> -#include <limits> -#include <set> -#include <zen/assert_static.h> #include <zen/build_info.h> +#include <zen/tick_count.h> +#include <zen/thread.h> #include <wx+/mouse_move_dlg.h> #include <wx+/image_tools.h> -#include <wx+/string_conv.h> +//#include <wx+/string_conv.h> #include <wx+/shell_execute.h> #include <wx+/std_button_order.h> -#include <wx/msgdlg.h> #include <wx/taskbar.h> -#include <wx/app.h> -#include <wx/utils.h> -#include <wx/menu.h> -#include <wx/utils.h> #include <wx/icon.h> //Linux needs this -#include <wx/timer.h> +#include <wx/app.h> #include "resources.h" #include "gui_generated.h" -#include "watcher.h" +#include "monitor.h" #include "../lib/resolve_path.h" using namespace rts; @@ -35,67 +27,149 @@ using namespace zen; namespace { -struct AbortCallback //never throw exceptions through a C-Layer (GUI)! +const std::int64_t TICKS_UPDATE_INTERVAL = rts::UI_UPDATE_INTERVAL* ticksPerSec() / 1000; +TickVal lastExec = getTicks(); + +bool updateUiIsAllowed() +{ + const TickVal now = getTicks(); //0 on error + if (dist(lastExec, now) >= TICKS_UPDATE_INTERVAL) //perform ui updates not more often than necessary + { + lastExec = now; + return true; + } + return false; +} + + +enum TrayMode { - virtual ~AbortCallback() {} - virtual void requestResume() = 0; - virtual void requestAbort() = 0; + TRAY_MODE_ACTIVE, + TRAY_MODE_WAITING, + TRAY_MODE_ERROR, }; -//RtsTrayIcon is a dumb class whose sole purpose is to enable wxWidgets deferred deletion -class RtsTrayIconRaw : public wxTaskBarIcon +class TrayIconObject : public wxTaskBarIcon { public: - RtsTrayIconRaw(AbortCallback& abortCb) : abortCb_(&abortCb) + TrayIconObject(const wxString& jobname) : + resumeRequested(false), + abortRequested(false), + showErrorMsgRequested(false), + mode(TRAY_MODE_ACTIVE), + iconFlashStatusLast(false), + jobName_(jobname), +#if defined ZEN_WIN || defined ZEN_MAC //16x16 seems to be the only size that is shown correctly on OS X + trayBmp(getResourceImage(L"RTS_tray_16x16")) //use a 16x16 bitmap +#elif defined ZEN_LINUX + trayBmp(getResourceImage(L"RTS_tray_24x24")) //use a 24x24 bitmap for perfect fit +#endif { - Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(RtsTrayIconRaw::OnDoubleClick), nullptr, this); + Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(TrayIconObject::OnDoubleClick), nullptr, this); + setMode(mode); } - void dontCallBackAnymore() { abortCb_ = nullptr; } //call before tray icon is marked for deferred deletion + //require polling: + bool resumeIsRequested() const { return resumeRequested; } + bool abortIsRequested () const { return abortRequested; } + + //during TRAY_MODE_ERROR those two functions are available: + void clearShowErrorRequested() { assert(mode == TRAY_MODE_ERROR); showErrorMsgRequested = false; } + bool getShowErrorRequested() const { assert(mode == TRAY_MODE_ERROR); return showErrorMsgRequested; } + + void setMode(TrayMode m) + { + mode = m; + timer.Stop(); + timer.Disconnect(wxEVT_TIMER, wxEventHandler(TrayIconObject::OnErrorFlashIcon), nullptr, this); + switch (m) + { + case TRAY_MODE_ACTIVE: + setTrayIcon(trayBmp, _("Directory monitoring active")); + break; + + case TRAY_MODE_WAITING: + setTrayIcon(greyScale(trayBmp), _("Waiting until all directories are available...")); + break; + + case TRAY_MODE_ERROR: + timer.Connect(wxEVT_TIMER, wxEventHandler(TrayIconObject::OnErrorFlashIcon), nullptr, this); + timer.Start(500); //timer interval in [ms] + break; + } + } private: + void OnErrorFlashIcon(wxEvent& event) + { + iconFlashStatusLast = !iconFlashStatusLast; + setTrayIcon(iconFlashStatusLast ? trayBmp : greyScale(trayBmp), _("Error")); + } + + void setTrayIcon(const wxBitmap& bmp, const wxString& statusTxt) + { + wxIcon realtimeIcon; + realtimeIcon.CopyFromBitmap(bmp); + wxString tooltip = L"RealtimeSync\n" + statusTxt; + if (!jobName_.empty()) + tooltip += L"\n\"" + jobName_ + L"\""; + SetIcon(realtimeIcon, tooltip); + } + enum Selection { - CONTEXT_RESTORE = 1, //wxWidgets: "A MenuItem ID of Zero does not work under Mac" + CONTEXT_RESTORE = 1, //wxWidgets: "A MenuItem ID of zero does not work under Mac" + CONTEXT_SHOW_ERROR, CONTEXT_ABORT = wxID_EXIT, CONTEXT_ABOUT = wxID_ABOUT }; virtual wxMenu* CreatePopupMenu() { - if (!abortCb_) - return nullptr; - wxMenu* contextMenu = new wxMenu; - contextMenu->Append(CONTEXT_RESTORE, _("&Restore")); + switch (mode) + { + case TRAY_MODE_ACTIVE: + case TRAY_MODE_WAITING: + contextMenu->Append(CONTEXT_RESTORE, _("&Restore")); + break; + case TRAY_MODE_ERROR: + contextMenu->Append(CONTEXT_SHOW_ERROR, _("&Show error")); + break; + } contextMenu->Append(CONTEXT_ABOUT, _("&About")); contextMenu->AppendSeparator(); contextMenu->Append(CONTEXT_ABORT, _("&Exit")); //event handling - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RtsTrayIconRaw::OnContextMenuSelection), nullptr, this); + contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TrayIconObject::OnContextMenuSelection), nullptr, this); return contextMenu; //ownership transferred to caller } void OnContextMenuSelection(wxCommandEvent& event) { - if (!abortCb_) - return; - switch (static_cast<Selection>(event.GetId())) { case CONTEXT_ABORT: - abortCb_->requestAbort(); + abortRequested = true; break; case CONTEXT_RESTORE: - abortCb_->requestResume(); + resumeRequested = true; + break; + + case CONTEXT_SHOW_ERROR: + showErrorMsgRequested = true; break; case CONTEXT_ABOUT: { - //build information + //ATTENTION: the modal dialog below does NOT disable all GUI input, e.g. user may still double-click on tray icon + //no crash in this context, but the double-click is remembered and executed after the modal dialog quits + SetEvtHandlerEnabled(false); + ZEN_ON_SCOPE_EXIT(SetEvtHandlerEnabled(true)); + wxString build = __TDATE__; #if wxUSE_UNICODE build += L" - Unicode"; @@ -103,7 +177,6 @@ private: build += L" - ANSI"; #endif //wxUSE_UNICODE - //compile time info about 32/64-bit build if (zen::is64BitBuild) build += L" x64"; else @@ -118,136 +191,86 @@ private: void OnDoubleClick(wxCommandEvent& event) { - if (abortCb_) - abortCb_->requestResume(); - } - - AbortCallback* abortCb_; -}; - - -class TrayIconHolder -{ -public: - TrayIconHolder(const wxString& jobname, AbortCallback& abortCb) : - jobName_(jobname), - trayMenu(new RtsTrayIconRaw(abortCb)) - { - showIconActive(); + switch (mode) + { + case TRAY_MODE_ACTIVE: + case TRAY_MODE_WAITING: + resumeRequested = true; //never throw exceptions through a C-Layer call stack (GUI)! + break; + case TRAY_MODE_ERROR: + showErrorMsgRequested = true; + break; + } } - ~TrayIconHolder() - { - trayMenu->RemoveIcon(); //(try to) hide icon until final deletion takes place - trayMenu->dontCallBackAnymore(); + bool resumeRequested; + bool abortRequested; + bool showErrorMsgRequested; - //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking) - if (!wxPendingDelete.Member(trayMenu)) - wxPendingDelete.Append(trayMenu); - } + TrayMode mode; - void doUiRefreshNow() - { - wxTheApp->Yield(); - } //yield is UI-layer which is represented by this tray icon + bool iconFlashStatusLast; //flash try icon for TRAY_MODE_ERROR + wxTimer timer; // - void showIconActive() - { - wxIcon realtimeIcon; -#if defined ZEN_WIN || defined ZEN_MAC //16x16 seems to be the only size that is shown correctly on OS X - realtimeIcon.CopyFromBitmap(getResourceImage(L"RTS_tray_16x16")); //use a 16x16 bitmap -#elif defined ZEN_LINUX - realtimeIcon.CopyFromBitmap(getResourceImage(L"RTS_tray_24x24")); //use a 24x24 bitmap for perfect fit -#endif - wxString tooltip = L"RealtimeSync"; - if (!jobName_.empty()) - tooltip += L"\n\"" + jobName_ + L"\""; - trayMenu->SetIcon(realtimeIcon, tooltip); - } - - void showIconWaiting() - { - wxIcon realtimeIcon; -#if defined ZEN_WIN || defined ZEN_MAC - realtimeIcon.CopyFromBitmap(greyScale(getResourceImage(L"RTS_tray_16x16"))); -#elif defined ZEN_LINUX - realtimeIcon.CopyFromBitmap(greyScale(getResourceImage(L"RTS_tray_24x24"))); -#endif - wxString tooltip = _("Waiting for missing directories..."); - if (!jobName_.empty()) - tooltip += L"\n\"" + jobName_ + L"\""; - trayMenu->SetIcon(realtimeIcon, tooltip); - } - -private: const wxString jobName_; //RTS job name, may be empty - RtsTrayIconRaw* trayMenu; + const wxBitmap trayBmp; }; -//############################################################################################################## - -struct AbortMonitoring//exception class +struct AbortMonitoring //exception class { AbortMonitoring(AbortReason reasonCode) : reasonCode_(reasonCode) {} AbortReason reasonCode_; }; -class StartSyncNowException {}; - -//############################################################################################################## -class WaitCallbackImpl : public rts::WaitCallback, private AbortCallback +//=> don't derive from wxEvtHandler or any other wxWidgets object unless instance is safely deleted (deferred) during idle event!!tray_icon.h +class TrayIconHolder { public: - WaitCallbackImpl(const wxString& jobname) : - trayIcon(jobname, *this), - nextSyncStart_(std::numeric_limits<long>::max()), - resumeRequested(false), - abortRequested(false) {} - - void notifyAllDirectoriesExist() { trayIcon.showIconActive(); } - void notifyDirectoryMissing () { trayIcon.showIconWaiting(); } + TrayIconHolder(const wxString& jobname) : + trayObj(new TrayIconObject(jobname)) {} - void scheduleNextSync(long nextSyncStart) { nextSyncStart_ = nextSyncStart; } - void clearSchedule() { nextSyncStart_ = std::numeric_limits<long>::max(); } + ~TrayIconHolder() + { + //harmonize with tray_icon.cpp!!! + trayObj->RemoveIcon(); + //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking) + wxPendingDelete.Append(trayObj); + } - //implement WaitCallback - virtual void requestUiRefresh(bool readyForSync) //throw StartSyncNowException, AbortMonitoring + void doUiRefreshNow() //throw AbortMonitoring { - if (resumeRequested) + wxTheApp->Yield(); //yield is UI-layer which is represented by this tray icon + + //advantage of polling vs callbacks: we can throw exceptions! + if (trayObj->resumeIsRequested()) throw AbortMonitoring(SHOW_GUI); - if (abortRequested) + if (trayObj->abortIsRequested()) throw AbortMonitoring(EXIT_APP); + } - if (readyForSync) - if (nextSyncStart_ <= wxGetLocalTime()) - throw StartSyncNowException(); //abort wait and start sync + void setMode(TrayMode m) { trayObj->setMode(m); } - if (updateUiIsAllowed()) - trayIcon.doUiRefreshNow(); - } + bool getShowErrorRequested() const { return trayObj->getShowErrorRequested(); } + void clearShowErrorRequested() { trayObj->clearShowErrorRequested(); } private: - //implement AbortCallback: used from C-GUI call stack - virtual void requestResume() { resumeRequested = true; } - virtual void requestAbort () { abortRequested = true; } - - TrayIconHolder trayIcon; - long nextSyncStart_; - bool resumeRequested; - bool abortRequested; + TrayIconObject* trayObj; }; +//############################################################################################################## - +//#define ERROR_DLG_ENABLE_TIMEOUT class ErrorDlgWithTimeout : public ErrorDlgGenerated { public: ErrorDlgWithTimeout(wxWindow* parent, const wxString& messageText) : - ErrorDlgGenerated(parent), - secondsLeft(15) //give user some time to read msg!? + ErrorDlgGenerated(parent) +#ifdef ERROR_DLG_ENABLE_TIMEOUT + , secondsLeft(15) //give user some time to read msg!? +#endif { #ifdef ZEN_WIN new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" @@ -257,11 +280,12 @@ public: m_bitmap10->SetBitmap(getResourceImage(L"msg_error")); m_textCtrl8->SetValue(messageText); +#ifdef ERROR_DLG_ENABLE_TIMEOUT //count down X seconds then automatically press "retry" timer.Connect(wxEVT_TIMER, wxEventHandler(ErrorDlgWithTimeout::OnTimerEvent), nullptr, this); timer.Start(1000); //timer interval in ms updateButtonLabel(); - +#endif Fit(); //child-element widths have changed: image was set m_buttonRetry->SetFocus(); } @@ -273,6 +297,7 @@ public: }; private: +#ifdef ERROR_DLG_ENABLE_TIMEOUT void OnTimerEvent(wxEvent& event) { if (secondsLeft <= 0) @@ -289,20 +314,23 @@ private: m_buttonRetry->SetLabel(_("&Retry") + L" (" + replaceCpy(_P("1 sec", "%x sec", secondsLeft), L"%x", numberTo<std::wstring>(secondsLeft)) + L")"); Layout(); } +#endif void OnClose(wxCloseEvent& event) { EndModal(BUTTON_ABORT); } void OnRetry(wxCommandEvent& event) { EndModal(BUTTON_RETRY); } void OnAbort(wxCommandEvent& event) { EndModal(BUTTON_ABORT); } +#ifdef ERROR_DLG_ENABLE_TIMEOUT int secondsLeft; wxTimer timer; +#endif }; -bool reportErrorTimeout(const std::wstring& msg) //return true if timeout or user selected "retry", else abort +bool reportErrorTimeout(const std::wstring& msg) //return true: "retry"; false: "abort" { ErrorDlgWithTimeout errorDlg(nullptr, msg); - //errorDlg.Raise(); -> don't steal focus every X seconds + errorDlg.Raise(); switch (static_cast<ErrorDlgWithTimeout::ButtonPressed>(errorDlg.ShowModal())) { case ErrorDlgWithTimeout::BUTTON_RETRY: @@ -312,120 +340,90 @@ bool reportErrorTimeout(const std::wstring& msg) //return true if timeout or use } return false; } - - -inline -wxString toString(DirWatcher::ActionType type) -{ - switch (type) - { - case DirWatcher::ACTION_CREATE: - return L"CREATE"; - case DirWatcher::ACTION_UPDATE: - return L"UPDATE"; - case DirWatcher::ACTION_DELETE: - return L"DELETE"; - } - return L"ERROR"; -} } -/* -Data Flow: ----------- - -TrayIconHolder (GUI output) - /|\ - | -WaitCallbackImpl - /|\ - | -startDirectoryMonitor() (wire dir-changes and execution of commandline) -*/ - rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& config, const wxString& jobname) { - std::vector<Zstring> dirList = toZ(config.directories); - vector_remove_if(dirList, [](Zstring str) -> bool { trim(str); return str.empty(); }); //remove empty entries WITHOUT formatting dirList yet! + std::vector<Zstring> dirNamesNonFmt = config.directories; + vector_remove_if(dirNamesNonFmt, [](Zstring str) -> bool { trim(str); return str.empty(); }); //remove empty entries WITHOUT formatting paths yet! - if (dirList.empty()) + if (dirNamesNonFmt.empty()) { - wxMessageBox(_("A folder input field is empty."), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(_("A folder input field is empty."), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR); return SHOW_GUI; } - wxString cmdLine = config.commandline; + Zstring cmdLine = config.commandline; trim(cmdLine); if (cmdLine.empty()) { - wxMessageBox(_("Invalid command line:") + L" \"\"", _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(_("Invalid command line:") + L" \"\"", L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR); return SHOW_GUI; } - try + struct MonitorCallbackImpl : public MonitorCallback { - DirWatcher::Entry lastChangeDetected; - WaitCallbackImpl callback(jobname); + MonitorCallbackImpl(const wxString& jobname, + const Zstring& cmdLine) : trayIcon(jobname), cmdLine_(cmdLine) {} + + virtual void setPhase(WatchPhase mode) + { + switch (mode) + { + case MONITOR_PHASE_ACTIVE: + trayIcon.setMode(TRAY_MODE_ACTIVE); + break; + case MONITOR_PHASE_WAITING: + trayIcon.setMode(TRAY_MODE_WAITING); + break; + } + } - auto execMonitoring = [&] //throw FileError, AbortMonitoring + virtual void executeExternalCommand() { - callback.notifyDirectoryMissing(); - callback.clearSchedule(); - waitForMissingDirs(dirList, callback); //throw FileError, StartSyncNowException(not scheduled yet), AbortMonitoring - callback.notifyAllDirectoriesExist(); + auto cmdLineExp = expandMacros(cmdLine_); + zen::shellExecute(cmdLineExp, zen::EXEC_TYPE_SYNC); + } - //schedule initial execution (*after* all directories have arrived, which could take some time which we don't want to include) - callback.scheduleNextSync(wxGetLocalTime() + static_cast<long>(config.delay)); + virtual void requestUiRefresh() + { + if (updateUiIsAllowed()) + trayIcon.doUiRefreshNow(); //throw AbortMonitoring + } + + virtual void reportError(const std::wstring& msg) + { + trayIcon.setMode(TRAY_MODE_ERROR); + trayIcon.clearShowErrorRequested(); - while (true) + //wait for some time, then return to retry + assert_static(15 * 1000 % UI_UPDATE_INTERVAL == 0); + for (int i = 0; i < 15 * 1000 / UI_UPDATE_INTERVAL; ++i) { - try + trayIcon.doUiRefreshNow(); //throw AbortMonitoring + + if (trayIcon.getShowErrorRequested()) { - while (true) - { - //wait for changes (and for all directories to become available) - WaitResult res = waitForChanges(dirList, callback); //throw FileError, StartSyncNowException, AbortMonitoring - switch (res.type) - { - case CHANGE_DIR_MISSING: //don't execute the commandline before all directories are available! - callback.notifyDirectoryMissing(); - callback.clearSchedule(); - waitForMissingDirs(dirList, callback); //throw FileError, StartSyncNowException(not scheduled yet), AbortMonitoring - callback.notifyAllDirectoriesExist(); - break; - - case CHANGE_DETECTED: - lastChangeDetected = res.changedItem_; - break; - } - callback.scheduleNextSync(wxGetLocalTime() + static_cast<long>(config.delay)); - } + if (reportErrorTimeout(msg)) //return true: "retry"; false: "abort" + return; + else + throw AbortMonitoring(SHOW_GUI); } - catch (StartSyncNowException&) {} - - ::wxSetEnv(L"change_path", utfCvrtTo<wxString>(lastChangeDetected.filename_)); //some way to output what file changed to the user - ::wxSetEnv(L"change_action", toString(lastChangeDetected.action_)); // - lastChangeDetected = DirWatcher::Entry(); //make sure old name is not shown again after a directory reappears - - //execute command - auto cmdLineExp = expandMacros(utfCvrtTo<Zstring>(cmdLine)); - zen::shellExecute(cmdLineExp, zen::EXEC_TYPE_SYNC); - callback.clearSchedule(); + boost::this_thread::sleep(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL)); } - }; + } - while (true) - try - { - execMonitoring(); //throw FileError, AbortMonitoring - } - catch (const zen::FileError& e) - { - if (!reportErrorTimeout(e.toString())) //return true if timeout or user selected "retry", else abort - return SHOW_GUI; - } + TrayIconHolder trayIcon; + const Zstring cmdLine_; + } cb(jobname, cmdLine); + + try + { + monitorDirectories(dirNamesNonFmt, config.delay, cb); //cb: throw AbortMonitoring + assert(false); + return SHOW_GUI; } catch (const AbortMonitoring& ab) { diff --git a/RealtimeSync/tray_menu.h b/RealtimeSync/tray_menu.h index 006dae82..1f71a017 100644 --- a/RealtimeSync/tray_menu.h +++ b/RealtimeSync/tray_menu.h @@ -4,13 +4,12 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef TRAYMENU_H_INCLUDED -#define TRAYMENU_H_INCLUDED +#ifndef TRAY_583967857420987534253245 +#define TRAY_583967857420987534253245 -#include "watcher.h" +#include <wx/string.h> #include "xml_proc.h" - namespace rts { enum AbortReason @@ -21,5 +20,4 @@ enum AbortReason AbortReason startDirectoryMonitor(const xmlAccess::XmlRealConfig& config, const wxString& jobname); //jobname may be empty } - -#endif // TRAYMENU_H_INCLUDED +#endif //TRAY_583967857420987534253245 diff --git a/RealtimeSync/watcher.h b/RealtimeSync/watcher.h deleted file mode 100644 index 2fd32119..00000000 --- a/RealtimeSync/watcher.h +++ /dev/null @@ -1,54 +0,0 @@ -// ************************************************************************** -// * 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 WATCHER_H_INCLUDED -#define WATCHER_H_INCLUDED - -#include <zen/dir_watcher.h> -#include <zen/file_error.h> - - -namespace rts -{ -const int UI_UPDATE_INTERVAL = 100; //unit: [ms]; perform ui updates not more often than necessary, 100 seems to be a good value with only a minimal performance loss -bool updateUiIsAllowed(); - - -class WaitCallback -{ -public: - virtual ~WaitCallback() {} - virtual void requestUiRefresh(bool readyForSync = false) = 0; //opportunity to abort must be implemented in a frequently executed method like requestUiRefresh() -}; - - -//wait until changes are detected or if a directory is not available (anymore) -enum ChangeType -{ - CHANGE_DETECTED, - CHANGE_DIR_MISSING -}; - -struct WaitResult -{ - WaitResult(const zen::DirWatcher::Entry& changedItem) : type(CHANGE_DETECTED), changedItem_(changedItem) {} - WaitResult(const Zstring& dirname) : type(CHANGE_DIR_MISSING), dirname_(dirname) {} - - ChangeType type; - zen::DirWatcher::Entry changedItem_; //for type == CHANGE_DETECTED: file or directory - Zstring dirname_; //for type == CHANGE_DIR_MISSING -}; - -WaitResult waitForChanges(const std::vector<Zstring>& dirNamesNonFmt, - //non-formatted dirnames that yet require call to getFormattedDirectoryName(); empty directories must be checked by caller! - WaitCallback& statusHandler); //throw FileError - -//wait until all directories become available (again) + logs in network share -void waitForMissingDirs(const std::vector<Zstring>& dirNamesNonFmt, - WaitCallback& statusHandler); //throw FileError -} - -#endif // WATCHER_H_INCLUDED diff --git a/RealtimeSync/xml_ffs.cpp b/RealtimeSync/xml_ffs.cpp index 1f3fb1b1..149b0bbe 100644 --- a/RealtimeSync/xml_ffs.cpp +++ b/RealtimeSync/xml_ffs.cpp @@ -7,7 +7,7 @@ #include "xml_ffs.h" #include "../lib/ffs_paths.h" #include <zen/zstring.h> -#include <wx+/string_conv.h> +//#include <wx+/string_conv.h> //include FreeFileSync xml headers #include "../lib/process_xml.h" @@ -17,8 +17,6 @@ using namespace zen; xmlAccess::XmlRealConfig convertBatchToReal(const xmlAccess::XmlBatchConfig& batchCfg, const Zstring& filename) { - xmlAccess::XmlRealConfig output; - std::set<Zstring, LessFilename> uniqueFolders; //add main folders @@ -35,13 +33,9 @@ xmlAccess::XmlRealConfig convertBatchToReal(const xmlAccess::XmlBatchConfig& bat uniqueFolders.erase(Zstring()); - output.directories.clear(); - std::transform(uniqueFolders.begin(), uniqueFolders.end(), std::back_inserter(output.directories), - [](const Zstring & fn) { return toWx(fn); }); - - output.commandline = std::wstring(L"\"") + zen::getFreeFileSyncLauncher() + L"\"" + - L" \"" + filename + L"\""; - + xmlAccess::XmlRealConfig output; + output.directories.assign(uniqueFolders.begin(), uniqueFolders.end()); + output.commandline = Zstr("\"") + zen::getFreeFileSyncLauncher() + Zstr("\" \"") + filename + Zstr("\""); return output; } diff --git a/RealtimeSync/xml_proc.h b/RealtimeSync/xml_proc.h index ab57e816..671a237f 100644 --- a/RealtimeSync/xml_proc.h +++ b/RealtimeSync/xml_proc.h @@ -8,7 +8,7 @@ #define XMLPROCESSING_H_INCLUDED #include <vector> -#include <wx/string.h> +//#include <wx/string.h> #include <zen/zstring.h> #include "../lib/xml_base.h" @@ -18,9 +18,9 @@ namespace xmlAccess struct XmlRealConfig { XmlRealConfig() : delay(10) {} - std::vector<wxString> directories; - wxString commandline; - size_t delay; + std::vector<Zstring> directories; + Zstring commandline; + unsigned int delay; }; void readRealConfig(const Zstring& filename, XmlRealConfig& config); //throw FfsXmlError diff --git a/algorithm.cpp b/algorithm.cpp index ab2494e5..2415cd18 100644 --- a/algorithm.cpp +++ b/algorithm.cpp @@ -57,9 +57,9 @@ private: //##################### schedule old temporary files for deletion #################### if (cat == FILE_LEFT_SIDE_ONLY && endsWith(fileObj.getShortName<LEFT_SIDE>(), TEMP_FILE_ENDING)) - return fileObj.setSyncDir(SYNC_DIR_LEFT); + return fileObj.setSyncDir(SyncDirection::LEFT); else if (cat == FILE_RIGHT_SIDE_ONLY && endsWith(fileObj.getShortName<RIGHT_SIDE>(), TEMP_FILE_ENDING)) - return fileObj.setSyncDir(SYNC_DIR_RIGHT); + return fileObj.setSyncDir(SyncDirection::RIGHT); //#################################################################################### switch (cat) @@ -81,13 +81,13 @@ private: break; case FILE_CONFLICT: case FILE_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize" - if (dirCfg.conflict == SYNC_DIR_NONE) + if (dirCfg.conflict == SyncDirection::NONE) fileObj.setSyncDirConflict(fileObj.getCatExtraDescription()); //take over category conflict else fileObj.setSyncDir(dirCfg.conflict); break; case FILE_EQUAL: - fileObj.setSyncDir(SYNC_DIR_NONE); + fileObj.setSyncDir(SyncDirection::NONE); break; } } @@ -110,7 +110,7 @@ private: break; case SYMLINK_CONFLICT: case SYMLINK_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize" - if (dirCfg.conflict == SYNC_DIR_NONE) + if (dirCfg.conflict == SyncDirection::NONE) linkObj.setSyncDirConflict(linkObj.getCatExtraDescription()); //take over category conflict else linkObj.setSyncDir(dirCfg.conflict); @@ -119,7 +119,7 @@ private: linkObj.setSyncDir(dirCfg.different); break; case SYMLINK_EQUAL: - linkObj.setSyncDir(SYNC_DIR_NONE); + linkObj.setSyncDir(SyncDirection::NONE); break; } } @@ -130,9 +130,9 @@ private: //########### schedule abandoned temporary recycle bin directory for deletion ########## if (cat == DIR_LEFT_SIDE_ONLY && endsWith(dirObj.getShortName<LEFT_SIDE>(), TEMP_FILE_ENDING)) - return setSyncDirectionRec(SYNC_DIR_LEFT, dirObj); // + return setSyncDirectionRec(SyncDirection::LEFT, dirObj); // else if (cat == DIR_RIGHT_SIDE_ONLY && endsWith(dirObj.getShortName<RIGHT_SIDE>(), TEMP_FILE_ENDING)) - return setSyncDirectionRec(SYNC_DIR_RIGHT, dirObj); //don't recurse below! + return setSyncDirectionRec(SyncDirection::RIGHT, dirObj); //don't recurse below! //####################################################################################### switch (cat) @@ -144,10 +144,10 @@ private: dirObj.setSyncDir(dirCfg.exRightSideOnly); break; case DIR_EQUAL: - dirObj.setSyncDir(SYNC_DIR_NONE); + dirObj.setSyncDir(SyncDirection::NONE); break; case DIR_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize" - if (dirCfg.conflict == SYNC_DIR_NONE) + if (dirCfg.conflict == SyncDirection::NONE) dirObj.setSyncDirConflict(dirObj.getCatExtraDescription()); //take over category conflict else dirObj.setSyncDir(dirCfg.conflict); @@ -408,9 +408,9 @@ private: //##################### schedule old temporary files for deletion #################### if (cat == FILE_LEFT_SIDE_ONLY && endsWith(fileObj.getShortName<LEFT_SIDE>(), TEMP_FILE_ENDING)) - return fileObj.setSyncDir(SYNC_DIR_LEFT); + return fileObj.setSyncDir(SyncDirection::LEFT); else if (cat == FILE_RIGHT_SIDE_ONLY && endsWith(fileObj.getShortName<RIGHT_SIDE>(), TEMP_FILE_ENDING)) - return fileObj.setSyncDir(SYNC_DIR_RIGHT); + return fileObj.setSyncDir(SyncDirection::RIGHT); //#################################################################################### //try to find corresponding database entry @@ -432,7 +432,7 @@ private: if (dbEntry && !stillInSync(dbEntry->second, cmpVar, fileTimeTolerance)) fileObj.setSyncDirConflict(txtDbNotInSync); else - fileObj.setSyncDir(changeOnLeft ? SYNC_DIR_RIGHT : SYNC_DIR_LEFT); + fileObj.setSyncDir(changeOnLeft ? SyncDirection::RIGHT : SyncDirection::LEFT); } else { @@ -468,7 +468,7 @@ private: if (dbEntry && !stillInSync(dbEntry->second, cmpVar, fileTimeTolerance)) linkObj.setSyncDirConflict(txtDbNotInSync); else - linkObj.setSyncDir(changeOnLeft ? SYNC_DIR_RIGHT : SYNC_DIR_LEFT); + linkObj.setSyncDir(changeOnLeft ? SyncDirection::RIGHT : SyncDirection::LEFT); } else { @@ -485,9 +485,9 @@ private: //########### schedule abandoned temporary recycle bin directory for deletion ########## if (cat == DIR_LEFT_SIDE_ONLY && endsWith(dirObj.getShortName<LEFT_SIDE>(), TEMP_FILE_ENDING)) - return setSyncDirectionRec(SYNC_DIR_LEFT, dirObj); // + return setSyncDirectionRec(SyncDirection::LEFT, dirObj); // else if (cat == DIR_RIGHT_SIDE_ONLY && endsWith(dirObj.getShortName<RIGHT_SIDE>(), TEMP_FILE_ENDING)) - return setSyncDirectionRec(SYNC_DIR_RIGHT, dirObj); //don't recurse below! + return setSyncDirectionRec(SyncDirection::RIGHT, dirObj); //don't recurse below! //####################################################################################### //try to find corresponding database entry @@ -511,7 +511,7 @@ private: if (dbEntry && !stillInSync(dbEntry->second)) dirObj.setSyncDirConflict(txtDbNotInSync); else - dirObj.setSyncDir(changeOnLeft ? SYNC_DIR_RIGHT : SYNC_DIR_LEFT); + dirObj.setSyncDir(changeOnLeft ? SyncDirection::RIGHT : SyncDirection::LEFT); } else { @@ -599,11 +599,11 @@ private: Algorithm: ---------- - DB-file left <--- (name, size, date) ---> DB-file right - | | - | (file ID, size, date) | (file ID, size, date) - \|/ \|/ - file left only file right only + DB-file left <--- (name, size, date) ---> DB-file right + | | + | (file ID, size, date) | (file ID, size, date) + \|/ \|/ + file left only file right only FAT caveat: File Ids are generally not stable when file is either moved or renamed! => 1. Move/rename operations on FAT cannot be detected reliably. @@ -1384,10 +1384,10 @@ void zen::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete auto cfgIter = baseDirCfgs.find(&fsObj.root()); if (cfgIter != baseDirCfgs.end()) { - SyncDirection newDir = SYNC_DIR_NONE; + SyncDirection newDir = SyncDirection::NONE; if (cfgIter->second.var == DirectionConfig::AUTOMATIC) - newDir = fsObj.isEmpty<LEFT_SIDE>() ? SYNC_DIR_RIGHT : SYNC_DIR_LEFT; + newDir = fsObj.isEmpty<LEFT_SIDE>() ? SyncDirection::RIGHT : SyncDirection::LEFT; else { const DirectionSet& dirCfg = extractDirections(cfgIter->second); diff --git a/comparison.cpp b/comparison.cpp index dff58786..c674269e 100644 --- a/comparison.cpp +++ b/comparison.cpp @@ -83,6 +83,26 @@ std::set<Zstring, LessFilename> determineExistentDirs(const std::set<Zstring, Le tryReportingError2([&] { + warn_static("remove after test") +#if 0 + dirsEx.clear(); + std::for_each(dirnames.begin(), dirnames.end(), + [&](const Zstring& dirname) + { + if (!dirname.empty()) + { + loginNetworkShare(dirname, allowUserInteraction); + + const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirname).c_str()); + if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) //returns true for (dir-)symlinks also + dirsEx.insert(dirname); + else + throw FileError(_("Cannot find the following folders:") + L"\n" + std::wstring(L"\n") + dirname, + attr == INVALID_FILE_ATTRIBUTES ? formatSystemError(L"GetFileAttributes", getLastError()) : L"not a directory!"); + } + }); + +#else dirsEx = getExistingDirsUpdating(dirnames, allowUserInteraction, callback); //check *all* directories on each try! //get list of not existing directories @@ -95,6 +115,7 @@ std::set<Zstring, LessFilename> determineExistentDirs(const std::set<Zstring, Le std::for_each(dirsMissing.begin(), dirsMissing.end(), [&](const Zstring& dirname) { msg += std::wstring(L"\n") + dirname; }); throw FileError(msg, _("You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization.")); } +#endif }, callback); return dirsEx; @@ -268,8 +289,8 @@ std::wstring getConflictInvalidDate(const Zstring& fileNameFull, Int64 utcTime) std::wstring getConflictSameDateDiffSize(const FilePair& fileObj) { return replaceCpy(_("Files %x have the same date but a different size."), L"%x", fmtFileName(fileObj.getObjRelativeName())) + L"\n" + - arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.getLastWriteTime<LEFT_SIDE >()) + L" " + _("Size:") + L" " + toGuiString(fileObj.getFileSize<LEFT_SIDE>()) + L"\n" + - arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(fileObj.getFileSize<RIGHT_SIDE>()); + L" " + arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.getLastWriteTime<LEFT_SIDE >()) + L" " + _("Size:") + L" " + toGuiString(fileObj.getFileSize<LEFT_SIDE>()) + L"\n" + + L" " + arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(fileObj.getFileSize<RIGHT_SIDE>()); } @@ -277,8 +298,8 @@ inline std::wstring getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj) { return _("Items differ in attributes only") + L"\n" + - arrowLeft + L" " + fmtFileName(fsObj.getShortName<LEFT_SIDE >()) + L"\n" + - arrowRight + L" " + fmtFileName(fsObj.getShortName<RIGHT_SIDE>()); + L" " + arrowLeft + L" " + fmtFileName(fsObj.getShortName<LEFT_SIDE >()) + L"\n" + + L" " + arrowRight + L" " + fmtFileName(fsObj.getShortName<RIGHT_SIDE>()); } @@ -286,8 +307,8 @@ template <class FileOrLinkPair> inline std::wstring getDescrDiffMetaDate(const FileOrLinkPair& fileObj) { return _("Items differ in attributes only") + L"\n" + - arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.template getLastWriteTime<LEFT_SIDE >()) + L"\n" + - arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.template getLastWriteTime<RIGHT_SIDE>()); + L" " + arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.template getLastWriteTime<LEFT_SIDE >()) + L"\n" + + L" " + arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.template getLastWriteTime<RIGHT_SIDE>()); } //----------------------------------------------------------------------------- diff --git a/file_hierarchy.cpp b/file_hierarchy.cpp index 13c73731..c20b971e 100644 --- a/file_hierarchy.cpp +++ b/file_hierarchy.cpp @@ -30,9 +30,8 @@ void HierarchyObject::removeEmptyRec() if (emptyExisting) //notify if actual deletion happened notifySyncCfgChanged(); //mustn't call this in ~FileSystemObject(), since parent, usually a DirPair, is already partially destroyed and existing as a pure HierarchyObject! - // for (auto& subDir : refSubDirs()) - // subDir.removeEmptyRec(); //recurse - std::for_each(refSubDirs().begin(), refSubDirs().end(), [&](HierarchyObject& hierObj) { hierObj.removeEmptyRec(); }); + for (auto& subDir : refSubDirs()) + subDir.removeEmptyRec(); //recurse } namespace @@ -42,7 +41,7 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult, SyncDirection syncDir, bool hasDirConflict) //perf: std::wstring was wasteful here { - assert(!hasDirConflict || syncDir == SYNC_DIR_NONE); + assert(!hasDirConflict || syncDir == SyncDirection::NONE); if (!selectedForSynchronization) return cmpResult == FILE_EQUAL ? @@ -54,11 +53,11 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult, case FILE_LEFT_SIDE_ONLY: switch (syncDir) { - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: return SO_DELETE_LEFT; //delete files on left - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: return SO_CREATE_NEW_RIGHT; //copy files to right - case SYNC_DIR_NONE: + case SyncDirection::NONE: return hasDirConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING; } break; @@ -66,11 +65,11 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult, case FILE_RIGHT_SIDE_ONLY: switch (syncDir) { - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: return SO_CREATE_NEW_LEFT; //copy files to left - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: return SO_DELETE_RIGHT; //delete files on right - case SYNC_DIR_NONE: + case SyncDirection::NONE: return hasDirConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING; } break; @@ -81,11 +80,11 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult, case FILE_CONFLICT: switch (syncDir) { - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: return SO_OVERWRITE_LEFT; //copy from right to left - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: return SO_OVERWRITE_RIGHT; //copy from left to right - case SYNC_DIR_NONE: + case SyncDirection::NONE: return hasDirConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING; } break; @@ -93,17 +92,17 @@ SyncOperation getIsolatedSyncOperation(CompareFilesResult cmpResult, case FILE_DIFFERENT_METADATA: switch (syncDir) { - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: return SO_COPY_METADATA_TO_LEFT; - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: return SO_COPY_METADATA_TO_RIGHT; - case SYNC_DIR_NONE: + case SyncDirection::NONE: return hasDirConflict ? SO_UNRESOLVED_CONFLICT : SO_DO_NOTHING; } break; case FILE_EQUAL: - assert(syncDir == SYNC_DIR_NONE); + assert(syncDir == SyncDirection::NONE); return SO_EQUAL; } diff --git a/file_hierarchy.h b/file_hierarchy.h index 7710e7ac..1675d223 100644 --- a/file_hierarchy.h +++ b/file_hierarchy.h @@ -379,7 +379,7 @@ public: //sync settings SyncDirection getSyncDir() const; void setSyncDir(SyncDirection newDir); - void setSyncDirConflict(const std::wstring& description); //set syncDir = SYNC_DIR_NONE + fill conflict description + void setSyncDirConflict(const std::wstring& description); //set syncDir = SyncDirection::NONE + fill conflict description bool isActive() const; void setActive(bool active); @@ -410,7 +410,7 @@ protected: CompareFilesResult defaultCmpResult) : cmpResult(defaultCmpResult), selectedForSynchronization(true), - syncDir_(static_cast<unsigned char>(SYNC_DIR_NONE)), + syncDir_(SyncDirection::NONE), shortNameLeft_(shortNameLeft), shortNameRight_(shortNameRight), //shortNameRight_(shortNameRight == shortNameLeft ? shortNameLeft : shortNameRight), -> strangely doesn't seem to shrink peak memory consumption at all! @@ -437,7 +437,7 @@ private: bool selectedForSynchronization; - unsigned char syncDir_; //use "char" instead of "SyncDirection" lacking C++11 custom size enum classes to optimize memory layout! + SyncDirection syncDir_; //1 byte: optimize memory layout! std::unique_ptr<std::wstring> syncDirConflict; //non-empty if we have a conflict setting sync-direction //get rid of std::wstring small string optimization (consumes 32/48 byte on VS2010 x86/x64!) @@ -641,14 +641,14 @@ std::wstring FileSystemObject::getCatExtraDescription() const inline SyncDirection FileSystemObject::getSyncDir() const { - return static_cast<SyncDirection>(syncDir_); + return syncDir_; } inline void FileSystemObject::setSyncDir(SyncDirection newDir) { - syncDir_ = static_cast<unsigned char>(newDir); + syncDir_ = newDir; syncDirConflict.reset(); notifySyncCfgChanged(); @@ -658,7 +658,7 @@ void FileSystemObject::setSyncDir(SyncDirection newDir) inline void FileSystemObject::setSyncDirConflict(const std::wstring& description) { - syncDir_ = static_cast<unsigned char>(SYNC_DIR_NONE); + syncDir_ = SyncDirection::NONE; syncDirConflict.reset(new std::wstring(description)); notifySyncCfgChanged(); @@ -751,7 +751,7 @@ void FileSystemObject::removeObject<LEFT_SIDE>() shortNameLeft_.clear(); removeObjectL(); - setSyncDir(SYNC_DIR_NONE); //calls notifySyncCfgChanged() + setSyncDir(SyncDirection::NONE); //calls notifySyncCfgChanged() } @@ -762,7 +762,7 @@ void FileSystemObject::removeObject<RIGHT_SIDE>() shortNameRight_.clear(); removeObjectR(); - setSyncDir(SYNC_DIR_NONE); //calls notifySyncCfgChanged() + setSyncDir(SyncDirection::NONE); //calls notifySyncCfgChanged() } @@ -772,7 +772,7 @@ void FileSystemObject::setSynced(const Zstring& shortName) assert(!isEmpty()); shortNameRight_ = shortNameLeft_ = shortName; cmpResult = FILE_EQUAL; - setSyncDir(SYNC_DIR_NONE); + setSyncDir(SyncDirection::NONE); } diff --git a/lib/ShadowCopy/Shadow_Server2003.vcxproj b/lib/ShadowCopy/Shadow_Server2003.vcxproj index 520a4d6d..a9b8f740 100644 --- a/lib/ShadowCopy/Shadow_Server2003.vcxproj +++ b/lib/ShadowCopy/Shadow_Server2003.vcxproj @@ -87,7 +87,7 @@ </BuildLog> <ClCompile> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions> <MinimalRebuild>true</MinimalRebuild> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> @@ -123,7 +123,7 @@ </Midl> <ClCompile> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions> <MinimalRebuild>true</MinimalRebuild> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> @@ -157,7 +157,7 @@ <ClCompile> <Optimization>MaxSpeed</Optimization> <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <FunctionLevelLinking>true</FunctionLevelLinking> <PrecompiledHeader> @@ -195,7 +195,7 @@ <ClCompile> <Optimization>MaxSpeed</Optimization> <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_2003;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <FunctionLevelLinking>true</FunctionLevelLinking> <PrecompiledHeader> diff --git a/lib/ShadowCopy/Shadow_Windows7.vcxproj b/lib/ShadowCopy/Shadow_Windows7.vcxproj index 1fe769d0..7c6f1f1d 100644 --- a/lib/ShadowCopy/Shadow_Windows7.vcxproj +++ b/lib/ShadowCopy/Shadow_Windows7.vcxproj @@ -87,7 +87,7 @@ </BuildLog> <ClCompile> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_WINDOWS7;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_WINDOWS7;%(PreprocessorDefinitions)</PreprocessorDefinitions> <MinimalRebuild>true</MinimalRebuild> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> @@ -133,7 +133,7 @@ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_WINDOWS7;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_WINDOWS7;%(PreprocessorDefinitions)</PreprocessorDefinitions> <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> <Link> @@ -157,7 +157,7 @@ <ClCompile> <Optimization>MaxSpeed</Optimization> <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_WINDOWS7;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_WINDOWS7;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <FunctionLevelLinking>true</FunctionLevelLinking> <PrecompiledHeader> @@ -205,7 +205,7 @@ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> - <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_WINDOWS7;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_WINDOWS7;%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> diff --git a/lib/ShadowCopy/Shadow_XP.vcxproj b/lib/ShadowCopy/Shadow_XP.vcxproj index 0d231f3d..0d54f2e4 100644 --- a/lib/ShadowCopy/Shadow_XP.vcxproj +++ b/lib/ShadowCopy/Shadow_XP.vcxproj @@ -86,7 +86,7 @@ </BuildLog> <ClCompile> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions> <MinimalRebuild>true</MinimalRebuild> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> @@ -123,7 +123,7 @@ </Midl> <ClCompile> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;_DEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions> <MinimalRebuild>true</MinimalRebuild> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> @@ -158,7 +158,7 @@ <ClCompile> <Optimization>MaxSpeed</Optimization> <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <FunctionLevelLinking>true</FunctionLevelLinking> <PrecompiledHeader> @@ -196,7 +196,7 @@ <ClCompile> <Optimization>MaxSpeed</Optimization> <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;_WINDOWS;_USRDLL;SHADOWDLL_EXPORTS;USE_SHADOW_XP;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <FunctionLevelLinking>true</FunctionLevelLinking> <PrecompiledHeader> diff --git a/lib/Thumbnail/Thumbnail.vcxproj b/lib/Thumbnail/Thumbnail.vcxproj index 87fb152b..1ec016be 100644 --- a/lib/Thumbnail/Thumbnail.vcxproj +++ b/lib/Thumbnail/Thumbnail.vcxproj @@ -94,7 +94,7 @@ <WarningLevel>Level4</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>EditAndContinue</DebugInformationFormat> - <DisableSpecificWarnings>4100</DisableSpecificWarnings> + <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> @@ -129,7 +129,7 @@ <WarningLevel>Level4</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> - <DisableSpecificWarnings>4100</DisableSpecificWarnings> + <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> @@ -161,7 +161,7 @@ <WarningLevel>Level4</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> - <DisableSpecificWarnings>4100</DisableSpecificWarnings> + <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> </ClCompile> @@ -198,7 +198,7 @@ <WarningLevel>Level4</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> - <DisableSpecificWarnings>4100</DisableSpecificWarnings> + <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> </ClCompile> diff --git a/lib/db_file.cpp b/lib/db_file.cpp index 2f699e3a..ab7f9212 100644 --- a/lib/db_file.cpp +++ b/lib/db_file.cpp @@ -91,7 +91,7 @@ StreamMapping loadStreams(const Zstring& filename) //throw FileError, FileErrorD { try { - BinStreamIn streamIn = loadBinStream<BinaryStream>(filename); //throw FileError, ErrorNotExisting + BinStreamIn streamIn = loadBinStream<BinaryStream>(filename); //throw FileError //read FreeFileSync file identifier char formatDescr[sizeof(FILE_FORMAT_DESCR)] = {}; @@ -118,10 +118,12 @@ StreamMapping loadStreams(const Zstring& filename) //throw FileError, FileErrorD } return output; } - catch (ErrorNotExisting&) + catch (FileError&) { - throw FileErrorDatabaseNotExisting(_("Initial synchronization:") + L" \n" + - replaceCpy(_("Database file %x does not yet exist."), L"%x", fmtFileName(filename))); + if (!somethingExists(filename)) //a benign(?) race condition with FileError + throw FileErrorDatabaseNotExisting(_("Initial synchronization:") + L" \n" + + replaceCpy(_("Database file %x does not yet exist."), L"%x", fmtFileName(filename))); + throw; } catch (UnexpectedEndOfStreamError&) { diff --git a/lib/dir_exist_async.h b/lib/dir_exist_async.h index dd77a36a..39ae6aff 100644 --- a/lib/dir_exist_async.h +++ b/lib/dir_exist_async.h @@ -47,7 +47,7 @@ std::set<Zstring, LessFilename> getExistingDirsUpdating(const std::set<Zstring, const boost::system_time endTime = boost::get_system_time() + boost::posix_time::seconds(10); //10 sec should be enough even if Win32 waits much longer auto itDirname = dirnames.begin(); - for (auto it = dirEx.begin(); it != dirEx.end(); ++it, ++itDirname) + for (auto it = dirEx.begin(); it != dirEx.end(); (void)++it, ++itDirname) //void: prevent ADL from dragging in boost's ,-overload: "MSVC warning C4913: user defined binary operator ',' exists but no overload could convert all operands" { procCallback.reportStatus(replaceCpy(_("Searching for folder %x..."), L"%x", fmtFileName(*itDirname), false)); //may throw! diff --git a/lib/dir_lock.cpp b/lib/dir_lock.cpp index 60d83a67..d7e3ba56 100644 --- a/lib/dir_lock.cpp +++ b/lib/dir_lock.cpp @@ -68,7 +68,7 @@ public: } catch (const std::exception& e) //exceptions must be catched per thread { - wxSafeShowMessage(_("An exception occurred"), utfCvrtTo<wxString>(e.what()) + L" (Dirlock)"); //simple wxMessageBox won't do for threads + wxSafeShowMessage(L"FreeFileSync - " + _("An exception occurred"), utfCvrtTo<wxString>(e.what()) + L" (Dirlock)"); //simple wxMessageBox won't do for threads } } @@ -123,7 +123,7 @@ private: namespace { -UInt64 getLockFileSize(const Zstring& filename) //throw FileError, ErrorNotExisting +UInt64 getLockFileSize(const Zstring& filename) //throw FileError { #ifdef ZEN_WIN WIN32_FIND_DATA fileInfo = {}; @@ -144,11 +144,7 @@ UInt64 getLockFileSize(const Zstring& filename) //throw FileError, ErrorNotExist const ErrorCode lastError = getLastError(); const std::wstring errorMsg = replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)); const std::wstring errorDescr = formatSystemError(functionName, lastError); - - if (errorCodeForNotExisting(lastError)) - throw ErrorNotExisting(errorMsg, errorDescr); - else - throw FileError(errorMsg, errorDescr); + throw FileError(errorMsg, errorDescr); } @@ -359,9 +355,9 @@ struct LockInformation //throw FileError //wxGetFullHostName() is a performance killer for some users, so don't touch! -LockInformation retrieveLockInfo(const Zstring& lockfilename) //throw FileError, ErrorNotExisting +LockInformation retrieveLockInfo(const Zstring& lockfilename) //throw FileError { - BinStreamIn streamIn = loadBinStream<BinaryStream>(lockfilename); //throw FileError, ErrorNotExisting + BinStreamIn streamIn = loadBinStream<BinaryStream>(lockfilename); //throw FileError try { return LockInformation(streamIn); //throw UnexpectedEndOfStreamError @@ -374,9 +370,9 @@ LockInformation retrieveLockInfo(const Zstring& lockfilename) //throw FileError, inline -std::string retrieveLockId(const Zstring& lockfilename) //throw FileError, ErrorNotExisting +std::string retrieveLockId(const Zstring& lockfilename) //throw FileError { - return retrieveLockInfo(lockfilename).lockId; + return retrieveLockInfo(lockfilename).lockId; //throw FileError } @@ -408,6 +404,7 @@ ProcessStatus getProcessStatus(const LockInformation& lockInfo) //throw FileErro const std::int64_t TICKS_PER_SEC = ticksPerSec(); //= 0 on error + void waitOnDirLock(const Zstring& lockfilename, DirLockCallback* callback) //throw FileError { const std::wstring infoMsg = replaceCpy(_("Waiting while directory is locked (%x)..."), L"%x", fmtFileName(lockfilename)); @@ -422,7 +419,7 @@ void waitOnDirLock(const Zstring& lockfilename, DirLockCallback* callback) //thr std::string originalLockId; //empty if it cannot be retrieved try { - const LockInformation& lockInfo = retrieveLockInfo(lockfilename); //throw FileError, ErrorNotExisting + const LockInformation& lockInfo = retrieveLockInfo(lockfilename); //throw FileError originalLockId = lockInfo.lockId; switch (getProcessStatus(lockInfo)) //throw FileError { @@ -443,7 +440,7 @@ void waitOnDirLock(const Zstring& lockfilename, DirLockCallback* callback) //thr while (true) { const TickVal now = getTicks(); - const UInt64 fileSizeNew = ::getLockFileSize(lockfilename); //throw FileError, ErrorNotExisting + const UInt64 fileSizeNew = ::getLockFileSize(lockfilename); //throw FileError if (TICKS_PER_SEC <= 0 || !lastLifeSign.isValid() || !now.isValid()) throw FileError(L"System Timer failed!"); //no i18n: "should" never throw ;) @@ -462,10 +459,10 @@ void waitOnDirLock(const Zstring& lockfilename, DirLockCallback* callback) //thr //now that the lock is in place check existence again: meanwhile another process may have deleted and created a new lock! if (!originalLockId.empty()) - if (retrieveLockId(lockfilename) != originalLockId) //throw FileError, ErrorNotExisting -> since originalLockId is filled, we are not expecting errors! + if (retrieveLockId(lockfilename) != originalLockId) //throw FileError -> since originalLockId is filled, we are not expecting errors! return; //another process has placed a new lock, leave scope: the wait for the old lock is technically over... - if (::getLockFileSize(lockfilename) != fileSizeOld) //throw FileError, ErrorNotExisting + if (::getLockFileSize(lockfilename) != fileSizeOld) //throw FileError continue; //late life sign removeFile(lockfilename); //throw FileError @@ -494,9 +491,11 @@ void waitOnDirLock(const Zstring& lockfilename, DirLockCallback* callback) //thr } } } - catch (const ErrorNotExisting&) + catch (FileError&) { - return; //what we are waiting for... + if (!somethingExists(lockfilename)) //a benign(?) race condition with FileError + return; //what we are waiting for... + throw; } } @@ -582,7 +581,7 @@ public: ~SharedDirLock() { threadObj.interrupt(); //thread lifetime is subset of this instances's life - threadObj.join(); //we assume precondition "threadObj.joinable()"!!! + threadObj.join(); //we assert precondition "threadObj.joinable()"!!! ::releaseLock(lockfilename_); //throw () } @@ -618,7 +617,7 @@ public: try //check based on lock GUID, deadlock prevention: "lockfilename" may be an alternative name for a lock already owned by this process { - const std::string lockId = retrieveLockId(lockfilename); //throw FileError, ErrorNotExisting + const std::string lockId = retrieveLockId(lockfilename); //throw FileError if (const std::shared_ptr<SharedDirLock>& activeLock = getActiveLock(lockId)) //returns null-lock if not found { fileToGuid[lockfilename] = lockId; //found an alias for one of our active locks @@ -629,7 +628,7 @@ public: //lock not owned by us => create a new one auto newLock = std::make_shared<SharedDirLock>(lockfilename, callback); //throw FileError - const std::string& newLockGuid = retrieveLockId(lockfilename); //throw FileError, ErrorNotExisting + const std::string& newLockGuid = retrieveLockId(lockfilename); //throw FileError //update registry fileToGuid[lockfilename] = newLockGuid; //throw() diff --git a/lib/generate_logfile.h b/lib/generate_logfile.h index 31f7bd43..ff97b63a 100644 --- a/lib/generate_logfile.h +++ b/lib/generate_logfile.h @@ -147,9 +147,9 @@ void saveToLastSyncsLog(const SummaryInfo& summary, //throw FileError Utf8String oldStream; try { - oldStream = loadBinStream<Utf8String>(filename); //throw FileError, ErrorNotExisting + oldStream = loadBinStream<Utf8String>(filename); //throw FileError } - catch (const ErrorNotExisting&) {} + catch (FileError&) {} if (!oldStream.empty()) { diff --git a/lib/icon_buffer.cpp b/lib/icon_buffer.cpp index cf916174..b0874d83 100644 --- a/lib/icon_buffer.cpp +++ b/lib/icon_buffer.cpp @@ -426,7 +426,7 @@ public: { assert(boost::this_thread::get_id() == mainThreadId ); { - boost::unique_lock<boost::mutex> dummy(lockFiles); + boost::lock_guard<boost::mutex> dummy(lockFiles); filesToLoad = newLoad; } conditionNewFiles.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 @@ -437,7 +437,7 @@ public: { assert(boost::this_thread::get_id() == mainThreadId ); { - boost::unique_lock<boost::mutex> dummy(lockFiles); + boost::lock_guard<boost::mutex> dummy(lockFiles); filesToLoad.push_back(newEntry); //set as next item to retrieve } conditionNewFiles.notify_all(); diff --git a/lib/parallel_scan.cpp b/lib/parallel_scan.cpp index df8ff095..2bac5690 100644 --- a/lib/parallel_scan.cpp +++ b/lib/parallel_scan.cpp @@ -180,7 +180,7 @@ public: errorMsg.clear(); errorResponse.reset(); - dummy.unlock(); //optimization for condition_variable::notify_one() + dummy.unlock(); //optimization for condition_variable::notify_all() conditionCanReportError.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 return rv; @@ -194,7 +194,7 @@ public: FillBufferCallback::HandleError rv = callback.reportError(copyStringTo<std::wstring>(errorMsg)); //throw! errorResponse = make_unique<FillBufferCallback::HandleError>(rv); - dummy.unlock(); //optimization for condition_variable::notify_one() + dummy.unlock(); //optimization for condition_variable::notify_all() conditionGotResponse.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 } } @@ -236,7 +236,7 @@ public: std::wstring statusText = copyStringTo<std::wstring>(textScanning); const long activeCount = activeWorker; if (activeCount >= 2) - statusText += L" " + replaceCpy(_P("[1 Thread]", "[%x Threads]", activeCount), L"%x", numberTo<std::wstring>(activeCount)); + statusText += L" [" + replaceCpy(_P("1 thread", "%x threads", activeCount), L"%x", numberTo<std::wstring>(activeCount)) + L"]"; statusText += L" " + fmtFileName(filename); return statusText; @@ -273,8 +273,8 @@ private: boost::detail::atomic_count itemsScanned; boost::detail::atomic_count activeWorker; }; -//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- struct TraverserShared { diff --git a/lib/parse_lng.h b/lib/parse_lng.h index 8cd8e943..be47b66b 100644 --- a/lib/parse_lng.h +++ b/lib/parse_lng.h @@ -294,7 +294,7 @@ private: static void normalize(std::string& text) { - zen::trim(text); //remmove whitespace from end + zen::trim(text); //remove whitespace from both ends //Delimiter: //---------- @@ -378,8 +378,11 @@ private: if (token().type == Token::TK_PLURAL_BEGIN) return parsePlural(pluralOut, pluralInfo); + if (token().type != Token::TK_TEXT) + throw ParsingError(L"Source text empty", scn.posRow(), scn.posCol()); std::string original = tk.text; - consumeToken(Token::TK_TEXT); + nextToken(); + consumeToken(Token::TK_SRC_END); consumeToken(Token::TK_TRG_BEGIN); diff --git a/lib/perf_check.cpp b/lib/perf_check.cpp index 33361201..0f5506b3 100644 --- a/lib/perf_check.cpp +++ b/lib/perf_check.cpp @@ -16,10 +16,10 @@ using namespace zen; PerfCheck::PerfCheck(unsigned int windowSizeRemainingTime, - unsigned int windowSizeBytesPerSecond) : + unsigned int windowSizeSpeed) : windowSizeRemTime(windowSizeRemainingTime), - windowSizeBPS(windowSizeBytesPerSecond), - windowMax(std::max(windowSizeRemainingTime, windowSizeBytesPerSecond)) {} + windowSizeSpeed_(windowSizeSpeed), + windowMax(std::max(windowSizeRemainingTime, windowSizeSpeed)) {} PerfCheck::~PerfCheck() @@ -43,9 +43,9 @@ PerfCheck::~PerfCheck() } -void PerfCheck::addSample(int objectsCurrent, double dataCurrent, long timeMs) +void PerfCheck::addSample(int itemsCurrent, double dataCurrent, long timeMs) { - samples.insert(samples.end(), std::make_pair(timeMs, Record(objectsCurrent, dataCurrent))); //use fact that time is monotonously ascending + samples.insert(samples.end(), std::make_pair(timeMs, Record(itemsCurrent, dataCurrent))); //use fact that time is monotonously ascending //remove all records earlier than "now - windowMax" const long newBegin = timeMs - windowMax; @@ -55,20 +55,32 @@ void PerfCheck::addSample(int objectsCurrent, double dataCurrent, long timeMs) } -std::wstring PerfCheck::getRemainingTime(double dataRemaining) const +inline +std::pair<const std::multimap<long, PerfCheck::Record>::value_type*, const std::multimap<long, PerfCheck::Record>::value_type*> PerfCheck::getBlockFromEnd(long windowSize) const { if (!samples.empty()) { - const auto& recordBack = *samples.rbegin(); + auto itBack = samples.rbegin(); //find start of records "window" - auto itFront = samples.upper_bound(recordBack.first - windowSizeRemTime); + auto itFront = samples.upper_bound(itBack->first - windowSize); if (itFront != samples.begin()) --itFront; //one point before window begin in order to handle "measurement holes" + return std::make_pair(&*itFront, &*itBack); + } + return std::make_pair(nullptr, nullptr); +} - const auto& recordFront = *itFront; + +zen::Opt<std::wstring> PerfCheck::getRemainingTime(double dataRemaining) const +{ + auto blk = getBlockFromEnd(windowSizeRemTime); + if (blk.first && blk.second) + { + const auto& itemFront = *blk.first; + const auto& itemBack = *blk.second; //----------------------------------------------------------------------------------------------- - const long timeDelta = recordBack.first - recordFront.first; - const double dataDelta = recordBack.second.data_ - recordFront.second.data_; + const long timeDelta = itemBack.first - itemFront.first; + const double dataDelta = itemBack.second.data_ - itemFront.second.data_; //objects model logical operations *NOT* disk accesses, so we better play safe and use "bytes" only! //http://sourceforge.net/p/freefilesync/feature-requests/197/ @@ -76,29 +88,43 @@ std::wstring PerfCheck::getRemainingTime(double dataRemaining) const if (!numeric::isNull(dataDelta)) //sign(dataRemaining) != sign(dataDelta) usually an error, so show it! return remainingTimeToString(dataRemaining * timeDelta / (1000.0 * dataDelta)); } - return L"-"; //fallback + return NoValue(); } -std::wstring PerfCheck::getBytesPerSecond() const +zen::Opt<std::wstring> PerfCheck::getBytesPerSecond() const { - if (!samples.empty()) + auto blk = getBlockFromEnd(windowSizeSpeed_); + if (blk.first && blk.second) { - const auto& recordBack = *samples.rbegin(); - //find start of records "window" - auto itFront = samples.upper_bound(recordBack.first - windowSizeBPS); - if (itFront != samples.begin()) - --itFront; //one point before window begin in order to handle "measurement holes" + const auto& itemFront = *blk.first; + const auto& itemBack = *blk.second; + //----------------------------------------------------------------------------------------------- + const long timeDelta = itemBack.first - itemFront.first; + const double dataDelta = itemBack.second.data_ - itemFront.second.data_; + + if (timeDelta != 0/* && dataDelta > 0*/) + return filesizeToShortString(zen::Int64(dataDelta * 1000.0 / timeDelta)) + _("/sec"); + } + return NoValue(); +} + - const auto& recordFront = *itFront; +zen::Opt<std::wstring> PerfCheck::getItemsPerSecond() const +{ + auto blk = getBlockFromEnd(windowSizeSpeed_); + if (blk.first && blk.second) + { + const auto& itemFront = *blk.first; + const auto& itemBack = *blk.second; //----------------------------------------------------------------------------------------------- - const long timeDelta = recordBack.first - recordFront.first; - const double dataDelta = recordBack.second.data_ - recordFront.second.data_; + const long timeDelta = itemBack.first - itemFront.first; + const int itemsDelta = itemBack.second.itemCount_ - itemFront.second.itemCount_; - if (timeDelta != 0 && dataDelta > 0) - return filesizeToShortString(zen::Int64(dataDelta * 1000 / timeDelta)) + _("/sec"); + if (timeDelta != 0) + return replaceCpy(_("%x items"), L"%x", formatThreeDigitPrecision(itemsDelta * 1000.0 / timeDelta)) + _("/sec"); } - return L"-"; //fallback + return NoValue(); } diff --git a/lib/perf_check.h b/lib/perf_check.h index 3e04b778..9d82be57 100644 --- a/lib/perf_check.h +++ b/lib/perf_check.h @@ -9,31 +9,37 @@ #include <map> #include <string> +#include <zen/optional.h> class PerfCheck { public: - PerfCheck(unsigned int windowSizeRemainingTime, //unit: [ms] - unsigned int windowSizeBytesPerSecond); // + PerfCheck(unsigned int windowSizeRemainingTime, //unit: [ms] + unsigned int windowSizeSpeed); // ~PerfCheck(); - void addSample(int objectsCurrent, double dataCurrent, long timeMs); //timeMs must be ascending! + void addSample(int itemsCurrent, double dataCurrent, long timeMs); //timeMs must be ascending! - std::wstring getRemainingTime(double dataRemaining) const; - std::wstring getBytesPerSecond() const; //for window + zen::Opt<std::wstring> getRemainingTime(double dataRemaining) const; + zen::Opt<std::wstring> getBytesPerSecond() const; //for window + zen::Opt<std::wstring> getItemsPerSecond() const; //for window private: - const long windowSizeRemTime; //unit: [ms] - const long windowSizeBPS; // - const long windowMax; - struct Record { - Record(int objCount, double data) : objCount_(objCount), data_(data) {} - int objCount_; + Record(int itemCount, double data) : itemCount_(itemCount), data_(data) {} + int itemCount_; double data_; //unit: [bytes] }; - std::multimap<long, Record> samples; //time, unit: [ms] + + std::pair<const std::multimap<long, Record>::value_type*, + const std::multimap<long, Record>::value_type*> getBlockFromEnd(long windowSize) const; + + const long windowSizeRemTime; //unit: [ms] + const long windowSizeSpeed_; // + const long windowMax; + + std::map<long, Record> samples; //time, unit: [ms] }; #endif // STATISTICS_H_INCLUDED diff --git a/lib/process_xml.cpp b/lib/process_xml.cpp index 2422b2ef..49e4c711 100644 --- a/lib/process_xml.cpp +++ b/lib/process_xml.cpp @@ -99,6 +99,7 @@ void xmlAccess::OptionalDialogs::resetDialogs() warningDirectoryLockFailed = true; popupOnConfigChange = true; confirmSyncStart = true; + confirmExternalCommandMassInvoke = true; } @@ -157,36 +158,6 @@ xmlAccess::XmlBatchConfig xmlAccess::convertGuiToBatchPreservingExistingBatch(co } -xmlAccess::MergeType xmlAccess::getMergeType(const std::vector<Zstring>& filenames) //throw() -{ - bool guiCfgExists = false; - bool batchCfgExists = false; - - for (auto it = filenames.begin(); it != filenames.end(); ++it) - { - switch (xmlAccess::getXmlType(*it)) //throw() - { - case XML_TYPE_GUI: - guiCfgExists = true; - break; - - case XML_TYPE_BATCH: - batchCfgExists = true; - break; - - case XML_TYPE_GLOBAL: - case XML_TYPE_OTHER: - return MERGE_OTHER; - } - } - - if (guiCfgExists) - return batchCfgExists ? MERGE_GUI_BATCH : MERGE_GUI; - else - return batchCfgExists ? MERGE_BATCH : MERGE_OTHER; -} - - namespace { std::vector<Zstring> splitFilterByLines(const Zstring& filterPhrase) @@ -243,13 +214,13 @@ void writeText(const SyncDirection& value, std::string& output) { switch (value) { - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: output = "left"; break; - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: output = "right"; break; - case SYNC_DIR_NONE: + case SyncDirection::NONE: output = "none"; break; } @@ -261,11 +232,11 @@ bool readText(const std::string& input, SyncDirection& value) std::string tmp = input; zen::trim(tmp); if (tmp == "left") - value = SYNC_DIR_LEFT; + value = SyncDirection::LEFT; else if (tmp == "right") - value = SYNC_DIR_RIGHT; + value = SyncDirection::RIGHT; else if (tmp == "none") - value = SYNC_DIR_NONE; + value = SyncDirection::NONE; else return false; return true; @@ -1037,6 +1008,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) inOpt["WarnDirectoryLockFailed" ].attribute("Enabled", config.optDialogs.warningDirectoryLockFailed); inOpt["ConfirmSaveConfig" ].attribute("Enabled", config.optDialogs.popupOnConfigChange); inOpt["ConfirmStartSync" ].attribute("Enabled", config.optDialogs.confirmSyncStart); + inOpt["ConfirmExternalCommandMassInvoke"].attribute("Enabled", config.optDialogs.confirmExternalCommandMassInvoke); //gui specific global settings (optional) XmlIn inGui = in["Gui"]; @@ -1415,6 +1387,7 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) outOpt["WarnDirectoryLockFailed" ].attribute("Enabled", config.optDialogs.warningDirectoryLockFailed); outOpt["ConfirmSaveConfig" ].attribute("Enabled", config.optDialogs.popupOnConfigChange); outOpt["ConfirmStartSync" ].attribute("Enabled", config.optDialogs.confirmSyncStart); + outOpt["ConfirmExternalCommandMassInvoke"].attribute("Enabled", config.optDialogs.confirmExternalCommandMassInvoke); //gui specific global settings (optional) XmlOut outGui = out["Gui"]; diff --git a/lib/process_xml.h b/lib/process_xml.h index 95fa644d..b189e51f 100644 --- a/lib/process_xml.h +++ b/lib/process_xml.h @@ -103,6 +103,7 @@ struct OptionalDialogs bool warningDirectoryLockFailed; bool popupOnConfigChange; bool confirmSyncStart; + bool confirmExternalCommandMassInvoke; }; @@ -278,15 +279,6 @@ void writeConfig(const XmlBatchConfig& config, const Zstring& filename); //th void writeConfig(const XmlGlobalSettings& config); // //convert (multiple) *.ffs_gui, *.ffs_batch files or combinations of both into target config structure: -enum MergeType -{ - MERGE_GUI, //pure gui config files - MERGE_BATCH, // " batch " - MERGE_GUI_BATCH, //gui and batch files - MERGE_OTHER -}; -MergeType getMergeType(const std::vector<Zstring>& filenames); //noexcept - void readAnyConfig(const std::vector<Zstring>& filenames, XmlGuiConfig& config); //throw FfsXmlError //config conversion utilities diff --git a/lib/resolve_path.cpp b/lib/resolve_path.cpp index 035e1d77..daa99d2a 100644 --- a/lib/resolve_path.cpp +++ b/lib/resolve_path.cpp @@ -620,20 +620,28 @@ void zen::loginNetworkShare(const Zstring& dirnameOrig, bool allowUserInteractio nullptr, // __in LPCTSTR lpPassword, nullptr, // __in LPCTSTR lpUsername, 0); //__in DWORD dwFlags + //53L ERROR_BAD_NETPATH The network path was not found. + //86L ERROR_INVALID_PASSWORD + //1219L ERROR_SESSION_CREDENTIAL_CONFLICT Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. + //1326L ERROR_LOGON_FAILURE Logon failure: unknown user name or bad password. + //1236L ERROR_CONNECTION_ABORTED if (somethingExists(trgRes.lpRemoteName)) //blocks! return; //success: connection usable! -> don't care about "rv" if (rv == ERROR_BAD_NETPATH || //Windows 7 - rv == ERROR_BAD_NET_NAME) //XP + rv == ERROR_BAD_NET_NAME|| //XP + rv == ERROR_CONNECTION_ABORTED) //failed to connect to a network that existed not too long ago; will later return ERROR_BAD_NETPATH return; //no need to show a prompt for an unreachable network device //2. if first attempt failed, we need to *force* prompt by using CONNECT_PROMPT if (allowUserInteraction) { //avoid problem II.) - DWORD rv2= WNetCancelConnection2(trgRes.lpRemoteName, //_In_ LPCTSTR lpName, - 0, //_In_ DWORD dwFlags, - true); //_In_ BOOL fForce + DWORD rv2= ::WNetCancelConnection2(trgRes.lpRemoteName, //_In_ LPCTSTR lpName, + 0, //_In_ DWORD dwFlags, + true); //_In_ BOOL fForce + //2250L ERROR_NOT_CONNECTED + //enforce login prompt DWORD rv3 = ::WNetAddConnection2(&trgRes, // __in LPNETRESOURCE lpNetResource, nullptr, // __in LPCTSTR lpPassword, @@ -641,11 +649,6 @@ void zen::loginNetworkShare(const Zstring& dirnameOrig, bool allowUserInteractio CONNECT_INTERACTIVE | CONNECT_PROMPT); //__in DWORD dwFlags (void)rv2; (void)rv3; - //Sample error codes: - //53L ERROR_BAD_NETPATH The network path was not found. - //86L ERROR_INVALID_PASSWORD - //1219L ERROR_SESSION_CREDENTIAL_CONFLICT Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. - //1326L ERROR_LOGON_FAILURE Logon failure: unknown user name or bad password. } }; diff --git a/lib/shadow.cpp b/lib/shadow.cpp index 1161646e..71372270 100644 --- a/lib/shadow.cpp +++ b/lib/shadow.cpp @@ -111,14 +111,11 @@ Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile, const std::function const Zstring volumeNamePf = appendSeparator(&volBuffer[0]); //msdn: if buffer is 1 char too short, GetVolumePathName() may skip last separator without error! //input file is always absolute! directory formatting takes care of this! Therefore volume name can always be found. - const size_t pos = filenameFinal.find(volumeNamePf); //filenameFinal needs NOT to begin with volumeNamePf: consider for example \\?\ prefix! + const size_t pos = filenameFinal.find(volumeNamePf); //filenameFinal needs NOT to begin with volumeNamePf: consider \\?\ prefix! if (pos == Zstring::npos) - { - std::wstring msg = _("Volume name %x not part of file name %y."); - replace(msg, L"%x", fmtFileName(volumeNamePf), false); - replace(msg, L"%y", fmtFileName(filenameFinal), false); - throw FileError(msg); - } + throw FileError(replaceCpy(replaceCpy(_("Volume name %x is not part of file path %y."), + L"%x", fmtFileName(volumeNamePf)), + L"%y", fmtFileName(filenameFinal))); //get or create instance of shadow volume VolNameShadowMap::const_iterator it = shadowVol.find(volumeNamePf); diff --git a/lib/status_handler.cpp b/lib/status_handler.cpp index c24c6f50..5fb80161 100644 --- a/lib/status_handler.cpp +++ b/lib/status_handler.cpp @@ -5,21 +5,12 @@ // ************************************************************************** #include "status_handler.h" -#include <wx/app.h> +//#include <wx/app.h> #include <zen/tick_count.h> using namespace zen; -void zen::updateUiNow() -{ - //process UI events and prevent application from "not responding" -> NO performance issue! - wxTheApp->Yield(); - - // while (wxTheApp->Pending()) - // wxTheApp->Dispatch(); -} - namespace { const std::int64_t TICKS_UPDATE_INTERVAL = UI_UPDATE_INTERVAL* ticksPerSec() / 1000; diff --git a/lib/status_handler.h b/lib/status_handler.h index ed496824..c19935b1 100644 --- a/lib/status_handler.h +++ b/lib/status_handler.h @@ -16,13 +16,12 @@ namespace zen { bool updateUiIsAllowed(); //test if a specific amount of time is over -void updateUiNow(); //do the updating /* Updating GUI is fast! time per single call to ProcessCallback::forceUiRefresh() - - Comparison 25 µs - - Synchronization 0.6 ms (despite complex graph control!) + - Comparison 0.025 ms + - Synchronization 0.74 ms (despite complex graph control!) */ //gui may want to abort process @@ -72,11 +71,13 @@ protected: virtual void requestUiRefresh() { - if (updateUiIsAllowed()) //test if specific time span between ui updates is over + if (abortRequested) //triggered by requestAbortion() + { + forceUiRefresh(); + abortThisProcess(); + } + else if (updateUiIsAllowed()) //test if specific time span between ui updates is over forceUiRefresh(); - - if (abortRequested) //check *after* GUI update, to have up-to-date screen - abortThisProcess(); //triggered by requestAbortion() } virtual void reportStatus(const std::wstring& text) { statusText_ = text; requestUiRefresh(); /*throw AbortThisProcess */ } diff --git a/lib/versioning.cpp b/lib/versioning.cpp index 1bf5a65e..a6458196 100644 --- a/lib/versioning.cpp +++ b/lib/versioning.cpp @@ -129,15 +129,14 @@ void moveObject(const Zstring& sourceFile, //throw FileError { assert(!dirExists(sourceFile) || symlinkExists(sourceFile)); //we process files and symlinks only - auto removeTarget = [&]() + auto removeTarget = [&] { //remove target object if (fileExists(targetFile)) //file or symlink removeFile(targetFile); //throw FileError else if (dirExists(targetFile)) //directory or symlink - removeDirectory(targetFile); //throw FileError - //we do not expect targetFile to be a directory in general => no callback required - else assert(false); + removeDirectory(targetFile); //throw FileError; we do not expect targetFile to be a directory in general => no callback required + //else assert(false); -> may simply not exist if ErrorDifferentVolume! }; //first try to move directly without copying diff --git a/process_callback.h b/process_callback.h index 036ad79a..fcf0384e 100644 --- a/process_callback.h +++ b/process_callback.h @@ -51,7 +51,7 @@ struct ProcessCallback //opportunity to abort must be implemented in a frequently executed method like requestUiRefresh() virtual void requestUiRefresh() = 0; //throw ? - virtual void forceUiRefresh () = 0; //throw ? - called before starting long running task which doesn't update regularly + virtual void forceUiRefresh () = 0; //throw ? - called before starting long running tasks which don't update regularly //called periodically after data was processed: expected(!) to request GUI update virtual void reportStatus(const std::wstring& text) = 0; //UI info only, should not be logged! @@ -71,4 +71,4 @@ struct ProcessCallback virtual void reportFatalError(const std::wstring& errorMessage) = 0; //non-recoverable error situation }; -#endif //PROC_HEADER_48257827842345454545
\ No newline at end of file +#endif //PROC_HEADER_48257827842345454545 diff --git a/structures.cpp b/structures.cpp index 1bcfd580..3bc2c181 100644 --- a/structures.cpp +++ b/structures.cpp @@ -55,21 +55,21 @@ DirectionSet zen::extractDirections(const DirectionConfig& cfg) throw std::logic_error("there are no predefined directions for automatic mode!"); case DirectionConfig::MIRROR: - output.exLeftSideOnly = SYNC_DIR_RIGHT; - output.exRightSideOnly = SYNC_DIR_RIGHT; - output.leftNewer = SYNC_DIR_RIGHT; - output.rightNewer = SYNC_DIR_RIGHT; - output.different = SYNC_DIR_RIGHT; - output.conflict = SYNC_DIR_RIGHT; + output.exLeftSideOnly = SyncDirection::RIGHT; + output.exRightSideOnly = SyncDirection::RIGHT; + output.leftNewer = SyncDirection::RIGHT; + output.rightNewer = SyncDirection::RIGHT; + output.different = SyncDirection::RIGHT; + output.conflict = SyncDirection::RIGHT; break; case DirectionConfig::UPDATE: - output.exLeftSideOnly = SYNC_DIR_RIGHT; - output.exRightSideOnly = SYNC_DIR_NONE; - output.leftNewer = SYNC_DIR_RIGHT; - output.rightNewer = SYNC_DIR_NONE; - output.different = SYNC_DIR_RIGHT; - output.conflict = SYNC_DIR_NONE; + output.exLeftSideOnly = SyncDirection::RIGHT; + output.exRightSideOnly = SyncDirection::NONE; + output.leftNewer = SyncDirection::RIGHT; + output.rightNewer = SyncDirection::NONE; + output.different = SyncDirection::RIGHT; + output.conflict = SyncDirection::NONE; break; case DirectionConfig::CUSTOM: @@ -83,12 +83,12 @@ DirectionSet zen::extractDirections(const DirectionConfig& cfg) DirectionSet zen::getTwoWaySet() { DirectionSet output; - output.exLeftSideOnly = SYNC_DIR_RIGHT; - output.exRightSideOnly = SYNC_DIR_LEFT; - output.leftNewer = SYNC_DIR_RIGHT; - output.rightNewer = SYNC_DIR_LEFT; - output.different = SYNC_DIR_NONE; - output.conflict = SYNC_DIR_NONE; + output.exLeftSideOnly = SyncDirection::RIGHT; + output.exRightSideOnly = SyncDirection::LEFT; + output.leftNewer = SyncDirection::RIGHT; + output.rightNewer = SyncDirection::LEFT; + output.different = SyncDirection::NONE; + output.conflict = SyncDirection::NONE; return output; } diff --git a/structures.h b/structures.h index 5ffb5620..1d3f5d4c 100644 --- a/structures.h +++ b/structures.h @@ -31,11 +31,11 @@ enum SymLinkHandling }; -enum SyncDirection +enum class SyncDirection : unsigned char //we need to save space for use in FileSystemObject! { - SYNC_DIR_LEFT = 0, - SYNC_DIR_RIGHT, - SYNC_DIR_NONE + LEFT = 0, + RIGHT, + NONE }; @@ -104,12 +104,12 @@ std::wstring getSymbol (SyncOperation op); //method used for exporting .csv struct DirectionSet { DirectionSet() : - exLeftSideOnly (SYNC_DIR_RIGHT), - exRightSideOnly(SYNC_DIR_LEFT), - leftNewer (SYNC_DIR_RIGHT), - rightNewer (SYNC_DIR_LEFT), - different (SYNC_DIR_NONE), - conflict (SYNC_DIR_NONE) {} + exLeftSideOnly (SyncDirection::RIGHT), + exRightSideOnly(SyncDirection::LEFT), + leftNewer (SyncDirection::RIGHT), + rightNewer (SyncDirection::LEFT), + different (SyncDirection::NONE), + conflict (SyncDirection::NONE) {} SyncDirection exLeftSideOnly; SyncDirection exRightSideOnly; diff --git a/synchronization.cpp b/synchronization.cpp index 783f1e7f..a0f19d27 100644 --- a/synchronization.cpp +++ b/synchronization.cpp @@ -316,9 +316,8 @@ public: try { tryCleanup(false); } catch (...) {} //always (try to) clean up, even if synchronization is aborted! /* - do not allow user callback: - - make sure this stays non-blocking! - - avoid throwing user abort exception again, leading to incomplete clean-up! + may block heavily, but still do not allow user callback: + -> avoid throwing user cancel exception again, leading to incomplete clean-up! */ } @@ -364,8 +363,6 @@ private: std::wstring txtRemovingFile; std::wstring txtRemovingSymlink; std::wstring txtRemovingDirectory; - - bool cleanedUp; }; @@ -380,8 +377,7 @@ DeletionHandling::DeletionHandling(DeletionPolicy handleDel, //nothrow! versioningDir_(versioningDir), versioningStyle_(versioningStyle), timeStamp_(timeStamp), - deletionPolicy_(handleDel), - cleanedUp(false) + deletionPolicy_(handleDel) { switch (deletionPolicy_) { @@ -480,42 +476,42 @@ Zstring DeletionHandling::getOrCreateRecyclerTempDirPf() //throw FileError void DeletionHandling::tryCleanup(bool allowUserCallback) //throw FileError { - if (!cleanedUp) + switch (deletionPolicy_) { - switch (deletionPolicy_) - { - case DELETE_PERMANENTLY: - break; + case DELETE_PERMANENTLY: + break; - case DELETE_TO_RECYCLER: + case DELETE_TO_RECYCLER: #ifdef ZEN_WIN - if (!recyclerTmpDir.empty()) - { - //move content of temporary directory to recycle bin in a single call - CallbackMassRecycling cbmr(procCallback_); - recycleOrDelete(toBeRecycled, allowUserCallback ? &cbmr : nullptr); //throw FileError + if (!toBeRecycled.empty()) + { + //move content of temporary directory to recycle bin in a single call + CallbackMassRecycling cbmr(procCallback_); + recycleOrDelete(toBeRecycled, allowUserCallback ? &cbmr : nullptr); //throw FileError + toBeRecycled.clear(); + } - //clean up temp directory itself (should contain remnant empty directories only) - removeDirectory(recyclerTmpDir); //throw FileError - } + //clean up temp directory itself (should contain remnant empty directories only) + if (!recyclerTmpDir.empty()) + { + removeDirectory(recyclerTmpDir); //throw FileError + recyclerTmpDir.clear(); + } #endif - break; - - case DELETE_TO_VERSIONING: - //if (versioner.get()) - //{ - // if (allowUserCallback) - // { - // procCallback_.reportStatus(_("Removing old versions...")); //throw ? - // versioner->limitVersions([&] { procCallback_.requestUiRefresh(); /*throw ? */ }); //throw FileError - // } - // else - // versioner->limitVersions([] {}); //throw FileError - //} - break; - } + break; - cleanedUp = true; + case DELETE_TO_VERSIONING: + //if (versioner.get()) + //{ + // if (allowUserCallback) + // { + // procCallback_.reportStatus(_("Removing old versions...")); //throw ? + // versioner->limitVersions([&] { procCallback_.requestUiRefresh(); /*throw ? */ }); //throw FileError + // } + // else + // versioner->limitVersions([] {}); //throw FileError + //} + break; } } @@ -856,9 +852,8 @@ private: //[...] //recurse into sub-dirs - std::for_each(hierObj.refSubDirs().begin(), hierObj.refSubDirs().end(), [&](const HierarchyObject& subDir) { this->recurse(subDir); }); - //for (auto& subDir : hierObj.refSubDirs()) - // recurse(subDir); + for (auto& subDir : hierObj.refSubDirs()) + recurse(subDir); } Int64 spaceNeededLeft; @@ -1063,7 +1058,7 @@ void SynchronizeFolderPair::prepare2StepMove(FilePair& sourceObj, sourceObj.removeObject<side>(); //remove only *after* evaluating "sourceObj, side"! //prepare move in second pass - tempFile.setSyncDir(side == LEFT_SIDE ? SYNC_DIR_LEFT : SYNC_DIR_RIGHT); + tempFile.setSyncDir(side == LEFT_SIDE ? SyncDirection::LEFT : SyncDirection::RIGHT); targetObj.setMoveRef(tempFile .getId()); tempFile .setMoveRef(targetObj.getId()); @@ -1146,7 +1141,8 @@ void SynchronizeFolderPair::manageFileMove(FilePair& sourceObj, return prepare2StepMove<side>(sourceObj, targetObj); //throw FileError //finally start move! this should work now: - synchronizeFile(sourceObj); //throw FileError + synchronizeFile(targetObj); //throw FileError + //SynchronizeFolderPair::synchronizeFileInt() is *not* expecting SO_MOVE_LEFT_SOURCE/SO_MOVE_RIGHT_SOURCE => start move from targetObj, not sourceObj! } //else: sourceObj will not be deleted, and is not standing in the way => delay to second pass //note: this case may include new "move sources" from two-step sub-routine!!! @@ -1504,6 +1500,7 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& fileObj, SyncOperation procCallback_.updateProcessedData(1, 0); } + else (assert(false)); break; case SO_OVERWRITE_LEFT: @@ -1532,7 +1529,10 @@ void SynchronizeFolderPair::synchronizeFileInt(FilePair& fileObj, SyncOperation //fileObj.removeObject<sideTrg>(); -> doesn't make sense for isFollowedSymlink(); "fileObj, sideTrg" evaluated below! - reportStatus(txtOverwritingFile, targetFile); //restore status text copy file + //if fail-safe file copy is active, then the next operation will be a simple "rename" + //=> don't risk reportStatus() throwing GuiAbortProcess() leaving the target deleted rather than updated! + if (!transactionalFileCopy_) + reportStatus(txtOverwritingFile, targetFile); //restore status text copy file }); //throw FileError //update FilePair @@ -1668,12 +1668,14 @@ void SynchronizeFolderPair::synchronizeLinkInt(SymlinkPair& linkObj, SyncOperati case SO_OVERWRITE_RIGHT: reportInfo(txtOverwritingLink, linkObj.getFullName<sideTrg>()); - reportStatus(getDelHandling<sideTrg>().getTxtRemovingSymLink(), linkObj.getFullName<sideTrg>()); + //reportStatus(getDelHandling<sideTrg>().getTxtRemovingSymLink(), linkObj.getFullName<sideTrg>()); getDelHandling<sideTrg>().removeLinkUpdating(linkObj.getFullName<sideTrg>(), linkObj.getObjRelativeName(), 0, [] {}); //throw FileError //linkObj.removeObject<sideTrg>(); -> "linkObj, sideTrg" evaluated below! - reportStatus(txtOverwritingLink, linkObj.getFullName<sideTrg>()); //restore status text + //=> don't risk reportStatus() throwing GuiAbortProcess() leaving the target deleted rather than updated: + + //reportStatus(txtOverwritingLink, linkObj.getFullName<sideTrg>()); //restore status text zen::copySymlink(linkObj.getFullName<sideSrc>(), linkObj.getBaseDirPf<sideTrg>() + linkObj.getRelativeName<sideSrc>(), //respect differences in case of source object copyFilePermissions_); //throw FileError @@ -2172,14 +2174,12 @@ void zen::synchronize(const TimeComp& timeStamp, //check if user accidentally selected wrong directories for sync if (!significantDiff.empty()) { - std::wstring msg = _("Significant difference detected:"); + std::wstring msg = _("The following folders are significantly different. Make sure you are matching the correct folders for synchronization."); for (auto it = significantDiff.begin(); it != significantDiff.end(); ++it) msg += std::wstring(L"\n\n") + it->first + L" <-> " + L"\n" + it->second; - msg += L"\n\n"; - msg += _("More than 50% of the total number of files will be copied or deleted."); callback.reportWarning(msg, warnings.warningSignificantDifference); } @@ -2245,12 +2245,11 @@ void zen::synchronize(const TimeComp& timeStamp, //loop through all directory pairs for (auto j = begin(folderCmp); j != end(folderCmp); ++j) { - //exclude some pathological case (leftdir, rightdir are empty) + //exclude pathological cases (e.g. leftdir, rightdir are empty) if (EqualFilename()(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>())) continue; //------------------------------------------------------------------------------------------ - //always report folder pairs for log file, even if there is no work to do callback.reportInfo(_("Synchronizing folder pair:") + L"\n" + L" " + j->getBaseDirPf<LEFT_SIDE >() + L"\n" + L" " + j->getBaseDirPf<RIGHT_SIDE>()); diff --git a/ui/Taskbar_Seven/Taskbar_Seven.vcxproj b/ui/Taskbar_Seven/Taskbar_Seven.vcxproj index 8b8ab5ce..02c4b67b 100644 --- a/ui/Taskbar_Seven/Taskbar_Seven.vcxproj +++ b/ui/Taskbar_Seven/Taskbar_Seven.vcxproj @@ -85,7 +85,7 @@ </BuildLog> <ClCompile> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;TASKBAR_SEVEN_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;_DEBUG;_WINDOWS;_USRDLL;TASKBAR_SEVEN_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <MinimalRebuild>true</MinimalRebuild> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> @@ -120,7 +120,7 @@ </Midl> <ClCompile> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;TASKBAR_SEVEN_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;_DEBUG;_WINDOWS;_USRDLL;TASKBAR_SEVEN_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <MinimalRebuild>true</MinimalRebuild> <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> @@ -153,7 +153,7 @@ <ClCompile> <Optimization>MaxSpeed</Optimization> <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;TASKBAR_SEVEN_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;_WINDOWS;_USRDLL;TASKBAR_SEVEN_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <FunctionLevelLinking>true</FunctionLevelLinking> <PrecompiledHeader> @@ -190,7 +190,7 @@ <ClCompile> <Optimization>MaxSpeed</Optimization> <IntrinsicFunctions>true</IntrinsicFunctions> - <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;TASKBAR_SEVEN_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;_WINDOWS;_USRDLL;TASKBAR_SEVEN_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <FunctionLevelLinking>true</FunctionLevelLinking> <PrecompiledHeader> diff --git a/ui/batch_status_handler.cpp b/ui/batch_status_handler.cpp index 0dfd6957..a72aab3e 100644 --- a/ui/batch_status_handler.cpp +++ b/ui/batch_status_handler.cpp @@ -8,8 +8,9 @@ #include <zen/file_handling.h> #include <zen/file_traverser.h> #include <zen/format_unit.h> -#include <wx+/app_main.h> +//#include <wx+/app_main.h> #include <wx+/shell_execute.h> +#include <wx/app.h> #include "msg_popup.h" #include "exec_finished_box.h" #include "../lib/ffs_paths.h" @@ -217,7 +218,7 @@ BatchStatusHandler::~BatchStatusHandler() } else { - if (progressDlg->getAsWindow()->IsShown()) + if (progressDlg->getWindowIfVisible()) showFinalResults = true; //execute "on completion" command (even in case of ignored errors) @@ -236,7 +237,7 @@ BatchStatusHandler::~BatchStatusHandler() if (showFinalResults) //warning: wxWindow::Show() is called within processHasFinished()! { //notify about (logical) application main window => program won't quit, but stay on this dialog - setMainWindow(progressDlg->getAsWindow()); + //setMainWindow(progressDlg->getAsWindow()); -> not required anymore since we block waiting until dialog is closed below //notify to progressDlg that current process has ended if (abortIsRequested()) @@ -257,7 +258,7 @@ BatchStatusHandler::~BatchStatusHandler() //-> nicely manages dialog lifetime while (progressDlg) { - updateUiNow(); //*first* refresh GUI (removing flicker) before sleeping! + wxTheApp->Yield(); //*first* refresh GUI (removing flicker) before sleeping! boost::this_thread::sleep(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL)); } } @@ -306,7 +307,7 @@ void BatchStatusHandler::reportWarning(const std::wstring& warningMessage, bool& forceUiRefresh(); bool dontWarnAgain = false; - switch (showWarningDlg(progressDlg->getAsWindow(), + switch (showWarningDlg(progressDlg->getWindowIfVisible(), ReturnWarningDlg::BUTTON_IGNORE | ReturnWarningDlg::BUTTON_SWITCH | ReturnWarningDlg::BUTTON_CANCEL, warningMessage + L"\n\n" + _("Press \"Switch\" to resolve issues in FreeFileSync main dialog."), dontWarnAgain)) @@ -352,7 +353,7 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er forceUiRefresh(); bool ignoreNextErrors = false; - switch (showErrorDlg(progressDlg->getAsWindow(), + switch (showErrorDlg(progressDlg->getWindowIfVisible(), ReturnErrorDlg::BUTTON_IGNORE | ReturnErrorDlg::BUTTON_RETRY | ReturnErrorDlg::BUTTON_CANCEL, errorMessage, &ignoreNextErrors)) { @@ -399,7 +400,7 @@ void BatchStatusHandler::reportFatalError(const std::wstring& errorMessage) forceUiRefresh(); bool ignoreNextErrors = false; - switch (showFatalErrorDlg(progressDlg->getAsWindow(), + switch (showFatalErrorDlg(progressDlg->getWindowIfVisible(), ReturnFatalErrorDlg::BUTTON_IGNORE | ReturnFatalErrorDlg::BUTTON_CANCEL, errorMessage, &ignoreNextErrors)) { diff --git a/ui/check_version.cpp b/ui/check_version.cpp index 2e4c387a..e51c1784 100644 --- a/ui/check_version.cpp +++ b/ui/check_version.cpp @@ -233,11 +233,11 @@ void zen::checkForUpdateNow(wxWindow* parent) wxLaunchDefaultBrowser(L"http://freefilesync.sourceforge.net/get_latest.php"); } else - wxMessageBox(_("FreeFileSync is up to date."), _("Information"), wxOK | wxICON_INFORMATION, parent); + wxMessageBox(_("FreeFileSync is up to date."), L"FreeFileSync - " + _("Information"), wxOK | wxICON_INFORMATION, parent); break; case GET_VER_NO_CONNECTION: - wxMessageBox(_("Unable to connect to sourceforge.net."), _("Error"), wxOK | wxICON_ERROR, parent); + wxMessageBox(_("Unable to connect to sourceforge.net."), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR, parent); break; case GET_VER_PAGE_NOT_FOUND: diff --git a/ui/custom_grid.cpp b/ui/custom_grid.cpp index 58e7a7e4..59e80100 100644 --- a/ui/custom_grid.cpp +++ b/ui/custom_grid.cpp @@ -811,19 +811,19 @@ public: break; case BLOCKPOS_LEFT: { - SyncDirectionEvent evt(rowFirst, rowLast, SYNC_DIR_LEFT); + SyncDirectionEvent evt(rowFirst, rowLast, SyncDirection::LEFT); evtHandler->ProcessEvent(evt); } break; case BLOCKPOS_MIDDLE: { - SyncDirectionEvent evt(rowFirst, rowLast, SYNC_DIR_NONE); + SyncDirectionEvent evt(rowFirst, rowLast, SyncDirection::NONE); evtHandler->ProcessEvent(evt); } break; case BLOCKPOS_RIGHT: { - SyncDirectionEvent evt(rowFirst, rowLast, SYNC_DIR_RIGHT); + SyncDirectionEvent evt(rowFirst, rowLast, SyncDirection::RIGHT); evtHandler->ProcessEvent(evt); } break; @@ -983,13 +983,13 @@ private: case BLOCKPOS_CHECK_BOX: break; case BLOCKPOS_LEFT: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_LEFT)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::LEFT)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer); break; case BLOCKPOS_MIDDLE: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_NONE)), rect, wxALIGN_CENTER, buffer); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::NONE)), rect, wxALIGN_CENTER, buffer); break; case BLOCKPOS_RIGHT: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_RIGHT)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, buffer); + drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::RIGHT)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, buffer); break; } else //default diff --git a/ui/dir_name.cpp b/ui/dir_name.cpp index 4a3939e9..7a068343 100644 --- a/ui/dir_name.cpp +++ b/ui/dir_name.cpp @@ -227,7 +227,7 @@ void DirectoryName<NameControl>::onSelectDir(wxCommandEvent& event) errorMsg); //out, optional: call freeString() after use! if (errorMsg) { - wxMessageBox(errorMsg, _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(errorMsg, /*L"FreeFileSync - " +*/ _("Error"), wxOK | wxICON_ERROR); //component used by FFS *and* RTS! return; } if (cancelled || !selectedFolder) diff --git a/ui/gui_generated.cpp b/ui/gui_generated.cpp index 3549519c..21f5f82d 100644 --- a/ui/gui_generated.cpp +++ b/ui/gui_generated.cpp @@ -1098,6 +1098,9 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi bSizer42 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer162StretchSpeedAndRemTimeIndependently; + bSizer162StretchSpeedAndRemTimeIndependently = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer157; bSizer157 = new wxBoxSizer( wxVERTICAL ); @@ -1142,10 +1145,10 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi bSizer157->Add( bSizerFilesRemaining, 0, 0, 5 ); - bSizer42->Add( bSizer157, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer162StretchSpeedAndRemTimeIndependently->Add( bSizer157, 0, wxALIGN_CENTER_VERTICAL, 5 ); - bSizer42->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer162StretchSpeedAndRemTimeIndependently->Add( 0, 0, 1, wxEXPAND, 5 ); sSizerSpeed = new wxBoxSizer( wxHORIZONTAL ); @@ -1160,11 +1163,17 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi sSizerSpeed->Add( m_staticTextSpeed, 0, wxLEFT|wxALIGN_BOTTOM, 5 ); - bSizer42->Add( sSizerSpeed, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer162StretchSpeedAndRemTimeIndependently->Add( sSizerSpeed, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer42->Add( bSizer162StretchSpeedAndRemTimeIndependently, 1, wxALIGN_CENTER_VERTICAL, 5 ); bSizer42->Add( 10, 0, 0, wxALIGN_CENTER_VERTICAL, 5 ); + wxBoxSizer* bSizer163; + bSizer163 = new wxBoxSizer( wxHORIZONTAL ); + sSizerTimeRemaining = new wxBoxSizer( wxHORIZONTAL ); m_staticTextTimeRemFixed = new wxStaticText( this, wxID_ANY, _("Time remaining:"), wxDefaultPosition, wxDefaultSize, 0 ); @@ -1178,10 +1187,10 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi sSizerTimeRemaining->Add( m_staticTextRemTime, 0, wxLEFT|wxALIGN_BOTTOM, 5 ); - bSizer42->Add( sSizerTimeRemaining, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer163->Add( sSizerTimeRemaining, 0, wxALIGN_CENTER_VERTICAL, 5 ); - bSizer42->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer163->Add( 0, 0, 1, wxEXPAND, 5 ); sSizerTimeElapsed = new wxBoxSizer( wxHORIZONTAL ); @@ -1197,7 +1206,10 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi sSizerTimeElapsed->Add( m_staticTextTimeElapsed, 0, wxLEFT|wxALIGN_BOTTOM, 5 ); - bSizer42->Add( sSizerTimeElapsed, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizer163->Add( sSizerTimeElapsed, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer42->Add( bSizer163, 1, wxALIGN_CENTER_VERTICAL, 5 ); bSizer40->Add( bSizer42, 0, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); @@ -1215,17 +1227,20 @@ CompareProgressDlgGenerated::~CompareProgressDlgGenerated() { } -SyncProgressDlgGenerated::SyncProgressDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style ) +SyncProgressPanelGenerated::SyncProgressPanelGenerated( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style ) : wxPanel( parent, id, pos, size, style ) { - this->SetSizeHints( wxSize( 470,260 ), wxDefaultSize ); - this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); - bSizerRoot = new wxBoxSizer( wxVERTICAL ); bSizer42 = new wxBoxSizer( wxHORIZONTAL ); + + bSizer42->Add( 32, 0, 0, 0, 5 ); + + + bSizer42->Add( 0, 0, 1, wxEXPAND, 5 ); + m_bitmapStatus = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 32,32 ), 0 ); - bSizer42->Add( m_bitmapStatus, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + bSizer42->Add( m_bitmapStatus, 0, wxALIGN_CENTER_VERTICAL|wxALL, 2 ); m_staticTextPhase = new wxStaticText( this, wxID_ANY, _("Synchronizing..."), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextPhase->Wrap( -1 ); @@ -1236,11 +1251,32 @@ SyncProgressDlgGenerated::SyncProgressDlgGenerated( wxWindow* parent, wxWindowID m_animCtrlSyncing = new wxAnimationCtrl( this, wxID_ANY, wxNullAnimation, wxDefaultPosition, wxSize( 32,32 ), wxAC_DEFAULT_STYLE ); m_animCtrlSyncing->SetMinSize( wxSize( 32,32 ) ); - bSizer42->Add( m_animCtrlSyncing, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + bSizer42->Add( m_animCtrlSyncing, 0, wxALIGN_CENTER_VERTICAL|wxALL, 2 ); - bSizerRoot->Add( bSizer42, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT, 5 ); + bSizer42->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_bpButtonMinimizeToTray = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 32,32 ), wxBU_AUTODRAW ); + m_bpButtonMinimizeToTray->SetToolTip( _("Minimize to notification area") ); + + bSizer42->Add( m_bpButtonMinimizeToTray, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizerRoot->Add( bSizer42, 0, wxALIGN_CENTER_HORIZONTAL|wxRIGHT|wxLEFT|wxEXPAND, 5 ); + + bSizerStatusText = new wxBoxSizer( wxVERTICAL ); + + m_staticTextStatus = new wxStaticText( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextStatus->Wrap( -1 ); + bSizerStatusText->Add( m_staticTextStatus, 0, wxEXPAND|wxLEFT, 10 ); + + bSizerStatusText->Add( 0, 5, 0, 0, 5 ); + + + bSizerRoot->Add( bSizerStatusText, 0, wxEXPAND, 5 ); + + wxStaticLine* m_staticlineHeader; m_staticlineHeader = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizerRoot->Add( m_staticlineHeader, 0, wxEXPAND, 5 ); @@ -1250,101 +1286,181 @@ SyncProgressDlgGenerated::SyncProgressDlgGenerated( wxWindow* parent, wxWindowID wxBoxSizer* bSizer173; bSizer173 = new wxBoxSizer( wxVERTICAL ); - m_staticTextStatus = new wxStaticText( m_panelProgress, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextStatus->Wrap( -1 ); - bSizer173->Add( m_staticTextStatus, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); - - m_gauge1 = new wxGauge( m_panelProgress, wxID_ANY, 100, wxDefaultPosition, wxSize( -1,14 ), wxGA_HORIZONTAL ); - bSizer173->Add( m_gauge1, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); - bSizer171 = new wxBoxSizer( wxHORIZONTAL ); bSizer171->Add( 10, 0, 0, 0, 5 ); - wxFlexGridSizer* fgSizer10; - fgSizer10 = new wxFlexGridSizer( 0, 2, 2, 5 ); - fgSizer10->SetFlexibleDirection( wxBOTH ); - fgSizer10->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + wxBoxSizer* bSizer164; + bSizer164 = new wxBoxSizer( wxVERTICAL ); + + m_panelItemsProcessed = new wxPanel( m_panelProgress, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_panelItemsProcessed->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + + wxBoxSizer* bSizer165; + bSizer165 = new wxBoxSizer( wxVERTICAL ); - m_staticTextLabelItemsProc = new wxStaticText( m_panelProgress, wxID_ANY, _("Items processed:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextLabelItemsProc->Wrap( -1 ); - fgSizer10->Add( m_staticTextLabelItemsProc, 0, wxALIGN_BOTTOM, 5 ); - bSizerItemsProc = new wxBoxSizer( wxHORIZONTAL ); + bSizer165->Add( 0, 5, 0, 0, 5 ); - m_staticTextProcessedObj = new wxStaticText( m_panelProgress, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + wxStaticText* m_staticText96; + m_staticText96 = new wxStaticText( m_panelItemsProcessed, wxID_ANY, _("Items processed:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText96->Wrap( -1 ); + bSizer165->Add( m_staticText96, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); + + wxBoxSizer* bSizer169; + bSizer169 = new wxBoxSizer( wxHORIZONTAL ); + + m_staticTextProcessedObj = new wxStaticText( m_panelItemsProcessed, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); m_staticTextProcessedObj->Wrap( -1 ); m_staticTextProcessedObj->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); - bSizerItemsProc->Add( m_staticTextProcessedObj, 0, wxALIGN_BOTTOM, 5 ); + bSizer169->Add( m_staticTextProcessedObj, 0, wxALIGN_BOTTOM, 5 ); - m_staticTextDataProcessed = new wxStaticText( m_panelProgress, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextDataProcessed = new wxStaticText( m_panelItemsProcessed, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextDataProcessed->Wrap( -1 ); - bSizerItemsProc->Add( m_staticTextDataProcessed, 0, wxALIGN_BOTTOM|wxLEFT, 5 ); + bSizer169->Add( m_staticTextDataProcessed, 0, wxLEFT|wxALIGN_BOTTOM, 5 ); + + + bSizer165->Add( bSizer169, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); + + + bSizer165->Add( 0, 5, 0, 0, 5 ); + + m_panelItemsProcessed->SetSizer( bSizer165 ); + m_panelItemsProcessed->Layout(); + bSizer165->Fit( m_panelItemsProcessed ); + bSizer164->Add( m_panelItemsProcessed, 0, wxEXPAND|wxTOP, 7 ); - fgSizer10->Add( bSizerItemsProc, 0, wxALIGN_BOTTOM, 5 ); + m_panelItemsRemaining = new wxPanel( m_panelProgress, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_panelItemsRemaining->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); - m_staticTextLabelItemsRem = new wxStaticText( m_panelProgress, wxID_ANY, _("Items remaining:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextLabelItemsRem->Wrap( -1 ); - fgSizer10->Add( m_staticTextLabelItemsRem, 0, wxALIGN_BOTTOM, 5 ); + wxBoxSizer* bSizer166; + bSizer166 = new wxBoxSizer( wxVERTICAL ); + + + bSizer166->Add( 0, 5, 0, 0, 5 ); + + wxStaticText* m_staticText97; + m_staticText97 = new wxStaticText( m_panelItemsRemaining, wxID_ANY, _("Items remaining:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText97->Wrap( -1 ); + bSizer166->Add( m_staticText97, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizerItemsRem = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer170; + bSizer170 = new wxBoxSizer( wxHORIZONTAL ); - m_staticTextRemainingObj = new wxStaticText( m_panelProgress, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_staticTextRemainingObj = new wxStaticText( m_panelItemsRemaining, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); m_staticTextRemainingObj->Wrap( -1 ); m_staticTextRemainingObj->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); - bSizerItemsRem->Add( m_staticTextRemainingObj, 0, wxALIGN_BOTTOM, 5 ); + bSizer170->Add( m_staticTextRemainingObj, 0, wxALIGN_BOTTOM, 5 ); - m_staticTextDataRemaining = new wxStaticText( m_panelProgress, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextDataRemaining = new wxStaticText( m_panelItemsRemaining, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextDataRemaining->Wrap( -1 ); - bSizerItemsRem->Add( m_staticTextDataRemaining, 0, wxALIGN_BOTTOM|wxLEFT, 5 ); + bSizer170->Add( m_staticTextDataRemaining, 0, wxLEFT|wxALIGN_BOTTOM, 5 ); - fgSizer10->Add( bSizerItemsRem, 0, wxALIGN_BOTTOM, 5 ); + bSizer166->Add( bSizer170, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); - m_staticText84 = new wxStaticText( m_panelProgress, wxID_ANY, _("Speed:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText84->Wrap( -1 ); - fgSizer10->Add( m_staticText84, 0, wxALIGN_BOTTOM, 5 ); - m_staticTextSpeed = new wxStaticText( m_panelProgress, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextSpeed->Wrap( -1 ); - m_staticTextSpeed->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); + bSizer166->Add( 0, 5, 0, 0, 5 ); - fgSizer10->Add( m_staticTextSpeed, 0, wxALIGN_BOTTOM, 5 ); - m_staticTextLabelRemTime = new wxStaticText( m_panelProgress, wxID_ANY, _("Time remaining:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextLabelRemTime->Wrap( -1 ); - fgSizer10->Add( m_staticTextLabelRemTime, 0, wxALIGN_BOTTOM, 5 ); + m_panelItemsRemaining->SetSizer( bSizer166 ); + m_panelItemsRemaining->Layout(); + bSizer166->Fit( m_panelItemsRemaining ); + bSizer164->Add( m_panelItemsRemaining, 0, wxTOP|wxEXPAND, 7 ); + + m_panelTimeRemaining = new wxPanel( m_panelProgress, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_panelTimeRemaining->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + + wxBoxSizer* bSizer167; + bSizer167 = new wxBoxSizer( wxVERTICAL ); - m_staticTextRemTime = new wxStaticText( m_panelProgress, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + + bSizer167->Add( 0, 5, 0, 0, 5 ); + + wxStaticText* m_staticText98; + m_staticText98 = new wxStaticText( m_panelTimeRemaining, wxID_ANY, _("Time remaining:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText98->Wrap( -1 ); + bSizer167->Add( m_staticText98, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); + + m_staticTextRemTime = new wxStaticText( m_panelTimeRemaining, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextRemTime->Wrap( -1 ); m_staticTextRemTime->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); - fgSizer10->Add( m_staticTextRemTime, 0, wxALIGN_BOTTOM, 5 ); + bSizer167->Add( m_staticTextRemTime, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); + - m_staticTextLabelElapsedTime = new wxStaticText( m_panelProgress, wxID_ANY, _("Time elapsed:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextLabelElapsedTime->Wrap( -1 ); - fgSizer10->Add( m_staticTextLabelElapsedTime, 0, wxALIGN_BOTTOM, 5 ); + bSizer167->Add( 0, 5, 0, 0, 5 ); - m_staticTextTimeElapsed = new wxStaticText( m_panelProgress, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + + m_panelTimeRemaining->SetSizer( bSizer167 ); + m_panelTimeRemaining->Layout(); + bSizer167->Fit( m_panelTimeRemaining ); + bSizer164->Add( m_panelTimeRemaining, 0, wxTOP|wxEXPAND, 7 ); + + wxPanel* m_panelTimeElapsed; + m_panelTimeElapsed = new wxPanel( m_panelProgress, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_panelTimeElapsed->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + + wxBoxSizer* bSizer168; + bSizer168 = new wxBoxSizer( wxVERTICAL ); + + + bSizer168->Add( 0, 5, 0, 0, 5 ); + + wxStaticText* m_staticText961; + m_staticText961 = new wxStaticText( m_panelTimeElapsed, wxID_ANY, _("Time elapsed:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText961->Wrap( -1 ); + bSizer168->Add( m_staticText961, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); + + m_staticTextTimeElapsed = new wxStaticText( m_panelTimeElapsed, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextTimeElapsed->Wrap( -1 ); m_staticTextTimeElapsed->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); - fgSizer10->Add( m_staticTextTimeElapsed, 0, wxALIGN_BOTTOM, 5 ); + bSizer168->Add( m_staticTextTimeElapsed, 0, wxALIGN_BOTTOM|wxRIGHT|wxLEFT, 5 ); + + + bSizer168->Add( 0, 5, 0, 0, 5 ); + + m_panelTimeElapsed->SetSizer( bSizer168 ); + m_panelTimeElapsed->Layout(); + bSizer168->Fit( m_panelTimeElapsed ); + bSizer164->Add( m_panelTimeElapsed, 0, wxTOP|wxEXPAND, 7 ); - bSizer171->Add( fgSizer10, 0, wxALIGN_CENTER_VERTICAL, 5 ); + + bSizer171->Add( bSizer164, 0, wxALIGN_CENTER_VERTICAL, 5 ); bSizer171->Add( 10, 0, 0, 0, 5 ); - m_panelGraph = new zen::Graph2D( m_panelProgress, wxID_ANY, wxDefaultPosition, wxSize( 340,150 ), wxTAB_TRAVERSAL ); - m_panelGraph->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxBoxSizer* bSizer161; + bSizer161 = new wxBoxSizer( wxVERTICAL ); + + + bSizer161->Add( 0, 15, 0, 0, 5 ); + + m_panelGraphBytes = new zen::Graph2D( m_panelProgress, wxID_ANY, wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_panelGraphBytes->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + bSizer161->Add( m_panelGraphBytes, 1, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); + + m_panelGraphItems = new zen::Graph2D( m_panelProgress, wxID_ANY, wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_panelGraphItems->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + bSizer161->Add( m_panelGraphItems, 1, wxEXPAND, 5 ); - bSizer171->Add( m_panelGraph, 1, wxALIGN_CENTER_VERTICAL|wxEXPAND|wxTOP, 10 ); + + bSizer161->Add( 420, 0, 0, 0, 5 ); + + + bSizer171->Add( bSizer161, 1, wxEXPAND, 5 ); + + + bSizer171->Add( 0, 230, 0, 0, 5 ); bSizer173->Add( bSizer171, 1, wxEXPAND, 5 ); @@ -1404,26 +1520,10 @@ SyncProgressDlgGenerated::SyncProgressDlgGenerated( wxWindow* parent, wxWindowID this->SetSizer( bSizerRoot ); this->Layout(); bSizerRoot->Fit( this ); - - this->Centre( wxBOTH ); - - // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncProgressDlgGenerated::OnClose ) ); - this->Connect( wxEVT_ICONIZE, wxIconizeEventHandler( SyncProgressDlgGenerated::OnIconize ) ); - m_buttonClose->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncProgressDlgGenerated::OnOkay ), NULL, this ); - m_buttonPause->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncProgressDlgGenerated::OnPause ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncProgressDlgGenerated::OnCancel ), NULL, this ); } -SyncProgressDlgGenerated::~SyncProgressDlgGenerated() +SyncProgressPanelGenerated::~SyncProgressPanelGenerated() { - // Disconnect Events - this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncProgressDlgGenerated::OnClose ) ); - this->Disconnect( wxEVT_ICONIZE, wxIconizeEventHandler( SyncProgressDlgGenerated::OnIconize ) ); - m_buttonClose->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncProgressDlgGenerated::OnOkay ), NULL, this ); - m_buttonPause->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncProgressDlgGenerated::OnPause ), NULL, this ); - m_buttonCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncProgressDlgGenerated::OnCancel ), NULL, this ); - } LogPanelGenerated::LogPanelGenerated( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style ) : wxPanel( parent, id, pos, size, style ) @@ -1439,13 +1539,13 @@ LogPanelGenerated::LogPanelGenerated( wxWindow* parent, wxWindowID id, const wxP wxBoxSizer* bSizer154; bSizer154 = new wxBoxSizer( wxVERTICAL ); - m_bpButtonErrors = new ToggleButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), wxBU_AUTODRAW ); + m_bpButtonErrors = new ToggleButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 49,49 ), wxBU_AUTODRAW ); bSizer154->Add( m_bpButtonErrors, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); - m_bpButtonWarnings = new ToggleButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), wxBU_AUTODRAW ); + m_bpButtonWarnings = new ToggleButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 49,49 ), wxBU_AUTODRAW ); bSizer154->Add( m_bpButtonWarnings, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); - m_bpButtonInfo = new ToggleButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 48,48 ), wxBU_AUTODRAW ); + m_bpButtonInfo = new ToggleButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 49,49 ), wxBU_AUTODRAW ); bSizer154->Add( m_bpButtonInfo, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); @@ -1481,6 +1581,200 @@ LogPanelGenerated::~LogPanelGenerated() } +SyncConfirmationDlgGenerated::SyncConfirmationDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + + wxBoxSizer* bSizer134; + bSizer134 = new wxBoxSizer( wxVERTICAL ); + + m_panelStatistics = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panelStatistics->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + + wxBoxSizer* bSizer169; + bSizer169 = new wxBoxSizer( wxHORIZONTAL ); + + m_bitmapSync = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + bSizer169->Add( m_bitmapSync, 0, wxALL, 10 ); + + m_staticline39 = new wxStaticLine( m_panelStatistics, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); + bSizer169->Add( m_staticline39, 0, wxEXPAND, 5 ); + + wxBoxSizer* bSizer162; + bSizer162 = new wxBoxSizer( wxVERTICAL ); + + wxBoxSizer* bSizer172; + bSizer172 = new wxBoxSizer( wxVERTICAL ); + + m_staticText84 = new wxStaticText( m_panelStatistics, wxID_ANY, _("Variant"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText84->Wrap( -1 ); + bSizer172->Add( m_staticText84, 0, wxALL, 5 ); + + m_staticTextVariant = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextVariant->Wrap( -1 ); + m_staticTextVariant->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); + + bSizer172->Add( m_staticTextVariant, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + + bSizer172->Add( 0, 10, 0, 0, 5 ); + + + bSizer162->Add( bSizer172, 0, wxEXPAND, 5 ); + + m_staticline14 = new wxStaticLine( m_panelStatistics, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer162->Add( m_staticline14, 0, wxEXPAND, 5 ); + + m_staticText83 = new wxStaticText( m_panelStatistics, wxID_ANY, _("Statistics"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText83->Wrap( -1 ); + bSizer162->Add( m_staticText83, 0, wxALL, 5 ); + + wxFlexGridSizer* fgSizer11; + fgSizer11 = new wxFlexGridSizer( 2, 7, 2, 5 ); + fgSizer11->SetFlexibleDirection( wxBOTH ); + fgSizer11->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + + m_bitmapCreateLeft = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + m_bitmapCreateLeft->SetToolTip( _("Number of files and folders that will be created") ); + + fgSizer11->Add( m_bitmapCreateLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + m_bitmapUpdateLeft = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + m_bitmapUpdateLeft->SetToolTip( _("Number of files that will be overwritten") ); + + fgSizer11->Add( m_bitmapUpdateLeft, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_bitmapDeleteLeft = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + m_bitmapDeleteLeft->SetToolTip( _("Number of files and folders that will be deleted") ); + + fgSizer11->Add( m_bitmapDeleteLeft, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_bitmapData = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + m_bitmapData->SetToolTip( _("Total bytes to copy") ); + + fgSizer11->Add( m_bitmapData, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + m_bitmapDeleteRight = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + m_bitmapDeleteRight->SetToolTip( _("Number of files and folders that will be deleted") ); + + fgSizer11->Add( m_bitmapDeleteRight, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_bitmapUpdateRight = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + m_bitmapUpdateRight->SetToolTip( _("Number of files that will be overwritten") ); + + fgSizer11->Add( m_bitmapUpdateRight, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + m_bitmapCreateRight = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); + m_bitmapCreateRight->SetToolTip( _("Number of files and folders that will be created") ); + + fgSizer11->Add( m_bitmapCreateRight, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + m_staticTextCreateLeft = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextCreateLeft->Wrap( -1 ); + m_staticTextCreateLeft->SetToolTip( _("Number of files and folders that will be created") ); + + fgSizer11->Add( m_staticTextCreateLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + m_staticTextUpdateLeft = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextUpdateLeft->Wrap( -1 ); + m_staticTextUpdateLeft->SetToolTip( _("Number of files that will be overwritten") ); + + fgSizer11->Add( m_staticTextUpdateLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + m_staticTextDeleteLeft = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextDeleteLeft->Wrap( -1 ); + m_staticTextDeleteLeft->SetToolTip( _("Number of files and folders that will be deleted") ); + + fgSizer11->Add( m_staticTextDeleteLeft, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_staticTextData = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextData->Wrap( -1 ); + m_staticTextData->SetToolTip( _("Total bytes to copy") ); + + fgSizer11->Add( m_staticTextData, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_staticTextDeleteRight = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextDeleteRight->Wrap( -1 ); + m_staticTextDeleteRight->SetToolTip( _("Number of files and folders that will be deleted") ); + + fgSizer11->Add( m_staticTextDeleteRight, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_staticTextUpdateRight = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextUpdateRight->Wrap( -1 ); + m_staticTextUpdateRight->SetToolTip( _("Number of files that will be overwritten") ); + + fgSizer11->Add( m_staticTextUpdateRight, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + m_staticTextCreateRight = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextCreateRight->Wrap( -1 ); + m_staticTextCreateRight->SetToolTip( _("Number of files and folders that will be created") ); + + fgSizer11->Add( m_staticTextCreateRight, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer162->Add( fgSizer11, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxRIGHT|wxLEFT, 25 ); + + + bSizer162->Add( 0, 5, 0, 0, 5 ); + + + bSizer169->Add( bSizer162, 0, 0, 5 ); + + + m_panelStatistics->SetSizer( bSizer169 ); + m_panelStatistics->Layout(); + bSizer169->Fit( m_panelStatistics ); + bSizer134->Add( m_panelStatistics, 0, wxEXPAND, 5 ); + + m_staticline12 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer134->Add( m_staticline12, 0, wxEXPAND, 5 ); + + wxBoxSizer* bSizer164; + bSizer164 = new wxBoxSizer( wxVERTICAL ); + + m_checkBoxDontShowAgain = new wxCheckBox( this, wxID_ANY, _("Don't show this dialog again"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizer164->Add( m_checkBoxDontShowAgain, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT, 5 ); + + bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); + + m_buttonStartSync = new wxButton( this, wxID_OK, _("Start"), wxDefaultPosition, wxSize( -1,30 ), 0 ); + m_buttonStartSync->SetDefault(); + m_buttonStartSync->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); + + bSizerStdButtons->Add( m_buttonStartSync, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1,30 ), 0 ); + bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bSizer164->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 ); + + + bSizer134->Add( bSizer164, 1, wxEXPAND, 5 ); + + + this->SetSizer( bSizer134 ); + this->Layout(); + bSizer134->Fit( this ); + + this->Centre( wxBOTH ); + + // Connect Events + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncConfirmationDlgGenerated::OnClose ) ); + m_buttonStartSync->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::OnStartSync ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::OnCancel ), NULL, this ); +} + +SyncConfirmationDlgGenerated::~SyncConfirmationDlgGenerated() +{ + // Disconnect Events + this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncConfirmationDlgGenerated::OnClose ) ); + m_buttonStartSync->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::OnStartSync ), NULL, this ); + m_buttonCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::OnCancel ), NULL, this ); + +} + CmpCfgDlgGenerated::CmpCfgDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) { this->SetSizeHints( wxDefaultSize, wxDefaultSize ); @@ -3191,200 +3485,6 @@ GlobalSettingsDlgGenerated::~GlobalSettingsDlgGenerated() } -SyncConfirmationDlgGenerated::SyncConfirmationDlgGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) -{ - this->SetSizeHints( wxDefaultSize, wxDefaultSize ); - this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); - - wxBoxSizer* bSizer134; - bSizer134 = new wxBoxSizer( wxVERTICAL ); - - m_panelStatistics = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - m_panelStatistics->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - - wxBoxSizer* bSizer169; - bSizer169 = new wxBoxSizer( wxHORIZONTAL ); - - m_bitmapSync = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - bSizer169->Add( m_bitmapSync, 0, wxALL, 10 ); - - m_staticline39 = new wxStaticLine( m_panelStatistics, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); - bSizer169->Add( m_staticline39, 0, wxEXPAND, 5 ); - - wxBoxSizer* bSizer162; - bSizer162 = new wxBoxSizer( wxVERTICAL ); - - wxBoxSizer* bSizer172; - bSizer172 = new wxBoxSizer( wxVERTICAL ); - - m_staticText84 = new wxStaticText( m_panelStatistics, wxID_ANY, _("Variant"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText84->Wrap( -1 ); - bSizer172->Add( m_staticText84, 0, wxALL, 5 ); - - m_staticTextVariant = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextVariant->Wrap( -1 ); - m_staticTextVariant->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); - - bSizer172->Add( m_staticTextVariant, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - - - bSizer172->Add( 0, 10, 0, 0, 5 ); - - - bSizer162->Add( bSizer172, 0, wxEXPAND, 5 ); - - m_staticline14 = new wxStaticLine( m_panelStatistics, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer162->Add( m_staticline14, 0, wxEXPAND, 5 ); - - m_staticText83 = new wxStaticText( m_panelStatistics, wxID_ANY, _("Statistics"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText83->Wrap( -1 ); - bSizer162->Add( m_staticText83, 0, wxALL, 5 ); - - wxFlexGridSizer* fgSizer11; - fgSizer11 = new wxFlexGridSizer( 2, 7, 2, 5 ); - fgSizer11->SetFlexibleDirection( wxBOTH ); - fgSizer11->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); - - m_bitmapCreateLeft = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - m_bitmapCreateLeft->SetToolTip( _("Number of files and folders that will be created") ); - - fgSizer11->Add( m_bitmapCreateLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); - - m_bitmapUpdateLeft = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - m_bitmapUpdateLeft->SetToolTip( _("Number of files that will be overwritten") ); - - fgSizer11->Add( m_bitmapUpdateLeft, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_bitmapDeleteLeft = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - m_bitmapDeleteLeft->SetToolTip( _("Number of files and folders that will be deleted") ); - - fgSizer11->Add( m_bitmapDeleteLeft, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_bitmapData = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - m_bitmapData->SetToolTip( _("Total bytes to copy") ); - - fgSizer11->Add( m_bitmapData, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); - - m_bitmapDeleteRight = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - m_bitmapDeleteRight->SetToolTip( _("Number of files and folders that will be deleted") ); - - fgSizer11->Add( m_bitmapDeleteRight, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_bitmapUpdateRight = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - m_bitmapUpdateRight->SetToolTip( _("Number of files that will be overwritten") ); - - fgSizer11->Add( m_bitmapUpdateRight, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); - - m_bitmapCreateRight = new wxStaticBitmap( m_panelStatistics, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); - m_bitmapCreateRight->SetToolTip( _("Number of files and folders that will be created") ); - - fgSizer11->Add( m_bitmapCreateRight, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); - - m_staticTextCreateLeft = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextCreateLeft->Wrap( -1 ); - m_staticTextCreateLeft->SetToolTip( _("Number of files and folders that will be created") ); - - fgSizer11->Add( m_staticTextCreateLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); - - m_staticTextUpdateLeft = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextUpdateLeft->Wrap( -1 ); - m_staticTextUpdateLeft->SetToolTip( _("Number of files that will be overwritten") ); - - fgSizer11->Add( m_staticTextUpdateLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); - - m_staticTextDeleteLeft = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextDeleteLeft->Wrap( -1 ); - m_staticTextDeleteLeft->SetToolTip( _("Number of files and folders that will be deleted") ); - - fgSizer11->Add( m_staticTextDeleteLeft, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_staticTextData = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextData->Wrap( -1 ); - m_staticTextData->SetToolTip( _("Total bytes to copy") ); - - fgSizer11->Add( m_staticTextData, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_staticTextDeleteRight = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextDeleteRight->Wrap( -1 ); - m_staticTextDeleteRight->SetToolTip( _("Number of files and folders that will be deleted") ); - - fgSizer11->Add( m_staticTextDeleteRight, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_staticTextUpdateRight = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextUpdateRight->Wrap( -1 ); - m_staticTextUpdateRight->SetToolTip( _("Number of files that will be overwritten") ); - - fgSizer11->Add( m_staticTextUpdateRight, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_staticTextCreateRight = new wxStaticText( m_panelStatistics, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextCreateRight->Wrap( -1 ); - m_staticTextCreateRight->SetToolTip( _("Number of files and folders that will be created") ); - - fgSizer11->Add( m_staticTextCreateRight, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - - bSizer162->Add( fgSizer11, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxRIGHT|wxLEFT, 25 ); - - - bSizer162->Add( 0, 5, 0, 0, 5 ); - - - bSizer169->Add( bSizer162, 0, 0, 5 ); - - - m_panelStatistics->SetSizer( bSizer169 ); - m_panelStatistics->Layout(); - bSizer169->Fit( m_panelStatistics ); - bSizer134->Add( m_panelStatistics, 0, wxEXPAND, 5 ); - - m_staticline12 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer134->Add( m_staticline12, 0, wxEXPAND, 5 ); - - wxBoxSizer* bSizer164; - bSizer164 = new wxBoxSizer( wxVERTICAL ); - - m_checkBoxDontShowAgain = new wxCheckBox( this, wxID_ANY, _("Don't show this dialog again"), wxDefaultPosition, wxDefaultSize, 0 ); - bSizer164->Add( m_checkBoxDontShowAgain, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT, 5 ); - - bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); - - m_buttonStartSync = new wxButton( this, wxID_OK, _("Start"), wxDefaultPosition, wxSize( -1,30 ), 0 ); - m_buttonStartSync->SetDefault(); - m_buttonStartSync->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); - - bSizerStdButtons->Add( m_buttonStartSync, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - - m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1,30 ), 0 ); - bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); - - - bSizer164->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 ); - - - bSizer134->Add( bSizer164, 1, wxEXPAND, 5 ); - - - this->SetSizer( bSizer134 ); - this->Layout(); - bSizer134->Fit( this ); - - this->Centre( wxBOTH ); - - // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncConfirmationDlgGenerated::OnClose ) ); - m_buttonStartSync->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::OnStartSync ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::OnCancel ), NULL, this ); -} - -SyncConfirmationDlgGenerated::~SyncConfirmationDlgGenerated() -{ - // Disconnect Events - this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncConfirmationDlgGenerated::OnClose ) ); - m_buttonStartSync->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::OnStartSync ), NULL, this ); - m_buttonCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::OnCancel ), NULL, this ); - -} - PopupDialogGenerated::PopupDialogGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) { this->SetSizeHints( wxDefaultSize, wxDefaultSize ); diff --git a/ui/gui_generated.h b/ui/gui_generated.h index 27395e33..f06ebf93 100644 --- a/ui/gui_generated.h +++ b/ui/gui_generated.h @@ -45,9 +45,9 @@ namespace zen{ class TripleSplitter; } #include <wx/gauge.h> #include <wx/animate.h> #include <wx/notebook.h> +#include <wx/dialog.h> #include <wx/tglbtn.h> #include <wx/choice.h> -#include <wx/dialog.h> #include <wx/spinctrl.h> #include <wx/hyperlink.h> #include <wx/grid.h> @@ -290,61 +290,48 @@ class CompareProgressDlgGenerated : public wxPanel }; /////////////////////////////////////////////////////////////////////////////// -/// Class SyncProgressDlgGenerated +/// Class SyncProgressPanelGenerated /////////////////////////////////////////////////////////////////////////////// -class SyncProgressDlgGenerated : public wxFrame +class SyncProgressPanelGenerated : public wxPanel { private: protected: - wxBoxSizer* bSizerRoot; wxBoxSizer* bSizer42; + wxBoxSizer* bSizer171; + wxStaticText* m_staticText87; + + public: + wxBoxSizer* bSizerRoot; wxStaticBitmap* m_bitmapStatus; wxStaticText* m_staticTextPhase; wxAnimationCtrl* m_animCtrlSyncing; - wxStaticLine* m_staticlineHeader; - wxPanel* m_panelProgress; + wxBitmapButton* m_bpButtonMinimizeToTray; + wxBoxSizer* bSizerStatusText; wxStaticText* m_staticTextStatus; - wxBoxSizer* bSizer171; - wxStaticText* m_staticTextLabelItemsProc; - wxBoxSizer* bSizerItemsProc; + wxPanel* m_panelProgress; + wxPanel* m_panelItemsProcessed; wxStaticText* m_staticTextProcessedObj; wxStaticText* m_staticTextDataProcessed; - wxStaticText* m_staticTextLabelItemsRem; - wxBoxSizer* bSizerItemsRem; + wxPanel* m_panelItemsRemaining; wxStaticText* m_staticTextRemainingObj; wxStaticText* m_staticTextDataRemaining; - wxStaticText* m_staticText84; - wxStaticText* m_staticTextSpeed; - wxStaticText* m_staticTextLabelRemTime; + wxPanel* m_panelTimeRemaining; wxStaticText* m_staticTextRemTime; - wxStaticText* m_staticTextLabelElapsedTime; wxStaticText* m_staticTextTimeElapsed; - zen::Graph2D* m_panelGraph; + zen::Graph2D* m_panelGraphBytes; + zen::Graph2D* m_panelGraphItems; wxNotebook* m_notebookResult; wxStaticLine* m_staticlineFooter; wxBoxSizer* bSizerStdButtons; wxBoxSizer* bSizerExecFinished; - wxStaticText* m_staticText87; ExecFinishedBox* m_comboBoxExecFinished; wxButton* m_buttonClose; wxButton* m_buttonPause; wxButton* m_buttonCancel; - // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnIconize( wxIconizeEvent& event ) { event.Skip(); } - virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } - virtual void OnPause( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } - - - public: - wxGauge* m_gauge1; - - SyncProgressDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL ); - - ~SyncProgressDlgGenerated(); + SyncProgressPanelGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxTAB_TRAVERSAL ); + ~SyncProgressPanelGenerated(); }; @@ -376,6 +363,54 @@ class LogPanelGenerated : public wxPanel }; /////////////////////////////////////////////////////////////////////////////// +/// Class SyncConfirmationDlgGenerated +/////////////////////////////////////////////////////////////////////////////// +class SyncConfirmationDlgGenerated : public wxDialog +{ + private: + + protected: + wxPanel* m_panelStatistics; + wxStaticBitmap* m_bitmapSync; + wxStaticLine* m_staticline39; + wxStaticText* m_staticText84; + wxStaticText* m_staticTextVariant; + wxStaticLine* m_staticline14; + wxStaticText* m_staticText83; + wxStaticBitmap* m_bitmapCreateLeft; + wxStaticBitmap* m_bitmapUpdateLeft; + wxStaticBitmap* m_bitmapDeleteLeft; + wxStaticBitmap* m_bitmapData; + wxStaticBitmap* m_bitmapDeleteRight; + wxStaticBitmap* m_bitmapUpdateRight; + wxStaticBitmap* m_bitmapCreateRight; + wxStaticText* m_staticTextCreateLeft; + wxStaticText* m_staticTextUpdateLeft; + wxStaticText* m_staticTextDeleteLeft; + wxStaticText* m_staticTextData; + wxStaticText* m_staticTextDeleteRight; + wxStaticText* m_staticTextUpdateRight; + wxStaticText* m_staticTextCreateRight; + wxStaticLine* m_staticline12; + wxCheckBox* m_checkBoxDontShowAgain; + wxBoxSizer* bSizerStdButtons; + wxButton* m_buttonStartSync; + wxButton* m_buttonCancel; + + // Virtual event handlers, overide them in your derived class + virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } + virtual void OnStartSync( wxCommandEvent& event ) { event.Skip(); } + virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + + + public: + + SyncConfirmationDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Start synchronization"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE ); + ~SyncConfirmationDlgGenerated(); + +}; + +/////////////////////////////////////////////////////////////////////////////// /// Class CmpCfgDlgGenerated /////////////////////////////////////////////////////////////////////////////// class CmpCfgDlgGenerated : public wxDialog @@ -571,7 +606,7 @@ class BatchDlgGenerated : public wxDialog public: FolderHistoryBox* m_logfileDir; - BatchDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Save as batch job"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, 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 = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); ~BatchDlgGenerated(); }; @@ -664,7 +699,7 @@ class MessageDlgGenerated : public wxDialog public: - MessageDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER ); + MessageDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); ~MessageDlgGenerated(); }; @@ -698,7 +733,7 @@ class DeleteDlgGenerated : public wxDialog public: - DeleteDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Delete"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER ); + DeleteDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Delete"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxRESIZE_BORDER ); ~DeleteDlgGenerated(); }; @@ -756,7 +791,7 @@ class FilterDlgGenerated : public wxDialog public: - FilterDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Configure filter"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER ); + FilterDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Configure filter"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxRESIZE_BORDER ); ~FilterDlgGenerated(); }; @@ -810,54 +845,6 @@ class GlobalSettingsDlgGenerated : public wxDialog }; /////////////////////////////////////////////////////////////////////////////// -/// Class SyncConfirmationDlgGenerated -/////////////////////////////////////////////////////////////////////////////// -class SyncConfirmationDlgGenerated : public wxDialog -{ - private: - - protected: - wxPanel* m_panelStatistics; - wxStaticBitmap* m_bitmapSync; - wxStaticLine* m_staticline39; - wxStaticText* m_staticText84; - wxStaticText* m_staticTextVariant; - wxStaticLine* m_staticline14; - wxStaticText* m_staticText83; - wxStaticBitmap* m_bitmapCreateLeft; - wxStaticBitmap* m_bitmapUpdateLeft; - wxStaticBitmap* m_bitmapDeleteLeft; - wxStaticBitmap* m_bitmapData; - wxStaticBitmap* m_bitmapDeleteRight; - wxStaticBitmap* m_bitmapUpdateRight; - wxStaticBitmap* m_bitmapCreateRight; - wxStaticText* m_staticTextCreateLeft; - wxStaticText* m_staticTextUpdateLeft; - wxStaticText* m_staticTextDeleteLeft; - wxStaticText* m_staticTextData; - wxStaticText* m_staticTextDeleteRight; - wxStaticText* m_staticTextUpdateRight; - wxStaticText* m_staticTextCreateRight; - wxStaticLine* m_staticline12; - wxCheckBox* m_checkBoxDontShowAgain; - wxBoxSizer* bSizerStdButtons; - wxButton* m_buttonStartSync; - wxButton* m_buttonCancel; - - // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnStartSync( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } - - - public: - - SyncConfirmationDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Start synchronization"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE ); - ~SyncConfirmationDlgGenerated(); - -}; - -/////////////////////////////////////////////////////////////////////////////// /// Class PopupDialogGenerated /////////////////////////////////////////////////////////////////////////////// class PopupDialogGenerated : public wxDialog diff --git a/ui/gui_status_handler.cpp b/ui/gui_status_handler.cpp index db92c24c..0131af3f 100644 --- a/ui/gui_status_handler.cpp +++ b/ui/gui_status_handler.cpp @@ -8,6 +8,7 @@ #include <wx/wupdlock.h> #include <wx+/shell_execute.h> #include <wx+/button.h> +#include <wx/app.h> #include "msg_popup.h" #include "main_dlg.h" #include "exec_finished_box.h" @@ -169,13 +170,12 @@ void CompareStatusHandler::abortThisProcess() //######################################################################################################## -SyncStatusHandler::SyncStatusHandler(wxTopLevelWindow* parentDlg, +SyncStatusHandler::SyncStatusHandler(wxFrame* parentDlg, size_t lastSyncsLogFileSizeMax, OnGuiError handleError, const std::wstring& jobName, const std::wstring& execWhenFinished, std::vector<std::wstring>& execFinishedHistory) : - parentDlg_(parentDlg), progressDlg(createProgressDialog(*this, [this] { this->onProgressDialogTerminate(); }, *this, parentDlg, true, jobName, execWhenFinished, execFinishedHistory)), lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax), handleError_(handleError), @@ -267,7 +267,7 @@ SyncStatusHandler::~SyncStatusHandler() //-> nicely manages dialog lifetime while (progressDlg) { - updateUiNow(); //*first* refresh GUI (removing flicker) before sleeping! + wxTheApp->Yield(); //*first* refresh GUI (removing flicker) before sleeping! boost::this_thread::sleep(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL)); } } @@ -313,7 +313,7 @@ ProcessCallback::Response SyncStatusHandler::reportError(const std::wstring& err forceUiRefresh(); bool ignoreNextErrors = false; - switch (showErrorDlg(parentDlg_, + switch (showErrorDlg(progressDlg->getWindowIfVisible(), ReturnErrorDlg::BUTTON_IGNORE | ReturnErrorDlg::BUTTON_RETRY | ReturnErrorDlg::BUTTON_CANCEL, errorMessage, &ignoreNextErrors)) @@ -357,7 +357,7 @@ void SyncStatusHandler::reportFatalError(const std::wstring& errorMessage) forceUiRefresh(); bool ignoreNextErrors = false; - switch (showFatalErrorDlg(parentDlg_, + switch (showFatalErrorDlg(progressDlg->getWindowIfVisible(), ReturnFatalErrorDlg::BUTTON_IGNORE | ReturnFatalErrorDlg::BUTTON_CANCEL, errorMessage, &ignoreNextErrors)) { @@ -395,7 +395,7 @@ void SyncStatusHandler::reportWarning(const std::wstring& warningMessage, bool& forceUiRefresh(); bool dontWarnAgain = false; - switch (showWarningDlg(parentDlg_, + switch (showWarningDlg(progressDlg->getWindowIfVisible(), ReturnWarningDlg::BUTTON_IGNORE | ReturnWarningDlg::BUTTON_CANCEL, warningMessage, dontWarnAgain)) { diff --git a/ui/gui_status_handler.h b/ui/gui_status_handler.h index f7b58f05..30efe3fb 100644 --- a/ui/gui_status_handler.h +++ b/ui/gui_status_handler.h @@ -49,7 +49,7 @@ private: class SyncStatusHandler : public zen::StatusHandler { public: - SyncStatusHandler(wxTopLevelWindow* parentDlg, + SyncStatusHandler(wxFrame* parentDlg, size_t lastSyncsLogFileSizeMax, xmlAccess::OnGuiError handleError, const std::wstring& jobName, @@ -70,7 +70,6 @@ private: virtual void abortThisProcess(); //throw GuiAbortProcess void onProgressDialogTerminate(); - wxTopLevelWindow* parentDlg_; SyncProgressDialog* progressDlg; //managed to have shorter lifetime than this handler! const size_t lastSyncsLogFileSizeMax_; xmlAccess::OnGuiError handleError_; diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp index ead99c6a..eaae0545 100644 --- a/ui/main_dlg.cpp +++ b/ui/main_dlg.cpp @@ -8,7 +8,6 @@ #include <zen/format_unit.h> #include <zen/file_handling.h> #include <zen/serialize.h> -//#include <zen/file_id.h> //#include <zen/perf.h> #include <zen/thread.h> #include <wx/clipbrd.h> @@ -97,23 +96,26 @@ public: virtual bool acceptDrop(const std::vector<wxString>& droppedFiles, const wxPoint& clientPos, const wxWindow& wnd) { - if (droppedFiles.empty()) + if (std::any_of(droppedFiles.begin(), droppedFiles.end(), [](const wxString& filename) + { + using namespace xmlAccess; + switch (getXmlType(utfCvrtTo<Zstring>(filename))) //throw() + { + case XML_TYPE_GUI: + case XML_TYPE_BATCH: + return true; + case XML_TYPE_GLOBAL: + case XML_TYPE_OTHER: + break; + } return false; - - switch (xmlAccess::getMergeType(toZ(droppedFiles))) //throw() + })) { - case xmlAccess::MERGE_BATCH: - case xmlAccess::MERGE_GUI: - case xmlAccess::MERGE_GUI_BATCH: - mainDlg_.loadConfiguration(toZ(droppedFiles)); - return false; - - case xmlAccess::MERGE_OTHER: - //=> return true: change directory selection via drag and drop - break; + mainDlg_.loadConfiguration(toZ(droppedFiles)); + return false; } - //mainDlg_.clearGrid(); + //=> return true: change directory selection via drag and drop return true; } @@ -321,51 +323,43 @@ xmlAccess::XmlGlobalSettings retrieveGlobalCfgFromDisk() //blocks on GUI on erro { assert(false); if (e.getSeverity() != FfsXmlError::WARNING) //ignore parsing errors: should be migration problems only *cross-fingers* - wxMessageBox(e.toString(), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(e.toString(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR); } return globalCfg; } } -void MainDialog::create(const std::vector<Zstring>& cfgFileNames) +void MainDialog::create() { using namespace xmlAccess; const XmlGlobalSettings globalSettings = retrieveGlobalCfgFromDisk(); - std::vector<Zstring> filenames; - if (!cfgFileNames.empty()) //1. this one has priority - filenames = cfgFileNames; - else //FFS default startup: use last used selection - { - filenames = globalSettings.gui.lastUsedConfigFiles; //2. now try last used files - - //------------------------------------------------------------------------------------------ - //check existence of all directories in parallel! + std::vector<Zstring> filenames = globalSettings.gui.lastUsedConfigFiles; //2. now try last used files - RunUntilFirstHit<NullType> findFirstMissing; + //------------------------------------------------------------------------------------------ + //check existence of all files in parallel: + RunUntilFirstHit<NullType> findFirstMissing; - std::for_each(filenames.begin(), filenames.end(), [&](const Zstring& filename) - { - findFirstMissing.addJob([=] { return filename.empty() /*ever empty??*/ || !fileExists(filename) ? zen::make_unique<NullType>() : nullptr; }); - }); - //potentially slow network access: give all checks 500ms to finish - const bool allFilesExist = findFirstMissing.timedWait(boost::posix_time::milliseconds(500)) && //false: time elapsed - !findFirstMissing.get(); //no missing - if (!allFilesExist) - filenames.clear(); //we do NOT want to show an error due to last config file missing on application start! - //------------------------------------------------------------------------------------------ + std::for_each(filenames.begin(), filenames.end(), [&](const Zstring& filename) + { + findFirstMissing.addJob([=] { return filename.empty() /*ever empty??*/ || !fileExists(filename) ? zen::make_unique<NullType>() : nullptr; }); + }); + //potentially slow network access: give all checks 500ms to finish + const bool allFilesExist = findFirstMissing.timedWait(boost::posix_time::milliseconds(500)) && //false: time elapsed + !findFirstMissing.get(); //no missing + if (!allFilesExist) + filenames.clear(); //we do NOT want to show an error due to last config file missing on application start! + //------------------------------------------------------------------------------------------ - if (filenames.empty()) - { - if (zen::fileExists(lastRunConfigName())) //3. try to load auto-save config - filenames.push_back(lastRunConfigName()); - } + if (filenames.empty()) + { + if (zen::fileExists(lastRunConfigName())) //3. try to load auto-save config + filenames.push_back(lastRunConfigName()); } XmlGuiConfig guiCfg; //structure to receive gui settings with default values - bool loadCfgSuccess = false; if (filenames.empty()) { //add default exclusion filter: this is only ever relevant when creating new configurations! @@ -379,58 +373,40 @@ void MainDialog::create(const std::vector<Zstring>& cfgFileNames) try { readAnyConfig(filenames, guiCfg); //throw FfsXmlError - loadCfgSuccess = true; } catch (const FfsXmlError& error) { if (error.getSeverity() == FfsXmlError::WARNING) - wxMessageBox(error.toString(), _("Warning"), wxOK | wxICON_WARNING); + wxMessageBox(error.toString(), L"FreeFileSync - " + _("Warning"), wxOK | wxICON_WARNING); //what about simulating changed config on parsing errors???? else - wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(error.toString(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR); } - const bool startComparisonImmediately = !cfgFileNames.empty() && loadCfgSuccess; - //------------------------------------------------------------------------------------------ - create_impl(guiCfg, filenames, globalSettings, startComparisonImmediately); -} - - -void MainDialog::create(const xmlAccess::XmlGuiConfig& guiCfg, - bool startComparison) -{ - create_impl(guiCfg, std::vector<Zstring>(), retrieveGlobalCfgFromDisk(), startComparison); + create(guiCfg, filenames, &globalSettings, false); } void MainDialog::create(const xmlAccess::XmlGuiConfig& guiCfg, const std::vector<Zstring>& referenceFiles, - const xmlAccess::XmlGlobalSettings& globalSettings, + const xmlAccess::XmlGlobalSettings* globalSettings, bool startComparison) { - create_impl(guiCfg, referenceFiles, globalSettings, startComparison); -} - - -void MainDialog::create_impl(const xmlAccess::XmlGuiConfig& guiCfg, - const std::vector<Zstring>& referenceFiles, - const xmlAccess::XmlGlobalSettings& globalSettings, - bool startComparison) -{ + const xmlAccess::XmlGlobalSettings& globSett = globalSettings ? *globalSettings : retrieveGlobalCfgFromDisk(); try { //we need to set language *before* creating MainDialog! - setLanguage(globalSettings.programLanguage); //throw FileError + setLanguage(globSett.programLanguage); //throw FileError } catch (const FileError& e) { - wxMessageBox(e.toString().c_str(), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(e.toString().c_str(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR); //continue! } - MainDialog* frame = new MainDialog(guiCfg, referenceFiles, globalSettings, startComparison); + MainDialog* frame = new MainDialog(guiCfg, referenceFiles, globSett, startComparison); frame->Show(); } @@ -757,7 +733,7 @@ MainDialog::~MainDialog() } catch (const xmlAccess::FfsXmlError& e) { - wxMessageBox(e.toString().c_str(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(e.toString().c_str(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR, this); } try //save "LastRun.ffs_gui" @@ -1026,7 +1002,7 @@ void MainDialog::copySelectionToClipboard(const std::vector<const Grid*>& gridRe } catch (const std::bad_alloc& e) { - wxMessageBox(_("Out of memory.") + L" " + utfCvrtTo<std::wstring>(e.what()), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(_("Out of memory.") + L" " + utfCvrtTo<std::wstring>(e.what()), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR, this); } } @@ -1064,10 +1040,9 @@ std::vector<FileSystemObject*> MainDialog::getTreeSelection() const if (auto root = dynamic_cast<const TreeView::RootNode*>(node.get())) { //select first level of child elements - std::transform(root->baseDirObj_.refSubDirs ().begin(), root->baseDirObj_.refSubDirs ().end(), std::back_inserter(output), [](FileSystemObject& fsObj) { return &fsObj; }); - std::transform(root->baseDirObj_.refSubFiles().begin(), root->baseDirObj_.refSubFiles().end(), std::back_inserter(output), [](FileSystemObject& fsObj) { return &fsObj; }); - std::transform(root->baseDirObj_.refSubLinks().begin(), root->baseDirObj_.refSubLinks().end(), std::back_inserter(output), [](FileSystemObject& fsObj) { return &fsObj; }); - //for (auto& fsObj : root->baseDirObj_.refSubLinks()) output.push_back(&fsObj); -> seriously MSVC, stop this disgrace and implement "range for"! + for (auto& fsObj : root->baseDirObj_.refSubDirs ()) output.push_back(&fsObj); + for (auto& fsObj : root->baseDirObj_.refSubFiles()) output.push_back(&fsObj); + for (auto& fsObj : root->baseDirObj_.refSubLinks()) output.push_back(&fsObj); } else if (auto dir = dynamic_cast<const TreeView::DirNode*>(node.get())) output.push_back(&(dir->dirObj_)); @@ -1170,7 +1145,7 @@ private: { //std::wstring msg = toGuiString(deletionCount) + mainDlg.setStatusBarFullText(statusMsg); - updateUiNow(); + wxTheApp->Yield(); } //context: C callstack message loop => throw()! @@ -1314,6 +1289,31 @@ void MainDialog::openExternalApplication(const wxString& commandline, const std: } } + const size_t massInvokeThreshold = 10; //more than this is likely a user mistake + + if (selectionTmp.size() > massInvokeThreshold) + if (globalCfg.optDialogs.confirmExternalCommandMassInvoke) + { + bool dontAskAgain = false; + switch (showQuestionDlg(this, + ReturnQuestionDlg::BUTTON_YES | ReturnQuestionDlg::BUTTON_CANCEL, + replaceCpy(replaceCpy(_P("Do you really want to execute the command %y for 1 item?", + "Do you really want to execute the command %y for %x items?", selectionTmp.size()), + L"%x", toGuiString(selectionTmp.size())), L"%y", L'\'' + commandline + L'\''), + QuestConfig().setCaption(_("Confirm")).setLabelYes(_("&Execute")). + showCheckBox(dontAskAgain, _("&Don't show this dialog again"), 0))) + { + case ReturnQuestionDlg::BUTTON_YES: + globalCfg.optDialogs.confirmExternalCommandMassInvoke = !dontAskAgain; + break; + + case ReturnQuestionDlg::BUTTON_NO: + assert(false); + case ReturnQuestionDlg::BUTTON_CANCEL: + return; + } + } + //regular command evaluation for (auto it = selectionTmp.begin(); it != selectionTmp.end(); ++it) //context menu calls this function only if selection is not empty! { @@ -1338,7 +1338,8 @@ void MainDialog::openExternalApplication(const wxString& commandline, const std: replace(command, Zstr("%item2_folder%"), dir2 ); auto cmdExp = expandMacros(command); - zen::shellExecute(cmdExp); //shows error message if command is malformed + //caveat: spawning too many threads asynchronously can easily kill a user's desktop session! + zen::shellExecute(cmdExp, selectionTmp.size() > massInvokeThreshold ? EXEC_TYPE_SYNC : EXEC_TYPE_ASYNC); } } @@ -1448,27 +1449,26 @@ void MainDialog::onProcessAsyncTasks(wxEvent& event) void MainDialog::disableAllElements(bool enableAbort) { + //disables all elements (except abort button) that might receive user input during long-running processes: //when changing consider: comparison, synchronization, manual deletion EnableCloseButton(false); //not allowed for synchronization! progress indicator is top window! -> not honored on OS X! - //disables all elements (except abort button) that might receive user input during long-running processes: comparison, deletion - m_panelViewFilter ->Disable(); + //OS X: wxWidgets portability promise is again a mess: http://wxwidgets.10942.n7.nabble.com/Disable-panel-and-appropriate-children-windows-linux-macos-td35357.html + + m_menubar1->EnableTop(0, false); + m_menubar1->EnableTop(1, false); + m_menubar1->EnableTop(2, false); m_bpButtonCmpConfig ->Disable(); - m_panelFilter ->Disable(); - m_panelConfig ->Disable(); m_bpButtonSyncConfig ->Disable(); m_buttonSync ->Disable(); - m_gridMainL ->Disable(); - m_gridMainC ->Disable(); - m_gridMainR ->Disable(); + m_panelDirectoryPairs->Disable(); + m_panelCenter ->Disable(); + m_panelViewFilter ->Disable(); + m_panelFilter ->Disable(); + m_panelConfig ->Disable(); m_panelStatistics ->Disable(); m_gridNavi ->Disable(); - m_panelDirectoryPairs->Disable(); - m_splitterMain ->Disable(); - m_menubar1->EnableTop(0, false); - m_menubar1->EnableTop(1, false); - m_menubar1->EnableTop(2, false); if (enableAbort) { @@ -1492,22 +1492,19 @@ void MainDialog::enableAllElements() EnableCloseButton(true); - m_panelViewFilter ->Enable(); + m_menubar1->EnableTop(0, true); + m_menubar1->EnableTop(1, true); + m_menubar1->EnableTop(2, true); m_bpButtonCmpConfig ->Enable(); - m_panelFilter ->Enable(); - m_panelConfig ->Enable(); m_bpButtonSyncConfig ->Enable(); m_buttonSync ->Enable(); - m_gridMainL ->Enable(); - m_gridMainC ->Enable(); - m_gridMainR ->Enable(); + m_panelDirectoryPairs->Enable(); + m_panelCenter ->Enable(); + m_panelViewFilter ->Enable(); + m_panelFilter ->Enable(); + m_panelConfig ->Enable(); m_panelStatistics ->Enable(); m_gridNavi ->Enable(); - m_panelDirectoryPairs->Enable(); - m_splitterMain ->Enable(); - m_menubar1->EnableTop(0, true); - m_menubar1->EnableTop(1, true); - m_menubar1->EnableTop(2, true); //show compare button m_buttonCancel->Disable(); @@ -1517,6 +1514,9 @@ void MainDialog::enableAllElements() m_panelTopButtons->Layout(); m_panelTopButtons->Enable(); + + //at least wxWidgets on OS X fails to do this after enabling: + Refresh(); } @@ -1540,12 +1540,14 @@ void MainDialog::OnResizeConfigPanel(wxEvent& event) event.Skip(); } + void MainDialog::OnResizeViewPanel(wxEvent& event) { updateSizerOrientation(*bSizerViewFilter, *m_panelViewFilter, 1.0); event.Skip(); } + void MainDialog::OnResizeStatisticsPanel(wxEvent& event) { //updateSizerOrientation(*bSizerStatistics, *m_panelStatistics); @@ -1619,19 +1621,19 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event) { case WXK_NUMPAD_LEFT: case WXK_LEFT: //ALT + <- - setSyncDirManually(getTreeSelection(), SYNC_DIR_LEFT); + setSyncDirManually(getTreeSelection(), SyncDirection::LEFT); return; case WXK_NUMPAD_RIGHT: case WXK_RIGHT: //ALT + -> - setSyncDirManually(getTreeSelection(), SYNC_DIR_RIGHT); + setSyncDirManually(getTreeSelection(), SyncDirection::RIGHT); return; case WXK_NUMPAD_UP: case WXK_NUMPAD_DOWN: case WXK_UP: /* ALT + /|\ */ case WXK_DOWN: /* ALT + \|/ */ - setSyncDirManually(getTreeSelection(), SYNC_DIR_NONE); + setSyncDirManually(getTreeSelection(), SyncDirection::NONE); return; } @@ -1704,19 +1706,19 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) { case WXK_NUMPAD_LEFT: case WXK_LEFT: //ALT + <- - setSyncDirManually(getGridSelection(), SYNC_DIR_LEFT); + setSyncDirManually(getGridSelection(), SyncDirection::LEFT); return; case WXK_NUMPAD_RIGHT: case WXK_RIGHT: //ALT + -> - setSyncDirManually(getGridSelection(), SYNC_DIR_RIGHT); + setSyncDirManually(getGridSelection(), SyncDirection::RIGHT); return; case WXK_NUMPAD_UP: case WXK_NUMPAD_DOWN: case WXK_UP: /* ALT + /|\ */ case WXK_DOWN: /* ALT + \|/ */ - setSyncDirManually(getGridSelection(), SYNC_DIR_NONE); + setSyncDirManually(getGridSelection(), SyncDirection::NONE); return; } @@ -1920,18 +1922,18 @@ void MainDialog::onNaviGridContext(GridClickEvent& event) return mirrorIfRtl(getSyncOpImage(selection[0]->getSyncOperation() != SO_EQUAL ? selection[0]->testSyncOperation(dir) : soDefault)); }; - const wxBitmap opRight = getImage(SYNC_DIR_RIGHT, SO_OVERWRITE_RIGHT); - const wxBitmap opNone = getImage(SYNC_DIR_NONE, SO_DO_NOTHING ); - const wxBitmap opLeft = getImage(SYNC_DIR_LEFT, SO_OVERWRITE_LEFT ); + const wxBitmap opRight = getImage(SyncDirection::RIGHT, SO_OVERWRITE_RIGHT); + const wxBitmap opNone = getImage(SyncDirection::NONE, SO_DO_NOTHING ); + const wxBitmap opLeft = getImage(SyncDirection::LEFT, SO_OVERWRITE_LEFT ); wxString shortCutLeft = L"\tAlt+Left"; wxString shortCutRight = L"\tAlt+Right"; if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) std::swap(shortCutLeft, shortCutRight); - menu.addItem(_("Set direction:") + L" ->" + shortCutRight, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_RIGHT); }, &opRight); - menu.addItem(_("Set direction:") + L" -" L"\tAlt+Down", [this, &selection] { setSyncDirManually(selection, SYNC_DIR_NONE); }, &opNone); - menu.addItem(_("Set direction:") + L" <-" + shortCutLeft, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_LEFT); }, &opLeft); + menu.addItem(_("Set direction:") + L" ->" + shortCutRight, [this, &selection] { setSyncDirManually(selection, SyncDirection::RIGHT); }, &opRight); + menu.addItem(_("Set direction:") + L" -" L"\tAlt+Down", [this, &selection] { setSyncDirManually(selection, SyncDirection::NONE); }, &opNone); + menu.addItem(_("Set direction:") + L" <-" + shortCutLeft, [this, &selection] { setSyncDirManually(selection, SyncDirection::LEFT); }, &opLeft); //Gtk needs a direction, "<-", because it has no context menu icons! //Gtk requires "no spaces" for shortcut identifiers! menu.addSeparator(); @@ -2012,18 +2014,18 @@ void MainDialog::onMainGridContextRim(bool leftSide) return mirrorIfRtl(getSyncOpImage(selection[0]->getSyncOperation() != SO_EQUAL ? selection[0]->testSyncOperation(dir) : soDefault)); }; - const wxBitmap opRight = getImage(SYNC_DIR_RIGHT, SO_OVERWRITE_RIGHT); - const wxBitmap opNone = getImage(SYNC_DIR_NONE, SO_DO_NOTHING ); - const wxBitmap opLeft = getImage(SYNC_DIR_LEFT, SO_OVERWRITE_LEFT ); + const wxBitmap opRight = getImage(SyncDirection::RIGHT, SO_OVERWRITE_RIGHT); + const wxBitmap opNone = getImage(SyncDirection::NONE, SO_DO_NOTHING ); + const wxBitmap opLeft = getImage(SyncDirection::LEFT, SO_OVERWRITE_LEFT ); wxString shortCutLeft = L"\tAlt+Left"; wxString shortCutRight = L"\tAlt+Right"; if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) std::swap(shortCutLeft, shortCutRight); - menu.addItem(_("Set direction:") + L" ->" + shortCutRight, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_RIGHT); }, &opRight); - menu.addItem(_("Set direction:") + L" -" L"\tAlt+Down", [this, &selection] { setSyncDirManually(selection, SYNC_DIR_NONE); }, &opNone); - menu.addItem(_("Set direction:") + L" <-" + shortCutLeft, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_LEFT); }, &opLeft); + menu.addItem(_("Set direction:") + L" ->" + shortCutRight, [this, &selection] { setSyncDirManually(selection, SyncDirection::RIGHT); }, &opRight); + menu.addItem(_("Set direction:") + L" -" L"\tAlt+Down", [this, &selection] { setSyncDirManually(selection, SyncDirection::NONE); }, &opNone); + menu.addItem(_("Set direction:") + L" <-" + shortCutLeft, [this, &selection] { setSyncDirManually(selection, SyncDirection::LEFT); }, &opLeft); //Gtk needs a direction, "<-", because it has no context menu icons! //Gtk requires "no spaces" for shortcut identifiers! menu.addSeparator(); @@ -2482,7 +2484,7 @@ void MainDialog::removeObsoleteCfgHistoryItems(const std::vector<Zstring>& filen std::vector<Zstring> missingFiles; auto itFut = fileEx.begin(); - for (auto it = filenames.begin(); it != filenames.end(); ++it, ++itFut) + for (auto it = filenames.begin(); it != filenames.end(); ++it, (void)++itFut) //void: prevent ADL from dragging in boost's ,-overload: "MSVC warning C4913: user defined binary operator ',' exists but no overload could convert all operands" if (itFut->is_ready() && !itFut->get()) //remove only files that are confirmed to be non-existent missingFiles.push_back(*it); @@ -2606,15 +2608,16 @@ bool MainDialog::trySaveConfig(const Zstring* fileNameGui) //return true if save } else { - wxString defaultFileName = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? toWx(activeConfigFiles[0]) : L"SyncSettings.ffs_gui"; + Zstring defaultFileName = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstr("SyncSettings.ffs_gui"); //attention: activeConfigFiles may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config! - if (endsWith(defaultFileName, L".ffs_batch")) - replace(defaultFileName, L".ffs_batch", L".ffs_gui", false); + if (endsWith(defaultFileName, Zstr(".ffs_batch"))) + replace(defaultFileName, Zstr(".ffs_batch"), Zstr(".ffs_gui"), false); wxFileDialog filePicker(this, //put modal dialog on stack: creating this on freestore leads to memleak! wxEmptyString, - wxEmptyString, - defaultFileName, + //OS X really needs dir/file separated like this: + utfCvrtTo<wxString>(beforeLast(defaultFileName, FILE_NAME_SEPARATOR)), //default dir; empty string if / not found + utfCvrtTo<wxString>(afterLast (defaultFileName, FILE_NAME_SEPARATOR)), //default file; whole string if / not found wxString(L"FreeFileSync (*.ffs_gui)|*.ffs_gui") + L"|" +_("All files") + L" (*.*)|*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (filePicker.ShowModal() != wxID_OK) @@ -2634,7 +2637,7 @@ bool MainDialog::trySaveConfig(const Zstring* fileNameGui) //return true if save } catch (const xmlAccess::FfsXmlError& e) { - wxMessageBox(e.toString().c_str(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(e.toString().c_str(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR, this); return false; } } @@ -2667,15 +2670,16 @@ bool MainDialog::trySaveBatchConfig(const Zstring* fileNameBatch) globalCfg.gui.onCompletionHistoryMax)) return false; - wxString defaultFileName = !activeCfgFilename.empty() ? toWx(activeCfgFilename) : L"BatchRun.ffs_batch"; + Zstring defaultFileName = !activeCfgFilename.empty() ? activeCfgFilename : Zstr("BatchRun.ffs_batch"); //attention: activeConfigFiles may be a *.ffs_gui file! We don't want to overwrite it with a BATCH config! - if (endsWith(defaultFileName, L".ffs_gui")) - replace(defaultFileName, L".ffs_gui", L".ffs_batch"); + if (endsWith(defaultFileName, Zstr(".ffs_gui"))) + replace(defaultFileName, Zstr(".ffs_gui"), Zstr(".ffs_batch")); wxFileDialog filePicker(this, //put modal dialog on stack: creating this on freestore leads to memleak! wxEmptyString, - wxEmptyString, - defaultFileName, + //OS X really needs dir/file separated like this: + utfCvrtTo<wxString>(beforeLast(defaultFileName, FILE_NAME_SEPARATOR)), //default dir; empty string if / not found + utfCvrtTo<wxString>(afterLast (defaultFileName, FILE_NAME_SEPARATOR)), //default file; whole string if / not found _("FreeFileSync batch") + L" (*.ffs_batch)|*.ffs_batch" + L"|" +_("All files") + L" (*.*)|*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (filePicker.ShowModal() != wxID_OK) @@ -2693,7 +2697,7 @@ bool MainDialog::trySaveBatchConfig(const Zstring* fileNameBatch) } catch (const xmlAccess::FfsXmlError& e) { - wxMessageBox(e.toString().c_str(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(e.toString().c_str(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR, this); return false; } } @@ -2710,7 +2714,7 @@ bool MainDialog::saveOldConfig() //return false on user abort if (!activeCfgFilename.empty()) //only if check is active and non-default config file loaded { - bool neverSave = !globalCfg.optDialogs.popupOnConfigChange; + bool dontAskAgain = false; switch (showQuestionDlg(this, ReturnQuestionDlg::BUTTON_YES | ReturnQuestionDlg::BUTTON_NO | ReturnQuestionDlg::BUTTON_CANCEL, @@ -2718,7 +2722,7 @@ bool MainDialog::saveOldConfig() //return false on user abort QuestConfig().setCaption(toWx(activeCfgFilename)). setLabelYes(_("&Save")). setLabelNo(_("Do&n't save")). - showCheckBox(neverSave, _("Never save changes"), ReturnQuestionDlg::BUTTON_YES))) + showCheckBox(dontAskAgain, _("Never save changes"), ReturnQuestionDlg::BUTTON_YES))) { case ReturnQuestionDlg::BUTTON_YES: @@ -2736,7 +2740,7 @@ bool MainDialog::saveOldConfig() //return false on user abort } case ReturnQuestionDlg::BUTTON_NO: - globalCfg.optDialogs.popupOnConfigChange = !neverSave; + globalCfg.optDialogs.popupOnConfigChange = !dontAskAgain; break; case ReturnQuestionDlg::BUTTON_CANCEL: @@ -2759,7 +2763,7 @@ void MainDialog::OnConfigLoad(wxCommandEvent& event) wxFileDialog filePicker(this, wxEmptyString, - toWx(beforeLast(activeCfgFilename, FILE_NAME_SEPARATOR)), //set default dir: empty string if "activeConfigFiles" is empty or has no path separator + utfCvrtTo<wxString>(beforeLast(activeCfgFilename, FILE_NAME_SEPARATOR)), //set default dir: empty string if "activeConfigFiles" is empty or has no path separator wxEmptyString, wxString(L"FreeFileSync (*.ffs_gui;*.ffs_batch)|*.ffs_gui;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", wxFD_OPEN | wxFD_MULTIPLE); @@ -2866,12 +2870,12 @@ bool MainDialog::loadConfiguration(const std::vector<Zstring>& filenames) { if (error.getSeverity() == xmlAccess::FfsXmlError::WARNING) { - wxMessageBox(error.toString(), _("Warning"), wxOK | wxICON_WARNING, this); + wxMessageBox(error.toString(), L"FreeFileSync - " + _("Warning"), wxOK | wxICON_WARNING, this); setConfig(newGuiCfg, filenames); setLastUsedConfig(filenames, xmlAccess::XmlGuiConfig()); //simulate changed config due to parsing errors } else - wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(error.toString(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR, this); return false; } } @@ -3308,7 +3312,8 @@ void MainDialog::OnCompare(wxCommandEvent& event) clearGrid(); //avoid memory peak by clearing old data first disableAllElements(true); //CompareStatusHandler will internally process Window messages, so avoid unexpected callbacks! - ZEN_ON_SCOPE_EXIT(updateUiNow(); enableAllElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks + auto app = wxTheApp; //fix lambda/wxWigets/VC fuck up + ZEN_ON_SCOPE_EXIT(app->Yield(); enableAllElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks try { @@ -4093,12 +4098,11 @@ void MainDialog::OnMenuGlobalSettings(wxCommandEvent& event) void MainDialog::OnMenuExportFileList(wxCommandEvent& event) { //get a filename - const wxString defaultFileName = L"FileList.csv"; //proposal wxFileDialog filePicker(this, //creating this on freestore leads to memleak! wxEmptyString, wxEmptyString, - defaultFileName, - _("Comma separated list") + L" (*.csv)|*.csv" + L"|" +_("All files") + L" (*.*)|*", + L"FileList.csv", //default file name + _("Comma-separated values") + L" (*.csv)|*.csv" + L"|" +_("All files") + L" (*.*)|*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (filePicker.ShowModal() != wxID_OK) return; @@ -4220,7 +4224,7 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) } catch (const FileError& e) { - wxMessageBox(e.toString(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(e.toString(), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR, this); } } } @@ -4288,7 +4292,7 @@ void MainDialog::switchProgramLanguage(int langID) newGlobalCfg.programLanguage = langID; //show new dialog, then delete old one - MainDialog::create(getConfig(), activeConfigFiles, newGlobalCfg, false); + MainDialog::create(getConfig(), activeConfigFiles, &newGlobalCfg, false); //we don't use Close(): //1. we don't want to show the prompt to save current config in OnClose() diff --git a/ui/main_dlg.h b/ui/main_dlg.h index 5575cefb..b655d95e 100644 --- a/ui/main_dlg.h +++ b/ui/main_dlg.h @@ -29,16 +29,15 @@ class CompareProgressDialog; class MainDialog : public MainDialogGenerated { public: - //default behavior, application start - static void create(const std::vector<Zstring>& cfgFileNames); //cfgFileNames empty: restore last config; non-empty load/merge given set of config files + //default behavior, application start, restores last used config + static void create(); - //load dynamically assembled config - static void create(const xmlAccess::XmlGuiConfig& guiCfg, bool startComparison); - - //when switching language or switching from batch run to GUI on warnings + //when loading dynamically assembled config, + //when switching language, + //or switching from batch run to GUI on warnings static void create(const xmlAccess::XmlGuiConfig& guiCfg, const std::vector<Zstring>& referenceFiles, - const xmlAccess::XmlGlobalSettings& globalSettings, //take over ownership => save on exit + const xmlAccess::XmlGlobalSettings* globalSettings, //optional: take over ownership => save on exit bool startComparison); void disableAllElements(bool enableAbort); //dis-/enables all elements (except abort button) that might receive user input @@ -47,11 +46,6 @@ public: void onQueryEndSession(); //last chance to do something useful before killing the application! private: - static void create_impl(const xmlAccess::XmlGuiConfig& guiCfg, - const std::vector<Zstring>& referenceFiles, - const xmlAccess::XmlGlobalSettings& globalSettings, - bool startComparison); - MainDialog(const xmlAccess::XmlGuiConfig& guiCfg, const std::vector<Zstring>& referenceFiles, const xmlAccess::XmlGlobalSettings& globalSettings, //take over ownership => save on exit diff --git a/ui/msg_popup.h b/ui/msg_popup.h index 5e7ea97f..d93b5002 100644 --- a/ui/msg_popup.h +++ b/ui/msg_popup.h @@ -7,6 +7,7 @@ #ifndef MESSAGEPOPUP_H_820780154723456 #define MESSAGEPOPUP_H_820780154723456 +#include <zen/string_tools.h> #include <wx/window.h> #include <wx/string.h> @@ -66,9 +67,11 @@ class QuestConfig public: QuestConfig() : checkBoxValue(), disabledButtonsWhenChecked_() {} QuestConfig& setCaption (const wxString& label) { caption = label; return *this; } - QuestConfig& setLabelYes(const wxString& label) { labelYes = label; return *this; } - QuestConfig& setLabelNo (const wxString& label) { labelNo = label; return *this; } - QuestConfig& showCheckBox(bool& value, const wxString& label, int disabledButtonsWhenChecked) { checkBoxValue = &value; checkBoxLabel = label; disabledButtonsWhenChecked_ = disabledButtonsWhenChecked; return *this; } + QuestConfig& setLabelYes(const wxString& label) { assert(contains(label, L"&")); labelYes = label; return *this; } + QuestConfig& setLabelNo (const wxString& label) { assert(contains(label, L"&")); labelNo = label; return *this; } + QuestConfig& showCheckBox(bool& value, + const wxString& label, + int disabledButtonsWhenChecked) { assert(contains(label, L"&")); checkBoxValue = &value; checkBoxLabel = label; disabledButtonsWhenChecked_ = disabledButtonsWhenChecked; return *this; } private: friend class ::QuestionDlg; diff --git a/ui/progress_indicator.cpp b/ui/progress_indicator.cpp index 732b21c7..fd1c305d 100644 --- a/ui/progress_indicator.cpp +++ b/ui/progress_indicator.cpp @@ -26,6 +26,7 @@ #include <wx+/font_size.h> #include <wx+/std_button_order.h> #include <zen/file_handling.h> +#include <zen/thread.h> #include "gui_generated.h" #include "../lib/ffs_paths.h" #include "../lib/resources.h" @@ -33,10 +34,14 @@ #include "tray_icon.h" #include "taskbar.h" #include "exec_finished_box.h" -//#include <wx/msgdlg.h> + +#include <wx/msgdlg.h> using namespace zen; +warn_static("remove after test") +#include <zen/perf.h> + namespace { @@ -51,7 +56,7 @@ const int WINDOW_BYTES_PER_SEC = 5000; // class CompareProgressDialog::Pimpl : public CompareProgressDlgGenerated { public: - Pimpl(wxTopLevelWindow& parentWindow); + Pimpl(wxFrame& parentWindow); void init(const Statistics& syncStat); //constructor/destructor semantics, but underlying Window is reused void finalize(); // @@ -60,7 +65,7 @@ public: void updateStatusPanelNow(); private: - wxTopLevelWindow& parentWindow_; + wxFrame& parentWindow_; wxString titleTextBackup; wxStopWatch timeElapsed; @@ -75,7 +80,7 @@ private: }; -CompareProgressDialog::Pimpl::Pimpl(wxTopLevelWindow& parentWindow) : +CompareProgressDialog::Pimpl::Pimpl(wxFrame& parentWindow) : CompareProgressDlgGenerated(&parentWindow), parentWindow_(parentWindow), binCompStartMs(0), @@ -214,11 +219,13 @@ void CompareProgressDialog::Pimpl::updateStatusPanelNow() perf->addSample(objectsCurrent, to<double>(dataCurrent), timeNow); //current speed -> Win 7 copy uses 1 sec update interval instead - setText(*m_staticTextSpeed, perf->getBytesPerSecond(), &layoutChanged); + Opt<std::wstring> bps = perf->getBytesPerSecond(); + setText(*m_staticTextSpeed, bps ? *bps : L"-", &layoutChanged); //remaining time: display with relative error of 10% - based on samples taken every 0.5 sec only //-> call more often than once per second to correctly show last few seconds countdown, but don't call too often to avoid occasional jitter - setText(*m_staticTextRemTime, perf->getRemainingTime(to<double>(dataTotal - dataCurrent)), &layoutChanged); + Opt<std::wstring> rt = perf->getRemainingTime(to<double>(dataTotal - dataCurrent)); + setText(*m_staticTextRemTime, rt ? *rt : L"-", &layoutChanged); } } break; @@ -238,17 +245,17 @@ void CompareProgressDialog::Pimpl::updateStatusPanelNow() wxTimeSpan::Seconds(timeElapSec).Format( L"%M:%S") : wxTimeSpan::Seconds(timeElapSec).Format(L"%H:%M:%S"), &layoutChanged); - //do the ui update if (layoutChanged) bSizer42->Layout(); - updateUiNow(); + //do the ui update + wxTheApp->Yield(); } //######################################################################################## //redirect to implementation -CompareProgressDialog::CompareProgressDialog(wxTopLevelWindow& parentWindow) : +CompareProgressDialog::CompareProgressDialog(wxFrame& parentWindow) : pimpl(new Pimpl(parentWindow)) {} //owned by parentWindow wxWindow* CompareProgressDialog::getAsWindow() @@ -715,7 +722,7 @@ private: } catch (const std::bad_alloc& e) { - wxMessageBox(_("Out of memory.") + L" " + utfCvrtTo<std::wstring>(e.what()), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(_("Out of memory.") + L" " + utfCvrtTo<std::wstring>(e.what()), L"FreeFileSync - " + _("Error"), wxOK | wxICON_ERROR); } } @@ -726,88 +733,212 @@ private: namespace { -class GraphDataBytes : public GraphData +class CurveDataStatistics : public SparseCurveData { public: - void addRecord(double currentBytes, long timeMs) + CurveDataStatistics() : SparseCurveData(true), timeNow(0) {} + + void clear() { samples.clear(); timeNow = 0; } + + void addRecord(long timeNowMs, double value) { - data.insert(data.end(), std::make_pair(timeMs, currentBytes)); + warn_static("review") + timeNow = timeNowMs; + if (!samples.empty() && samples.rbegin()->second == value) + return; //don't insert duplicate values + + samples.insert(samples.end(), std::make_pair(timeNowMs, value)); //use fact that time is monotonously ascending //documentation differs about whether "hint" should be before or after the to be inserted element! //however "std::map<>::end()" is interpreted correctly by GCC and VS2010 - if (data.size() > MAX_BUFFER_SIZE) //limit buffer size - data.erase(data.begin()); + if (samples.size() > MAX_BUFFER_SIZE) //limit buffer size + samples.erase(samples.begin()); } - void clear() { data.clear(); } +private: + virtual std::pair<double, double> getRangeX() const final + { + if (samples.empty()) return std::make_pair(0.0, 0.0); - virtual double getXBegin() const { return data.empty() ? 0 : data.begin()->first / 1000.0; } //need not start with 0, e.g. "binary comparison, graph reset, followed by sync" - virtual double getXEnd () const { return data.empty() ? 0 : (--data.end())->first / 1000.0; } + double upperEndMs = std::max(timeNow, samples.rbegin()->first); -private: - static const size_t MAX_BUFFER_SIZE = 2500000; //sizeof(single node) worst case ~ 3 * 8 byte ptr + 16 byte key/value = 40 byte + //request some additional width by 5% elapsed time to make graph recalibrate before hitting the right border + //caveat: graph for batch mode binary comparison does NOT start at elapsed time 0!! PHASE_COMPARING_CONTENT and PHASE_SYNCHRONIZING! + //=> consider width of current sample set! + upperEndMs += 0.05 *(upperEndMs - samples.begin()->first); - virtual double getValue(double x) const //x: seconds since begin + return std::make_pair(samples.begin()->first / 1000.0, //need not start with 0, e.g. "binary comparison, graph reset, followed by sync" + upperEndMs / 1000.0); + } + + virtual Opt<CurvePoint> getLessEq(double x) const final //x: seconds since begin { - auto it = data.lower_bound(x * 1000); - if (it == data.end()) - return data.empty() ? 0 : (--data.end())->second; - return it->second; + const long timex = std::floor(x * 1000); + //------ add artifical last sample value ------- + if (!samples.empty() && samples.rbegin()->first < timeNow) + if (timeNow <= timex) + return CurvePoint(timeNow / 1000.0, samples.rbegin()->second); + //-------------------------------------------------- + + //find first key > x, then go one step back: => samples must be a std::map, NOT std::multimap!!! + auto it = samples.upper_bound(timex); + if (it == samples.begin()) + return NoValue(); + //=> samples not empty in this context + --it; + return CurvePoint(it->first / 1000.0, it->second); } - //example: two-element range is accessible within [0, 2) - std::map<long, double> data; + virtual Opt<CurvePoint> getGreaterEq(double x) const final + { + const long timex = std::ceil(x * 1000); + //------ add artifical last sample value ------- + if (!samples.empty() && samples.rbegin()->first < timeNow) + if (samples.rbegin()->first < timex && timex <= timeNow) + return CurvePoint(timeNow / 1000.0, samples.rbegin()->second); + //-------------------------------------------------- + + auto it = samples.lower_bound(timex); + if (it == samples.end()) + return NoValue(); + return CurvePoint(it->first / 1000.0, it->second); + } + + static const size_t MAX_BUFFER_SIZE = 2500000; //sizeof(single node) worst case ~ 3 * 8 byte ptr + 16 byte key/value = 40 byte + + std::map<long, double> samples; //time, unit: [ms] !don't use std::multimap, see getLessEq() + long timeNow; //help create an artificial record at the end of samples to visualize current time! }; -class GraphDataConstLine : public GraphData //a constant line +class CurveDataCurrentValue : public CurveData { public: - GraphDataConstLine() : value_(0) {} + CurveDataCurrentValue() : x(0), yCurrent_(0), yTotal_(0) {} - void setValue(double val) { value_ = val; } + void setValue(long xTimeNowMs, double yCurrent, double yTotal) { x = xTimeNowMs / 1000.0; yCurrent_ = yCurrent; yTotal_ = yTotal; } private: - virtual double getValue(double x) const { return value_; } - virtual double getXBegin() const { return -std::numeric_limits<double>::infinity(); } - virtual double getXEnd () const { return std::numeric_limits<double>::infinity(); } + virtual std::pair<double, double> getRangeX() const final { return std::make_pair(x, x); } //conceptually just a vertical line! - double value_; + virtual void getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const final + { + + warn_static("remove after test") +#if 0 + if (yTotal_ > 100) + { +points.push_back(CurvePoint(0.38552801074951426,0.3861846045528107)); +points.push_back(CurvePoint(-0.5565680734345084,1.793989720937398)); +points.push_back(CurvePoint(2.85210684934041,3.339141677944872)); +points.push_back(CurvePoint(0.45404408959926135,0.7810567713436095)); +points.push_back(CurvePoint(2.303978218542433,-0.6610850551966995)); +points.push_back(CurvePoint(-2.5606633797896112,-0.4035597290287872)); +points.push_back(CurvePoint(-0.5394390537220716,0.40335295963067147)); + + + //points.push_back(CurvePoint(0.2885508231771302,-1.9264175407823294)); + //points.push_back(CurvePoint(-1.9332518577512143,0.6244007597162101)); + //points.push_back(CurvePoint(3.116299689813205,1.6973640131005165)); + //points.push_back(CurvePoint(0.0,0.0)); + //points.push_back(CurvePoint(-5.993091301993007,0.5231778112837284)); + return; + } +#endif + + + + + + + if (x <= maxX) + { + points.push_back(CurvePoint(x, 0)); + points.push_back(CurvePoint(x, yCurrent_)); + points.push_back(CurvePoint(maxX, yCurrent_)); + points.push_back(CurvePoint(x, yCurrent_)); + points.push_back(CurvePoint(x, yTotal_)); + } + } + + double x; //time elapsed in seconds + double yCurrent_; //current bytes/items + double yTotal_; }; -inline -double bestFit(double val, double low, double high) { return val < (high + low) / 2 ? low : high; } +class CurveDataTotalValue : public CurveData +{ +public: + CurveDataTotalValue () : x(0), yTotal_(0) {} + + void setValue(long xTimeNowMs, double yTotal) { x = xTimeNowMs / 1000.0; yTotal_ = yTotal; } + +private: + virtual std::pair<double, double> getRangeX() const final { return std::make_pair(x, x); } //conceptually just a vertical line! + + virtual void getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const final + { + if (x <= maxX) + { + points.push_back(CurvePoint(x, yTotal_)); + points.push_back(CurvePoint(maxX, yTotal_)); + } + } + + double x; //time elapsed in seconds + double yTotal_; +}; struct LabelFormatterBytes : public LabelFormatter { virtual double getOptimalBlockSize(double bytesProposed) const { - if (bytesProposed <= 0) - return 0; + if (bytesProposed <= 1) //never smaller than 1 byte + return 1; - bytesProposed *= 1.5; //enlarge block default size + bytesProposed *= 1.4; //enlarge block default size //round to next number which is a convenient to read block size const double k = std::floor(std::log(bytesProposed) / std::log(2.0)); const double e = std::pow(2.0, k); if (numeric::isNull(e)) return 0; - const double a = bytesProposed / e; //bytesProposed = a * 2^k with a in (1, 2) - assert(1 < a && a < 2); - return bestFit(a, 1, 2) * e; + const double a = bytesProposed / e; //bytesProposed = a * 2^k with a in [1, 2) + assert(1 <= a && a < 2); + const double steps[] = { 1, 2 }; + return e * numeric::nearMatch(a, std::begin(steps), std::end(steps)); } virtual wxString formatText(double value, double optimalBlockSize) const { return filesizeToShortString(Int64(value)); }; }; +struct LabelFormatterItemCount : public LabelFormatter +{ + virtual double getOptimalBlockSize(double itemsProposed) const + { + const double steps[] = { 1, 2, 5, 10 }; + if (itemsProposed <= 10) + return numeric::nearMatch(itemsProposed, std::begin(steps), std::end(steps)); //similar to nextNiceNumber(), but without the 2.5 step! + return nextNiceNumber(itemsProposed); + } + + virtual wxString formatText(double value, double optimalBlockSize) const + { + return toGuiString(numeric::round(value)); //not enough room for a "%x items" representation + }; +}; + + struct LabelFormatterTimeElapsed : public LabelFormatter { + LabelFormatterTimeElapsed(bool drawLabel) : drawLabel_(drawLabel) {} + virtual double getOptimalBlockSize(double secProposed) const { - const double stepsSec[] = { 10, 20, 30, 60 }; //10 sec: minimum block size; no 15: avoid flicker between 10<->15<->20 sec blocks + const double stepsSec[] = { 20, 30, 60 }; //20 sec: minimum block size; (no 15: avoid flicker between 10<->15<->20 sec blocks) if (secProposed <= 60) return numeric::nearMatch(secProposed, std::begin(stepsSec), std::end(stepsSec)); @@ -816,215 +947,302 @@ struct LabelFormatterTimeElapsed : public LabelFormatter return 60.0 * numeric::nearMatch(secProposed / 60, std::begin(stepsMin), std::end(stepsMin)); if (secProposed <= 3600 * 24) - return nextNiceNumber(secProposed / 3600) * 3600; + return nextNiceNumber(secProposed / 3600) * 3600; //round up to full hours - return nextNiceNumber(secProposed / (24 * 3600)) * 24 * 3600; //round up to full days + return nextNiceNumber(secProposed / (24 * 3600)) * 24 * 3600; //round to full days } virtual wxString formatText(double timeElapsed, double optimalBlockSize) const { + if (!drawLabel_) return wxString(); return timeElapsed < 60 ? replaceCpy(_P("1 sec", "%x sec", numeric::round(timeElapsed)), L"%x", zen::numberTo<std::wstring>(numeric::round(timeElapsed))) : timeElapsed < 3600 ? wxTimeSpan::Seconds(timeElapsed).Format( L"%M:%S") : wxTimeSpan::Seconds(timeElapsed).Format(L"%H:%M:%S"); } -}; -//void fitHeight(wxTopLevelWindow& wnd) -//{ -// if (wnd.IsMaximized()) -// return; -// //Fit() height only: -// int width = wnd.GetSize().GetWidth(); -// wnd.Fit(); -// int height = wnd.GetSize().GetHeight(); -// wnd.SetSize(wxSize(width, height)); -//} +private: + bool drawLabel_; +}; } -class SyncProgressDialogImpl : private SyncProgressDlgGenerated, public SyncProgressDialog +template <class TopLevelDialog> //can be a wxFrame or wxDialog +class SyncProgressDialogImpl : private TopLevelDialog, public SyncProgressDialog +/*we need derivation, not composition! + 1. SyncProgressDialogImpl IS a wxFrame/wxDialog + 2. implement virtual ~wxFrame() + 3. event handling below assumes lifetime is larger-equal than wxFrame's +*/ { public: - SyncProgressDialogImpl(AbortCallback& abortCb, + SyncProgressDialogImpl(long style, //wxFrame/wxDialog style + const std::function<wxFrame*(TopLevelDialog& progDlg)>& getTaskbarFrame, + AbortCallback& abortCb, const std::function<void()>& notifyWindowTerminate, const Statistics& syncStat, - wxTopLevelWindow* parentWindow, + wxFrame* parentFrame, bool showProgress, const wxString& jobName, const std::wstring& execWhenFinished, std::vector<std::wstring>& execFinishedHistory); - ~SyncProgressDialogImpl(); + virtual ~SyncProgressDialogImpl(); //call this in StatusUpdater derived class destructor at the LATEST(!) to prevent access to currentStatusUpdater virtual void processHasFinished(SyncResult resultId, const ErrorLog& log); virtual void closeWindowDirectly(); - virtual wxWindow* getAsWindow() { return this; } + virtual wxWindow* getWindowIfVisible() { return this->IsShown() ? this : nullptr; } + //workaround OS X bug: if "this" is used as parent window for a modal dialog then this dialog will erroneously un-hide its parent! + virtual void initNewPhase(); virtual void notifyProgressChange(); - virtual void updateGui() { updateGuiInt(true); } - - virtual std::wstring getExecWhenFinishedCommand() const; + virtual void updateGui() { + + + warn_static("remove after test") +#if 0 + static bool init = false; + static wxStopWatch sw; + if (!init) + { + init = true; + sw.Start(); + } + if (sw.Time() > 1000 * 10) + { + PERF_START + for (int i = 0; i < 10000; ++i) + //for (int i = 0; i < 1000000; ++i) + updateGuiInt(true); + sw.Start(); + sw.Pause(); + } +#endif + + + + + + + updateGuiInt(true); } + + virtual std::wstring getExecWhenFinishedCommand() const { return pnl.m_comboBoxExecFinished->getValue(); } virtual void stopTimer() //halt all internal counters! { - m_animCtrlSyncing->Stop(); + pnl.m_animCtrlSyncing->Stop(); timeElapsed.Pause (); } virtual void resumeTimer() { - m_animCtrlSyncing->Play(); + pnl.m_animCtrlSyncing->Play(); timeElapsed.Resume(); } private: void updateGuiInt(bool allowYield); - void minimizeToTray(); void OnKeyPressed(wxKeyEvent& event); - virtual void OnOkay (wxCommandEvent& event); - virtual void OnPause (wxCommandEvent& event); - virtual void OnCancel (wxCommandEvent& event); - virtual void OnClose (wxCloseEvent& event); - virtual void OnIconize(wxIconizeEvent& event); - - void updateDialogStatus(); + void OnOkay (wxCommandEvent& event); + void OnPause (wxCommandEvent& event); + void OnCancel (wxCommandEvent& event); + void OnClose (wxCloseEvent& event); + void OnIconize(wxIconizeEvent& event); + void OnMinimizeToTray(wxCommandEvent& event) { minimizeToTray(); } + void minimizeToTray(); void resumeFromSystray(); - void OnResumeFromTray(wxCommandEvent& event); + void updateDialogStatus(); void setExternalStatus(const wxString& status, const wxString& progress); //progress may be empty! + SyncProgressPanelGenerated& pnl; //wxPanel containing the GUI controls of *this + const wxString jobName_; wxStopWatch timeElapsed; - wxTopLevelWindow* mainDialog; //optional + wxFrame* parentFrame_; //optional std::function<void()> notifyWindowTerminate_; //call once in OnClose(), NOT in destructor which is called far too late somewhere in wxWidgets main loop! + bool wereDead; //after wxWindow::Delete, which is equal to "delete this" on OS X! + //status variables const Statistics* syncStat_; // AbortCallback* abortCb_; //valid only while sync is running bool paused_; //valid only while sync is running SyncResult finalResult; //set after sync - bool isZombie; //wxGTK sends iconize event *after* wxWindow::Destroy, sigh... - //remaining time std::unique_ptr<PerfCheck> perf; long lastStatCallSpeed; //used for calculating intervals between collecting perf samples //help calculate total speed long phaseStartMs; //begin of current phase in [ms] - std::shared_ptr<GraphDataBytes> graphDataBytes; - std::shared_ptr<GraphDataConstLine> graphDataBytesTotal; + std::shared_ptr<CurveDataStatistics> curveDataBytes; + std::shared_ptr<CurveDataStatistics> curveDataItems; - wxString titelTextBackup; + std::shared_ptr<CurveDataTotalValue > curveDataBytesTotal; + std::shared_ptr<CurveDataCurrentValue> curveDataBytesCurrent; + std::shared_ptr<CurveDataTotalValue > curveDataItemsTotal; + std::shared_ptr<CurveDataCurrentValue> curveDataItemsCurrent; + + wxString parentFrameTitleBackup; std::unique_ptr<FfsTrayIcon> trayIcon; //optional: if filled all other windows should be hidden and conversely std::unique_ptr<Taskbar> taskbar_; }; -SyncProgressDialogImpl::SyncProgressDialogImpl(AbortCallback& abortCb, - const std::function<void()>& notifyWindowTerminate, - const Statistics& syncStat, - wxTopLevelWindow* parentWindow, - bool showProgress, - const wxString& jobName, - const std::wstring& execWhenFinished, - std::vector<std::wstring>& execFinishedHistory) : - SyncProgressDlgGenerated(parentWindow, - wxID_ANY, - parentWindow ? wxString() : (wxString(L"FreeFileSync - ") + _("Folder Comparison and Synchronization")), - wxDefaultPosition, wxDefaultSize, - parentWindow ? - wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL | wxFRAME_NO_TASKBAR | wxFRAME_FLOAT_ON_PARENT : //wxTAB_TRAVERSAL is needed for standard button handling: wxID_OK/wxID_CANCEL - wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL), +template <class TopLevelDialog> +SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxFrame/wxDialog style + const std::function<wxFrame*(TopLevelDialog& progDlg)>& getTaskbarFrame, + AbortCallback& abortCb, + const std::function<void()>& notifyWindowTerminate, + const Statistics& syncStat, + wxFrame* parentFrame, + bool showProgress, + const wxString& jobName, + const std::wstring& execWhenFinished, + std::vector<std::wstring>& execFinishedHistory) : + TopLevelDialog(parentFrame, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, style), //title is overwritten anyway in setExternalStatus() + pnl(*new SyncProgressPanelGenerated(this)), //ownership passed to "this" jobName_ (jobName), - mainDialog(parentWindow), + parentFrame_(parentFrame), notifyWindowTerminate_(notifyWindowTerminate), + wereDead(false), syncStat_ (&syncStat), abortCb_ (&abortCb), paused_ (false), finalResult(RESULT_ABORTED), //dummy value - isZombie(false), lastStatCallSpeed(-1000000), //some big number phaseStartMs(0) { + assert_static((IsSameType<TopLevelDialog, wxFrame >::value || + IsSameType<TopLevelDialog, wxDialog>::value)); + assert((IsSameType<TopLevelDialog, wxFrame>::value == !parentFrame)); + + //finish construction of this dialog: + this->SetMinSize(wxSize(470, 280)); //== minimum size! no idea why SetMinSize() is not used... + wxBoxSizer* bSizer170 = new wxBoxSizer(wxVERTICAL); + bSizer170->Add(&pnl, 1, wxEXPAND); + this->SetSizer(bSizer170); //pass ownership + //this->Layout(); + //bSizer170->Fit(this); + this->Centre(wxBOTH); + + //lifetime of event sources is subset of this instance's lifetime => no wxEvtHandler::Disconnect() needed + this->Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler (SyncProgressDialogImpl<TopLevelDialog>::OnClose)); + this->Connect(wxEVT_ICONIZE, wxIconizeEventHandler(SyncProgressDialogImpl<TopLevelDialog>::OnIconize)); + this->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::OnKeyPressed), nullptr, this); + pnl.m_buttonClose ->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnOkay ), NULL, this); + pnl.m_buttonPause ->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnPause ), NULL, this); + pnl.m_buttonCancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnCancel), NULL, this); + pnl.m_bpButtonMinimizeToTray->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnMinimizeToTray), NULL, this); + #ifdef ZEN_WIN new MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" #endif - assert(m_buttonClose->GetId() == wxID_OK); //we cannot use wxID_CLOSE else Esc key won't work: yet another wxWidgets bug?? + assert(pnl.m_buttonClose->GetId() == wxID_OK); //we cannot use wxID_CLOSE else Esc key won't work: yet another wxWidgets bug?? - setRelativeFontSize(*m_staticTextPhase, 1.5); + setRelativeFontSize(*pnl.m_staticTextPhase, 1.5); - if (mainDialog) - titelTextBackup = mainDialog->GetTitle(); //save old title (will be used as progress indicator) + if (parentFrame_) + parentFrameTitleBackup = parentFrame_->GetTitle(); //save old title (will be used as progress indicator) - m_animCtrlSyncing->SetAnimation(GlobalResources::instance().aniWorking); - m_animCtrlSyncing->Play(); + pnl.m_animCtrlSyncing->SetAnimation(GlobalResources::instance().aniWorking); + pnl.m_animCtrlSyncing->Play(); - SetIcon(GlobalResources::instance().programIconFFS); - - //initialize gauge - m_gauge1->SetRange(GAUGE_FULL_RANGE); - m_gauge1->SetValue(0); + this->SetIcon(GlobalResources::instance().programIconFFS); - EnableCloseButton(false); //this is NOT honored on OS X or during system shutdown on Windows! - - if (IsShown()) //don't steal focus when starting in sys-tray! - m_buttonCancel->SetFocus(); + this->EnableCloseButton(false); //this is NOT honored on OS X or during system shutdown on Windows! timeElapsed.Start(); //measure total time - try //try to get access to Windows 7/Ubuntu taskbar - { - taskbar_ = make_unique<Taskbar>(mainDialog ? *static_cast<wxTopLevelWindow*>(mainDialog) : *this); - } - catch (const TaskbarNotAvailable&) {} + if (wxFrame* frame = getTaskbarFrame(*this)) + try //try to get access to Windows 7/Ubuntu taskbar + { + taskbar_ = make_unique<Taskbar>(*frame); //throw TaskbarNotAvailable + } + catch (const TaskbarNotAvailable&) {} //hide "processed" statistics until end of process - m_notebookResult ->Hide(); - m_staticTextLabelItemsProc->Show(false); - bSizerItemsProc ->Show(false); - m_buttonClose ->Show(false); + pnl.m_notebookResult ->Hide(); + pnl.m_panelItemsProcessed->Hide(); + pnl.m_buttonClose ->Show(false); //set std order after button visibility was set - setStandardButtonOrder(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonPause).setCancel(m_buttonCancel)); + setStandardButtonOrder(*pnl.bSizerStdButtons, StdButtons().setAffirmative(pnl.m_buttonPause).setCancel(pnl.m_buttonCancel)); - //register key event - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::OnKeyPressed), nullptr, this); + pnl.m_bpButtonMinimizeToTray->SetBitmapLabel(getResourceImage(L"minimize_to_tray")); //init graph - graphDataBytes = std::make_shared<GraphDataBytes>(); - graphDataBytesTotal = std::make_shared<GraphDataConstLine>(); + curveDataBytes = std::make_shared<CurveDataStatistics>(); + curveDataItems = std::make_shared<CurveDataStatistics>(); + curveDataBytesTotal = std::make_shared<CurveDataTotalValue>(); + curveDataBytesCurrent = std::make_shared<CurveDataCurrentValue>(); + curveDataItemsTotal = std::make_shared<CurveDataTotalValue>(); + curveDataItemsCurrent = std::make_shared<CurveDataCurrentValue>(); + + const int xLabelHeight = 18; //we need to use the same height for both graphs to make sure they stretch evenly + const int yLabelWidth = 70; + pnl.m_panelGraphBytes->setAttributes(Graph2D::MainAttributes(). + setLabelX(Graph2D::X_LABEL_BOTTOM, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(true)). + setLabelY(Graph2D::Y_LABEL_RIGHT, yLabelWidth, std::make_shared<LabelFormatterBytes>()). + setSelectionMode(Graph2D::SELECT_NONE)); + + pnl.m_panelGraphItems->setAttributes(Graph2D::MainAttributes(). + setLabelX(Graph2D::X_LABEL_BOTTOM, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(false)). + setLabelY(Graph2D::Y_LABEL_RIGHT, yLabelWidth, std::make_shared<LabelFormatterItemCount>()). + setSelectionMode(Graph2D::SELECT_NONE)); + + pnl.m_panelGraphBytes->setCurve(curveDataBytes, Graph2D::CurveAttributes().setLineWidth(2). + fillCurveArea(wxColor(205, 255, 202)). //faint green + setColor (wxColor( 20, 200, 0))); //medium green + + pnl.m_panelGraphItems->setCurve(curveDataItems, Graph2D::CurveAttributes().setLineWidth(2). + fillCurveArea(wxColor(198, 206, 255)). //faint blue + setColor (wxColor( 90, 120, 255))); //medium blue + + pnl.m_panelGraphBytes->addCurve(curveDataBytesTotal, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(12, 128, 0))); //dark green + pnl.m_panelGraphBytes->addCurve(curveDataBytesCurrent, Graph2D::CurveAttributes().setLineWidth(1).setColor(wxColor(12, 128, 0))); // + + pnl.m_panelGraphItems->addCurve(curveDataItemsTotal, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(53, 25, 255))); //dark blue + pnl.m_panelGraphItems->addCurve(curveDataItemsCurrent, Graph2D::CurveAttributes().setLineWidth(1).setColor(wxColor(53, 25, 255))); // + + //allow changing on completion command + pnl.m_comboBoxExecFinished->initHistory(execFinishedHistory, execFinishedHistory.size()); //-> we won't use addItemHistory() later + pnl.m_comboBoxExecFinished->setValue(execWhenFinished); + + updateDialogStatus(); //null-status will be shown while waiting for dir locks + + + warn_static("remove after test") +#if 0 + pnl.m_panelGraphBytes->setAttributes(Graph2D::MainAttributes().setMinX(-1).setMaxX(1).setMinY(-1).setMaxY(1)); + pnl.m_panelGraphBytes->setCurve(curveDataBytesCurrent, Graph2D::CurveAttributes().setLineWidth(6).setColor(wxColor(12, 128, 0)) + .fillCurveArea(wxColor(198, 206, 255)) //faint blue + ); // +#endif + + + - m_panelGraph->setAttributes(Graph2D::MainAttributes(). - setLabelX(Graph2D::X_LABEL_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()). - setLabelY(Graph2D::Y_LABEL_RIGHT, 70, std::make_shared<LabelFormatterBytes>()). - setSelectionMode(Graph2D::SELECT_NONE)); - m_panelGraph->setData(graphDataBytes, - Graph2D::CurveAttributes().setLineWidth(2). - setColor (wxColor( 0, 192, 0)). //medium green - fillCurveArea(wxColor(192, 255, 192))); //faint green - m_panelGraph->addData(graphDataBytesTotal, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 64, 0))); //dark green - //allow changing on completion command - m_comboBoxExecFinished->initHistory(execFinishedHistory, execFinishedHistory.size()); //-> we won't use addItemHistory() later - m_comboBoxExecFinished->setValue(execWhenFinished); - updateDialogStatus(); //null-status will be shown while waiting for dir locks (if at all) - Fit(); - Layout(); + + this->Fit(); + pnl.Layout(); if (showProgress) { - Show(); + this->Show(); + pnl.m_buttonCancel->SetFocus(); //don't steal focus when starting in sys-tray! + //clear gui flicker, remove dummy texts: window must be visible to make this work! updateGuiInt(true); //at least on OS X a real Yield() is required to flush pending GUI updates; Update() is not enough } @@ -1033,24 +1251,25 @@ SyncProgressDialogImpl::SyncProgressDialogImpl(AbortCallback& abortCb, } -SyncProgressDialogImpl::~SyncProgressDialogImpl() +template <class TopLevelDialog> +SyncProgressDialogImpl<TopLevelDialog>::~SyncProgressDialogImpl() { - if (mainDialog) + if (parentFrame_) { - mainDialog->SetTitle(titelTextBackup); //restore title text + parentFrame_->SetTitle(parentFrameTitleBackup); //restore title text //make sure main dialog is shown again if still "minimized to systray"! see SyncProgressDialog::closeWindowDirectly() - if (mainDialog->IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! - mainDialog->Iconize(false); - - mainDialog->Show(); + parentFrame_->Show(); + //if (parentFrame_->IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! + // parentFrame_->Iconize(false); } //our client is NOT expecting a second call via notifyWindowTerminate_()! } -void SyncProgressDialogImpl::OnKeyPressed(wxKeyEvent& event) +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::OnKeyPressed(wxKeyEvent& event) { const int keyCode = event.GetKeyCode(); if (keyCode == WXK_ESCAPE) @@ -1058,15 +1277,15 @@ void SyncProgressDialogImpl::OnKeyPressed(wxKeyEvent& event) wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED); //simulate click on abort button - if (m_buttonCancel->IsShown()) //delegate to "cancel" button if available + if (pnl.m_buttonCancel->IsShown()) //delegate to "cancel" button if available { - if (wxEvtHandler* handler = m_buttonCancel->GetEventHandler()) + if (wxEvtHandler* handler = pnl.m_buttonCancel->GetEventHandler()) handler->ProcessEvent(dummy); return; } - else if (m_buttonClose->IsShown()) + else if (pnl.m_buttonClose->IsShown()) { - if (wxEvtHandler* handler = m_buttonClose->GetEventHandler()) + if (wxEvtHandler* handler = pnl.m_buttonClose->GetEventHandler()) handler->ProcessEvent(dummy); return; } @@ -1076,29 +1295,33 @@ void SyncProgressDialogImpl::OnKeyPressed(wxKeyEvent& event) } -void SyncProgressDialogImpl::initNewPhase() +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::initNewPhase() { updateDialogStatus(); //evaluates "syncStat_->currentPhase()" - //reset graph (e.g. after binary comparison) - graphDataBytes->clear(); + //reset graphs (e.g. after binary comparison) + curveDataBytesTotal ->setValue(0, 0); + curveDataBytesCurrent->setValue(0, 0, 0); + curveDataItemsTotal ->setValue(0, 0); + curveDataItemsCurrent->setValue(0, 0, 0); + curveDataBytes->clear(); + curveDataItems->clear(); + notifyProgressChange(); //start new measurement perf = make_unique<PerfCheck>(WINDOW_REMAINING_TIME, WINDOW_BYTES_PER_SEC); - lastStatCallSpeed = -1000000; //some big number + lastStatCallSpeed = -1000000; //some big number phaseStartMs = timeElapsed.Time(); - //set to 0 even if totalDataToProcess is 0: due to a bug in wxGauge::SetValue, it doesn't change to determinate mode when setting the old value again - //so give updateGui() a chance to set a different value - m_gauge1->SetValue(0); - updateGuiInt(false); } -void SyncProgressDialogImpl::notifyProgressChange() //noexcept! +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::notifyProgressChange() //noexcept! { if (syncStat_) //sync running switch (syncStat_->currentPhase()) @@ -1109,13 +1332,9 @@ void SyncProgressDialogImpl::notifyProgressChange() //noexcept! break; case ProcessCallback::PHASE_COMPARING_CONTENT: case ProcessCallback::PHASE_SYNCHRONIZING: - { - const double currentData = to<double>(syncStat_->getDataCurrent(syncStat_->currentPhase())); - - //add sample for perf measurements + calc. of remaining time - graphDataBytes->addRecord(currentData, timeElapsed.Time()); - } - break; + curveDataBytes->addRecord(timeElapsed.Time(), to<double>(syncStat_->getDataCurrent (syncStat_->currentPhase()))); + curveDataItems->addRecord(timeElapsed.Time(), syncStat_->getObjectsCurrent(syncStat_->currentPhase())); + break; } } @@ -1140,7 +1359,7 @@ Zorder evaluateZorder(const wxWindow& top, const wxWindow& bottom) if (hAbove == hTop) return ZORDER_CORRECT; - for (HWND hAbove = ::GetNextWindow(hTop, GW_HWNDPREV); hAbove; hAbove = ::GetNextWindow(hAbove, GW_HWNDPREV)) + for (HWND hAbove = hTop; hAbove; hAbove = ::GetNextWindow(hAbove, GW_HWNDPREV)) if (hAbove == hBottom) return ZORDER_WRONG; @@ -1183,7 +1402,8 @@ std::wstring getDialogPhaseText(const Statistics* syncStat, bool paused, SyncPro } -void SyncProgressDialogImpl::setExternalStatus(const wxString& status, const wxString& progress) //progress may be empty! +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::setExternalStatus(const wxString& status, const wxString& progress) //progress may be empty! { //sys tray: order "top-down": jobname, status, progress wxString newTrayInfo = jobName_.empty() ? status : L"\"" + jobName_ + L"\"\n" + status; @@ -1200,10 +1420,10 @@ void SyncProgressDialogImpl::setExternalStatus(const wxString& status, const wxS trayIcon->setToolTip(newTrayInfo); //show text in dialog title (and at the same time in taskbar) - if (mainDialog) + if (parentFrame_) { - if (mainDialog->GetTitle() != newCaption) - mainDialog->SetTitle(newCaption); + if (parentFrame_->GetTitle() != newCaption) + parentFrame_->SetTitle(newCaption); } //always set a title: we don't wxGTK to show "nameless window" instead @@ -1212,7 +1432,8 @@ void SyncProgressDialogImpl::setExternalStatus(const wxString& status, const wxS } -void SyncProgressDialogImpl::updateGuiInt(bool allowYield) +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield) { if (!syncStat_) //sync not running return; @@ -1223,7 +1444,7 @@ void SyncProgressDialogImpl::updateGuiInt(bool allowYield) const long timeNow = timeElapsed.Time(); //sync status text - setText(*m_staticTextStatus, replaceCpy(syncStat_->currentStatusText(), L'\n', L' ')); //no layout update for status texts! + setText(*pnl.m_staticTextStatus, replaceCpy(syncStat_->currentStatusText(), L'\n', L' ')); //no layout update for status texts! switch (syncStat_->currentPhase()) //no matter if paused or not { @@ -1233,33 +1454,30 @@ void SyncProgressDialogImpl::updateGuiInt(bool allowYield) setExternalStatus(getDialogPhaseText(syncStat_, paused_, finalResult), toGuiString(syncStat_->getObjectsCurrent(ProcessCallback::PHASE_SCANNING))); //status text may be "paused"! //progress indicators - m_gauge1->Pulse(); - if (trayIcon.get()) trayIcon->setProgress(1); //1 = regular FFS logo + if (trayIcon.get()) trayIcon->setProgress(1); //100% = regular FFS logo - //taskbar_ status is Taskbar::STATUS_INDETERMINATE - - //constant line graph - graphDataBytesTotal->setValue(0); + //ignore graphs: should already have been cleared in initNewPhase() //remaining objects and data - setText(*m_staticTextRemainingObj , L"-", &layoutChanged); - setText(*m_staticTextDataRemaining, L"", &layoutChanged); + setText(*pnl.m_staticTextRemainingObj , L"-", &layoutChanged); + setText(*pnl.m_staticTextDataRemaining, L"", &layoutChanged); //remaining time and speed - setText(*m_staticTextSpeed, L"-", &layoutChanged); - setText(*m_staticTextRemTime, L"-", &layoutChanged); + setText(*pnl.m_staticTextRemTime, L"-", &layoutChanged); + pnl.m_panelGraphBytes->setAttributes(pnl.m_panelGraphBytes->getAttributes().setCornerText(wxString(), Graph2D::CORNER_TOP_LEFT)); + pnl.m_panelGraphItems->setAttributes(pnl.m_panelGraphItems->getAttributes().setCornerText(wxString(), Graph2D::CORNER_TOP_LEFT)); break; case ProcessCallback::PHASE_COMPARING_CONTENT: case ProcessCallback::PHASE_SYNCHRONIZING: { - auto objectsCurrent = syncStat_->getObjectsCurrent(syncStat_->currentPhase()); - auto objectsTotal = syncStat_->getObjectsTotal (syncStat_->currentPhase()); - auto dataCurrent = syncStat_->getDataCurrent (syncStat_->currentPhase()); - auto dataTotal = syncStat_->getDataTotal (syncStat_->currentPhase()); + const int itemsCurrent = syncStat_->getObjectsCurrent(syncStat_->currentPhase()); + const int itemsTotal = syncStat_->getObjectsTotal (syncStat_->currentPhase()); + const Int64 dataCurrent = syncStat_->getDataCurrent (syncStat_->currentPhase()); + const Int64 dataTotal = syncStat_->getDataTotal (syncStat_->currentPhase()); //add both data + obj-count, to handle "deletion-only" cases - const double fraction = dataTotal + objectsTotal == 0 ? 1 : std::max(0.0, to<double>(dataCurrent + objectsCurrent) / to<double>(dataTotal + objectsTotal)); + const double fraction = dataTotal + itemsTotal == 0 ? 1 : std::max(0.0, to<double>(dataCurrent + itemsCurrent) / to<double>(dataTotal + itemsTotal)); //yes, this may legitimately become < 0: failed rename operation falls-back to copy + delete, reducing "dataCurrent" to potentially < 0! //---------------------------------------------------------------------------------------------------- @@ -1267,16 +1485,22 @@ void SyncProgressDialogImpl::updateGuiInt(bool allowYield) setExternalStatus(getDialogPhaseText(syncStat_, paused_, finalResult), fractionToString(fraction)); //status text may be "paused"! //progress indicators - m_gauge1->SetValue(numeric::round(fraction * GAUGE_FULL_RANGE)); if (trayIcon.get()) trayIcon->setProgress(fraction); if (taskbar_.get()) taskbar_->setProgress(fraction); //constant line graph - graphDataBytesTotal->setValue(to<double>(dataTotal)); - + curveDataBytesTotal ->setValue(timeNow, to<double>(dataTotal)); + curveDataBytesCurrent->setValue(timeNow, to<double>(dataCurrent), to<double>(dataTotal)); + curveDataItemsTotal ->setValue(timeNow, itemsTotal); + curveDataItemsCurrent->setValue(timeNow, itemsCurrent, itemsTotal); + //even though notifyProgressChange() already set the latest data, let's add another sample to have all curves consider "timeNow" + //no problem with adding too many records: CurveDataStatistics will remove duplicate entries! + curveDataBytes->addRecord(timeNow, to<double>(dataCurrent)); + curveDataItems->addRecord(timeNow, itemsCurrent); + //remaining objects and data - setText(*m_staticTextRemainingObj, toGuiString(objectsTotal - objectsCurrent), &layoutChanged); - setText(*m_staticTextDataRemaining, L"(" + filesizeToShortString(dataTotal - dataCurrent) + L")", &layoutChanged); + setText(*pnl.m_staticTextRemainingObj, toGuiString(itemsTotal - itemsCurrent), &layoutChanged); + setText(*pnl.m_staticTextDataRemaining, L"(" + filesizeToShortString(dataTotal - dataCurrent) + L")", &layoutChanged); //it's possible data remaining becomes shortly negative if last file synced has ADS data and the dataTotal was not yet corrected! //remaining time and speed @@ -1287,48 +1511,52 @@ void SyncProgressDialogImpl::updateGuiInt(bool allowYield) lastStatCallSpeed = timeNow; if (numeric::dist(phaseStartMs, timeNow) >= 1000) //discard stats for first second: probably messy - perf->addSample(objectsCurrent, to<double>(dataCurrent), timeNow); + perf->addSample(itemsCurrent, to<double>(dataCurrent), timeNow); //current speed -> Win 7 copy uses 1 sec update interval instead - setText(*m_staticTextSpeed, perf->getBytesPerSecond(), &layoutChanged); + Opt<std::wstring> bps = perf->getBytesPerSecond(); + Opt<std::wstring> ips = perf->getItemsPerSecond(); + pnl.m_panelGraphBytes->setAttributes(pnl.m_panelGraphBytes->getAttributes().setCornerText(bps ? *bps : L"", Graph2D::CORNER_TOP_LEFT)); + pnl.m_panelGraphItems->setAttributes(pnl.m_panelGraphItems->getAttributes().setCornerText(ips ? *ips : L"", Graph2D::CORNER_TOP_LEFT)); + //setText(*pnl.m_staticTextSpeed, perf->getBytesPerSecond(), &layoutChanged); //remaining time: display with relative error of 10% - based on samples taken every 0.5 sec only //-> call more often than once per second to correctly show last few seconds countdown, but don't call too often to avoid occasional jitter - setText(*m_staticTextRemTime, perf->getRemainingTime(to<double>(dataTotal - dataCurrent)), &layoutChanged); + Opt<std::wstring> rt = perf->getRemainingTime(to<double>(dataTotal - dataCurrent)); + setText(*pnl.m_staticTextRemTime, rt ? *rt : L"-", &layoutChanged); } } break; } - m_panelGraph->setAttributes(m_panelGraph->getAttributes().setMinX(graphDataBytes->getXBegin())); - m_panelGraph->setAttributes(m_panelGraph->getAttributes().setMaxX(graphDataBytes->getXEnd())); - m_panelGraph->Refresh(); + pnl.m_panelGraphBytes->Refresh(); + pnl.m_panelGraphItems->Refresh(); //time elapsed const long timeElapSec = timeNow / 1000; - setText(*m_staticTextTimeElapsed, + setText(*pnl.m_staticTextTimeElapsed, timeElapSec < 3600 ? wxTimeSpan::Seconds(timeElapSec).Format( L"%M:%S") : wxTimeSpan::Seconds(timeElapSec).Format(L"%H:%M:%S"), &layoutChanged); - //do the ui update + //adapt layout after content changes above if (layoutChanged) { - // Layout(); - // bSizerItemsRem->Layout(); - // bSizer171->Layout(); - //bSizerProgressStat->Layout(); // - m_panelProgress->Layout(); //both needed - //m_panelBackground->Layout(); //we use a dummy panel as actual background: replaces simple "Layout()" call - //-> it seems this layout is not required, and even harmful: resets m_comboBoxExecFinished dropdown while user is selecting! + pnl.m_panelProgress->Layout(); + //small statistics panels: + //pnl.m_panelItemsProcessed->Layout(); + pnl.m_panelItemsRemaining->Layout(); + pnl.m_panelTimeRemaining ->Layout(); + //pnl.m_panelTimeElapsed->Layout(); -> needed? } #ifdef ZEN_WIN //workaround Windows 7 bug messing up z-order after temporary application hangs: https://sourceforge.net/tracker/index.php?func=detail&aid=3376523&group_id=234430&atid=1093080 - if (mainDialog) - if (evaluateZorder(*this, *mainDialog) == ZORDER_WRONG) + //This is still needed no matter if wxDialog or wxPanel is used! (2013-07) + if (parentFrame_) + if (evaluateZorder(*this, *parentFrame_) == ZORDER_WRONG) { - HWND hProgress = static_cast<HWND>(GetHWND()); + HWND hProgress = static_cast<HWND>(this->GetHWND()); if (::IsWindowVisible(hProgress)) { ::ShowWindow(hProgress, SW_HIDE); //make Windows recalculate z-order @@ -1342,47 +1570,58 @@ void SyncProgressDialogImpl::updateGuiInt(bool allowYield) //support for pause button if (paused_) { + /* + ZEN_ON_SCOPE_EXIT(resumeTimer()); -> crashes on Fedora; WHY??? + => likely compiler bug!!! + 1. no crash on Fedora for: ZEN_ON_SCOPE_EXIT(this->resumeTimer()); + 1. no crash if we derive from wxFrame instead of template "TopLevelDialog" + 2. no crash on Ubuntu GCC + 3. following makes GCC crash already during compilation: auto dfd = zen::makeGuard([this]{ resumeTimer(); }); + */ + stopTimer(); - ZEN_ON_SCOPE_EXIT(resumeTimer()); + while (paused_) { - wxMilliSleep(UI_UPDATE_INTERVAL); - updateUiNow(); //receive UI message that end pause + wxTheApp->Yield(); //receive UI message that end pause OR forceful termination! + //*first* refresh GUI (removing flicker) before sleeping! + boost::this_thread::sleep(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL)); } + //if SyncProgressDialogImpl::OnClose() already wxWindow::Destroy() on OS X then this instance is already toast! + if (wereDead) + return; //GTFO and don't call this->resumeTimer() + + resumeTimer(); } - /* - /|\ - | keep this sequence to ensure one full progress update before entering pause mode! - \|/ - */ - updateUiNow(); //receive UI message that sets pause status + else + /* + /|\ + | keep this sequence to ensure one full progress update before entering pause mode! + \|/ + */ + wxTheApp->Yield(); //receive UI message that sets pause status OR forceful termination! } else - Update(); //don't wait until next idle event (who knows what blocking process comes next?) + this->Update(); //don't wait until next idle event (who knows what blocking process comes next?) } -std::wstring SyncProgressDialogImpl::getExecWhenFinishedCommand() const -{ - return m_comboBoxExecFinished->getValue(); -} - - -void SyncProgressDialogImpl::updateDialogStatus() //depends on "syncStat_, paused_, finalResult" +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::updateDialogStatus() //depends on "syncStat_, paused_, finalResult" { const wxString dlgStatusTxt = getDialogPhaseText(syncStat_, paused_, finalResult); - m_staticTextPhase->SetLabel(dlgStatusTxt); + pnl.m_staticTextPhase->SetLabel(dlgStatusTxt); //status bitmap if (syncStat_) //sync running { auto setStatusBitmap = [&](const wchar_t* bmpName) { - m_animCtrlSyncing->Hide(); - m_bitmapStatus->SetBitmap(getResourceImage(bmpName)); - m_bitmapStatus->SetToolTip(dlgStatusTxt); - m_bitmapStatus->Show(); + pnl.m_animCtrlSyncing->Hide(); + pnl.m_bitmapStatus->SetBitmap(getResourceImage(bmpName)); + pnl.m_bitmapStatus->SetToolTip(dlgStatusTxt); + pnl.m_bitmapStatus->Show(); }; if (paused_) @@ -1391,8 +1630,8 @@ void SyncProgressDialogImpl::updateDialogStatus() //depends on "syncStat_, pause switch (syncStat_->currentPhase()) { case ProcessCallback::PHASE_NONE: - m_animCtrlSyncing->Hide(); - m_bitmapStatus->Hide(); + pnl.m_animCtrlSyncing->Hide(); + pnl.m_bitmapStatus->Hide(); break; case ProcessCallback::PHASE_SCANNING: @@ -1404,11 +1643,11 @@ void SyncProgressDialogImpl::updateDialogStatus() //depends on "syncStat_, pause break; case ProcessCallback::PHASE_SYNCHRONIZING: - m_bitmapStatus->SetBitmap(getResourceImage(L"status_syncing")); - m_bitmapStatus->SetToolTip(dlgStatusTxt); - m_bitmapStatus->Show(); - m_animCtrlSyncing->Show(); - m_animCtrlSyncing->SetToolTip(dlgStatusTxt); + pnl.m_bitmapStatus->SetBitmap(getResourceImage(L"status_syncing")); + pnl.m_bitmapStatus->SetToolTip(dlgStatusTxt); + pnl.m_bitmapStatus->Show(); + pnl.m_animCtrlSyncing->Show(); + pnl.m_animCtrlSyncing->SetToolTip(dlgStatusTxt); break; } } @@ -1416,10 +1655,10 @@ void SyncProgressDialogImpl::updateDialogStatus() //depends on "syncStat_, pause { auto setStatusBitmap = [&](const wchar_t* bmpName, const std::wstring& tooltip) { - m_animCtrlSyncing->Hide(); - m_bitmapStatus->SetBitmap(getResourceImage(bmpName)); - m_bitmapStatus->Show(); - m_bitmapStatus->SetToolTip(tooltip); + pnl.m_animCtrlSyncing->Hide(); + pnl.m_bitmapStatus->SetBitmap(getResourceImage(bmpName)); + pnl.m_bitmapStatus->Show(); + pnl.m_bitmapStatus->SetToolTip(tooltip); }; switch (finalResult) { @@ -1479,21 +1718,15 @@ void SyncProgressDialogImpl::updateDialogStatus() //depends on "syncStat_, pause //pause button if (syncStat_) //sync running - { - if (paused_) - m_buttonPause->SetLabel(_("Continue")); - else - m_buttonPause->SetLabel(_("Pause")); - } + pnl.m_buttonPause->SetLabel(paused_ ? _("&Continue") : _("&Pause")); - Layout(); - Refresh(); //a few pixels below the status text need refreshing + pnl.Layout(); + this->Refresh(); //a few pixels below the status text need refreshing } -warn_static("osx: minimize to systray?") - -void SyncProgressDialogImpl::closeWindowDirectly() //this should really be called: do not call back + schedule deletion +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::closeWindowDirectly() //this should really be called: do not call back + schedule deletion { paused_ = false; //you never know? //ATTENTION: dialog may live a little longer, so watch callbacks! @@ -1502,11 +1735,12 @@ void SyncProgressDialogImpl::closeWindowDirectly() //this should really be calle abortCb_ = nullptr; //resumeFromSystray(); -> NO, instead ~SyncProgressDialogImpl() makes sure that main dialog is shown again! - Close(); //generate close event: do NOT destroy window unconditionally! + this->Close(); //generate close event: do NOT destroy window unconditionally! } -void SyncProgressDialogImpl::processHasFinished(SyncResult resultId, const ErrorLog& log) //essential to call this in StatusHandler derived class destructor +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::processHasFinished(SyncResult resultId, const ErrorLog& log) //essential to call this in StatusHandler derived class destructor { //at the LATEST(!) to prevent access to currentStatusHandler //enable okay and close events; may be set in this method ONLY @@ -1530,36 +1764,30 @@ void SyncProgressDialogImpl::processHasFinished(SyncResult resultId, const Error case ProcessCallback::PHASE_COMPARING_CONTENT: case ProcessCallback::PHASE_SYNCHRONIZING: { - auto objectsCurrent = syncStat_->getObjectsCurrent(syncStat_->currentPhase()); - auto objectsTotal = syncStat_->getObjectsTotal (syncStat_->currentPhase()); - auto dataCurrent = syncStat_->getDataCurrent (syncStat_->currentPhase()); - auto dataTotal = syncStat_->getDataTotal (syncStat_->currentPhase()); + const int itemsCurrent = syncStat_->getObjectsCurrent(syncStat_->currentPhase()); + const int itemsTotal = syncStat_->getObjectsTotal (syncStat_->currentPhase()); + const Int64 dataCurrent = syncStat_->getDataCurrent (syncStat_->currentPhase()); + const Int64 dataTotal = syncStat_->getDataTotal (syncStat_->currentPhase()); assert(dataCurrent <= dataTotal); //set overall speed (instead of current speed) - auto getOverallBytesPerSecond = [&]() -> wxString - { - const long timeDelta = timeElapsed.Time() - phaseStartMs; //we need to consider "time within current phase" not total "timeElapsed"! - if (timeDelta != 0) - return filesizeToShortString(dataCurrent * 1000 / timeDelta) + _("/sec"); - return L"-"; //fallback - }; + const long timeDelta = timeElapsed.Time() - phaseStartMs; //we need to consider "time within current phase" not total "timeElapsed"! + + const wxString overallBytesPerSecond = timeDelta == 0 ? wxString() : filesizeToShortString(dataCurrent * 1000 / timeDelta) + _("/sec"); + const wxString overallItemsPerSecond = timeDelta == 0 ? wxString() : replaceCpy(_("%x items"), L"%x", formatThreeDigitPrecision(itemsCurrent * 1000.0 / timeDelta)) + _("/sec"); - m_staticTextSpeed->SetLabel(getOverallBytesPerSecond()); + pnl.m_panelGraphBytes->setAttributes(pnl.m_panelGraphBytes->getAttributes().setCornerText(overallBytesPerSecond, Graph2D::CORNER_TOP_LEFT)); + pnl.m_panelGraphItems->setAttributes(pnl.m_panelGraphItems->getAttributes().setCornerText(overallItemsPerSecond, Graph2D::CORNER_TOP_LEFT)); //show new element "items processed" - m_staticTextLabelItemsProc->Show(true); - bSizerItemsProc ->Show(true); - m_staticTextProcessedObj ->SetLabel(toGuiString(objectsCurrent)); - m_staticTextDataProcessed->SetLabel(L"(" + filesizeToShortString(dataCurrent) + L")"); + pnl.m_panelItemsProcessed->Show(); + pnl.m_staticTextProcessedObj ->SetLabel(toGuiString(itemsCurrent)); + pnl.m_staticTextDataProcessed->SetLabel(L"(" + filesizeToShortString(dataCurrent) + L")"); //hide remaining elements... - if (objectsCurrent == objectsTotal && //...if everything was processed successfully - dataCurrent == dataTotal) - { - m_staticTextLabelItemsRem->Show(false); - bSizerItemsRem ->Show(false); - } + if (itemsCurrent == itemsTotal && //...if everything was processed successfully + dataCurrent == dataTotal) + pnl.m_panelItemsRemaining->Hide(); } break; } @@ -1576,62 +1804,64 @@ void SyncProgressDialogImpl::processHasFinished(SyncResult resultId, const Error resumeFromSystray(); //if in tray mode... - EnableCloseButton(true); + this->EnableCloseButton(true); - m_buttonCancel->Disable(); - m_buttonCancel->Hide(); - m_buttonPause->Disable(); - m_buttonPause->Hide(); - m_buttonClose->Show(); - m_buttonClose->Enable(); + pnl.m_bpButtonMinimizeToTray->Hide(); + pnl.m_buttonCancel->Disable(); + pnl.m_buttonCancel->Hide(); + pnl.m_buttonPause->Disable(); + pnl.m_buttonPause->Hide(); + pnl.m_buttonClose->Show(); + pnl.m_buttonClose->Enable(); - bSizerExecFinished->Show(false); + pnl.m_buttonClose->SetFocus(); - //set std order after button visibility was set - setStandardButtonOrder(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonClose)); - - if (IsShown()) //don't steal focus when residing in sys-tray! - m_buttonClose->SetFocus(); + pnl.bSizerExecFinished->Show(false); - //m_animCtrlSyncing->Stop(); - //m_animCtrlSyncing->Hide(); + //set std order after button visibility was set + setStandardButtonOrder(*pnl.bSizerStdButtons, StdButtons().setAffirmative(pnl.m_buttonClose)); //hide current operation status - m_staticTextStatus->Hide(); + pnl.bSizerStatusText->Show(false); - //show and prepare final statistics - m_notebookResult->Show(); + //show and prepare final statistics + pnl.m_notebookResult->Show(); #if defined ZEN_WIN || defined ZEN_LINUX - m_staticlineFooter->Hide(); //win: m_notebookResult already has a window frame + pnl.m_staticlineFooter->Hide(); //win: m_notebookResult already has a window frame #endif - //show total time - m_staticTextLabelElapsedTime->SetLabel(_("Total time:")); //it's not "elapsed time" anymore - //hide remaining time - m_staticTextLabelRemTime->Show(false); - m_staticTextRemTime ->Show(false); + pnl.m_panelTimeRemaining->Hide(); //1. re-arrange graph into results listbook - bSizerRoot->Detach(m_panelProgress); - m_panelProgress->Reparent(m_notebookResult); + pnl.bSizerRoot->Detach(pnl.m_panelProgress); + pnl.m_panelProgress->Reparent(pnl.m_notebookResult); #ifdef ZEN_LINUX //does not seem to be required on Win or OS X wxTheApp->Yield(); //wxGTK 2.9.3 fails miserably at "reparent" whithout this #endif - m_notebookResult->AddPage(m_panelProgress, _("Statistics"), true); //AddPage() takes ownership! + pnl.m_notebookResult->AddPage(pnl.m_panelProgress, _("Statistics"), true); //AddPage() takes ownership! //2. log file const size_t posLog = 1; - LogPanel* logPanel = new LogPanel(m_notebookResult, log); //owned by m_notebookResult - m_notebookResult->AddPage(logPanel, _("Logging"), false); + LogPanel* logPanel = new LogPanel(pnl.m_notebookResult, log); //owned by m_notebookResult + pnl.m_notebookResult->AddPage(logPanel, _("Log"), false); //bSizerHoldStretch->Insert(0, logPanel, 1, wxEXPAND); //show log instead of graph if errors occurred! (not required for ignored warnings) if (log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR) > 0) - m_notebookResult->ChangeSelection(posLog); + pnl.m_notebookResult->ChangeSelection(posLog); + + //this->Fit(); //not a good idea: will shrink even if window is maximized or was enlarged by the user + pnl.Layout(); + + pnl.m_panelProgress->Layout(); + //small statistics panels: + pnl.m_panelItemsProcessed->Layout(); + pnl.m_panelItemsRemaining->Layout(); + //pnl.m_panelTimeRemaining->Layout(); + //pnl.m_panelTimeElapsed->Layout(); -> needed? - Layout(); //play (optional) sound notification after sync has completed -> only play when waiting on results dialog, seems to be pointless otherwise! switch (finalResult) { @@ -1648,19 +1878,19 @@ void SyncProgressDialogImpl::processHasFinished(SyncResult resultId, const Error break; } - //Raise(); -> don't! user may be watching a movie in the meantime ;) - warn_static("was ist mit resumeFromSystray:: Raise()??") + //Raise(); -> don't! user may be watching a movie in the meantime ;) note: resumeFromSystray() also calls Raise()! } -void SyncProgressDialogImpl::OnOkay(wxCommandEvent& event) +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::OnOkay(wxCommandEvent& event) { - isZombie = true; //on Fedora an iconize event is issued *before* entering SyncProgressDialogImpl::OnClose()!!! - Close(); //generate close event: do NOT destroy window unconditionally! + this->Close(); //generate close event: do NOT destroy window unconditionally! } -void SyncProgressDialogImpl::OnCancel(wxCommandEvent& event) +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::OnCancel(wxCommandEvent& event) { paused_ = false; updateDialogStatus(); //update status + pause button @@ -1671,14 +1901,16 @@ void SyncProgressDialogImpl::OnCancel(wxCommandEvent& event) } -void SyncProgressDialogImpl::OnPause(wxCommandEvent& event) +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::OnPause(wxCommandEvent& event) { paused_ = !paused_; updateDialogStatus(); //update status + pause button } -void SyncProgressDialogImpl::OnClose(wxCloseEvent& event) +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::OnClose(wxCloseEvent& event) { //this event handler may be called *during* sync, e.g. due to a system shutdown (Windows), anytime (OS X) //try to stop sync gracefully and cross fingers: @@ -1695,68 +1927,86 @@ void SyncProgressDialogImpl::OnClose(wxCloseEvent& event) syncStat_ = nullptr; abortCb_ = nullptr; - isZombie = true; //it "lives" until cleanup in next idle event - Destroy(); -} - - -void SyncProgressDialogImpl::OnIconize(wxIconizeEvent& event) -{ - if (isZombie) return; //wxGTK sends iconize event *after* wxWindow::Destroy, sigh... - - if (event.IsIconized()) //ATTENTION: iconize event is also triggered on "Restore"! (at least under Linux) - minimizeToTray(); - else - resumeFromSystray(); //may be initiated by "show desktop" although all windows are hidden! + wereDead = true; + this->Destroy(); //wxWidgets OS X: simple "delete"!!!!!!! } -void SyncProgressDialogImpl::OnResumeFromTray(wxCommandEvent& event) +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::OnIconize(wxIconizeEvent& event) { - resumeFromSystray(); + /* + propagate progress dialog minimize/maximize to parent + ----------------------------------------------------- + Fedora/Debian/Ubuntu: + - wxDialog cannot be minimized + - worse, wxGTK sends stray iconize events *after* wxDialog::Destroy() + - worse, on Fedora an iconize event is issued directly after calling Close() + - worse, even wxDialog::Hide() causes iconize event! + => nothing to do + SUSE: + - wxDialog can be minimized (it just vanishes!) and in general also minimizes parent: except for our progress wxDialog!!! + - worse, wxDialog::Hide() causes iconize event + - probably the same issues with stray iconize events like Fedora/Debian/Ubuntu + - minimize button is always shown, even if wxMINIMIZE_BOX is omitted! + => nothing to do + Mac OS X: + - wxDialog can be minimized and automatically minimizes parent + - no iconize events seen by wxWidgets! + => nothing to do + Windows: + - wxDialog can be minimized but does not also minimize parent + - iconize events only seen for manual minimize + => propagate event to parent + */ +#ifdef ZEN_WIN + if (parentFrame_) + if (parentFrame_->IsIconized() != event.IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! + parentFrame_->Iconize(event.IsIconized()); +#endif + event.Skip(); } -void SyncProgressDialogImpl::minimizeToTray() +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::minimizeToTray() { -// wxMessageBox(L"hi"); - if (!trayIcon.get()) { - trayIcon = make_unique<FfsTrayIcon>(); - trayIcon->Connect(FFS_REQUEST_RESUME_TRAY_EVENT, wxCommandEventHandler(SyncProgressDialogImpl::OnResumeFromTray), nullptr, this); - //tray icon has shorter lifetime than this => no need to disconnect event later + trayIcon = make_unique<FfsTrayIcon>([this] { this->resumeFromSystray(); }); //FfsTrayIcon lifetime is a subset of "this"'s lifetime! + //we may destroy FfsTrayIcon even while in the FfsTrayIcon callback!!!! updateGuiInt(false); //set tray tooltip + progress: e.g. no updates while paused - Hide(); - if (mainDialog) - mainDialog->Hide(); + this->Hide(); + if (parentFrame_) + parentFrame_->Hide(); } } -void SyncProgressDialogImpl::resumeFromSystray() +template <class TopLevelDialog> +void SyncProgressDialogImpl<TopLevelDialog>::resumeFromSystray() { if (trayIcon) { trayIcon.reset(); - if (mainDialog) + if (parentFrame_) { - if (mainDialog->IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! - mainDialog->Iconize(false); - mainDialog->Show(); - mainDialog->Raise(); + //if (parentFrame_->IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! + // parentFrame_->Iconize(false); + parentFrame_->Show(); + parentFrame_->Raise(); } - if (IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! - Iconize(false); - Show(); - Raise(); - SetFocus(); + //if (IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! + // Iconize(false); + this->Show(); + this->Raise(); + this->SetFocus(); - updateDialogStatus(); //restore Windows 7 task bar status (e.g. required in pause mode) + updateDialogStatus(); //restore Windows 7 task bar status (e.g. required in pause mode) updateGuiInt(false); //restore Windows 7 task bar progress (e.g. required in pause mode) } } @@ -1766,11 +2016,18 @@ void SyncProgressDialogImpl::resumeFromSystray() SyncProgressDialog* createProgressDialog(zen::AbortCallback& abortCb, const std::function<void()>& notifyWindowTerminate, //note: user closing window cannot be prevented on OS X! (And neither on Windows during system shutdown!) const zen::Statistics& syncStat, - wxTopLevelWindow* parentWindow, //may be nullptr + wxFrame* parentWindow, //may be nullptr bool showProgress, const wxString& jobName, const std::wstring& execWhenFinished, std::vector<std::wstring>& execFinishedHistory) { - return new SyncProgressDialogImpl(abortCb, notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, execWhenFinished, execFinishedHistory); + if (parentWindow) //sync from GUI + return new SyncProgressDialogImpl<wxDialog>(wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxMINIMIZE_BOX | wxRESIZE_BORDER, + [&](wxDialog& progDlg) { return parentWindow; }, abortCb, + notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, execWhenFinished, execFinishedHistory); + else //FFS batch job + return new SyncProgressDialogImpl<wxFrame>(wxDEFAULT_FRAME_STYLE, + [](wxFrame& progDlg) { return &progDlg; }, abortCb, + notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, execWhenFinished, execFinishedHistory); } diff --git a/ui/progress_indicator.h b/ui/progress_indicator.h index 0789ca80..e2dbb99c 100644 --- a/ui/progress_indicator.h +++ b/ui/progress_indicator.h @@ -10,7 +10,7 @@ #include <functional> #include <zen/error_log.h> //#include <zen/zstring.h> -#include <wx/toplevel.h> +#include <wx/frame.h> #include "../lib/status_handler.h" //#include "main_dlg.h" @@ -18,7 +18,7 @@ class CompareProgressDialog { public: - CompareProgressDialog(wxTopLevelWindow& parentWindow); //CompareProgressDialog will be owned by parentWindow! + CompareProgressDialog(wxFrame& parentWindow); //CompareProgressDialog will be owned by parentWindow! wxWindow* getAsWindow(); //convenience! don't abuse! @@ -52,7 +52,7 @@ struct SyncProgressDialog //--------------------------------------------------------------------------- - virtual wxWindow* getAsWindow() = 0; //convenience! don't abuse! + virtual wxWindow* getWindowIfVisible() = 0; //may be nullptr; don't abuse, use as parent for modal dialogs only! virtual void initNewPhase() = 0; //call after "StatusHandler::initNewPhase" virtual void notifyProgressChange() = 0; //throw (), required by graph! @@ -71,7 +71,7 @@ protected: SyncProgressDialog* createProgressDialog(zen::AbortCallback& abortCb, const std::function<void()>& notifyWindowTerminate, //note: user closing window cannot be prevented on OS X! (And neither on Windows during system shutdown!) const zen::Statistics& syncStat, - wxTopLevelWindow* parentWindow, //may be nullptr + wxFrame* parentWindow, //may be nullptr bool showProgress, const wxString& jobName, const std::wstring& execWhenFinished, diff --git a/ui/small_dlgs.cpp b/ui/small_dlgs.cpp index 304e4269..b7b7828b 100644 --- a/ui/small_dlgs.cpp +++ b/ui/small_dlgs.cpp @@ -384,10 +384,9 @@ void DeleteDialog::updateGui() { wxWindowUpdateLocker dummy(this); //avoid display distortion - const std::pair<Zstring, int> delInfo = zen::deleteFromGridAndHDPreview( - rowsToDeleteOnLeft, - rowsToDeleteOnRight, - m_checkBoxDeleteBothSides->GetValue()); + const std::pair<Zstring, int> delInfo = zen::deleteFromGridAndHDPreview(rowsToDeleteOnLeft, + rowsToDeleteOnRight, + m_checkBoxDeleteBothSides->GetValue()); wxString header; if (m_checkBoxUseRecycler->GetValue()) { @@ -406,7 +405,13 @@ void DeleteDialog::updateGui() const wxString& fileList = utfCvrtTo<wxString>(delInfo.first); m_textCtrlFileList->ChangeValue(fileList); - + /* + There is a nasty bug on wxGTK under Ubuntu: If a multi-line wxTextCtrl contains so many lines that scrollbars are shown, + it re-enables all windows that are supposed to be disabled during the current modal loop! + This only affects Ubuntu/wxGTK! No such issue on Debian/wxGTK or Suse/wxGTK + => another Unity problem like the following? + http://trac.wxwidgets.org/ticket/14823 "Menu not disabled when showing modal dialogs in wxGTK under Unity" + */ Layout(); } diff --git a/ui/switch_to_gui.h b/ui/switch_to_gui.h index a3cf6827..20fe81de 100644 --- a/ui/switch_to_gui.h +++ b/ui/switch_to_gui.h @@ -27,7 +27,7 @@ public: void execute() const { - MainDialog::create(guiCfg, referenceFiles, globalSettings_, true); //new toplevel window + MainDialog::create(guiCfg, referenceFiles, &globalSettings_, true); //new toplevel window } private: diff --git a/ui/sync_cfg.cpp b/ui/sync_cfg.cpp index 806914a3..97518be1 100644 --- a/ui/sync_cfg.cpp +++ b/ui/sync_cfg.cpp @@ -107,15 +107,15 @@ void updateConfigIcons(const DirectionConfig& directionCfg, switch (dirCfg.exLeftSideOnly) { - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: buttonLeftOnly->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_create_right"))); buttonLeftOnly->SetToolTip(getSyncOpDescription(SO_CREATE_NEW_RIGHT)); break; - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: buttonLeftOnly->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_delete_left"))); buttonLeftOnly->SetToolTip(getSyncOpDescription(SO_DELETE_LEFT)); break; - case SYNC_DIR_NONE: + case SyncDirection::NONE: buttonLeftOnly->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_none"))); buttonLeftOnly->SetToolTip(getSyncOpDescription(SO_DO_NOTHING)); break; @@ -123,15 +123,15 @@ void updateConfigIcons(const DirectionConfig& directionCfg, switch (dirCfg.exRightSideOnly) { - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: buttonRightOnly->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_delete_right"))); buttonRightOnly->SetToolTip(getSyncOpDescription(SO_DELETE_RIGHT)); break; - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: buttonRightOnly->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_create_left"))); buttonRightOnly->SetToolTip(getSyncOpDescription(SO_CREATE_NEW_LEFT)); break; - case SYNC_DIR_NONE: + case SyncDirection::NONE: buttonRightOnly->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_none"))); buttonRightOnly->SetToolTip(getSyncOpDescription(SO_DO_NOTHING)); break; @@ -139,15 +139,15 @@ void updateConfigIcons(const DirectionConfig& directionCfg, switch (dirCfg.leftNewer) { - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: buttonLeftNewer->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_update_right"))); buttonLeftNewer->SetToolTip(getSyncOpDescription(SO_OVERWRITE_RIGHT)); break; - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: buttonLeftNewer->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_update_left"))); buttonLeftNewer->SetToolTip(getSyncOpDescription(SO_OVERWRITE_LEFT)); break; - case SYNC_DIR_NONE: + case SyncDirection::NONE: buttonLeftNewer->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_none"))); buttonLeftNewer->SetToolTip(getSyncOpDescription(SO_DO_NOTHING)); break; @@ -155,15 +155,15 @@ void updateConfigIcons(const DirectionConfig& directionCfg, switch (dirCfg.rightNewer) { - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: buttonRightNewer->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_update_right"))); buttonRightNewer->SetToolTip(getSyncOpDescription(SO_OVERWRITE_RIGHT)); break; - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: buttonRightNewer->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_update_left"))); buttonRightNewer->SetToolTip(getSyncOpDescription(SO_OVERWRITE_LEFT)); break; - case SYNC_DIR_NONE: + case SyncDirection::NONE: buttonRightNewer->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_none"))); buttonRightNewer->SetToolTip(getSyncOpDescription(SO_DO_NOTHING)); break; @@ -171,15 +171,15 @@ void updateConfigIcons(const DirectionConfig& directionCfg, switch (dirCfg.different) { - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: buttonDifferent->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_update_right"))); buttonDifferent->SetToolTip(getSyncOpDescription(SO_OVERWRITE_RIGHT)); break; - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: buttonDifferent->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_update_left"))); buttonDifferent->SetToolTip(getSyncOpDescription(SO_OVERWRITE_LEFT)); break; - case SYNC_DIR_NONE: + case SyncDirection::NONE: buttonDifferent->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_none"))); buttonDifferent->SetToolTip(getSyncOpDescription(SO_DO_NOTHING)); break; @@ -187,15 +187,15 @@ void updateConfigIcons(const DirectionConfig& directionCfg, switch (dirCfg.conflict) { - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: buttonConflict->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_update_right"))); buttonConflict->SetToolTip(getSyncOpDescription(SO_OVERWRITE_RIGHT)); break; - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: buttonConflict->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"so_update_left"))); buttonConflict->SetToolTip(getSyncOpDescription(SO_OVERWRITE_LEFT)); break; - case SYNC_DIR_NONE: + case SyncDirection::NONE: buttonConflict->SetBitmapLabel(mirrorIfRtl(getResourceImage(L"cat_conflict"))); //silent dependency to algorithm.cpp::Redetermine!!! buttonConflict->SetToolTip(_("Leave as unresolved conflict")); break; @@ -495,14 +495,14 @@ void toggleSyncDirection(SyncDirection& current) { switch (current) { - case SYNC_DIR_RIGHT: - current = SYNC_DIR_LEFT; + case SyncDirection::RIGHT: + current = SyncDirection::LEFT; break; - case SYNC_DIR_LEFT: - current = SYNC_DIR_NONE; + case SyncDirection::LEFT: + current = SyncDirection::NONE; break; - case SYNC_DIR_NONE: - current = SYNC_DIR_RIGHT; + case SyncDirection::NONE: + current = SyncDirection::RIGHT; break; } } diff --git a/ui/taskbar.cpp b/ui/taskbar.cpp index 0e019d04..e6aadc90 100644 --- a/ui/taskbar.cpp +++ b/ui/taskbar.cpp @@ -30,7 +30,7 @@ using namespace tbseven; class Taskbar::Pimpl //throw TaskbarNotAvailable { public: - Pimpl(const wxTopLevelWindow& window) : + Pimpl(const wxFrame& window) : assocWindow(window.GetHWND()), setStatus_ (getDllName(), funName_setStatus), setProgress_(getDllName(), funName_setProgress) @@ -86,7 +86,7 @@ const char FFS_DESKTOP_FILE[] = "freefilesync.desktop"; class Taskbar::Pimpl //throw (TaskbarNotAvailable) { public: - Pimpl(const wxTopLevelWindow& window) : + Pimpl(const wxFrame& window) : tbEntry(unity_launcher_entry_get_for_desktop_id(FFS_DESKTOP_FILE)) //tbEntry(unity_launcher_entry_get_for_app_uri("application://freefilesync.desktop")) { @@ -133,7 +133,7 @@ private: class Taskbar::Pimpl { public: - Pimpl(const wxTopLevelWindow& window) {} + Pimpl(const wxFrame& window) {} ~Pimpl() { setDockText(""); } @@ -161,7 +161,7 @@ private: class Taskbar::Pimpl { public: - Pimpl(const wxTopLevelWindow& window) { throw TaskbarNotAvailable(); } + Pimpl(const wxFrame& window) { throw TaskbarNotAvailable(); } void setStatus(Status status) {} void setProgress(double fraction) {} }; @@ -169,7 +169,7 @@ public: //######################################################################################################## -Taskbar::Taskbar(const wxTopLevelWindow& window) : pimpl_(new Pimpl(window)) {} //throw TaskbarNotAvailable +Taskbar::Taskbar(const wxFrame& window) : pimpl_(new Pimpl(window)) {} //throw TaskbarNotAvailable Taskbar::~Taskbar() {} void Taskbar::setStatus(Status status) { pimpl_->setStatus(status); } diff --git a/ui/taskbar.h b/ui/taskbar.h index ad708794..82e08656 100644 --- a/ui/taskbar.h +++ b/ui/taskbar.h @@ -8,7 +8,7 @@ #define TASKBARPROGRESS_H_INCLUDED #include <memory> -#include <wx/toplevel.h> +#include <wx/frame.h> /* Windows 7; show progress in windows superbar via ITaskbarList3 Interface: http://msdn.microsoft.com/en-us/library/dd391692(VS.85).aspx @@ -27,7 +27,7 @@ class TaskbarNotAvailable {}; class Taskbar { public: - Taskbar(const wxTopLevelWindow& window); //throw TaskbarNotAvailable() + Taskbar(const wxFrame& window); //throw TaskbarNotAvailable ~Taskbar(); enum Status diff --git a/ui/tray_icon.cpp b/ui/tray_icon.cpp index 6a9c640c..3826458f 100644 --- a/ui/tray_icon.cpp +++ b/ui/tray_icon.cpp @@ -14,8 +14,6 @@ #include "../lib/resources.h" -const wxEventType FFS_REQUEST_RESUME_TRAY_EVENT = wxNewEventType(); - namespace { void fillRange(wxImage& img, int pixelFirst, int pixelLast, const wxColor& col) //tolerant input range @@ -52,90 +50,56 @@ void fillRange(wxImage& img, int pixelFirst, int pixelLast, const wxColor& col) wxIcon generateProgressIcon(const wxImage& logo, double fraction) //generate icon with progress indicator { - if (!logo.IsOk()) + if (!logo.IsOk() || logo.GetWidth() <= 0 || logo.GetHeight() <= 0) return wxIcon(); const int pixelCount = logo.GetWidth() * logo.GetHeight(); - const int startFillPixel = std::min(numeric::round(fraction * pixelCount), pixelCount); + const int startFillPixel = numeric::confineCpy(numeric::round(fraction * pixelCount), 0, pixelCount); //minor optimization static std::pair<int, wxIcon> buffer = std::make_pair(-1, wxNullIcon); if (buffer.first != startFillPixel) { - //progress bar - if (logo.GetWidth() > 0 && - logo.GetHeight() > 0) - { - wxImage genImage(logo.Copy()); //workaround wxWidgets' screwed-up design from hell: their copy-construction implements reference-counting WITHOUT copy-on-write! - - //gradually make FFS icon brighter while nearing completion - zen::brighten(genImage, -200 * (1 - fraction)); - - //fill black border row - if (startFillPixel <= pixelCount - genImage.GetWidth()) - { - /* - -------- - ---bbbbb - bbbbSyyy S : start yellow remainder - yyyyyyyy - */ - int bStart = startFillPixel - genImage.GetWidth(); - if (bStart % genImage.GetWidth() != 0) //add one more black pixel, see ascii-art - --bStart; - fillRange(genImage, bStart, startFillPixel, *wxBLACK); - } - else if (startFillPixel < pixelCount) - { - //special handling for last row - /* - -------- - -------- - ---bbbbb - ---bSyyy S : start yellow remainder - */ - int bStart = startFillPixel - genImage.GetWidth() - 1; - int bEnd = (bStart / genImage.GetWidth() + 1) * genImage.GetWidth(); - - fillRange(genImage, bStart, bEnd, *wxBLACK); - fillRange(genImage, startFillPixel - 1, startFillPixel, *wxBLACK); - } + wxImage genImage(logo.Copy()); //workaround wxWidgets' screwed-up design from hell: their copy-construction implements reference-counting WITHOUT copy-on-write! - //fill yellow remainder - fillRange(genImage, startFillPixel, pixelCount, wxColour(240, 200, 0)); + //gradually make FFS icon brighter while nearing completion + zen::brighten(genImage, -200 * (1 - fraction)); + //fill black border row + if (startFillPixel <= pixelCount - genImage.GetWidth()) + { /* - const int indicatorWidth = genImage.GetWidth() * .4; - const int indicatorXBegin = std::ceil((genImage.GetWidth() - indicatorWidth) / 2.0); - const int indicatorYBegin = genImage.GetHeight() - indicatorHeight; - - //draw progress indicator: do NOT use wxDC::DrawRectangle! Doesn't respect alpha in Windows, but does in Linux! - //We need a simple, working solution: - - for (int row = indicatorYBegin; row < genImage.GetHeight(); ++row) - { - for (int col = indicatorXBegin; col < indicatorXBegin + indicatorWidth; ++col) - { - unsigned char* const pixelBegin = data + (row * genImage.GetWidth() + col) * 3; - pixelBegin[0] = 240; //red - pixelBegin[1] = 200; //green - pixelBegin[2] = 0; //blue - } - } - - if (genImage.HasAlpha()) - { - unsigned char* const alpha = genImage.GetAlpha(); - //make progress indicator fully opaque: - for (int row = indicatorYBegin; row < genImage.GetHeight(); ++row) - ::memset(alpha + row * genImage.GetWidth() + indicatorXBegin, wxIMAGE_ALPHA_OPAQUE, indicatorWidth); - } + -------- + ---bbbbb + bbbbSyyy S : start yellow remainder + yyyyyyyy */ - buffer.second.CopyFromBitmap(wxBitmap(genImage)); + int bStart = startFillPixel - genImage.GetWidth(); + if (bStart % genImage.GetWidth() != 0) //add one more black pixel, see ascii-art + --bStart; + fillRange(genImage, bStart, startFillPixel, *wxBLACK); + } + else if (startFillPixel < pixelCount) + { + //special handling for last row + /* + -------- + -------- + ---bbbbb + ---bSyyy S : start yellow remainder + */ + int bStart = startFillPixel - genImage.GetWidth() - 1; + int bEnd = (bStart / genImage.GetWidth() + 1) * genImage.GetWidth(); + + fillRange(genImage, bStart, bEnd, *wxBLACK); + fillRange(genImage, startFillPixel - 1, startFillPixel, *wxBLACK); } - else - buffer.second = wxIcon(); + + //fill yellow remainder + fillRange(genImage, startFillPixel, pixelCount, wxColour(240, 200, 0)); + + buffer.second.CopyFromBitmap(wxBitmap(genImage)); } return buffer.second; @@ -145,7 +109,7 @@ wxIcon generateProgressIcon(const wxImage& logo, double fraction) //generate ico enum Selection { - CONTEXT_RESTORE = 1, //wxWidgets: "A MenuItem ID of Zero does not work under Mac" + CONTEXT_RESTORE = 1, //wxWidgets: "A MenuItem ID of zero does not work under Mac" CONTEXT_ABOUT = wxID_ABOUT }; } @@ -154,95 +118,107 @@ enum Selection class FfsTrayIcon::TaskBarImpl : public wxTaskBarIcon { public: - TaskBarImpl(FfsTrayIcon& parent) : parent_(&parent) {} + TaskBarImpl(const std::function<void()>& onRequestResume) : onRequestResume_(onRequestResume) + { + Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(TaskBarImpl::OnDoubleClick), nullptr, this); //register double-click + } - void parentHasDied() { parent_ = nullptr; } + //virtual ~TaskBarImpl(){} + + void dontCallbackAnymore() { onRequestResume_ = nullptr; } private: virtual wxMenu* CreatePopupMenu() { - if (!parent_) + if (!onRequestResume_) return nullptr; wxMenu* contextMenu = new wxMenu; - contextMenu->Append(CONTEXT_ABOUT, _("&About")); - contextMenu->AppendSeparator(); contextMenu->Append(CONTEXT_RESTORE, _("&Restore")); + contextMenu->AppendSeparator(); + contextMenu->Append(CONTEXT_ABOUT, _("&About")); //event handling - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(FfsTrayIcon::OnContextMenuSelection), nullptr, parent_); + contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TaskBarImpl::OnContextMenuSelection), nullptr, this); return contextMenu; //ownership transferred to caller } - FfsTrayIcon* parent_; + void OnContextMenuSelection(wxCommandEvent& event) + { + switch (static_cast<Selection>(event.GetId())) + { + case CONTEXT_ABOUT: + { + //ATTENTION: the modal dialog below does NOT disable all GUI input, e.g. user may still double-click on tray icon + //which will implicitly destroy the tray icon while still showing the modal dialog + SetEvtHandlerEnabled(false); + ZEN_ON_SCOPE_EXIT(SetEvtHandlerEnabled(true)); + + zen::showAboutDialog(nullptr); + } + break; + + case CONTEXT_RESTORE: + if (onRequestResume_) + onRequestResume_(); + break; + } + } + + void OnDoubleClick(wxCommandEvent& event) + { + if (onRequestResume_) + onRequestResume_(); + } + + std::function<void()> onRequestResume_; }; -FfsTrayIcon::FfsTrayIcon() : - trayIcon(new TaskBarImpl(*this)), - fractionLast(1), //show FFS logo by default +FfsTrayIcon::FfsTrayIcon(const std::function<void()>& onRequestResume) : + trayIcon(new TaskBarImpl(onRequestResume)), + activeFraction(1), //show FFS logo by default #if defined ZEN_WIN || defined ZEN_MAC //16x16 seems to be the only size that is shown correctly on OS X logo(getResourceImage(L"FFS_tray_16x16").ConvertToImage()) #elif defined ZEN_LINUX logo(getResourceImage(L"FFS_tray_24x24").ConvertToImage()) #endif { - trayIcon->SetIcon(generateProgressIcon(logo, fractionLast), L"FreeFileSync"); - trayIcon->Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(FfsTrayIcon::OnDoubleClick), nullptr, this); //register double-click + trayIcon->SetIcon(generateProgressIcon(logo, activeFraction), L"FreeFileSync"); } FfsTrayIcon::~FfsTrayIcon() { - trayIcon->RemoveIcon(); //hide icon until final deletion takes place - trayIcon->Disconnect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(FfsTrayIcon::OnDoubleClick), nullptr, this); - trayIcon->parentHasDied(); //TaskBarImpl (potentially) has longer lifetime than FfsTrayIcon: avoid callback! - - //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking) - if (!wxPendingDelete.Member(trayIcon)) - wxPendingDelete.Append(trayIcon); -} + trayIcon->dontCallbackAnymore(); //TaskBarImpl has longer lifetime than FfsTrayIcon: avoid callback! + /* + This is not working correctly on OS X! It seems both wxTaskBarIcon::RemoveIcon() and ~wxTaskBarIcon() are broken and do NOT immediately + remove the icon from the system tray! Only some time later in the event loop which called these functions they will be removed. + Maybe some system component has still shared ownership? Objective C auto release pools are freed at the end of the current event loop... + Anyway, wxWidgets fails to disconnect the wxTaskBarIcon event handlers before calling "[m_statusitem release]"! -void FfsTrayIcon::setToolTip(const wxString& toolTip) -{ - toolTipLast = toolTip; - trayIcon->SetIcon(generateProgressIcon(logo, fractionLast), toolTip); //another wxWidgets design bug: non-orthogonal method! -} + => !!!clicking on the icon after ~wxTaskBarIcon ran crashes the application!!! + - if ~wxTaskBarIcon() ran from the SyncProgressDialog::updateGui() event loop (e.g. user manually clicking the icon) => icon removed on return + - if ~wxTaskBarIcon() ran from SyncProgressDialog::closeWindowDirectly() => leaves the icon dangling until user closes this dialog and outter event loop runs! + */ -void FfsTrayIcon::setProgress(double fraction) -{ - fractionLast = fraction; - trayIcon->SetIcon(generateProgressIcon(logo, fraction), toolTipLast); + trayIcon->RemoveIcon(); //required on Windows: unlike on OS X, wxPendingDelete does not kick in before main event loop! + //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking) + wxPendingDelete.Append(trayIcon); } -void FfsTrayIcon::OnContextMenuSelection(wxCommandEvent& event) +void FfsTrayIcon::setToolTip(const wxString& toolTip) { - switch (static_cast<Selection>(event.GetId())) - { - case CONTEXT_ABOUT: - { - //ATTENTION: the modal dialog below does NOT disable all GUI input, e.g. user may still double-click on tray icon - //which will implicitly destroy the tray icon while still showing the modal dialog - trayIcon->SetEvtHandlerEnabled(false); - zen::showAboutDialog(nullptr); - trayIcon->SetEvtHandlerEnabled(true); - } - break; - case CONTEXT_RESTORE: - { - wxCommandEvent dummy(FFS_REQUEST_RESUME_TRAY_EVENT); - ProcessEvent(dummy); - } - } + activeToolTip = toolTip; + trayIcon->SetIcon(generateProgressIcon(logo, activeFraction), activeToolTip); //another wxWidgets design bug: non-orthogonal method! } -void FfsTrayIcon::OnDoubleClick(wxCommandEvent& event) +void FfsTrayIcon::setProgress(double fraction) { - wxCommandEvent dummy(FFS_REQUEST_RESUME_TRAY_EVENT); - ProcessEvent(dummy); + activeFraction = fraction; + trayIcon->SetIcon(generateProgressIcon(logo, activeFraction), activeToolTip); } - diff --git a/ui/tray_icon.h b/ui/tray_icon.h index 866d79d5..24c97eb0 100644 --- a/ui/tray_icon.h +++ b/ui/tray_icon.h @@ -4,38 +4,42 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef TRAYICON_H_INCLUDED -#define TRAYICON_H_INCLUDED +#ifndef TRAYICON_H_84217830427534285 +#define TRAYICON_H_84217830427534285 -#include <wx/event.h> +#include <functional> #include <wx/image.h> -//show tray icon with progress during lifetime of this instance -//emits the following wxCommandEvent in case user double-clicks on tray icon or selects corresponding context menu item: -extern const wxEventType FFS_REQUEST_RESUME_TRAY_EVENT; +/* +show tray icon with progress during lifetime of this instance -class FfsTrayIcon : public wxEvtHandler +ATTENTION: wxWidgets never assumes that an object indirectly destroys itself while processing an event! + this includes wxEvtHandler-derived objects!!! + it seems ProcessEvent() works (on Windows), but AddPendingEvent() will crash since it uses "this" after the event processing! + +=> don't derive from wxEvtHandler or any other wxWidgets object here!!!!!! +=> use simple std::function as callback instead => instance may now be safely deleted in callback! +*/ + +class FfsTrayIcon { public: - FfsTrayIcon(); + FfsTrayIcon(const std::function<void()>& onRequestResume); //callback only held during lifetime of this instance ~FfsTrayIcon(); void setToolTip(const wxString& toolTip); void setProgress(double fraction); //number between [0, 1], for small progress indicator private: - FfsTrayIcon(const FfsTrayIcon&); - FfsTrayIcon& operator=(const FfsTrayIcon&); - - void OnContextMenuSelection(wxCommandEvent& event); - void OnDoubleClick(wxCommandEvent& event); + FfsTrayIcon(const FfsTrayIcon&); //=delete + FfsTrayIcon& operator=(const FfsTrayIcon&); //=delete class TaskBarImpl; - TaskBarImpl* trayIcon; //actual tray icon (don't use inheritance to enable delayed deletion) + TaskBarImpl* trayIcon; - wxString toolTipLast; - double fractionLast; + wxString activeToolTip; + double activeFraction; wxImage logo; }; -#endif // TRAYICON_H_INCLUDED +#endif //TRAYICON_H_84217830427534285 diff --git a/ui/tree_view.cpp b/ui/tree_view.cpp index 10037380..791fa6cc 100644 --- a/ui/tree_view.cpp +++ b/ui/tree_view.cpp @@ -46,11 +46,11 @@ void TreeView::extractVisibleSubtree(HierarchyObject& hierObj, //in if (fileObj.isActive()) switch (fileObj.getSyncDir()) { - case SYNC_DIR_LEFT: + case SyncDirection::LEFT: return fileObj.getFileSize<RIGHT_SIDE>(); - case SYNC_DIR_RIGHT: + case SyncDirection::RIGHT: return fileObj.getFileSize<LEFT_SIDE>(); - case SYNC_DIR_NONE: + case SyncDirection::NONE: break; } return std::max(fileObj.getFileSize<LEFT_SIDE>(), fileObj.getFileSize<RIGHT_SIDE>()); diff --git a/version/version.h b/version/version.h index 9bf4aba7..42589d07 100644 --- a/version/version.h +++ b/version/version.h @@ -3,7 +3,7 @@ namespace zen { - const wchar_t currentVersion[] = L"5.18"; //internal linkage! +const wchar_t currentVersion[] = L"5.19"; //internal linkage! } #endif diff --git a/wx+/dc.h b/wx+/dc.h new file mode 100644 index 00000000..d0f5c805 --- /dev/null +++ b/wx+/dc.h @@ -0,0 +1,127 @@ +// ************************************************************************** +// * 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 DC_3813704987123956832143243214 +#define DC_3813704987123956832143243214 + +#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER + +namespace zen +{ +/* +1. wxDCClipper does *not* stack: another fix for yet another poor wxWidgets implementation + +class RecursiveDcClipper +{ + RecursiveDcClipper(wxDC& dc, const wxRect& r) : dc_(dc) +}; + +------------------------------------------------------------------------------------------------ + +2. wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active: a fix for a poor wxWidgets implementation +class BufferedPaintDC +{ + BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer); +}; +*/ + + + + + + + + + + + +//---------------------- implementation ------------------------ +class RecursiveDcClipper +{ +public: + RecursiveDcClipper(wxDC& dc, const wxRect& r) : dc_(dc) + { + auto it = refDcToAreaMap().find(&dc); + if (it != refDcToAreaMap().end()) + { + oldRect.reset(new wxRect(it->second)); + + wxRect tmp = r; + tmp.Intersect(*oldRect); //better safe than sorry + dc_.SetClippingRegion(tmp); // + it->second = tmp; + } + else + { + dc_.SetClippingRegion(r); + refDcToAreaMap().insert(std::make_pair(&dc_, r)); + } + } + + ~RecursiveDcClipper() + { + dc_.DestroyClippingRegion(); + if (oldRect.get() != nullptr) + { + dc_.SetClippingRegion(*oldRect); + refDcToAreaMap()[&dc_] = *oldRect; + } + else + refDcToAreaMap().erase(&dc_); + } + +private: + //associate "active" clipping area with each DC + static hash_map<wxDC*, wxRect>& refDcToAreaMap() { static hash_map<wxDC*, wxRect> clippingAreas; return clippingAreas; } + + std::unique_ptr<wxRect> oldRect; + wxDC& dc_; +}; + + +#ifndef wxALWAYS_NATIVE_DOUBLE_BUFFER +#error we need this one! +#endif + +#if wxALWAYS_NATIVE_DOUBLE_BUFFER +struct BufferedPaintDC : public wxPaintDC { BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer) : wxPaintDC(&wnd) {} }; + +#else +class BufferedPaintDC : public wxMemoryDC +{ +public: + BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer) : buffer_(buffer), paintDc(&wnd) + { + const wxSize clientSize = wnd.GetClientSize(); + if (!buffer_ || clientSize != wxSize(buffer->GetWidth(), buffer->GetHeight())) + buffer.reset(new wxBitmap(clientSize.GetWidth(), clientSize.GetHeight())); + + SelectObject(*buffer); + + if (paintDc.IsOk() && paintDc.GetLayoutDirection() == wxLayout_RightToLeft) + SetLayoutDirection(wxLayout_RightToLeft); + } + + ~BufferedPaintDC() + { + if (GetLayoutDirection() == wxLayout_RightToLeft) + { + paintDc.SetLayoutDirection(wxLayout_LeftToRight); //workaround bug in wxDC::Blit() + SetLayoutDirection(wxLayout_LeftToRight); // + } + + const wxPoint origin = GetDeviceOrigin(); + paintDc.Blit(0, 0, buffer_->GetWidth(), buffer_->GetHeight(), this, -origin.x, -origin.y); + } + +private: + std::unique_ptr<wxBitmap>& buffer_; + wxPaintDC paintDc; +}; +#endif +} + +#endif //DC_3813704987123956832143243214 diff --git a/wx+/graph.cpp b/wx+/graph.cpp index 0d14ae69..cbedfa53 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -9,8 +9,9 @@ #include <algorithm> #include <numeric> #include <zen/basic_math.h> +#include <zen/scope_guard.h> #include <wx/settings.h> -#include "rtl.h" +#include "dc.h" using namespace zen; @@ -31,8 +32,8 @@ double zen::nextNiceNumber(double blockSize) //round to next number which is a c const double e = std::pow(10, k); if (numeric::isNull(e)) return 0; - const double a = blockSize / e; //blockSize = a * 10^k with a in (1, 10) - assert(1 < a && a < 10); + const double a = blockSize / e; //blockSize = a * 10^k with a in [1, 10) + assert(1 <= a && a < 10); //have a look at leading two digits: "nice" numbers start with 1, 2, 2.5 and 5 const double steps[] = { 1, 2, 2.5, 5, 10 }; @@ -78,18 +79,22 @@ public: ConvertCoord(double valMin, double valMax, size_t screenSize) : min_(valMin), scaleToReal(screenSize == 0 ? 0 : (valMax - valMin) / screenSize), - scaleToScr(numeric::isNull((valMax - valMin)) ? 0 : screenSize / (valMax - valMin)) {} + scaleToScr(numeric::isNull((valMax - valMin)) ? 0 : screenSize / (valMax - valMin)), + outOfBoundsLow (-1 * scaleToReal + valMin), + outOfBoundsHigh((screenSize + 1) * scaleToReal + valMin) { if (outOfBoundsLow > outOfBoundsHigh) std::swap(outOfBoundsLow, outOfBoundsHigh); } double screenToReal(double screenPos) const //input value: [0, screenSize - 1] { - return screenPos * scaleToReal + min_; //come close to valMax, but NEVER reach it! + return screenPos * scaleToReal + min_; } double realToScreen(double realPos) const //return screen position in pixel (but with double precision!) { return (realPos - min_) * scaleToScr; } - int realToScreenRound(double realPos) const //useful to find "proper" y-pixel positions + int realToScreenRound(double realPos) const //returns -1 and screenSize + 1 if out of bounds! { + //catch large double values: if double is larger than what int can represent => undefined behavior! + numeric::confine(realPos , outOfBoundsLow, outOfBoundsHigh); return numeric::round(realToScreen(realPos)); } @@ -97,19 +102,22 @@ private: double min_; double scaleToReal; double scaleToScr; + + double outOfBoundsLow; + double outOfBoundsHigh; }; //enlarge value range to display to a multiple of a "useful" block size void widenRange(double& valMin, double& valMax, //in/out int& blockCount, //out - int graphAreaSize, //in pixel - int optimalBlockSize, // + int graphAreaSize, //in pixel + int optimalBlockSizePx, // const LabelFormatter& labelFmt) { if (graphAreaSize > 0) { - double valRangePerBlock = (valMax - valMin) * optimalBlockSize / graphAreaSize; //proposal + double valRangePerBlock = (valMax - valMin) * optimalBlockSizePx / graphAreaSize; //proposal valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock); if (!numeric::isNull(valRangePerBlock)) { @@ -145,7 +153,7 @@ void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const Conver //draw x axis labels const wxString label = labelFmt.formatText(valX, valRangePerBlock); - wxSize labelExtent = dc.GetMultiLineTextExtent(label); + const wxSize labelExtent = dc.GetMultiLineTextExtent(label); dc.DrawText(label, wxPoint(x - labelExtent.GetWidth() / 2, labelArea.y + (labelArea.height - labelExtent.GetHeight()) / 2)); //center } } @@ -173,31 +181,223 @@ void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const Conver //draw y axis labels const wxString label = labelFmt.formatText(valY, valRangePerBlock); - wxSize labelExtent = dc.GetMultiLineTextExtent(label); + const wxSize labelExtent = dc.GetMultiLineTextExtent(label); dc.DrawText(label, wxPoint(labelArea.x + (labelArea.width - labelExtent.GetWidth()) / 2, y - labelExtent.GetHeight() / 2)); //center } } -template <class StdContainter> -void subsample(StdContainter& cont, size_t factor) +void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Graph2D::PosCorner pos) { - if (factor <= 1) return; + if (txt.empty()) return; + const int borderX = 5; + const int borderY = 2; //it looks like wxDC::GetMultiLineTextExtent() precisely returns width, but too large a height: maybe they consider "text row height"? - auto itOut = cont.begin(); - for (auto itIn = cont.begin(); cont.end() - itIn >= static_cast<ptrdiff_t>(factor); itIn += factor) //don't even let iterator point out of range! - *itOut++ = std::accumulate(itIn, itIn + factor, 0.0) / static_cast<double>(factor); + wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels + wxSize txtExtent = dc.GetMultiLineTextExtent(txt); + txtExtent.x += 2 * borderX; + txtExtent.y += 2 * borderY; - cont.erase(itOut, cont.end()); + wxPoint drawPos = graphArea.GetTopLeft(); + switch (pos) + { + case Graph2D::CORNER_TOP_LEFT: + break; + case Graph2D::CORNER_TOP_RIGHT: + drawPos.x += graphArea.width - txtExtent.GetWidth(); + break; + case Graph2D::CORNER_BOTTOM_LEFT: + drawPos.y += graphArea.height - txtExtent.GetHeight(); + break; + case Graph2D::CORNER_BOTTOM_RIGHT: + drawPos.x += graphArea.width - txtExtent.GetWidth(); + drawPos.y += graphArea.height - txtExtent.GetHeight(); + break; + } + dc.DrawText(txt, drawPos + wxPoint(borderX, borderY)); } + + +warn_static("review") + +template <class Function, class Function2> +void cutPoints(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, Function isInside, Function2 getIntersection) +{ + assert(curvePoints.size() == oobMarker.size()); + if (curvePoints.size() != oobMarker.size() || curvePoints.empty()) return; + auto isMarkedOob = [&](size_t index) { return oobMarker[index] != 0; }; + + std::vector<CurvePoint> curvePointsTmp; + std::vector<char> oobMarkerTmp; + auto savePoint = [&](const CurvePoint& pt, bool markedOob) { curvePointsTmp.push_back(pt); oobMarkerTmp.push_back(markedOob); }; + + warn_static("perf: avoid these push_backs") + + bool lastPointInside = isInside(curvePoints[0]); + if (lastPointInside) + savePoint(curvePoints[0], isMarkedOob(0)); + + for (auto it = curvePoints.begin() + 1; it != curvePoints.end(); ++it) + { + const size_t index = it - curvePoints.begin(); + + const bool pointInside = isInside(*it); + if (pointInside != lastPointInside) + { + lastPointInside = pointInside; + + const CurvePoint is = getIntersection(*(it - 1), *it); //getIntersection returns *it when delta is zero + savePoint(is, !pointInside || isMarkedOob(index - 1)); + } + if (pointInside) + savePoint(*it, isMarkedOob(index)); + } + curvePointsTmp.swap(curvePoints); + oobMarkerTmp .swap(oobMarker); +} + + +struct GetIntersectionX +{ + GetIntersectionX(double x) : x_(x) {} + CurvePoint operator()(const CurvePoint& from, const CurvePoint& to) const + { + const double deltaX = to.x - from.x; + const double deltaY = to.y - from.y; + return !numeric::isNull(deltaX) ? CurvePoint(x_, from.y + (x_ - from.x) / deltaX * deltaY) : to; + }; +private: + double x_; +}; + +struct GetIntersectionY +{ + GetIntersectionY(double y) : y_(y) {} + CurvePoint operator()(const CurvePoint& from, const CurvePoint& to) const + { + const double deltaX = to.x - from.x; + const double deltaY = to.y - from.y; + return !numeric::isNull(deltaY) ? CurvePoint(from.x + (y_ - from.y) / deltaY * deltaX, y_) : to; + }; +private: + double y_; +}; + +void cutPointsOutsideX(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, double minX, double maxX) +{ + cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.x >= minX; }, GetIntersectionX(minX)); + cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.x <= maxX; }, GetIntersectionX(maxX)); +} + +void cutPointsOutsideY(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, double minY, double maxY) +{ + cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.y >= minY; }, GetIntersectionY(minY)); + cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.y <= maxY; }, GetIntersectionY(maxY)); +} +} + + +warn_static("review") +void ContinuousCurveData::getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const +{ + if (pixelWidth <= 1) return; + const ConvertCoord cvrtX(minX, maxX, pixelWidth - 1); //map [minX, maxX] to [0, pixelWidth - 1] + + std::pair<double, double> rangeX = getRangeX(); + //catch large double values: if double is larger than what int can represent => undefined behavior! + const double xOutOfBoundsLow = cvrtX.screenToReal(-1); + const double xOutOfBoundsHigh = cvrtX.screenToReal(pixelWidth); + numeric::confine(rangeX.first , xOutOfBoundsLow, xOutOfBoundsHigh); //don't confine to [minX, maxX] which + numeric::confine(rangeX.second, xOutOfBoundsLow, xOutOfBoundsHigh); //would prevent empty ranges + + const int posFrom = std::ceil (cvrtX.realToScreen(std::max(rangeX.first, minX))); //do not step outside [minX, maxX] + const int posTo = std::floor(cvrtX.realToScreen(std::min(rangeX.second, maxX))); // + //conversion from std::floor/std::ceil double return value to int is loss-free for full value range of 32-bit int! tested successfully on MSVC + + for (int i = posFrom; i <= posTo; ++i) + { + const double x = cvrtX.screenToReal(i); + points.push_back(CurvePoint(x, getValue(x))); + } +} + + +void SparseCurveData::getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const +{ + if (pixelWidth <= 1) return; + const ConvertCoord cvrtX(minX, maxX, pixelWidth - 1); //map [minX, maxX] to [0, pixelWidth - 1] + const std::pair<double, double> rangeX = getRangeX(); + + auto addPoint = [&](const CurvePoint& pt) + { + warn_static("verify steps") + if (addSteps_ && !points.empty()) + if (pt.y != points.back().y) + points.push_back(CurvePoint(pt.x, points.back().y)); + points.push_back(pt); + }; + + const int posFrom = cvrtX.realToScreenRound(std::max(rangeX.first, minX)); + const int posTo = cvrtX.realToScreenRound(std::min(rangeX.second, maxX)); + + for (int i = posFrom; i <= posTo; ++i) + { + const double x = cvrtX.screenToReal(i); + Opt<CurvePoint> ptLe = getLessEq(x); + Opt<CurvePoint> ptGe = getGreaterEq(x); + //both non-existent and invalid return values are mapped to out of expected range: => check on posLe/posGe NOT ptLe/ptGE in the following! + const int posLe = ptLe ? cvrtX.realToScreenRound(ptLe->x) : i + 1; + const int posGe = ptGe ? cvrtX.realToScreenRound(ptGe->x) : i - 1; + assert(!ptLe || posLe <= i); //check for invalid return values + assert(!ptGe || posGe >= i); // + + if (posGe == i) //test if point would be mapped to pixel x-position i + { + if (posLe == i) // + addPoint(x - ptLe->x < ptGe->x - x ? *ptLe : *ptGe); + else + addPoint(*ptGe); + } + else + { + if (posLe == i) + addPoint(*ptLe); + else //no point for x-position i + { + if (i == posFrom && posGe > i) + { + if (posLe < i) + addPoint(*ptLe); //use first point outside display area! + else if (posGe > posTo) //curve starts outside the draw range! + break; + } + } + + if (posGe < i) + break; + + if (posGe > posTo) //last point outside the display area! + { + if (i == posTo && posLe == i) //no need for outside point if last position was already set above + break; + + addPoint(*ptGe); + break; + } + if (posGe > i) //skip sparse area + i = posGe - 1; + } + } } + Graph2D::Graph2D(wxWindow* parent, wxWindowID winid, const wxPoint& pos, const wxSize& size, long style, - const wxString& name) : wxPanel(parent, winid, pos, size, style, name) + const wxString& name) : wxPanel(parent, winid, pos, size, style, name), + labelFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial") { Connect(wxEVT_PAINT, wxPaintEventHandler(Graph2D::onPaintEvent), nullptr, this); Connect(wxEVT_SIZE, wxSizeEventHandler (Graph2D::onSizeEvent ), nullptr, this); @@ -272,14 +472,14 @@ void Graph2D::OnMouseCaptureLost(wxMouseCaptureLostEvent& event) } -void Graph2D::setData(const std::shared_ptr<GraphData>& data, const CurveAttributes& ca) +void Graph2D::setCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca) { curves_.clear(); - addData(data, ca); + addCurve(data, ca); } -void Graph2D::addData(const std::shared_ptr<GraphData>& data, const CurveAttributes& ca) +void Graph2D::addCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca) { CurveAttributes newAttr = ca; if (newAttr.autoColor) @@ -288,22 +488,13 @@ void Graph2D::addData(const std::shared_ptr<GraphData>& data, const CurveAttribu Refresh(); } -namespace //putting this into function scope makes MSVC crash... -{ -struct CurveSamples -{ - CurveSamples() : offsetX(0) {} - std::vector<double> yValues; //actual y-values at each screen pixel position - int offsetX; //x-value offset in pixels -}; -} void Graph2D::render(wxDC& dc) const { using namespace numeric; //set label font right at the start so that it is considered by wxDC::GetTextExtent below! - dc.SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial")); + dc.SetFont(labelFont); const wxRect clientRect = GetClientRect(); //DON'T use wxDC::GetSize()! DC may be larger than visible area! { @@ -365,27 +556,25 @@ void Graph2D::render(wxDC& dc) const //set label areas respecting graph area border! const wxRect xLabelArea(graphArea.x, xLabelPosY, graphArea.width, attr.xLabelHeight); const wxRect yLabelArea(yLabelPosX, graphArea.y, attr.yLabelWidth, graphArea.height); - const wxPoint graphAreaOrigin = graphArea.GetTopLeft(); //detect x value range double minX = attr.minXauto ? std::numeric_limits<double>::infinity() : attr.minX; //automatic: ensure values are initialized by first curve double maxX = attr.maxXauto ? -std::numeric_limits<double>::infinity() : attr.maxX; // - if (!curves_.empty()) - for (auto it = curves_.begin(); it != curves_.end(); ++it) - if (it->first.get()) - { - const GraphData& graph = *it->first; - assert(graph.getXBegin() <= graph.getXEnd() + 1.0e-9); - //GCC fucks up badly when comparing two *binary identical* doubles and finds "begin > end" with diff of 1e-18 - - if (attr.minXauto) - minX = std::min(minX, graph.getXBegin()); - if (attr.maxXauto) - maxX = std::max(maxX, graph.getXEnd()); - } + for (auto it = curves_.begin(); it != curves_.end(); ++it) + if (const CurveData* curve = it->first.get()) + { + const std::pair<double, double> rangeX = curve->getRangeX(); + assert(rangeX.first <= rangeX.second + 1.0e-9); + //GCC fucks up badly when comparing two *binary identical* doubles and finds "begin > end" with diff of 1e-18 + + if (attr.minXauto) + minX = std::min(minX, rangeX.first); + if (attr.maxXauto) + maxX = std::max(maxX, rangeX.second); + } - if (minX < maxX && maxX - minX < std::numeric_limits<double>::infinity()) //valid x-range + if (minX <= maxX && maxX - minX < std::numeric_limits<double>::infinity()) //valid x-range { int blockCountX = 0; //enlarge minX, maxX to a multiple of a "useful" block size @@ -396,39 +585,37 @@ void Graph2D::render(wxDC& dc) const dc.GetTextExtent(L"100000000000000").GetWidth(), *attr.labelFmtX); - //detect y value range - std::vector<CurveSamples> yValuesList(curves_.size()); + //get raw values + detect y value range double minY = attr.minYauto ? std::numeric_limits<double>::infinity() : attr.minY; //automatic: ensure values are initialized by first curve double maxY = attr.maxYauto ? -std::numeric_limits<double>::infinity() : attr.maxY; // - { - const int AVG_FACTOR = 2; //some averaging of edgy input data to smoothen behavior on window resize - const ConvertCoord cvrtX(minX, maxX, graphArea.width * AVG_FACTOR); - for (auto it = curves_.begin(); it != curves_.end(); ++it) - if (const GraphData* graph = it->first.get()) - { - CurveSamples& samples = yValuesList[it - curves_.begin()]; - { - const int posFirst = std::ceil(cvrtX.realToScreen(std::max(graph->getXBegin(), minX))); //do not step outside [xBegin, xEnd) range => 2 x ceil! - const int posLast = std::ceil(cvrtX.realToScreen(std::min(graph->getXEnd (), maxX))); // - //conversion from std::ceil double return valute to int is loss-free for full value range of 32-bit int! tested successfully on MSVC + std::vector<std::vector<CurvePoint>> curvePoints(curves_.size()); + std::vector<std::vector<char>> oobMarker (curves_.size()); //effectively a std::vector<bool> marking points that start a out of bounds line - for (int i = posFirst; i < posLast; ++i) - samples.yValues.push_back(graph->getValue(cvrtX.screenToReal(i))); + for (auto it = curves_.begin(); it != curves_.end(); ++it) + if (const CurveData* curve = it->first.get()) + { + const size_t index = it - curves_.begin(); + std::vector<CurvePoint>& points = curvePoints[index]; + auto& marker = oobMarker[index]; - subsample(samples.yValues, AVG_FACTOR); - samples.offsetX = posFirst / AVG_FACTOR; - } - if (!samples.yValues.empty()) - { - if (attr.minYauto) - minY = std::min(minY, *std::min_element(samples.yValues.begin(), samples.yValues.end())); - if (attr.maxYauto) - maxY = std::max(maxY, *std::max_element(samples.yValues.begin(), samples.yValues.end())); - } + curve->getPoints(minX, maxX, graphArea.width, points); + + //cut points outside visible x-range now in order to calculate height of visible points only! + marker.resize(points.size()); //default value: false + cutPointsOutsideX(points, marker, minX, maxX); + + if ((attr.minYauto || attr.maxYauto) && !points.empty()) + { + auto itPair = std::minmax_element(points.begin(), points.end(), [](const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.y < rhs.y; }); + if (attr.minYauto) + minY = std::min(minY, itPair.first->y); + if (attr.maxYauto) + maxY = std::max(maxY, itPair.second->y); } - } - if (minY < maxY) //valid y-range + } + + if (minY <= maxY) //valid y-range { int blockCountY = 0; //enlarge minY, maxY to a multiple of a "useful" block size @@ -439,44 +626,55 @@ void Graph2D::render(wxDC& dc) const 3 * dc.GetTextExtent(L"1").GetHeight(), *attr.labelFmtY); - const ConvertCoord cvrtX(minX, maxX, graphArea.width); //map [minX, maxX) to [0, graphWidth) - const ConvertCoord cvrtY(maxY, minY, graphArea.height <= 0 ? 0 : graphArea.height - 1); //map [minY, maxY] to [graphHeight - 1, 0] + if (graphArea.width <= 1 || graphArea.height <= 1) return; + const ConvertCoord cvrtX(minX, maxX, graphArea.width - 1); //map [minX, maxX] to [0, pixelWidth - 1] + const ConvertCoord cvrtY(maxY, minY, graphArea.height - 1); //map [minY, maxY] to [pixelHeight - 1, 0] //calculate curve coordinates on graph area - auto getCurvePoints = [&](size_t index, std::vector<wxPoint>& points) - { - if (index < yValuesList.size()) - { - CurveSamples& samples = yValuesList[index]; + std::vector<std::vector<wxPoint>> drawPoints(curves_.size()); - for (auto it = samples.yValues.begin(); it != samples.yValues.end(); ++it) - points.push_back(wxPoint(samples.offsetX + (it - samples.yValues.begin()), - cvrtY.realToScreenRound(*it)) + graphAreaOrigin); - } - }; + for (size_t index = 0; index < curves_.size(); ++index) + { + //cut points outside visible y-range before calculating pixels: + //1. realToScreenRound() deforms out-of-range values! + //2. pixels that are grossly out of range may become a severe performance problem when drawing on the DC (Windows) + cutPointsOutsideY(curvePoints[index], oobMarker[index], minY, maxY); + + auto& points = drawPoints[index]; + for (const auto& pt : curvePoints[index]) + points.push_back(wxPoint(cvrtX.realToScreenRound(pt.x), + cvrtY.realToScreenRound(pt.y)) + graphAreaOrigin); + } //update active mouse selection if (activeSel.get() && graphArea.width > 0 && graphArea.height > 0) { - wxPoint startPos = activeSel->getStartPos() - graphAreaOrigin; //make relative to graphArea - wxPoint currentPos = activeSel->refCurrentPos() - graphAreaOrigin; - - //normalize positions: a mouse selection is symmetric and *not* an half-open range! - confine(startPos .x, 0, graphArea.width - 1); - confine(currentPos.x, 0, graphArea.width - 1); - confine(startPos .y, 0, graphArea.height - 1); - confine(currentPos.y, 0, graphArea.height - 1); - - auto& from = activeSel->refSelection().from; - auto& to = activeSel->refSelection().to; + auto widen = [](double* low, double* high) + { + if (*low > *high) + std::swap(low, high); + *low -= 0.5; + *high += 0.5; + }; - //save current selection as double coordinates - from.x = cvrtX.screenToReal(startPos .x + (startPos.x <= currentPos.x ? 0 : 1)); // use full pixel range for selection! - to .x = cvrtX.screenToReal(currentPos.x + (startPos.x <= currentPos.x ? 1 : 0)); + const wxPoint screenStart = activeSel->getStartPos() - graphAreaOrigin; //make relative to graphArea + const wxPoint screenCurrent = activeSel->refCurrentPos() - graphAreaOrigin; - from.y = cvrtY.screenToReal(startPos .y + (startPos.y <= currentPos.y ? 0 : 1)); - to .y = cvrtY.screenToReal(currentPos.y + (startPos.y <= currentPos.y ? 1 : 0)); + //normalize positions: a mouse selection is symmetric and *not* an half-open range! + double screenFromX = confineCpy(screenStart .x, 0, graphArea.width - 1); + double screenFromY = confineCpy(screenStart .y, 0, graphArea.height - 1); + double screenToX = confineCpy(screenCurrent.x, 0, graphArea.width - 1); + double screenToY = confineCpy(screenCurrent.y, 0, graphArea.height - 1); + widen(&screenFromX, &screenToX); //use full pixel range for selection! + widen(&screenFromY, &screenToY); + + //save current selection as "double" coordinates + activeSel->refSelection().from = CurvePoint(cvrtX.screenToReal(screenFromX), + cvrtY.screenToReal(screenFromY)); + + activeSel->refSelection().to = CurvePoint(cvrtX.screenToReal(screenToX), + cvrtY.screenToReal(screenToY)); } //#################### begin drawing #################### @@ -484,8 +682,7 @@ void Graph2D::render(wxDC& dc) const for (auto it = curves_.begin(); it != curves_.end(); ++it) if (it->second.drawCurveArea) { - std::vector<wxPoint> points; - getCurvePoints(it - curves_.begin(), points); + std::vector<wxPoint> points = drawPoints[it - curves_.begin()]; if (!points.empty()) { points.push_back(wxPoint(points.back ().x, graphArea.GetBottom())); //add lower right and left corners @@ -502,36 +699,39 @@ void Graph2D::render(wxDC& dc) const if (activeSel) allSelections.push_back(activeSel->refSelection()); { - //alpha channel (not yet) supported on wxMSW, so draw selection before curves + //alpha channel not supported on wxMSW, so draw selection before curves wxDCBrushChanger dummy(dc, wxColor(168, 202, 236)); //light blue wxDCPenChanger dummy2(dc, wxColor(51, 153, 255)); //dark blue - for (auto it = allSelections.begin(); it != allSelections.end(); ++it) + auto shrink = [](double* low, double* high) { - //harmonize with active mouse selection above! - wxPoint pixelFrom(cvrtX.realToScreenRound(it->from.x), - cvrtY.realToScreenRound(it->from.y)); - wxPoint pixelTo(cvrtX.realToScreenRound(it->to.x), - cvrtY.realToScreenRound(it->to.y)); - //convert half-open to inclusive ranges for use with wxDC::DrawRectangle - if (pixelFrom.x != pixelTo.x) //no matter how small the selection, always draw at least one pixel! - { - pixelFrom.x -= pixelFrom.x < pixelTo.x ? 0 : 1; - pixelTo .x -= pixelFrom.x < pixelTo.x ? 1 : 0; - } - if (pixelFrom.y != pixelTo.y) - { - pixelFrom.y -= pixelFrom.y < pixelTo.y ? 0 : 1; - pixelTo .y -= pixelFrom.y < pixelTo.y ? 1 : 0; - } - confine(pixelFrom.x, 0, graphArea.width - 1); - confine(pixelTo .x, 0, graphArea.width - 1); - confine(pixelFrom.y, 0, graphArea.height - 1); - confine(pixelTo .y, 0, graphArea.height - 1); - - pixelFrom += graphAreaOrigin; - pixelTo += graphAreaOrigin; + if (*low > *high) + std::swap(low, high); + *low += 0.5; + *high -= 0.5; + if (*low > *high) + *low = *high = (*low + *high) / 2; + }; + for (auto it = allSelections.begin(); it != allSelections.end(); ++it) + { + //harmonize with active mouse selection above + double screenFromX = cvrtX.realToScreen(it->from.x); + double screenFromY = cvrtY.realToScreen(it->from.y); + double screenToX = cvrtX.realToScreen(it->to.x); + double screenToY = cvrtY.realToScreen(it->to.y); + shrink(&screenFromX, &screenToX); + shrink(&screenFromY, &screenToY); + + confine(screenFromX, 0.0, graphArea.width - 1.0); + confine(screenFromY, 0.0, graphArea.height - 1.0); + confine(screenToX, 0.0, graphArea.width - 1.0); + confine(screenToY, 0.0, graphArea.height - 1.0); + + const wxPoint pixelFrom = wxPoint(numeric::round(screenFromX), + numeric::round(screenFromY)) + graphAreaOrigin; + const wxPoint pixelTo = wxPoint(numeric::round(screenToX), + numeric::round(screenToY)) + graphAreaOrigin; switch (attr.mouseSelMode) { case SELECT_NONE: @@ -554,17 +754,42 @@ void Graph2D::render(wxDC& dc) const drawYLabel(dc, minY, maxY, blockCountY, cvrtY, graphArea, yLabelArea, *attr.labelFmtY); //4. finally draw curves - for (auto it = curves_.begin(); it != curves_.end(); ++it) { - std::vector<wxPoint> points; - getCurvePoints(it - curves_.begin(), points); - if (!points.empty()) + dc.SetClippingRegion(graphArea); //prevent thick curves from drawing slightly outside + ZEN_ON_SCOPE_EXIT(dc.DestroyClippingRegion()); + + for (auto it = curves_.begin(); it != curves_.end(); ++it) { wxDCPenChanger dummy(dc, wxPen(it->second.color, it->second.lineWidth)); - dc.DrawLines(static_cast<int>(points.size()), &points[0]); - dc.DrawPoint(points.back()); //wxDC::DrawLines() doesn't draw last pixel + + const size_t index = it - curves_.begin(); + std::vector<wxPoint>& points = drawPoints[index]; //alas wxDC::DrawLines() is not const-correct!!! + auto& marker = oobMarker [index]; + assert(points.size() == marker.size()); + warn_static("review") + + //draw all parts of the curve except for the out-of-bounds ranges + size_t pointsIndexFirst = 0; + while (pointsIndexFirst < points.size()) + { + size_t pointsIndexLast = std::find(marker.begin() + pointsIndexFirst, marker.end(), true) - marker.begin(); + if (pointsIndexLast < points.size()) ++ pointsIndexLast; + + const int pointCount = static_cast<int>(pointsIndexLast - pointsIndexFirst); + if (pointCount > 0) + { + if (pointCount >= 2) //on OS X wxWidgets has a nasty assert on this + dc.DrawLines(pointCount, &points[pointsIndexFirst]); + dc.DrawPoint(points[pointsIndexLast - 1]); //wxDC::DrawLines() doesn't draw last pixel + } + pointsIndexFirst = std::find(marker.begin() + pointsIndexLast, marker.end(), false) - marker.begin(); + } } } + + //5. draw corner texts + for (auto it = attr.cornerTexts.begin(); it != attr.cornerTexts.end(); ++it) + drawCornerText(dc, graphArea, it->second, it->first); } } } diff --git a/wx+/graph.h b/wx+/graph.h index 8f816b08..fe008a38 100644 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -7,13 +7,15 @@ #ifndef WX_PLOT_HEADER_2344252459 #define WX_PLOT_HEADER_2344252459 +#include <map> #include <vector> #include <memory> #include <wx/panel.h> -#include <wx/dcbuffer.h> +//#include <wx/dcbuffer.h> #include <zen/string_tools.h> +#include <zen/optional.h> -//simple 2D graph as wxPanel specialization +//elegant 2D graph as wxPanel specialization namespace zen { @@ -21,62 +23,90 @@ namespace zen Example: //init graph (optional) m_panelGraph->setAttributes(Graph2D::MainAttributes(). - setLabelX(Graph2D::POSLX_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()). - setLabelY(Graph2D::POSLY_RIGHT, 60, std::make_shared<LabelFormatterBytes>())); + setLabelX(Graph2D::X_LABEL_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()). + setLabelY(Graph2D::Y_LABEL_RIGHT, 60, std::make_shared<LabelFormatterBytes>())); //set graph data - std::shared_ptr<GraphData> graphDataBytes = ... - m_panelGraph->setData(graphDataBytes, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0))); + std::shared_ptr<CurveData> curveDataBytes = ... + m_panelGraph->setCurve(curveDataBytes, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0))); */ -//------------------------------------------------------------------------------------------------------------ -struct GraphData +struct CurvePoint { - virtual ~GraphData() {} - virtual double getValue (double x) const = 0; - virtual double getXBegin() const = 0; - virtual double getXEnd () const = 0; //upper bound for x, getValue() is NOT evaluated at this position! Similar to std::vector::end() + CurvePoint() : x(0), y(0) {} + CurvePoint(double xVal, double yVal) : x(xVal), y(yVal) {} + double x; + double y; }; +inline bool operator==(const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } +inline bool operator!=(const CurvePoint& lhs, const CurvePoint& rhs) { return !(lhs == rhs); } +struct CurveData +{ + virtual ~CurveData() {} -//reference data implementation -class RangeData : public GraphData + virtual std::pair<double, double> getRangeX() const = 0; + virtual void getPoints(double minX, double maxX, int pixelWidth, + std::vector<CurvePoint>& points) const = 0; //points outside the draw area are automatically trimmed! +}; + +//special curve types: +struct ContinuousCurveData : public CurveData { -public: - std::vector<double>& refData() { return data; } + virtual double getValue(double x) const = 0; private: - virtual double getValue(double x) const - { - const size_t pos = static_cast<size_t>(x); - return pos < data.size() ? data[pos] : 0; - } - virtual double getXBegin() const { return 0; } - virtual double getXEnd() const { return data.size(); } //example: two-element range is accessible within [0, 2) + virtual void getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const final; +}; - std::vector<double> data; +struct SparseCurveData : public CurveData +{ + SparseCurveData(bool addSteps = false) : addSteps_(addSteps) {} //addSteps: add points to get a staircase effect or connect points via a direct line + + virtual Opt<CurvePoint> getLessEq (double x) const = 0; + virtual Opt<CurvePoint> getGreaterEq(double x) const = 0; + +private: + virtual void getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const final; + bool addSteps_; }; -/* -//reference data implementation -class VectorData : public GraphData +struct ArrayCurveData : public SparseCurveData { -public: - operator std::vector<double>& () { return data; } + virtual double getValue(size_t pos) const = 0; + virtual size_t getSize() const = 0; private: - virtual double getValue(double x) const + virtual std::pair<double, double> getRangeX() const final { const size_t sz = getSize(); return std::make_pair(0.0, sz == 0 ? 0.0 : sz - 1.0); } + + virtual Opt<CurvePoint> getLessEq(double x) const final { - const size_t pos = static_cast<size_t>(x); - return pos < data.size() ? data[pos] : 0; + const size_t sz = getSize(); + const size_t pos = std::min<ptrdiff_t>(std::floor(x), sz - 1); //[!] expect unsigned underflow if empty! + if (pos < sz) + return CurvePoint(pos, getValue(pos)); + return NoValue(); } - virtual double getXBegin() const { return 0; } - virtual double getXEnd() const { return data.size(); } //example: two-element range is accessible within [0, 2) - std::vector<double> data; + virtual Opt<CurvePoint> getGreaterEq(double x) const final + { + const size_t pos = std::max<ptrdiff_t>(std::ceil(x), 0); //[!] use std::max with signed type! + if (pos < getSize()) + return CurvePoint(pos, getValue(pos)); + return NoValue(); + } +}; + +struct VectorCurveData : public ArrayCurveData +{ + std::vector<double>& refData() { return data; } +private: + virtual double getValue(size_t pos) const final { return pos < data.size() ? data[pos] : 0; } + virtual size_t getSize() const final { return data.size(); } + std::vector<double> data; }; -*/ //------------------------------------------------------------------------------------------------------------ + struct LabelFormatter { virtual ~LabelFormatter() {} @@ -106,16 +136,8 @@ extern const wxEventType wxEVT_GRAPH_SELECTION; struct SelectionBlock { - struct Point - { - Point() : x(0), y(0) {} - Point(double xVal, double yVal) : x(xVal), y(yVal) {} - double x; - double y; - }; - - Point from; - Point to; + CurvePoint from; + CurvePoint to; }; class GraphSelectEvent : public wxCommandEvent @@ -135,8 +157,8 @@ typedef void (wxEvtHandler::*GraphSelectEventFunction)(GraphSelectEvent&); #define GraphSelectEventHandler(func) \ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GraphSelectEventFunction, &func) - //------------------------------------------------------------------------------------------------------------ + class Graph2D : public wxPanel { public: @@ -168,8 +190,8 @@ public: int lineWidth; }; - void setData(const std::shared_ptr<GraphData>& data, const CurveAttributes& ca = CurveAttributes()); - void addData(const std::shared_ptr<GraphData>& data, const CurveAttributes& ca = CurveAttributes()); + void setCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca = CurveAttributes()); + void addCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca = CurveAttributes()); enum PosLabelY { @@ -185,6 +207,14 @@ public: X_LABEL_NONE }; + enum PosCorner + { + CORNER_TOP_LEFT, + CORNER_TOP_RIGHT, + CORNER_BOTTOM_LEFT, + CORNER_BOTTOM_RIGHT, + }; + enum SelMode { SELECT_NONE, @@ -238,6 +268,8 @@ public: return *this; } + MainAttributes& setCornerText(const wxString& txt, PosCorner pos) { cornerTexts[pos] = txt; return *this; } + MainAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; } private: @@ -261,6 +293,8 @@ public: int yLabelWidth; std::shared_ptr<LabelFormatter> labelFmtY; + std::map<PosCorner, wxString> cornerTexts; + SelMode mouseSelMode; }; void setAttributes(const MainAttributes& newAttr) { attr = newAttr; Refresh(); } @@ -311,8 +345,9 @@ private: std::unique_ptr<wxBitmap> doubleBuffer; - typedef std::vector<std::pair<std::shared_ptr<GraphData>, CurveAttributes>> GraphList; - GraphList curves_; + typedef std::vector<std::pair<std::shared_ptr<CurveData>, CurveAttributes>> CurveList; + CurveList curves_; + wxFont labelFont; //perf!!! generating the font is *very* expensive! don't do this repeatedly in Graph2D::render()! }; } diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 46c0c406..c8d418ee 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -17,8 +17,9 @@ #include <zen/scope_guard.h> #include <zen/utf.h> #include <zen/format_unit.h> -#include "image_tools.h" -#include "rtl.h" +//#include "image_tools.h" +//#include "rtl.h" +#include "dc.h" #ifdef ZEN_LINUX #include <gtk/gtk.h> @@ -63,50 +64,6 @@ const wxColor COLOR_LABEL_GRADIENT_TO_FOCUS = COLOR_LABEL_GRADIENT_TO; wxColor getColorMainWinBackground() { return wxListBox::GetClassDefaultAttributes().colBg; } //cannot be initialized statically on wxGTK! const wxColor colorGridLine = wxColour(192, 192, 192); //light grey - -//------------------------------------------------------------ - -//another fix for yet another poor wxWidgets implementation (wxDCClipper does *not* stack) -hash_map<wxDC*, wxRect> clippingAreas; //associate "active" clipping area with each DC - -class DcClipper -{ -public: - DcClipper(wxDC& dc, const wxRect& r) : dc_(dc) - { - auto it = clippingAreas.find(&dc); - if (it != clippingAreas.end()) - { - oldRect.reset(new wxRect(it->second)); - - wxRect tmp = r; - tmp.Intersect(*oldRect); //better safe than sorry - dc_.SetClippingRegion(tmp); // - it->second = tmp; - } - else - { - dc_.SetClippingRegion(r); - clippingAreas.insert(std::make_pair(&dc_, r)); - } - } - - ~DcClipper() - { - dc_.DestroyClippingRegion(); - if (oldRect.get() != nullptr) - { - dc_.SetClippingRegion(*oldRect); - clippingAreas[&dc_] = *oldRect; - } - else - clippingAreas.erase(&dc_); - } - -private: - std::unique_ptr<wxRect> oldRect; - wxDC& dc_; -}; } //---------------------------------------------------------------------------------------------------------------- @@ -208,7 +165,7 @@ wxString getTruncatedText(const wxString& text, Function textFits) void drawTextLabelFitting(wxDC& dc, const wxString& text, const wxRect& rect, int alignment) { - DcClipper clip(dc, rect); //wxDC::DrawLabel doesn't care about width, WTF? + RecursiveDcClipper clip(dc, rect); //wxDC::DrawLabel doesn't care about width, WTF? /* performance notes: @@ -555,7 +512,7 @@ private: wxRect textRect = rect; textRect.Deflate(1); { - DcClipper clip(dc, textRect); //wxDC::DrawLabel doesn't care about with, WTF? + RecursiveDcClipper clip(dc, textRect); //wxDC::DrawLabel doesn't care about with, WTF? dc.DrawLabel(formatRow(row), textRect, wxALIGN_CENTRE); } @@ -691,7 +648,7 @@ private: highlight ? col == highlight->first && compPos == highlight->second : false; - DcClipper clip(dc, rect); + RecursiveDcClipper clip(dc, rect); dataView->renderColumnLabel(refParent(), dc, rect, colType, isHighlighted); //draw move target location @@ -978,7 +935,7 @@ private: { //draw background lines { - DcClipper dummy2(dc, rect); //solve issues with drawBackground() painting in area outside of rect (which is not also refreshed by renderCell()) -> keep small scope! + RecursiveDcClipper dummy2(dc, rect); //solve issues with drawBackground() painting in area outside of rect (which is not also refreshed by renderCell()) -> keep small scope! for (int row = rowFirst; row < rowLast; ++row) drawBackground(*prov, dc, wxRect(cellAreaTL + wxPoint(0, row * rowHeight), wxSize(compWidth, rowHeight)), row, compPos); } @@ -995,7 +952,7 @@ private: for (int row = rowFirst; row < rowLast; ++row) { const wxRect& cellRect = wxRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, width, rowHeight); - DcClipper clip(dc, cellRect); + RecursiveDcClipper clip(dc, cellRect); prov->renderCell(refParent(), dc, cellRect, row, iterCol->type_); } cellAreaTL.x += width; diff --git a/wx+/no_flicker.h b/wx+/no_flicker.h index ea9efb4d..fd64628f 100644 --- a/wx+/no_flicker.h +++ b/wx+/no_flicker.h @@ -15,20 +15,27 @@ namespace zen inline void setText(wxTextCtrl& control, const wxString& newText, bool* additionalLayoutChange = nullptr) { + const wxString& label = control.GetValue(); //perf: don't call twice! if (additionalLayoutChange && !*additionalLayoutChange) //never revert from true to false! - *additionalLayoutChange = control.GetValue().length() != newText.length(); //avoid screen flicker: update layout only when necessary + *additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary - if (control.GetValue() != newText) + if (label != newText) control.ChangeValue(newText); } inline -void setText(wxStaticText& control, const wxString& newText, bool* additionalLayoutChange = nullptr) +void setText(wxStaticText& control, wxString newText, bool* additionalLayoutChange = nullptr) { +#ifdef ZEN_WIN + //wxStaticText handles ampersands incorrectly: https://sourceforge.net/p/freefilesync/bugs/279/ + replace(newText, L'&', L"&&"); +#endif + + const wxString& label = control.GetLabel(); //perf: don't call twice! if (additionalLayoutChange && !*additionalLayoutChange) - *additionalLayoutChange = control.GetLabel().length() != newText.length(); //avoid screen flicker: update layout only when necessary + *additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary - if (control.GetLabel() != newText) + if (label != newText) control.SetLabel(newText); } } @@ -13,7 +13,6 @@ #include <wx/image.h> #include <wx/icon.h> #include <wx/app.h> -#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER namespace zen { @@ -38,17 +37,6 @@ void drawIconRtlNoMirror(wxDC& dc, //wxDC::DrawIcon DOES mirror by default wxBitmap mirrorIfRtl(const wxBitmap& bmp); - -/* -class BufferedPaintDC -{ -public: - BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer); -}; -*/ -//a fix for a poor wxWidgets implementation (wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active) - - //manual text flow correction: http://www.w3.org/International/articles/inline-bidi-markup/ @@ -127,48 +115,6 @@ wxBitmap mirrorIfRtl(const wxBitmap& bmp) else return bmp; } - - -#ifndef wxALWAYS_NATIVE_DOUBLE_BUFFER -#error we need this one! -#endif - -#if wxALWAYS_NATIVE_DOUBLE_BUFFER -struct BufferedPaintDC : public wxPaintDC { BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer) : wxPaintDC(&wnd) {} }; - -#else -class BufferedPaintDC : public wxMemoryDC -{ -public: - BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer) : buffer_(buffer), paintDc(&wnd) - { - const wxSize clientSize = wnd.GetClientSize(); - if (!buffer_ || clientSize != wxSize(buffer->GetWidth(), buffer->GetHeight())) - buffer.reset(new wxBitmap(clientSize.GetWidth(), clientSize.GetHeight())); - - SelectObject(*buffer); - - if (paintDc.IsOk() && paintDc.GetLayoutDirection() == wxLayout_RightToLeft) - SetLayoutDirection(wxLayout_RightToLeft); - } - - ~BufferedPaintDC() - { - if (GetLayoutDirection() == wxLayout_RightToLeft) - { - paintDc.SetLayoutDirection(wxLayout_LeftToRight); //workaround bug in wxDC::Blit() - SetLayoutDirection(wxLayout_LeftToRight); // - } - - const wxPoint origin = GetDeviceOrigin(); - paintDc.Blit(0, 0, buffer_->GetWidth(), buffer_->GetHeight(), this, -origin.x, -origin.y); - } - -private: - std::unique_ptr<wxBitmap>& buffer_; - wxPaintDC paintDc; -}; -#endif } #endif //RTL_H_0183487180058718273432148 diff --git a/wx+/shell_execute.h b/wx+/shell_execute.h index 8965186e..1d67aa21 100644 --- a/wx+/shell_execute.h +++ b/wx+/shell_execute.h @@ -42,12 +42,15 @@ void shellExecute(const Zstring& command, ExecutionType type = EXEC_TYPE_ASYNC) { #ifdef ZEN_WIN //parse commandline + Zstring commandTmp = command; + trim(commandTmp, true, false); //CommandLineToArgvW() does not like leading spaces + std::vector<std::wstring> argv; int argc = 0; - if (LPWSTR* tmp = ::CommandLineToArgvW(command.c_str(), &argc)) + if (LPWSTR* tmp = ::CommandLineToArgvW(commandTmp.c_str(), &argc)) { + ZEN_ON_SCOPE_EXIT(::LocalFree(tmp)); std::copy(tmp, tmp + argc, std::back_inserter(argv)); - ::LocalFree(tmp); } std::wstring filename; @@ -74,7 +77,7 @@ void shellExecute(const Zstring& command, ExecutionType type = EXEC_TYPE_ASYNC) if (!::ShellExecuteEx(&execInfo)) //__inout LPSHELLEXECUTEINFO lpExecInfo { wxString cmdFmt = L"File: " + filename + L"\nArg: " + arguments; - wxMessageBox(_("Invalid command line:") + L"\n" + cmdFmt + L"\n\n" + formatSystemError(L"ShellExecuteEx", getLastError())); + wxMessageBox(_("Invalid command line:") + L"\n" + cmdFmt + L"\n\n" + formatSystemError(L"ShellExecuteEx", getLastError()), /*L"FreeFileSync - " + */_("Error"), wxOK | wxICON_ERROR); return; } @@ -99,7 +102,7 @@ void shellExecute(const Zstring& command, ExecutionType type = EXEC_TYPE_ASYNC) //Posix::system - execute a shell command int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect... if (rv == -1 || WEXITSTATUS(rv) == 127) //http://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)" - wxMessageBox(_("Invalid command line:") + L"\n" + utfCvrtTo<wxString>(command)); + wxMessageBox(_("Invalid command line:") + L"\n" + utfCvrtTo<wxString>(command), /*L"FreeFileSync - " +*/ _("Error"), wxOK | wxICON_ERROR); } else async([=] { int rv = ::system(command.c_str()); (void)rv; }); diff --git a/wx+/string_conv.h b/wx+/string_conv.h index b919a3ee..e9150223 100644 --- a/wx+/string_conv.h +++ b/wx+/string_conv.h @@ -23,7 +23,6 @@ inline std::vector<Zstring> toZ(const std::vector<wxString>& strList) std::transform(strList.begin(), strList.end(), std::back_inserter(tmp), [](const wxString& str) { return toZ(str); }); return tmp; } - } #endif // STRINGCONV_H_INCLUDED diff --git a/zen/FindFilePlus/FindFilePlus.vcxproj b/zen/FindFilePlus/FindFilePlus.vcxproj index d7dfe219..73b7f70e 100644 --- a/zen/FindFilePlus/FindFilePlus.vcxproj +++ b/zen/FindFilePlus/FindFilePlus.vcxproj @@ -172,7 +172,7 @@ <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <SuppressStartupBanner>true</SuppressStartupBanner> - <GenerateDebugInformation>false</GenerateDebugInformation> + <GenerateDebugInformation>true</GenerateDebugInformation> <SubSystem>Windows</SubSystem> <OptimizeReferences>true</OptimizeReferences> <EnableCOMDATFolding>true</EnableCOMDATFolding> @@ -209,7 +209,7 @@ <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> <SuppressStartupBanner>true</SuppressStartupBanner> - <GenerateDebugInformation>false</GenerateDebugInformation> + <GenerateDebugInformation>true</GenerateDebugInformation> <SubSystem>Windows</SubSystem> <OptimizeReferences>true</OptimizeReferences> <EnableCOMDATFolding>true</EnableCOMDATFolding> diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp index fdabc46f..ea58005e 100644 --- a/zen/FindFilePlus/find_file_plus.cpp +++ b/zen/FindFilePlus/find_file_plus.cpp @@ -113,10 +113,10 @@ public: FileSearcher(const wchar_t* dirname); //throw NtFileError ~FileSearcher(); - void readDir(FileInformation& output); //throw NtFileError + bool readDir(FileInformation& output); //throw NtFileError; returns false if "no more files" private: - template <class QueryPolicy> void readDirImpl(FileInformation& output); //throw NtFileError + template <class QueryPolicy> bool readDirImpl(FileInformation& output); //throw NtFileError UNICODE_STRING dirnameNt; //it seems hDir implicitly keeps a reference to this, at least ::FindFirstFile() does no cleanup before ::FindClose()! HANDLE hDir; @@ -271,11 +271,11 @@ struct DirQueryFileId inline -void FileSearcher::readDir(FileInformation& output) { readDirImpl<DirQueryFileId>(output); } //throw NtFileError +bool FileSearcher::readDir(FileInformation& output) { return readDirImpl<DirQueryFileId>(output); } //throw NtFileError template <class QueryPolicy> -void FileSearcher::readDirImpl(FileInformation& output) //throw NtFileError +bool FileSearcher::readDirImpl(FileInformation& output) //throw NtFileError; returns false if "no more files" { //although FILE_ID_FULL_DIR_INFORMATION should suffice for our purposes, there are problems on Windows XP for certain directories, e.g. "\\Vboxsvr\build" //making NtQueryDirectoryFile() return with STATUS_INVALID_PARAMETER while other directories, e.g. "C:\" work fine for some reason @@ -337,25 +337,30 @@ void FileSearcher::readDirImpl(FileInformation& output) //throw NtFileError STATUS_NO_SUCH_FILE is abused by some citrix shares instead of "STATUS_INVALID_PARAMETER" so we treat it as such! => since the directory is "truly empty" a fallback won't hurt */ - if (rv == STATUS_INVALID_LEVEL || - rv == STATUS_NOT_SUPPORTED || + if (rv == STATUS_NO_MORE_FILES) //perf: don't throw an exception in this common case! => 8% perf boost for FFS comparison phase! + return false; + + if (rv == STATUS_NOT_SUPPORTED || + rv == STATUS_INVALID_LEVEL || + rv == STATUS_NO_SUCH_FILE || //[!] rv == STATUS_UNEXPECTED_NETWORK_ERROR || rv == STATUS_INVALID_PARAMETER || rv == STATUS_INVALID_NETWORK_RESPONSE || rv == STATUS_INVALID_INFO_CLASS || + //rv == STATUS_NOT_IMPLEMENTED || -> first confirm that these codes + //rv == STATUS_INVALID_DEVICE_REQUEST || -> are in fact used! rv == STATUS_UNSUCCESSFUL || - rv == STATUS_ACCESS_VIOLATION || - rv == STATUS_NO_SUCH_FILE) //[!] + rv == STATUS_ACCESS_VIOLATION) rv = STATUS_NOT_SUPPORTED; throw NtFileError(rv); //throws STATUS_NO_MORE_FILES when finished } - //for (NTSTATUS i = 0xC0000000L; i != -1; ++i) - //{ - // if (rtlNtStatusToDosError(i) == 59) //ERROR_UNEXP_NET_ERR - // __asm int 3; - //} + // for (NTSTATUS i = 0xC0000000L; i != -1; ++i) + // { + // if (rtlNtStatusToDosError(i) == 59) //ERROR_UNEXP_NET_ERR + // __debugbreak(); //__asm int 3; + // } if (status.Information == 0) //except for the first call to call ::NtQueryDirectoryFile(): throw NtFileError(STATUS_BUFFER_OVERFLOW); //if buffer size is too small, return value is STATUS_SUCCESS and Information == 0 -> we don't expect this! @@ -399,6 +404,7 @@ void FileSearcher::readDirImpl(FileInformation& output) //throw NtFileError static_assert(sizeof(output.lastWriteTime) == sizeof(dirInfo.LastWriteTime), "dang!"); static_assert(sizeof(output.fileSize) == sizeof(dirInfo.EndOfFile), "dang!"); static_assert(sizeof(output.fileAttributes) == sizeof(dirInfo.FileAttributes), "dang!"); + return true; } @@ -425,7 +431,11 @@ bool findplus::readDir(FindHandle hnd, FileInformation& output) { try { - hnd->readDir(output); //throw NtFileError + if (!hnd->readDir(output)) //throw NtFileError + { + setWin32Error(rtlNtStatusToDosError(STATUS_NO_MORE_FILES)); + return false; + } return true; } catch (const NtFileError& e) diff --git a/zen/basic_math.h b/zen/basic_math.h index f01421c7..05d892ee 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -36,11 +36,6 @@ void confine(T& val, const T& minVal, const T& maxVal); //make sure minVal <= va 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); -template <class InputIterator, class Compare> -std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last, Compare comp); - template <class T, class InputIterator> //precondition: range must be sorted! auto nearMatch(const T& val, InputIterator first, InputIterator last) -> typename std::iterator_traits<InputIterator>::value_type; @@ -160,6 +155,8 @@ void confine(T& val, const T& minVal, const T& maxVal) //name trim, clamp? } +/* +part of C++11 now! template <class InputIterator, class Compare> inline std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last, Compare compLess) { @@ -200,7 +197,7 @@ std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, Input { return minMaxElement(first, last, std::less<typename std::iterator_traits<InputIterator>::value_type>()); } - +*/ template <class T, class InputIterator> inline auto nearMatch(const T& val, InputIterator first, InputIterator last) -> typename std::iterator_traits<InputIterator>::value_type diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index a800fb80..e53b63e2 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -96,7 +96,7 @@ public: //context of main thread - void getChanges(std::vector<DirWatcher::Entry>& output) //throw FileError, ErrorNotExisting + void getChanges(std::vector<DirWatcher::Entry>& output) //throw FileError { boost::lock_guard<boost::mutex> dummy(lockAccess); @@ -105,9 +105,6 @@ public: { const std::wstring msg = copyStringTo<std::wstring>(errorInfo->msg); const std::wstring descr = copyStringTo<std::wstring>(errorInfo->descr); - - if (errorCodeForNotExisting(errorInfo->errorCode)) - throw ErrorNotExisting(msg, descr); throw FileError(msg, descr); } @@ -163,9 +160,6 @@ public: const DWORD lastError = ::GetLastError(); //copy before making other system calls! const std::wstring errorMsg = replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)); const std::wstring errorDescr = formatSystemError(L"CreateFile", lastError); - - if (errorCodeForNotExisting(lastError)) - throw ErrorNotExisting(errorMsg, errorDescr); throw FileError(errorMsg, errorDescr); } @@ -199,7 +193,6 @@ public: const DWORD lastError = ::GetLastError(); //copy before making other system calls! return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirnamePf)), formatSystemError(L"CreateEvent", lastError), lastError); } - ZEN_ON_SCOPE_EXIT(::CloseHandle(overlapped.hEvent)); DWORD bytesReturned = 0; //should not be needed for async calls, still pass it to help broken drivers @@ -465,9 +458,6 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError const ErrorCode lastError = getLastError(); //copy before making other system calls! const std::wstring errorMsg = replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subdir)); const std::wstring errorDescr = formatSystemError(L"inotify_add_watch", lastError); - - if (errorCodeForNotExisting(lastError)) - throw ErrorNotExisting(errorMsg, errorDescr); throw FileError(errorMsg, errorDescr); } diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h index b0df48bd..eaee5aab 100644 --- a/zen/dir_watcher.h +++ b/zen/dir_watcher.h @@ -27,12 +27,12 @@ namespace zen Renaming of top watched directory handled incorrectly: Not notified(!) + additional changes in subfolders now do report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!) - Overcome all issues portably: check existence of top watched directory externally + reinstall watch after changes in directory structure (added directories) are possible + Overcome all issues portably: check existence of top watched directory externally + reinstall watch after changes in directory structure (added directories) are detected */ class DirWatcher { public: - DirWatcher(const Zstring& directory); //throw FileError, ErrorNotExisting + DirWatcher(const Zstring& directory); //throw FileError ~DirWatcher(); enum ActionType diff --git a/zen/file_error.h b/zen/file_error.h index 71619834..db8b371d 100644 --- a/zen/file_error.h +++ b/zen/file_error.h @@ -30,7 +30,6 @@ private: #define DEFINE_NEW_FILE_ERROR(X) struct X : public FileError { X(const std::wstring& msg) : FileError(msg) {} X(const std::wstring& msg, const std::wstring& descr) : FileError(msg, descr) {} }; -DEFINE_NEW_FILE_ERROR(ErrorNotExisting); DEFINE_NEW_FILE_ERROR(ErrorTargetExisting); DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing); DEFINE_NEW_FILE_ERROR(ErrorFileLocked); diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index 398e88e8..103a39f7 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -45,67 +45,111 @@ using namespace zen; +warn_static("remove after test") -bool zen::fileExists(const Zstring& filename) +namespace +{ +void writeSysErrorIfNeeded(std::wstring* sysErrorMsg, const wchar_t* functionName, ErrorCode lastError) +{ + if (sysErrorMsg) + { + //skip uninteresting error codes: +#ifdef ZEN_WIN + if (lastError == ERROR_FILE_NOT_FOUND || + lastError == ERROR_PATH_NOT_FOUND) return; + //lastError == ERROR_BAD_NETPATH || //e.g. for a path like: \\192.168.1.1\test + //lastError == ERROR_NETNAME_DELETED; + +#elif defined ZEN_LINUX || defined ZEN_MAC + if (lastError == ENOENT) return; +#endif + *sysErrorMsg = formatSystemError(functionName, lastError); + } +} +} + + +bool zen::fileExists(const Zstring& filename, std::wstring* sysErrorMsg) { //symbolic links (broken or not) are also treated as existing files! #ifdef ZEN_WIN + const wchar_t functionName[] = L"GetFileAttributes"; const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); - return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also + if (attr != INVALID_FILE_ATTRIBUTES) + return (attr & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also #elif defined ZEN_LINUX || defined ZEN_MAC + const wchar_t functionName[] = L"lstat"; struct ::stat fileInfo = {}; - return ::lstat(filename.c_str(), &fileInfo) == 0 && - (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)); //in Linux a symbolic link is neither file nor directory + if (::lstat(filename.c_str(), &fileInfo) == 0) + return S_ISREG(fileInfo.st_mode) || S_ISLNK(fileInfo.st_mode); //in Linux a symbolic link is neither file nor directory #endif + writeSysErrorIfNeeded(sysErrorMsg, functionName, getLastError()); + return false; } -bool zen::dirExists(const Zstring& dirname) +bool zen::dirExists(const Zstring& dirname, std::wstring* sysErrorMsg) { //symbolic links (broken or not) are also treated as existing directories! #ifdef ZEN_WIN + const wchar_t functionName[] = L"GetFileAttributes"; const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirname).c_str()); - return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for (dir-)symlinks also + if (attr != INVALID_FILE_ATTRIBUTES) + return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for (dir-)symlinks also #elif defined ZEN_LINUX || defined ZEN_MAC + const wchar_t functionName[] = L"lstat"; struct ::stat dirInfo = {}; - return ::lstat(dirname.c_str(), &dirInfo) == 0 && - (S_ISLNK(dirInfo.st_mode) || S_ISDIR(dirInfo.st_mode)); //in Linux a symbolic link is neither file nor directory + if (::lstat(dirname.c_str(), &dirInfo) == 0) + return S_ISDIR(dirInfo.st_mode) || S_ISLNK(dirInfo.st_mode); //in Linux a symbolic link is neither file nor directory #endif + writeSysErrorIfNeeded(sysErrorMsg, functionName, getLastError()); + return false; } -bool zen::symlinkExists(const Zstring& linkname) +bool zen::symlinkExists(const Zstring& linkname, std::wstring* sysErrorMsg) { #ifdef ZEN_WIN - WIN32_FIND_DATA fileInfo = {}; + const wchar_t functionName[] = L"FindFirstFile"; + WIN32_FIND_DATA linkInfo = {}; + const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(linkname).c_str(), &linkInfo); + if (searchHandle != INVALID_HANDLE_VALUE) { - const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(linkname).c_str(), &fileInfo); - if (searchHandle == INVALID_HANDLE_VALUE) - return false; ::FindClose(searchHandle); + return isSymlink(linkInfo); } - return isSymlink(fileInfo); #elif defined ZEN_LINUX || defined ZEN_MAC - struct ::stat fileInfo = {}; - return ::lstat(linkname.c_str(), &fileInfo) == 0 && - S_ISLNK(fileInfo.st_mode); //symbolic link + const wchar_t functionName[] = L"lstat"; + struct ::stat linkInfo = {}; + if (::lstat(linkname.c_str(), &linkInfo) == 0) + return S_ISLNK(linkInfo.st_mode); #endif + writeSysErrorIfNeeded(sysErrorMsg, functionName, getLastError()); + return false; } -bool zen::somethingExists(const Zstring& objname) +bool zen::somethingExists(const Zstring& objname, std::wstring* sysErrorMsg) { #ifdef ZEN_WIN + const wchar_t functionName[] = L"GetFileAttributes"; const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(objname).c_str()); - return attr != INVALID_FILE_ATTRIBUTES || ::GetLastError() == ERROR_SHARING_VIOLATION; //"C:\pagefile.sys" + if (attr != INVALID_FILE_ATTRIBUTES) + return true; + if (::GetLastError() == ERROR_SHARING_VIOLATION) //"C:\pagefile.sys" + return true; #elif defined ZEN_LINUX || defined ZEN_MAC + const wchar_t functionName[] = L"lstat"; struct ::stat fileInfo = {}; - return ::lstat(objname.c_str(), &fileInfo) == 0; + if (::lstat(objname.c_str(), &fileInfo) == 0) + return true; #endif + writeSysErrorIfNeeded(sysErrorMsg, functionName, getLastError()); + return false; } @@ -275,9 +319,6 @@ bool zen::removeFile(const Zstring& filename) //throw FileError #endif { ErrorCode lastError = getLastError(); - if (errorCodeForNotExisting(lastError)) //no error situation if file is not existing! manual deletion relies on it! - return false; - #ifdef ZEN_WIN if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only { @@ -288,7 +329,6 @@ bool zen::removeFile(const Zstring& filename) //throw FileError lastError = ::GetLastError(); } #endif - //after "lastError" evaluation it *may* be redundant to check existence again, but better be safe than sorry: if (!somethingExists(filename)) //warning: changes global error code!! return false; //neither file nor any other object (e.g. broken symlink) with that name existing @@ -2119,7 +2159,7 @@ void copyFileLinuxMac(const Zstring& sourceFile, FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting { //open sourceFile for reading - FileInputUnbuffered fileIn(sourceFile); //throw FileError, ErrorNotExisting + FileInputUnbuffered fileIn(sourceFile); //throw FileError struct ::stat sourceInfo = {}; if (::fstat(fileIn.getDescriptor(), &sourceInfo) != 0) //read file attributes from source diff --git a/zen/file_handling.h b/zen/file_handling.h index f677358b..3cb8a68c 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -18,10 +18,11 @@ struct CallbackRemoveDir; struct CallbackCopyFile; -bool fileExists (const Zstring& filename); //noexcept; check whether file *or* (file) symlink exists -bool dirExists (const Zstring& dirname); //noexcept; check whether directory *or* (dir) symlink exists -bool symlinkExists (const Zstring& linkname); //noexcept; check whether a symbolic link exists -bool somethingExists(const Zstring& objname); //noexcept; check whether any object with this name exists +bool fileExists (const Zstring& filename, std::wstring* sysErrorMsg = nullptr); //noexcept; check whether file *or* (file) symlink exists +bool dirExists (const Zstring& dirname , std::wstring* sysErrorMsg = nullptr); //noexcept; check whether directory *or* (dir) symlink exists +bool symlinkExists (const Zstring& linkname, std::wstring* sysErrorMsg = nullptr); //noexcept; check whether a symbolic link exists +bool somethingExists(const Zstring& objname , std::wstring* sysErrorMsg = nullptr); //noexcept; check whether any object with this name exists +//sysErrorMsg: optional in + optional out! written only for non-expected errors other than ERROR_FILE_NOT_FOUND/ENOENT, ect... enum SymLinkType { diff --git a/zen/file_io.cpp b/zen/file_io.cpp index e0f8c12e..45cbd028 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -77,8 +77,7 @@ void checkForUnsupportedType(const Zstring& filename) //throw FileError FileInput::FileInput(FileHandle handle, const Zstring& filename) : FileInputBase(filename), fileHandle(handle) {} -FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExisting - FileInputBase(filename) +FileInput::FileInput(const Zstring& filename) : FileInputBase(filename) //throw FileError { #ifdef ZEN_WIN const wchar_t functionName[] = L"CreateFile"; @@ -104,8 +103,8 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExist while FILE_FLAG_RANDOM_ACCESS offers best performance for - same physical disk (HDD <-> HDD) - Problem: bad XP implementation of prefetch makes flag FILE_FLAG_SEQUENTIAL_SCAN effectively load two files at once from one drive - swapping every 64 kB (or similar). File access times explode! + Problem: bad XP implementation of prefetch makes flag FILE_FLAG_SEQUENTIAL_SCAN effectively load two files at the same time + from one drive, swapping every 64 kB (or similar). File access times explode! => For XP it is critical to use FILE_FLAG_RANDOM_ACCESS (to disable prefetch) if reading two files on same disk and FILE_FLAG_SEQUENTIAL_SCAN when reading from different disk (e.g. massive performance improvement compared to random access for DVD <-> HDD!) => there is no compromise that satisfies all cases! (on XP) @@ -122,9 +121,7 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExist #endif { const ErrorCode lastError = getLastError(); //copy before making other system calls! - const std::wstring errorMsg = errorCodeForNotExisting(lastError) ? - replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filename)) : - replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename)); + const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filename)); std::wstring errorDescr = formatSystemError(functionName, lastError); #ifdef ZEN_WIN @@ -136,10 +133,6 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExist errorDescr = _("The file is locked by another process:") + L"\n" + procList; } #endif - - if (errorCodeForNotExisting(lastError)) - throw ErrorNotExisting(errorMsg, errorDescr); - throw FileError(errorMsg, errorDescr); } } @@ -319,7 +312,7 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro #if defined ZEN_LINUX || defined ZEN_MAC //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 +FileInputUnbuffered::FileInputUnbuffered(const Zstring& filename) : FileInputBase(filename) //throw FileError { checkForUnsupportedType(filename); //throw FileError; reading a named pipe would block forever! @@ -327,15 +320,8 @@ FileInputUnbuffered::FileInputUnbuffered(const Zstring& filename) : FileInputBas if (fdFile == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle { const ErrorCode lastError = getLastError(); //copy before making other system calls! - - const std::wstring errorMsg = errorCodeForNotExisting(lastError) ? - replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filename)) : - replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename)); + const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filename)); const std::wstring errorDescr = formatSystemError(L"open", lastError); - - if (errorCodeForNotExisting(lastError)) - throw ErrorNotExisting(errorMsg, errorDescr); - throw FileError(errorMsg, errorDescr); } } diff --git a/zen/file_io.h b/zen/file_io.h index 93267a39..0c7506a1 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -39,7 +39,7 @@ typedef FILE* FileHandle; class FileInput : public FileInputBase { public: - FileInput(const Zstring& filename); //throw FileError, ErrorNotExisting + FileInput(const Zstring& filename); //throw FileError FileInput(FileHandle handle, const Zstring& filename); //takes ownership! ~FileInput(); @@ -68,7 +68,7 @@ private: class FileInputUnbuffered : public FileInputBase { public: - FileInputUnbuffered(const Zstring& filename); //throw FileError, ErrorNotExisting + FileInputUnbuffered(const Zstring& filename); //throw FileError ~FileInputUnbuffered(); //considering safe-read.c it seems buffer size should be a multiple of 8192 diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 9e5975d9..4f16e3e1 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -24,47 +24,45 @@ using namespace zen; +std::wstring zen::formatThreeDigitPrecision(double value) +{ + //print at least three digits: 0,01 | 0,11 | 1,11 | 11,1 | 111 + if (numeric::abs(value) < 9.995) //9.999 must not be formatted as "10.00" + return printNumber<std::wstring>(L"%.2f", value); + if (numeric::abs(value) < 99.95) //99.99 must not be formatted as "100.0" + return printNumber<std::wstring>(L"%.1f", value); + return numberTo<std::wstring>(numeric::round(value)); +} + + std::wstring zen::filesizeToShortString(Int64 size) { //if (size < 0) return _("Error"); -> really? if (numeric::abs(size) <= 999) - return replaceCpy(_P("1 Byte", "%x Bytes", to<int>(size)), L"%x", numberTo<std::wstring>(size)); - - auto formatUnitSize = [](double sizeInUnit, const std::wstring& unitTxt) -> std::wstring - { - //print just three significant digits: 0,01 | 0,11 | 1,11 | 11,1 | 111 - const size_t fullunits = static_cast<size_t>(numeric::abs(sizeInUnit)); - const int precisionDigits = fullunits < 10 ? 2 : fullunits < 100 ? 1 : 0; //sprintf requires "int" - - wchar_t buffer[50]; -#ifdef __MINGW32__ - int charsWritten = ::_snwprintf(buffer, 50, L"%.*f", precisionDigits, sizeInUnit); //MinGW does not comply to the C standard here -#else - int charsWritten = std::swprintf(buffer, 50, L"%.*f", precisionDigits, sizeInUnit); -#endif - return charsWritten > 0 ? replaceCpy(unitTxt, L"%x", std::wstring(buffer, charsWritten)) : _("Error"); - }; + return replaceCpy(_P("1 byte", "%x bytes", to<int>(size)), L"%x", numberTo<std::wstring>(size)); double sizeInUnit = to<double>(size); + auto formatUnit = [&](const std::wstring& unitTxt) { return replaceCpy(unitTxt, L"%x", formatThreeDigitPrecision(sizeInUnit)); }; + sizeInUnit /= 1024; - if (numeric::abs(sizeInUnit) <= 999) - return formatUnitSize(sizeInUnit, _("%x KB")); + if (numeric::abs(sizeInUnit) < 999.5) + return formatUnit(_("%x KB")); sizeInUnit /= 1024; - if (numeric::abs(sizeInUnit) <= 999) - return formatUnitSize(sizeInUnit, _("%x MB")); + if (numeric::abs(sizeInUnit) < 999.5) + return formatUnit(_("%x MB")); sizeInUnit /= 1024; - if (numeric::abs(sizeInUnit) <= 999) - return formatUnitSize(sizeInUnit, _("%x GB")); + if (numeric::abs(sizeInUnit) < 999.5) + return formatUnit(_("%x GB")); sizeInUnit /= 1024; - if (numeric::abs(sizeInUnit) <= 999) - return formatUnitSize(sizeInUnit, _("%x TB")); + if (numeric::abs(sizeInUnit) < 999.5) + return formatUnit(_("%x TB")); sizeInUnit /= 1024; - return formatUnitSize(sizeInUnit, _("%x PB")); + return formatUnit(_("%x PB")); } @@ -148,7 +146,7 @@ std::wstring zen::remainingTimeToString(double timeInSec) std::wstring zen::fractionToString(double fraction) { - return printNumber<std::wstring>(L"%3.2f", fraction * 100.0) + L'%'; //no need to internationalize fraction!? + return printNumber<std::wstring>(L"%.2f", fraction * 100.0) + L'%'; //no need to internationalize fraction!? } diff --git a/zen/format_unit.h b/zen/format_unit.h index 86477999..e3e2d107 100644 --- a/zen/format_unit.h +++ b/zen/format_unit.h @@ -16,19 +16,12 @@ namespace zen std::wstring filesizeToShortString(Int64 filesize); std::wstring remainingTimeToString(double timeInSec); std::wstring fractionToString(double fraction); //within [0, 1] - -template <class NumberType> -std::wstring toGuiString(NumberType number); //format integer number including thousands separator - std::wstring utcToLocalTimeString(Int64 utcTime); //like Windows Explorer would... +std::wstring formatThreeDigitPrecision(double value); //= *at least* three digits - - - - - - +template <class NumberType> +std::wstring toGuiString(NumberType number); //format integer number including thousands separator @@ -68,6 +68,7 @@ std::wstring translate(const std::wstring& text) inline std::wstring translate(const std::wstring& singular, const std::wstring& plural, int n) { + if (n < 0) n = -n; return getTranslator() ? getTranslator()->translate(singular, plural, n) : n == 1 ? singular : plural; } diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h index db1fbdca..cdff09fa 100644 --- a/zen/long_path_prefix.h +++ b/zen/long_path_prefix.h @@ -57,6 +57,11 @@ Zstring applyLongPathPrefixImpl(const Zstring& path) assert(!path.empty()); //nicely check almost all WinAPI accesses! assert(!zen::isWhiteSpace(path[0])); + //http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx#naming_conventions)) + /* + - special names like ".NUL" create all kinds of trouble (e.g. CreateDirectory() reports success, but does nothing) + unless prefix is supplied => accept as limitation + */ if (path.length() >= maxPath || //maximum allowed path length without prefix is (MAX_PATH - 1) endsWith(path, L' ') || //by default all Win32 APIs trim trailing spaces and period, unless long path prefix is supplied! endsWith(path, L'.')) //note: adding long path prefix might screw up relative paths "." and ".."! diff --git a/zen/optional.h b/zen/optional.h index a6a53103..6e54408a 100644 --- a/zen/optional.h +++ b/zen/optional.h @@ -45,6 +45,7 @@ public: if (tmp.valid) value = tmp.value; valid = tmp.valid; + return *this; } ////rvalue optimization: only basic exception safety: diff --git a/zen/serialize.h b/zen/serialize.h index 415bd430..64c07213 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -56,7 +56,7 @@ private: //---------------------------------------------------------------------- //functions based on binary container abstraction template <class BinContainer> void saveBinStream(const Zstring& filename, const BinContainer& cont); //throw FileError -template <class BinContainer> BinContainer loadBinStream(const Zstring& filename); //throw FileError, ErrorNotExisting +template <class BinContainer> BinContainer loadBinStream(const Zstring& filename); //throw FileError /* @@ -157,11 +157,11 @@ void saveBinStream(const Zstring& filename, const BinContainer& cont) //throw Fi template <class BinContainer> inline -BinContainer loadBinStream(const Zstring& filename) //throw FileError, ErrorNotExisting +BinContainer loadBinStream(const Zstring& filename) //throw FileError { assert_static(sizeof(typename BinContainer::value_type) == 1); //expect: bytes (until further) - FileInput fileIn(filename); //throw FileError, ErrorNotExisting + FileInput fileIn(filename); //throw FileError BinContainer contOut; const size_t blockSize = 128 * 1024; diff --git a/zen/stl_tools.h b/zen/stl_tools.h index c93f2d61..bf47bb1c 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -183,6 +183,9 @@ template <class K, class V> class hash_map : public std::map<K, V> {}; #else template <class T> class hash_set : public std::unordered_set<T> {}; template <class K, class V> class hash_map : public std::unordered_map<K, V> {}; +//C++11: +//template <class T> using hash_set = std::unordered_set<T>; +//template <class K, class V> using hash_map = std::unordered_map<K, V>; #endif //as long as variadic templates are not available in MSVC diff --git a/zen/string_base.h b/zen/string_base.h index 591ed62b..31a09e63 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -21,7 +21,7 @@ namespace zen Allocator Policy: ----------------- void* allocate(size_t size) //throw std::bad_alloc - void deallocate(void* ptr) + void deallocate(void* ptr) //must handle deallocate(nullptr)! size_t calcCapacity(size_t length) */ class AllocatorOptimalSpeed //exponential growth + min size @@ -30,8 +30,9 @@ public: //::operator new/ ::operator delete show same performance characterisics like malloc()/free()! static void* allocate(size_t size) { return ::operator new(size); } //throw std::bad_alloc static void deallocate(void* ptr) { ::operator delete(ptr); } - static size_t calcCapacity(size_t length) { return std::max<size_t>(std::max<size_t>(16, length), length + length / 2); } //size_t might overflow! - //any growth rate should not exceed golden ratio: 1.618033989 + static size_t calcCapacity(size_t length) { return std::max<size_t>(16, std::max(length + length / 2, length)); } + //- size_t might overflow! => better catch here than return a too small size covering up the real error: a way too large length! + //- any growth rate should not exceed golden ratio: 1.618033989 }; @@ -52,7 +53,7 @@ template <typename Char, //Character Type Char* create(size_t size) Char* create(size_t size, size_t minCapacity) Char* clone(Char* ptr) - void destroy(Char* ptr) + void destroy(Char* ptr) //must handle destroy(nullptr)! bool canWrite(const Char* ptr, size_t minCapacity) //needs to be checked before writing to "ptr" size_t length(const Char* ptr) void setLength(Char* ptr, size_t newLength) @@ -87,7 +88,7 @@ protected: return newData; } - static void destroy(Char* ptr) { AP::deallocate(descr(ptr)); } + static void destroy(Char* ptr) { AP::deallocate(descr(ptr)); } //should support destroy(nullptr)! //this needs to be checked before writing to "ptr" static bool canWrite(const Char* ptr, size_t minCapacity) { return minCapacity <= descr(ptr)->capacity; } @@ -141,6 +142,7 @@ protected: static void destroy(Char* ptr) { + if (!ptr) return; //support destroy(nullptr) assert(descr(ptr)->refCount > 0); if (--descr(ptr)->refCount == 0) //operator--() is overloaded to decrement and evaluate in a single atomic operation! { @@ -251,7 +253,7 @@ public: private: Zbase(int); // - Zbase& operator=(int); //detect usage errors + Zbase& operator=(int); //detect usage errors by creating an intentional ambiguity with "Char" Zbase& operator+=(int); // void push_back(int); // @@ -294,15 +296,6 @@ template <class Char, template <class, class> class SP, class AP> inline Zbase<C - - - - - - - - - //################################# implementation ######################################## template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP>::Zbase() @@ -361,16 +354,9 @@ Zbase<Char, SP, AP>::Zbase(const Zbase<Char, SP, AP>& source) template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP>::Zbase(Zbase<Char, SP, AP>&& tmp) { - if (this->canWrite(tmp.rawStr, 0)) //perf: following optimization saves about 4% - { - //do not increment ref-count of an unshared string! We'd lose optimization opportunity of reusing its memory! - //instead create a dummy string and swap: - rawStr = this->create(0); //no perf issue! see comment in default constructor - rawStr[0] = 0; - swap(tmp); - } - else //shared representation: yet another "add ref" won't hurt - rawStr = this->clone(tmp.rawStr); + rawStr = tmp.rawStr; + tmp.rawStr = nullptr; //usually nullptr would violate the class invarants, but it is good enough for the destructor! + //caveat: do not increment ref-count of an unshared string! We'd lose optimization opportunity of reusing its memory! } @@ -388,7 +374,7 @@ Zbase<Char, SP, AP>::Zbase(const S& other, typename S::value_type) template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP>::~Zbase() { - this->destroy(rawStr); + this->destroy(rawStr); //rawStr may be nullptr; see move constructor! } @@ -499,25 +485,25 @@ Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::replace(size_t pos1, size_t n1, const template <class Char, template <class, class> class SP, class AP> inline void Zbase<Char, SP, AP>::resize(size_t newSize, Char fillChar) { + const size_t oldSize = length(); if (this->canWrite(rawStr, newSize)) { - if (length() < newSize) - std::fill(rawStr + length(), rawStr + newSize, fillChar); + if (oldSize < newSize) + std::fill(rawStr + oldSize, rawStr + newSize, fillChar); rawStr[newSize] = 0; - this->setLength(rawStr, newSize); //keep after call to length() + this->setLength(rawStr, newSize); } else { Char* newStr = this->create(newSize); - newStr[newSize] = 0; - - if (length() < newSize) + if (oldSize < newSize) { - std::copy(rawStr, rawStr + length(), newStr); - std::fill(newStr + length(), newStr + newSize, fillChar); + std::copy(rawStr, rawStr + oldSize, newStr); + std::fill(newStr + oldSize, newStr + newSize, fillChar); } else std::copy(rawStr, rawStr + newSize, newStr); + newStr[newSize] = 0; this->destroy(rawStr); rawStr = newStr; @@ -614,7 +600,7 @@ void Zbase<Char, SP, AP>::clear() { if (this->canWrite(rawStr, 0)) { - rawStr[0] = 0; //keep allocated memory + rawStr[0] = 0; //keep allocated memory this->setLength(rawStr, 0); // } else @@ -636,8 +622,9 @@ void Zbase<Char, SP, AP>::reserve(size_t minCapacity) //make unshared and check if (!this->canWrite(rawStr, minCapacity)) { //allocate a new string - Char* newStr = this->create(length(), std::max(minCapacity, length())); //reserve() must NEVER shrink the string: logical const! - std::copy(rawStr, rawStr + length() + 1, newStr); //include 0-termination + const size_t len = length(); + Char* newStr = this->create(len, std::max(len, minCapacity)); //reserve() must NEVER shrink the string: logical const! + std::copy(rawStr, rawStr + len + 1, newStr); //include 0-termination this->destroy(rawStr); rawStr = newStr; diff --git a/zen/sys_error.h b/zen/sys_error.h index bbac2eaa..cea2f5f9 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -28,14 +28,14 @@ namespace zen typedef DWORD ErrorCode; #elif defined ZEN_LINUX || defined ZEN_MAC typedef int ErrorCode; +#else +#error define a platform! #endif ErrorCode getLastError(); std::wstring formatSystemError(const std::wstring& functionName, ErrorCode lastError); -bool errorCodeForNotExisting(ErrorCode lastError); //check for "not existing" aliases - //A low-level exception class giving (non-translated) detail information only - same conceptional level like "GetLastError()"! class SysError @@ -105,20 +105,6 @@ std::wstring formatSystemError(const std::wstring& functionName, ErrorCode lastE return output; } - - -inline -bool errorCodeForNotExisting(ErrorCode lastError) -{ -#ifdef ZEN_WIN - return lastError == ERROR_FILE_NOT_FOUND || - lastError == ERROR_PATH_NOT_FOUND || - lastError == ERROR_BAD_NETPATH || - lastError == ERROR_NETNAME_DELETED; -#elif defined ZEN_LINUX || defined ZEN_MAC - return lastError == ENOENT; -#endif -} } #endif //LAST_ERROR_H_3284791347018951324534 diff --git a/zen/thread.h b/zen/thread.h index 638d9474..76596513 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -128,7 +128,7 @@ public: void reportFinished(std::unique_ptr<T>&& result) { { - boost::unique_lock<boost::mutex> dummy(lockResult); + boost::lock_guard<boost::mutex> dummy(lockResult); ++jobsFinished; if (!result_) result_ = std::move(result); @@ -149,7 +149,6 @@ public: std::unique_ptr<T> getResult(size_t jobsTotal) { boost::unique_lock<boost::mutex> dummy(lockResult); - while (!jobDone(jobsTotal)) conditionJobDone.timed_wait(dummy, boost::posix_time::milliseconds(50)); //interruption point! diff --git a/zen/type_tools.h b/zen/type_tools.h index e5ce29bd..76a12a5a 100644 --- a/zen/type_tools.h +++ b/zen/type_tools.h @@ -29,10 +29,10 @@ template <class T, class U> struct SelectIf<false, T, U> : ResultType<U> {}; //------------------------------------------------------ template <class T, class U> -struct IsSameType : StaticBool<false> {}; +struct IsSameType : FalseType {}; template <class T> -struct IsSameType<T, T> : StaticBool<true> {}; +struct IsSameType<T, T> : TrueType {}; //------------------------------------------------------ template <bool, class T = void> diff --git a/zen/type_traits.h b/zen/type_traits.h index 4f71f961..a90b9793 100644 --- a/zen/type_traits.h +++ b/zen/type_traits.h @@ -21,6 +21,9 @@ struct StaticInt template <bool b> struct StaticBool : StaticInt<b> {}; +typedef StaticBool<true> TrueType; +typedef StaticBool<false> FalseType; + template <class EnumType, EnumType val> struct StaticEnum { @@ -45,7 +48,7 @@ template <class T> struct IsArithmetic; //IsInteger or IsFloat //remaining non-arithmetic types: bool, char, wchar_t //optional: specialize new types like: -//template <> struct IsUnsignedInt<UInt64> : StaticBool<true> {}; +//template <> struct IsUnsignedInt<UInt64> : TrueType {}; //################# Class Members ######################## @@ -81,10 +84,10 @@ template <class T> struct IsArithmetic; //IsInteger or IsFloat //################ implementation ###################### -#define ZEN_SPECIALIZE_TRAIT(X, Y) template <> struct X<Y> : StaticBool<true> {}; +#define ZEN_SPECIALIZE_TRAIT(X, Y) template <> struct X<Y> : TrueType {}; template <class T> -struct IsUnsignedInt : StaticBool<false> {}; +struct IsUnsignedInt : FalseType {}; ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned char); ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned short int); @@ -94,7 +97,7 @@ ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned long long int); //new with C++11 - //------------------------------------------------------ template <class T> -struct IsSignedInt : StaticBool<false> {}; +struct IsSignedInt : FalseType {}; ZEN_SPECIALIZE_TRAIT(IsSignedInt, signed char); ZEN_SPECIALIZE_TRAIT(IsSignedInt, short int); @@ -104,7 +107,7 @@ ZEN_SPECIALIZE_TRAIT(IsSignedInt, long long int); //new with C++11 - same type a //------------------------------------------------------ template <class T> -struct IsFloat : StaticBool<false> {}; +struct IsFloat : FalseType {}; ZEN_SPECIALIZE_TRAIT(IsFloat, float); ZEN_SPECIALIZE_TRAIT(IsFloat, double); @@ -143,7 +146,7 @@ struct IsArithmetic : StaticBool<IsInteger<T>::value || IsFloat<T>::value> {}; }; \ \ template<class T> \ - struct HasMemberImpl_##NAME<false, T> : StaticBool<false> {}; \ + struct HasMemberImpl_##NAME<false, T> : FalseType {}; \ \ template<typename T> \ struct HasMember_##NAME : StaticBool<HasMemberImpl_##NAME<std::is_class<T>::value, T>::value> {}; @@ -23,7 +23,6 @@ #endif #include <windows.h> - #endif //------------------------------------------------------ diff --git a/zen/zstring.cpp b/zen/zstring.cpp index a469ade2..1cc037e8 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -33,19 +33,15 @@ public: void insert(const void* ptr, size_t size) { boost::lock_guard<boost::mutex> dummy(lockActStrings); - if (activeStrings.find(ptr) != activeStrings.end()) + if (!activeStrings.insert(std::make_pair(ptr, size)).second) reportProblem("Fatal Error: New memory points into occupied space: " + rawMemToString(ptr, size)); - - activeStrings[ptr] = size; } void remove(const void* ptr) { boost::lock_guard<boost::mutex> dummy(lockActStrings); - if (activeStrings.find(ptr) == activeStrings.end()) + if (activeStrings.erase(ptr) != 1) reportProblem("Fatal Error: No memory available for deallocation at this location!"); - - activeStrings.erase(ptr); } static LeakChecker& instance() { static LeakChecker inst; return inst; } diff --git a/zen/zstring.h b/zen/zstring.h index 80c267e3..f103faf7 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -28,11 +28,11 @@ class AllocatorFreeStoreChecked public: static void* allocate(size_t size) //throw std::bad_alloc { - void* newMem = ::operator new(size); + void* ptr = zen::AllocatorOptimalSpeed::allocate(size); #ifndef NDEBUG - z_impl::leakCheckerInsert(newMem, size); //test Zbase for memory leaks + z_impl::leakCheckerInsert(ptr, size); //test Zbase for memory leaks #endif - return newMem; + return ptr; } static void deallocate(void* ptr) @@ -40,10 +40,10 @@ public: #ifndef NDEBUG z_impl::leakCheckerRemove(ptr); //check for memory leaks #endif - ::operator delete(ptr); + zen::AllocatorOptimalSpeed::deallocate(ptr); } - static size_t calcCapacity(size_t length) { return std::max<size_t>(16, length + length / 2); } //exponential growth + min size + static size_t calcCapacity(size_t length) { return zen::AllocatorOptimalSpeed::calcCapacity(length); } }; |