diff options
56 files changed, 1323 insertions, 968 deletions
@@ -44,6 +44,12 @@ https://github.com/curl/curl/issues/4342 + result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_LIST_TYPE); __________________________________________________________________________________________________________ +/lib/multi.c +/lib/multihandle.h +https://github.com/curl/curl/issues/6146 + +remove patch! regression for Windows upload performance +__________________________________________________________________________________________________________ -------------------------- diff --git a/Changelog.txt b/Changelog.txt index c25ef6d8..eb00a09e 100755 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,19 @@ +FreeFileSync 11.4 [2020-12-04] +------------------------------ +New progress graph "this one sparks joy" +Remember progress dialog size +New config file context menu option: Show in file manager +Work around libcurl performance bug during FTP upload +Only log modification time errors after comparing by size or content +Smaller icon size for efficient screen layout (Linux) +Use system-native recycle bin icon +Fixed DeviceIoControl(IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS): ERROR_MORE_DATA +Support MTP devices lacking a friendly name +Fix grid scrolling with small mouse rotations (macOS) +Faster mouse scrolling on high-DPI resolution displays +Keep previous windows size when maximized during auto-exit + + FreeFileSync 11.3 [2020-11-01] ------------------------------ Enhanced main grid color scheme @@ -973,7 +989,7 @@ FreeFileSync 7.3 [2015-08-01] New context menu option to copy selected files to alternate folder (create diffs) Fill a folder pair by dropping two folders at a time from Explorer Added option to set non-standard SFTP port -Prevent recursive creation of temporary Recycle Bin directories (Windows) +Prevent recursive creation of temporary recycle bin directories (Windows) Retrieve grid column label colors from the system Fixed detection of already existing files when moving (Linux) Follow OS convention for preferences (OS X) diff --git a/FreeFileSync/Build/Resources/Icons.zip b/FreeFileSync/Build/Resources/Icons.zip Binary files differindex 9c64e2ff..a0acf0c7 100644 --- a/FreeFileSync/Build/Resources/Icons.zip +++ b/FreeFileSync/Build/Resources/Icons.zip diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip Binary files differindex cfb06090..e93cfc95 100644 --- a/FreeFileSync/Build/Resources/Languages.zip +++ b/FreeFileSync/Build/Resources/Languages.zip diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile index b3024912..13f80878 100755 --- a/FreeFileSync/Source/Makefile +++ b/FreeFileSync/Source/Makefile @@ -4,7 +4,7 @@ cxxFlags = -std=c++2a -pipe -DWXINTL_NO_GETTEXT_MACRO -I../.. -I../../zenXml -in -Wall -Wfatal-errors -Wmissing-include-dirs -Wswitch-enum -Wcast-align -Wnon-virtual-dtor -Wno-unused-function -Wshadow -Wno-maybe-uninitialized \ -O3 -DNDEBUG `wx-config --cxxflags --debug=no` -pthread -linkFlags = -s -no-pie `wx-config --libs std, aui --debug=no` -pthread +linkFlags = -s -no-pie `wx-config --libs std, aui, richtext --debug=no` -pthread cxxFlags += `pkg-config --cflags openssl` diff --git a/FreeFileSync/Source/RealTimeSync/Makefile b/FreeFileSync/Source/RealTimeSync/Makefile index 97d600a9..9c73d83a 100755 --- a/FreeFileSync/Source/RealTimeSync/Makefile +++ b/FreeFileSync/Source/RealTimeSync/Makefile @@ -4,7 +4,7 @@ cxxFlags = -std=c++2a -pipe -DWXINTL_NO_GETTEXT_MACRO -I../../.. -I../../../zenX -Wall -Wfatal-errors -Wmissing-include-dirs -Wswitch-enum -Wcast-align -Wnon-virtual-dtor -Wno-unused-function -Wshadow -Wno-maybe-uninitialized \ -O3 -DNDEBUG `wx-config --cxxflags --debug=no` -pthread -linkFlags = -s -no-pie `wx-config --libs std, aui --debug=no` -pthread +linkFlags = -s -no-pie `wx-config --libs std, aui, richtext --debug=no` -pthread #Gtk - support "no button border" cxxFlags += `pkg-config --cflags gtk+-2.0` diff --git a/FreeFileSync/Source/RealTimeSync/config.cpp b/FreeFileSync/Source/RealTimeSync/config.cpp index 478f67f8..4d6a80cd 100644 --- a/FreeFileSync/Source/RealTimeSync/config.cpp +++ b/FreeFileSync/Source/RealTimeSync/config.cpp @@ -185,7 +185,7 @@ wxLanguage rts::getProgramLanguage() //throw FileError XmlIn in(doc); wxLanguage lng = wxLANGUAGE_UNKNOWN; - in["General"]["Language"].attribute("Name", lng); + in["Language"].attribute("Name", lng); checkXmlMappingErrors(in, filePath); //throw FileError return lng; diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp index 4fe86a4f..fbaf1b44 100644 --- a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp +++ b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp @@ -212,7 +212,7 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr bSizer151->Add( m_panelMainFolder, 0, wxRIGHT|wxLEFT|wxEXPAND, 5 ); m_scrolledWinFolders = new wxScrolledWindow( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); - m_scrolledWinFolders->SetScrollRate( 10, 10 ); + m_scrolledWinFolders->SetScrollRate( 5, 5 ); m_scrolledWinFolders->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); bSizerFolders = new wxBoxSizer( wxVERTICAL ); diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index 257f4073..a2c5b921 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -75,6 +75,9 @@ MainDialog::MainDialog(const Zstring& cfgFileName) : setRelativeFontSize(*m_buttonStart, 1.5); + const int scrollDelta = m_buttonSelectFolderMain->GetSize().y; //more approriate than GetCharHeight() here + m_scrolledWinFolders->SetScrollRate(scrollDelta, scrollDelta); + m_txtCtrlDirectoryMain->SetMinSize({fastFromDIP(300), -1}); m_spinCtrlDelay ->SetMinSize({fastFromDIP( 70), -1}); //Hack: set size (why does wxWindow::Size() not work?) diff --git a/FreeFileSync/Source/afs/abstract.cpp b/FreeFileSync/Source/afs/abstract.cpp index dac36d17..89743343 100644 --- a/FreeFileSync/Source/afs/abstract.cpp +++ b/FreeFileSync/Source/afs/abstract.cpp @@ -173,6 +173,7 @@ AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& apSource, con const std::function<void()>& onDeleteTargetFile, const IOCallback& notifyUnbufferedIO /*throw X*/) { + auto copyFilePlain = [&](const AbstractPath& apTargetTmp) { //caveat: typeid returns static type for pointers, dynamic type for references!!! diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp index 4bcaf2a7..68a01d04 100644 --- a/FreeFileSync/Source/afs/native.cpp +++ b/FreeFileSync/Source/afs/native.cpp @@ -684,6 +684,9 @@ bool fff::acceptsItemPathPhraseNative(const Zstring& itemPathPhrase) //noexcept trim(path); + if (path.empty()) //eat up empty paths before other AFS implementations get a chance! + return true; + if (startsWith(path, Zstr('['))) //drive letter by volume name syntax return true; diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index 40aae38f..45f10427 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -565,7 +565,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat std::set<AbstractPath> logFilePathsToKeep; - for (const ConfigFileItem& item : globalCfg.gui.mainDlg.cfgFileHistory) + for (const ConfigFileItem& item : globalCfg.mainDlg.cfgFileHistory) logFilePathsToKeep.insert(item.logFilePath); const std::chrono::system_clock::time_point syncStartTime = std::chrono::system_clock::now(); @@ -575,9 +575,10 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat extractJobName(cfgFilePath), syncStartTime, batchCfg.mainCfg.ignoreErrors, - batchCfg.mainCfg.automaticRetryCount, - batchCfg.mainCfg.automaticRetryDelay, + batchCfg.mainCfg.autoRetryCount, + batchCfg.mainCfg.autoRetryDelay, globalCfg.soundFileSyncFinished, + globalCfg.progressDlg.dlgSize, globalCfg.progressDlg.isMaximized, batchCfg.batchExCfg.autoCloseSummary, batchCfg.batchExCfg.postSyncAction, batchCfg.batchExCfg.batchErrorHandling); @@ -627,6 +628,9 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat //*INDENT-ON* } + globalCfg.progressDlg.dlgSize = r.dlgSize; + globalCfg.progressDlg.isMaximized = r.dlgIsMaximized; + //email sending, or saving log file failed? at the very least this should affect the exit code: if (r.logStats.error > 0) raiseExitCode(exitCode, FFS_EXIT_ERROR); @@ -635,7 +639,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat //update last sync stats for the selected cfg file - for (ConfigFileItem& cfi : globalCfg.gui.mainDlg.cfgFileHistory) + for (ConfigFileItem& cfi : globalCfg.mainDlg.cfgFileHistory) if (equalNativePath(cfi.cfgFilePath, cfgFilePath)) { if (r.syncResult != SyncResult::aborted) diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp index d61de963..0e5c6ec2 100644 --- a/FreeFileSync/Source/base/dir_lock.cpp +++ b/FreeFileSync/Source/base/dir_lock.cpp @@ -158,7 +158,7 @@ struct LockInformation //throw FileError std::string userId; //identify running process - SessionId sessionId = 0; //Windows: parent process id; Linux/OS X: session of the process, NOT the user + SessionId sessionId = 0; //Windows: parent process id; Linux/macOS: session of the process, NOT the user ProcessId processId = 0; }; diff --git a/FreeFileSync/Source/base/icon_loader.cpp b/FreeFileSync/Source/base/icon_loader.cpp index 0c9dfd94..956f13ee 100644 --- a/FreeFileSync/Source/base/icon_loader.cpp +++ b/FreeFileSync/Source/base/icon_loader.cpp @@ -104,7 +104,10 @@ ImageHolder imageHolderFromGicon(GIcon& gicon, int maxSize) //throw SysError GtkIconTheme* const defaultTheme = ::gtk_icon_theme_get_default(); //not owned! ASSERT_SYSERROR(defaultTheme); //no more error details - GtkIconInfo* const iconInfo = ::gtk_icon_theme_lookup_by_gicon(defaultTheme, &gicon, maxSize, GTK_ICON_LOOKUP_USE_BUILTIN); + GtkIconInfo* const iconInfo = ::gtk_icon_theme_lookup_by_gicon(defaultTheme, //GtkIconTheme* icon_theme, + &gicon, //GIcon* icon, + maxSize, //gint size, + GTK_ICON_LOOKUP_USE_BUILTIN); //GtkIconLookupFlags flags if (!iconInfo) throw SysError(formatSystemError("gtk_icon_theme_lookup_by_gicon", L"", L"Icon not available.")); #if GTK_MAJOR_VERSION == 2 @@ -136,12 +139,12 @@ FileIconHolder fff::getIconByTemplatePath(const Zstring& templatePath, int maxSi 0, //gsize data_size, nullptr); //gboolean* result_uncertain if (!contentType) - throw SysError(formatSystemError("g_content_type_guess", L"", L"Unknown content type.")); + throw SysError(formatSystemError("g_content_type_guess(" + copyStringTo<std::string>(templatePath) + ')', L"", L"Unknown content type.")); ZEN_ON_SCOPE_EXIT(::g_free(contentType)); GIcon* const fileIcon = ::g_content_type_get_icon(contentType); if (!fileIcon) - throw SysError(formatSystemError("g_content_type_get_icon", L"", L"Icon not available.")); + throw SysError(formatSystemError("g_content_type_get_icon(" + std::string(contentType) + ')', L"", L"Icon not available.")); return FileIconHolder(fileIcon /*pass ownership*/, maxSize); @@ -153,7 +156,7 @@ FileIconHolder fff::genericFileIcon(int maxSize) //throw SysError //we're called by getDisplayIcon()! -> avoid endless recursion! GIcon* const fileIcon = ::g_content_type_get_icon("text/plain"); if (!fileIcon) - throw SysError(formatSystemError("g_content_type_get_icon", L"", L"Icon not available.")); + throw SysError(formatSystemError("g_content_type_get_icon(text/plain)", L"", L"Icon not available.")); return FileIconHolder(fileIcon /*pass ownership*/, maxSize); @@ -164,13 +167,24 @@ FileIconHolder fff::genericDirIcon(int maxSize) //throw SysError { GIcon* const dirIcon = ::g_content_type_get_icon("inode/directory"); //should contain fallback to GTK_STOCK_DIRECTORY ("gtk-directory") if (!dirIcon) - throw SysError(formatSystemError("g_content_type_get_icon", L"", L"Icon not available.")); + throw SysError(formatSystemError("g_content_type_get_icon(inode/directory)", L"", L"Icon not available.")); return FileIconHolder(dirIcon /*pass ownership*/, maxSize); } +FileIconHolder fff::getTrashIcon(int maxSize) //throw SysError +{ + GIcon* const trashIcon = ::g_themed_icon_new("user-trash-full"); //empty: "user-trash" + if (!trashIcon) + throw SysError(formatSystemError("g_themed_icon_new(user-trash)", L"", L"Icon not available.")); + + return FileIconHolder(trashIcon /*pass ownership*/, maxSize); + +} + + FileIconHolder fff::getFileIcon(const Zstring& filePath, int maxSize) //throw SysError { GFile* file = ::g_file_new_for_path(filePath.c_str()); //documented to "never fail" diff --git a/FreeFileSync/Source/base/icon_loader.h b/FreeFileSync/Source/base/icon_loader.h index cd76a8cb..ae4b7b43 100644 --- a/FreeFileSync/Source/base/icon_loader.h +++ b/FreeFileSync/Source/base/icon_loader.h @@ -21,6 +21,7 @@ namespace fff zen::FileIconHolder getIconByTemplatePath(const Zstring& templatePath, int maxSize); //throw SysError zen::FileIconHolder genericFileIcon(int maxSize); //throw SysError zen::FileIconHolder genericDirIcon (int maxSize); //throw SysError +zen::FileIconHolder getTrashIcon (int maxSize); //throw SysError zen::FileIconHolder getFileIcon(const Zstring& filePath, int maxSize); //throw SysError zen::ImageHolder getThumbnailImage(const Zstring& filePath, int maxSize); //throw SysError diff --git a/FreeFileSync/Source/base/structures.h b/FreeFileSync/Source/base/structures.h index 5cbdab06..6671f1ff 100644 --- a/FreeFileSync/Source/base/structures.h +++ b/FreeFileSync/Source/base/structures.h @@ -371,8 +371,8 @@ struct MainConfiguration std::map<AfsDevice, size_t /*parallel operations*/> deviceParallelOps; //should only include devices with >= 2 parallel ops bool ignoreErrors = false; //true: errors will still be logged - size_t automaticRetryCount = 0; - std::chrono::seconds automaticRetryDelay{5}; + size_t autoRetryCount = 0; + std::chrono::seconds autoRetryDelay{5}; Zstring postSyncCommand; //user-defined command line PostSyncCondition postSyncCondition = PostSyncCondition::completion; diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp index 788fede3..5f28ce75 100644 --- a/FreeFileSync/Source/base/synchronization.cpp +++ b/FreeFileSync/Source/base/synchronization.cpp @@ -1511,9 +1511,6 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) nullptr, //onDeleteTargetFile: nothing to delete //if existing: undefined behavior! (e.g. fail/overwrite/auto-rename) statReporter); //throw FileError, ThreadStopRequest - if (result.errorModTime) - errorsModTime_.push_back(*result.errorModTime); //show all warnings later as a single message - statReporter.reportDelta(1, 0); //update FilePair @@ -1523,6 +1520,18 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) result.targetFileId, result.sourceFileId, false, file.isFollowedSymlink<sideSrc>()); + + if (result.errorModTime) + switch (file.base().getCompVariant()) + { + case CompareVariant::timeSize: + errorsModTime_.push_back(*result.errorModTime); //show all warnings later as a single message + break; + case CompareVariant::content: //just log, no warning: + case CompareVariant::size: //e.g. FTP server not supporting MFMT command + acb_.reportInfo(result.errorModTime->toString()); + break; + } } catch (const FileError& e) { @@ -1638,9 +1647,6 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) targetPathResolvedNew, onDeleteTargetFile, statReporter); //throw FileError, ThreadStopRequest, X - if (result.errorModTime) - errorsModTime_.push_back(*result.errorModTime); //show all warnings later as a single message - statReporter.reportDelta(1, 0); //we model "delete + copy" as ONE logical operation //update FilePair @@ -1651,6 +1657,18 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) result.sourceFileId, file.isFollowedSymlink<sideTrg>(), file.isFollowedSymlink<sideSrc>()); + + if (result.errorModTime) + switch (file.base().getCompVariant()) + { + case CompareVariant::timeSize: + errorsModTime_.push_back(*result.errorModTime); //show all warnings later as a single message + break; + case CompareVariant::content: //just log, no warning: + case CompareVariant::size: //e.g. FTP server not supporting MFMT command + acb_.reportInfo(result.errorModTime->toString()); + break; + } } break; @@ -2598,7 +2616,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime //------------------------------------------------------------------------------------------ if (folderCmp.size() > 1) - callback.reportInfo(_("Synchronizing folder pair:") + L' ' + getVariantNameWithSymbol(folderPairCfg.syncVariant) + L'\n' + //throw X + callback.reportInfo(_("Synchronizing folder pair:") + L' ' + getVariantNameWithSymbol(folderPairCfg.syncVar) + L'\n' + //throw X L" " + AFS::getDisplayPath(baseFolder.getAbstractPath< LEFT_SIDE>()) + L'\n' + L" " + AFS::getDisplayPath(baseFolder.getAbstractPath<RIGHT_SIDE>())); //------------------------------------------------------------------------------------------ diff --git a/FreeFileSync/Source/base/synchronization.h b/FreeFileSync/Source/base/synchronization.h index 6fce469b..cb66a4ad 100644 --- a/FreeFileSync/Source/base/synchronization.h +++ b/FreeFileSync/Source/base/synchronization.h @@ -76,7 +76,7 @@ private: struct FolderPairSyncCfg { - SyncVariant syncVariant; + SyncVariant syncVar; bool saveSyncDB; //save database if in automatic mode or dection of moved files is active DeletionPolicy handleDeletion; Zstring versioningFolderPhrase; //unresolved directory names as entered by user! diff --git a/FreeFileSync/Source/base_tools.cpp b/FreeFileSync/Source/base_tools.cpp index cdd7056a..8860ecef 100644 --- a/FreeFileSync/Source/base_tools.cpp +++ b/FreeFileSync/Source/base_tools.cpp @@ -261,11 +261,11 @@ MainConfiguration fff::merge(const std::vector<MainConfiguration>& mainCfgs) cfgOut.ignoreErrors = std::all_of(mainCfgs.begin(), mainCfgs.end(), [](const MainConfiguration& mainCfg) { return mainCfg.ignoreErrors; }); - cfgOut.automaticRetryCount = std::max_element(mainCfgs.begin(), mainCfgs.end(), - [](const MainConfiguration& lhs, const MainConfiguration& rhs) { return lhs.automaticRetryCount < rhs.automaticRetryCount; })->automaticRetryCount; + cfgOut.autoRetryCount = std::max_element(mainCfgs.begin(), mainCfgs.end(), + [](const MainConfiguration& lhs, const MainConfiguration& rhs) { return lhs.autoRetryCount < rhs.autoRetryCount; })->autoRetryCount; - cfgOut.automaticRetryDelay = std::max_element(mainCfgs.begin(), mainCfgs.end(), - [](const MainConfiguration& lhs, const MainConfiguration& rhs) { return lhs.automaticRetryDelay < rhs.automaticRetryDelay; })->automaticRetryDelay; + cfgOut.autoRetryDelay = std::max_element(mainCfgs.begin(), mainCfgs.end(), + [](const MainConfiguration& lhs, const MainConfiguration& rhs) { return lhs.autoRetryDelay < rhs.autoRetryDelay; })->autoRetryDelay; for (const MainConfiguration& mainCfg : mainCfgs) if (!mainCfg.altLogFolderPathPhrase.empty()) diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp index 179979f9..5dba953c 100644 --- a/FreeFileSync/Source/config.cpp +++ b/FreeFileSync/Source/config.cpp @@ -21,7 +21,7 @@ using namespace fff; //functionally needed for correct overload resolution!!! namespace { //------------------------------------------------------------------------------------------------------------------------------- -const int XML_FORMAT_GLOBAL_CFG = 19; //2020-10-13 +const int XML_FORMAT_GLOBAL_CFG = 20; //2020-12-03 const int XML_FORMAT_SYNC_CFG = 17; //2020-10-14 //------------------------------------------------------------------------------------------------------------------------------- } @@ -1411,8 +1411,8 @@ void readConfig(const XmlIn& in, MainConfiguration& mainCfg, int formatVer) else { inMain["Errors"].attribute("Ignore", mainCfg.ignoreErrors); - inMain["Errors"].attribute("Retry", mainCfg.automaticRetryCount); - inMain["Errors"].attribute("Delay", mainCfg.automaticRetryDelay); + inMain["Errors"].attribute("Retry", mainCfg.autoRetryCount); + inMain["Errors"].attribute("Delay", mainCfg.autoRetryDelay); } //TODO: remove if parameter migration after some time! 2017-10-24 @@ -1577,26 +1577,31 @@ void readConfig(const XmlIn& in, XmlBatchConfig& cfg, int formatVer) void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) { - XmlIn inGeneral = in["General"]; + XmlIn in2 = in; - //TODO: remove old parameter after migration! 2016-01-18 - if (in["Shared"]) - inGeneral = in["Shared"]; + if (in["Shared"]) //TODO: remove old parameter after migration! 2016-01-18 + in2 = in["Shared"]; + else if (in["General"]) //TODO: remove old parameter after migration! 2020-12-03 + in2 = in["General"]; - inGeneral["Language"].attribute("Name", cfg.programLanguage); + in2["Language"].attribute("Name", cfg.programLanguage); - inGeneral["FailSafeFileCopy" ].attribute("Enabled", cfg.failSafeFileCopy); - inGeneral["CopyLockedFiles" ].attribute("Enabled", cfg.copyLockedFiles); - inGeneral["CopyFilePermissions" ].attribute("Enabled", cfg.copyFilePermissions); - inGeneral["FileTimeTolerance" ].attribute("Seconds", cfg.fileTimeTolerance); - inGeneral["RunWithBackgroundPriority"].attribute("Enabled", cfg.runWithBackgroundPriority); - inGeneral["LockDirectoriesDuringSync"].attribute("Enabled", cfg.createLockFile); - inGeneral["VerifyCopiedFiles" ].attribute("Enabled", cfg.verifyFileCopy); - inGeneral["LogFiles" ].attribute("MaxAge", cfg.logfilesMaxAgeDays); - inGeneral["LogFiles" ].attribute("Format", cfg.logFormat); - inGeneral["NotificationSound" ].attribute("CompareFinished", cfg.soundFileCompareFinished); - inGeneral["NotificationSound" ].attribute("SyncFinished", cfg.soundFileSyncFinished); - inGeneral["ProgressDialog" ].attribute("AutoClose", cfg.autoCloseProgressDialog); + in2["FailSafeFileCopy" ].attribute("Enabled", cfg.failSafeFileCopy); + in2["CopyLockedFiles" ].attribute("Enabled", cfg.copyLockedFiles); + in2["CopyFilePermissions" ].attribute("Enabled", cfg.copyFilePermissions); + in2["FileTimeTolerance" ].attribute("Seconds", cfg.fileTimeTolerance); + in2["RunWithBackgroundPriority"].attribute("Enabled", cfg.runWithBackgroundPriority); + in2["LockDirectoriesDuringSync"].attribute("Enabled", cfg.createLockFile); + in2["VerifyCopiedFiles" ].attribute("Enabled", cfg.verifyFileCopy); + in2["LogFiles" ].attribute("MaxAge", cfg.logfilesMaxAgeDays); + in2["LogFiles" ].attribute("Format", cfg.logFormat); + in2["NotificationSound" ].attribute("CompareFinished", cfg.soundFileCompareFinished); + in2["NotificationSound" ].attribute("SyncFinished", cfg.soundFileSyncFinished); + + in2["ProgressDialog"].attribute("Width", cfg.progressDlg.dlgSize.x); + in2["ProgressDialog"].attribute("Height", cfg.progressDlg.dlgSize.y); + in2["ProgressDialog"].attribute("Maximized", cfg.progressDlg.isMaximized); + in2["ProgressDialog"].attribute("AutoClose", cfg.progressDlg.autoClose); //TODO: remove if parameter migration after some time! 2019-05-29 if (formatVer < 13) @@ -1618,7 +1623,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) //TODO: remove old parameter after migration! 2018-02-04 if (formatVer < 8) { - XmlIn inOpt = inGeneral["OptionalDialogs"]; + XmlIn inOpt = in2["OptionalDialogs"]; inOpt["ConfirmStartSync" ].attribute("Enabled", cfg.confirmDlgs.confirmSyncStart); inOpt["ConfirmSaveConfig" ].attribute("Enabled", cfg.confirmDlgs.confirmSaveConfig); inOpt["ConfirmExternalCommandMassInvoke"].attribute("Enabled", cfg.confirmDlgs.confirmCommandMassInvoke); @@ -1635,7 +1640,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) } else { - XmlIn inOpt = inGeneral["OptionalDialogs"]; + XmlIn inOpt = in2["OptionalDialogs"]; inOpt["ConfirmStartSync" ].attribute("Show", cfg.confirmDlgs.confirmSyncStart); inOpt["ConfirmSaveConfig" ].attribute("Show", cfg.confirmDlgs.confirmSaveConfig); if (formatVer < 12) //TODO: remove old parameter after migration! 2019-02-09 @@ -1657,86 +1662,88 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) inOpt["WarnVersioningFolderPartOfSync"].attribute("Show", cfg.warnDlgs.warnVersioningFolderPartOfSync); } - //GUI-specific global settings (optional) - XmlIn inGui = in["Gui"]; - XmlIn inWnd = inGui["MainDialog"]; + XmlIn inMainWin = in["MainDialog"]; + + //TODO: remove old parameter after migration! 2020-12-03 + if (in["Gui"]) + inMainWin = in["Gui"]["MainDialog"]; //read application window size and position - inWnd.attribute("Width", cfg.gui.mainDlg.dlgSize.x); - inWnd.attribute("Height", cfg.gui.mainDlg.dlgSize.y); - inWnd.attribute("PosX", cfg.gui.mainDlg.dlgPos.x); - inWnd.attribute("PosY", cfg.gui.mainDlg.dlgPos.y); - inWnd.attribute("Maximized", cfg.gui.mainDlg.isMaximized); + inMainWin.attribute("Width", cfg.mainDlg.dlgSize.x); + inMainWin.attribute("Height", cfg.mainDlg.dlgSize.y); + inMainWin.attribute("PosX", cfg.mainDlg.dlgPos.x); + inMainWin.attribute("PosY", cfg.mainDlg.dlgPos.y); + inMainWin.attribute("Maximized", cfg.mainDlg.isMaximized); //########################################################### //TODO: remove old parameter after migration! 2018-02-04 if (formatVer < 8) - inWnd["CaseSensitiveSearch"].attribute("Enabled", cfg.gui.mainDlg.textSearchRespectCase); + inMainWin["CaseSensitiveSearch"].attribute("Enabled", cfg.mainDlg.textSearchRespectCase); else //TODO: remove if parameter migration after some time! 2018-09-09 if (formatVer < 11) - inWnd["Search"].attribute("CaseSensitive", cfg.gui.mainDlg.textSearchRespectCase); + inMainWin["Search"].attribute("CaseSensitive", cfg.mainDlg.textSearchRespectCase); else - inWnd["SearchPanel"].attribute("CaseSensitive", cfg.gui.mainDlg.textSearchRespectCase); + inMainWin["SearchPanel"].attribute("CaseSensitive", cfg.mainDlg.textSearchRespectCase); //TODO: remove if parameter migration after some time! 2018-09-09 if (formatVer < 11) - inWnd["FolderPairsVisible" ].attribute("Max", cfg.gui.mainDlg.folderPairsVisibleMax); + inMainWin["FolderPairsVisible" ].attribute("Max", cfg.mainDlg.folderPairsVisibleMax); //########################################################### - XmlIn inConfig = inWnd["ConfigPanel"]; - inConfig.attribute("ScrollPos", cfg.gui.mainDlg.cfgGridTopRowPos); - inConfig.attribute("SyncOverdue", cfg.gui.mainDlg.cfgGridSyncOverdueDays); - inConfig.attribute("SortByColumn", cfg.gui.mainDlg.cfgGridLastSortColumn); - inConfig.attribute("SortAscending", cfg.gui.mainDlg.cfgGridLastSortAscending); + XmlIn inConfig = inMainWin["ConfigPanel"]; + inConfig.attribute("ScrollPos", cfg.mainDlg.cfgGridTopRowPos); + inConfig.attribute("SyncOverdue", cfg.mainDlg.cfgGridSyncOverdueDays); + inConfig.attribute("SortByColumn", cfg.mainDlg.cfgGridLastSortColumn); + inConfig.attribute("SortAscending", cfg.mainDlg.cfgGridLastSortAscending); - inConfig["Columns"](cfg.gui.mainDlg.cfgGridColumnAttribs); + inConfig["Columns"](cfg.mainDlg.cfgGridColumnAttribs); //TODO: remove after migration! 2018-07-27 if (formatVer < 10) //reset once to show the new log column - cfg.gui.mainDlg.cfgGridColumnAttribs = XmlGlobalSettings().gui.mainDlg.cfgGridColumnAttribs; + cfg.mainDlg.cfgGridColumnAttribs = XmlGlobalSettings().mainDlg.cfgGridColumnAttribs; //TODO: remove parameter migration after some time! 2018-01-08 if (formatVer < 6) { - inGui["ConfigHistory"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); + in["Gui"]["ConfigHistory"].attribute("MaxSize", cfg.mainDlg.cfgHistItemsMax); //TODO: remove parameter migration after some time! 2016-09-23 if (formatVer < 4) - cfg.gui.mainDlg.cfgHistItemsMax = std::max<size_t>(cfg.gui.mainDlg.cfgHistItemsMax, 100); + cfg.mainDlg.cfgHistItemsMax = std::max<size_t>(cfg.mainDlg.cfgHistItemsMax, 100); std::vector<Zstring> cfgHist; - inGui["ConfigHistory"](cfgHist); + in["Gui"]["ConfigHistory"](cfgHist); for (const Zstring& cfgPath : cfgHist) - cfg.gui.mainDlg.cfgFileHistory.emplace_back( + cfg.mainDlg.cfgFileHistory.emplace_back( cfgPath, 0, getNullPath(), SyncResult::finishedSuccess, wxNullColour); } //TODO: remove after migration! 2018-07-27 else if (formatVer < 10) { - inConfig["Configurations"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); + inConfig["Configurations"].attribute("MaxSize", cfg.mainDlg.cfgHistItemsMax); std::vector<ConfigFileItemV9> cfgFileHistory; inConfig["Configurations"](cfgFileHistory); for (const ConfigFileItemV9& item : cfgFileHistory) - cfg.gui.mainDlg.cfgFileHistory.emplace_back(item.filePath, item.lastSyncTime, getNullPath(), SyncResult::finishedSuccess, wxNullColour); + cfg.mainDlg.cfgFileHistory.emplace_back(item.filePath, item.lastSyncTime, getNullPath(), SyncResult::finishedSuccess, wxNullColour); } else { - inConfig["Configurations"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); - inConfig["Configurations"].attribute("LastSelected", cfg.gui.mainDlg.cfgFileLastSelected); - inConfig["Configurations"](cfg.gui.mainDlg.cfgFileHistory); + inConfig["Configurations"].attribute("MaxSize", cfg.mainDlg.cfgHistItemsMax); + inConfig["Configurations"].attribute("LastSelected", cfg.mainDlg.cfgFileLastSelected); + inConfig["Configurations"](cfg.mainDlg.cfgFileHistory); } //TODO: remove after migration! 2019-11-30 if (formatVer < 15) { const Zstring lastRunConfigPath = getConfigDirPathPf() + Zstr("LastRun.ffs_gui"); - for (ConfigFileItem& item : cfg.gui.mainDlg.cfgFileHistory) + for (ConfigFileItem& item : cfg.mainDlg.cfgFileHistory) if (equalNativePath(item.cfgFilePath, lastRunConfigPath)) item.backColor = wxColor(0xdd, 0xdd, 0xdd); //light grey from onCfgGridContext() } @@ -1744,7 +1751,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) //TODO: remove parameter migration after some time! 2018-01-08 if (formatVer < 6) { - inGui["LastUsedConfig"](cfg.gui.mainDlg.cfgFilesLastUsed); + in["Gui"]["LastUsedConfig"](cfg.mainDlg.cfgFilesLastUsed); } else { @@ -1754,101 +1761,101 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) for (Zstring& filePath : cfgPaths) filePath = resolveFreeFileSyncDriveMacro(filePath); - cfg.gui.mainDlg.cfgFilesLastUsed = cfgPaths; + cfg.mainDlg.cfgFilesLastUsed = cfgPaths; } } //########################################################### - XmlIn inOverview = inWnd["OverviewPanel"]; - inOverview.attribute("ShowPercentage", cfg.gui.mainDlg.treeGridShowPercentBar); - inOverview.attribute("SortByColumn", cfg.gui.mainDlg.treeGridLastSortColumn); - inOverview.attribute("SortAscending", cfg.gui.mainDlg.treeGridLastSortAscending); + XmlIn inOverview = inMainWin["OverviewPanel"]; + inOverview.attribute("ShowPercentage", cfg.mainDlg.treeGridShowPercentBar); + inOverview.attribute("SortByColumn", cfg.mainDlg.treeGridLastSortColumn); + inOverview.attribute("SortAscending", cfg.mainDlg.treeGridLastSortAscending); //read column attributes XmlIn inColTree = inOverview["Columns"]; - inColTree(cfg.gui.mainDlg.treeGridColumnAttribs); + inColTree(cfg.mainDlg.treeGridColumnAttribs); - XmlIn inFileGrid = inWnd["FilePanel"]; + XmlIn inFileGrid = inMainWin["FilePanel"]; //TODO: remove parameter migration after some time! 2018-01-08 if (formatVer < 6) - inFileGrid = inWnd["CenterPanel"]; + inFileGrid = inMainWin["CenterPanel"]; //TODO: remove after migration! 2020-10-13 if (formatVer < 19) ; //new icon layout => let user re-evaluate settings else { - inFileGrid.attribute("ShowIcons", cfg.gui.mainDlg.showIcons); - inFileGrid.attribute("IconSize", cfg.gui.mainDlg.iconSize); + inFileGrid.attribute("ShowIcons", cfg.mainDlg.showIcons); + inFileGrid.attribute("IconSize", cfg.mainDlg.iconSize); } - inFileGrid.attribute("SashOffset", cfg.gui.mainDlg.sashOffset); + inFileGrid.attribute("SashOffset", cfg.mainDlg.sashOffset); //TODO: remove if parameter migration after some time! 2018-09-09 if (formatVer < 11) ; //TODO: remove if parameter migration after some time! 2020-01-30 else if (formatVer < 16) - inFileGrid.attribute("MaxFolderPairsShown", cfg.gui.mainDlg.folderPairsVisibleMax); + inFileGrid.attribute("MaxFolderPairsShown", cfg.mainDlg.folderPairsVisibleMax); else - inFileGrid.attribute("FolderPairsMax", cfg.gui.mainDlg.folderPairsVisibleMax); + inFileGrid.attribute("FolderPairsMax", cfg.mainDlg.folderPairsVisibleMax); - inFileGrid["ColumnsLeft"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatLeftGrid); - inFileGrid["ColumnsLeft"](cfg.gui.mainDlg.columnAttribLeft); + inFileGrid["ColumnsLeft"].attribute("PathFormat", cfg.mainDlg.itemPathFormatLeftGrid); + inFileGrid["ColumnsLeft"](cfg.mainDlg.columnAttribLeft); - inFileGrid["ColumnsRight"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatRightGrid); - inFileGrid["ColumnsRight"](cfg.gui.mainDlg.columnAttribRight); + inFileGrid["ColumnsRight"].attribute("PathFormat", cfg.mainDlg.itemPathFormatRightGrid); + inFileGrid["ColumnsRight"](cfg.mainDlg.columnAttribRight); - inFileGrid["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); - inFileGrid["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); + inFileGrid["FolderHistoryLeft" ](cfg.mainDlg.folderHistoryLeft); + inFileGrid["FolderHistoryRight"](cfg.mainDlg.folderHistoryRight); - inFileGrid["FolderHistoryLeft" ].attribute("LastSelected", cfg.gui.mainDlg.folderLastSelectedLeft); - inFileGrid["FolderHistoryRight"].attribute("LastSelected", cfg.gui.mainDlg.folderLastSelectedRight); + inFileGrid["FolderHistoryLeft" ].attribute("LastSelected", cfg.mainDlg.folderLastSelectedLeft); + inFileGrid["FolderHistoryRight"].attribute("LastSelected", cfg.mainDlg.folderLastSelectedRight); //TODO: remove parameter migration after some time! 2018-01-08 if (formatVer < 6) { - inGui["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); - inGui["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); + in["Gui"]["FolderHistoryLeft" ](cfg.mainDlg.folderHistoryLeft); + in["Gui"]["FolderHistoryRight"](cfg.mainDlg.folderHistoryRight); } //########################################################### - XmlIn inCopyTo = inWnd["ManualCopyTo"]; - inCopyTo.attribute("KeepRelativePaths", cfg.gui.mainDlg.copyToCfg.keepRelPaths); - inCopyTo.attribute("OverwriteIfExists", cfg.gui.mainDlg.copyToCfg.overwriteIfExists); + XmlIn inCopyTo = inMainWin["ManualCopyTo"]; + inCopyTo.attribute("KeepRelativePaths", cfg.mainDlg.copyToCfg.keepRelPaths); + inCopyTo.attribute("OverwriteIfExists", cfg.mainDlg.copyToCfg.overwriteIfExists); XmlIn inCopyToHistory = inCopyTo["FolderHistory"]; - inCopyToHistory(cfg.gui.mainDlg.copyToCfg.folderHistory); - inCopyToHistory.attribute("TargetFolder", cfg.gui.mainDlg.copyToCfg.targetFolderPath); - inCopyToHistory.attribute("LastSelected", cfg.gui.mainDlg.copyToCfg.targetFolderLastSelected); + inCopyToHistory(cfg.mainDlg.copyToCfg.folderHistory); + inCopyToHistory.attribute("TargetFolder", cfg.mainDlg.copyToCfg.targetFolderPath); + inCopyToHistory.attribute("LastSelected", cfg.mainDlg.copyToCfg.targetFolderLastSelected); //########################################################### //TODO: remove after migration! 2020-10-13 if (formatVer < 19) { - if (const XmlElement* elem = inWnd["DefaultViewFilter"].get()) - readViewFilterDefaultV19(*elem, cfg.gui.mainDlg.viewFilterDefault); + if (const XmlElement* elem = inMainWin["DefaultViewFilter"].get()) + readViewFilterDefaultV19(*elem, cfg.mainDlg.viewFilterDefault); } else { - inWnd["DefaultViewFilter"](cfg.gui.mainDlg.viewFilterDefault); + inMainWin["DefaultViewFilter"](cfg.mainDlg.viewFilterDefault); } //TODO: remove old parameter after migration! 2018-02-04 if (formatVer < 8) { - XmlIn sharedView = inWnd["DefaultViewFilter"]["Shared"]; - sharedView.attribute("Equal", cfg.gui.mainDlg.viewFilterDefault.equal); - sharedView.attribute("Conflict", cfg.gui.mainDlg.viewFilterDefault.conflict); - sharedView.attribute("Excluded", cfg.gui.mainDlg.viewFilterDefault.excluded); + XmlIn sharedView = inMainWin["DefaultViewFilter"]["Shared"]; + sharedView.attribute("Equal", cfg.mainDlg.viewFilterDefault.equal); + sharedView.attribute("Conflict", cfg.mainDlg.viewFilterDefault.conflict); + sharedView.attribute("Excluded", cfg.mainDlg.viewFilterDefault.excluded); } //TODO: remove old parameter after migration! 2018-01-16 if (formatVer < 7) - inWnd["Perspective5"](cfg.gui.mainDlg.guiPerspectiveLast); + inMainWin["Perspective5"](cfg.mainDlg.guiPerspectiveLast); else - inWnd["Perspective"](cfg.gui.mainDlg.guiPerspectiveLast); + inMainWin["Perspective"](cfg.mainDlg.guiPerspectiveLast); //TODO: remove after migration! 2019-11-30 auto splitEditMerge = [](wxString& perspective, wchar_t delim, const std::function<void(wxString& item)>& editItem) @@ -1869,7 +1876,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) //TODO: remove after migration! 2018-07-27 if (formatVer < 10) - splitEditMerge(cfg.gui.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) + splitEditMerge(cfg.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) { if (contains(paneCfg, L"name=TopPanel")) replace(paneCfg, L";row=2;", L";row=3;"); @@ -1882,7 +1889,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) std::optional<int> tpDir; std::optional<int> tpLayer; std::optional<int> tpRow; - splitEditMerge(cfg.gui.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) + splitEditMerge(cfg.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) { if (contains(paneCfg, L"name=TopPanel")) splitEditMerge(paneCfg, L';', [&](wxString& paneAttr) @@ -1903,7 +1910,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) numberTo<wxString>(*tpLayer) + L"," + numberTo<wxString>(*tpRow ) + L")="; - splitEditMerge(cfg.gui.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) + splitEditMerge(cfg.mainDlg.guiPerspectiveLast, L'|', [&](wxString& paneCfg) { if (startsWith(paneCfg, tpSize)) paneCfg = tpSize + L"0"; @@ -1914,52 +1921,96 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) //TODO: remove if parameter migration after some time! 2020-01-30 if (formatVer < 16) ; + else if (formatVer < 20) //TODO: remove old parameter after migration! 2020-12-03 + in["Gui"]["FolderHistory" ].attribute("MaxSize", cfg.folderHistoryMax); else - inGui["FolderHistory" ].attribute("MaxSize", cfg.gui.folderHistoryMax); + in["FolderHistory" ].attribute("MaxSize", cfg.folderHistoryMax); - inGui["CsvExport" ].attribute("LastSelected", cfg.gui.csvFileLastSelected); - inGui["SftpKeyFile"].attribute("LastSelected", cfg.gui.sftpKeyFileLastSelected); + if (formatVer < 20) //TODO: remove old parameter after migration! 2020-12-03 + { + in["Gui"]["CsvExport" ].attribute("LastSelected", cfg.csvFileLastSelected); + in["Gui"]["SftpKeyFile"].attribute("LastSelected", cfg.sftpKeyFileLastSelected); + } + else + { + in["CsvExport" ].attribute("LastSelected", cfg.csvFileLastSelected); + in["SftpKeyFile"].attribute("LastSelected", cfg.sftpKeyFileLastSelected); + } - std::vector<Zstring> tmp = splitFilterByLines(cfg.gui.defaultExclusionFilter); //default value - inGui["DefaultExclusionFilter"](tmp); - cfg.gui.defaultExclusionFilter = mergeFilterLines(tmp); + std::vector<Zstring> tmp = splitFilterByLines(cfg.defaultExclusionFilter); //default value + if (formatVer < 20) //TODO: remove old parameter after migration! 2020-12-03 + in["Gui"]["DefaultExclusionFilter"](tmp); + else + in["DefaultExclusionFilter"](tmp); + cfg.defaultExclusionFilter = mergeFilterLines(tmp); - inGui["VersioningFolderHistory"](cfg.gui.versioningFolderHistory); - inGui["VersioningFolderHistory"].attribute("LastSelected", cfg.gui.versioningFolderLastSelected); + if (formatVer < 20) //TODO: remove old parameter after migration! 2020-12-03 + { + in["Gui"]["VersioningFolderHistory"](cfg.versioningFolderHistory); + in["Gui"]["VersioningFolderHistory"].attribute("LastSelected", cfg.versioningFolderLastSelected); + } + else + { + in["VersioningFolderHistory"](cfg.versioningFolderHistory); + in["VersioningFolderHistory"].attribute("LastSelected", cfg.versioningFolderLastSelected); + } - inGui["LogFolderHistory"](cfg.gui.logFolderHistory); - inGui["LogFolderHistory"].attribute("LastSelected", cfg.gui.logFolderLastSelected); + if (formatVer < 20) //TODO: remove old parameter after migration! 2020-12-03 + { + in["Gui"]["LogFolderHistory"](cfg.logFolderHistory); + in["Gui"]["LogFolderHistory"].attribute("LastSelected", cfg.logFolderLastSelected); + } + else + { + in["LogFolderHistory"](cfg.logFolderHistory); + in["LogFolderHistory"].attribute("LastSelected", cfg.logFolderLastSelected); + } - inGui["EmailHistory"](cfg.gui.emailHistory); - inGui["EmailHistory"].attribute("MaxSize", cfg.gui.emailHistoryMax); + if (formatVer < 20) //TODO: remove old parameter after migration! 2020-12-03 + { + in["Gui"]["EmailHistory"](cfg.emailHistory); + in["Gui"]["EmailHistory"].attribute("MaxSize", cfg.emailHistoryMax); + } + else + { + in["EmailHistory"](cfg.emailHistory); + in["EmailHistory"].attribute("MaxSize", cfg.emailHistoryMax); + } //TODO: remove if clause after migration! 2017-10-24 if (formatVer < 5) { - inGui["OnCompletionHistory"](cfg.gui.commandHistory); - inGui["OnCompletionHistory"].attribute("MaxSize", cfg.gui.commandHistoryMax); + in["Gui"]["OnCompletionHistory"](cfg.commandHistory); + in["Gui"]["OnCompletionHistory"].attribute("MaxSize", cfg.commandHistoryMax); + } + else if (formatVer < 20) //TODO: remove old parameter after migration! 2020-12-03 + { + in["Gui"]["CommandHistory"](cfg.commandHistory); + in["Gui"]["CommandHistory"].attribute("MaxSize", cfg.commandHistoryMax); } else { - inGui["CommandHistory"](cfg.gui.commandHistory); - inGui["CommandHistory"].attribute("MaxSize", cfg.gui.commandHistoryMax); + in["CommandHistory"](cfg.commandHistory); + in["CommandHistory"].attribute("MaxSize", cfg.commandHistoryMax); } //TODO: remove if parameter migration after some time! 2020-01-30 if (formatVer < 15) - if (cfg.gui.commandHistoryMax <= 8) - cfg.gui.commandHistoryMax = XmlGlobalSettings().gui.commandHistoryMax; + if (cfg.commandHistoryMax <= 8) + cfg.commandHistoryMax = XmlGlobalSettings().commandHistoryMax; //TODO: remove old parameter after migration! 2018-01-16 if (formatVer < 7) ; //reset this old crap + else if (formatVer < 20) //TODO: remove old parameter after migration! 2020-12-03 + in["Gui"]["ExternalApps"](cfg.externalApps); else - inGui["ExternalApps"](cfg.gui.externalApps); + in["ExternalApps"](cfg.externalApps); //TODO: remove after migration! 2019-11-30 if (formatVer < 15) - for (ExternalApp& item : cfg.gui.externalApps) + for (ExternalApp& item : cfg.externalApps) { replace(item.cmdLine, Zstr("%folder_path%"), Zstr("%parent_path%")); replace(item.cmdLine, Zstr("%folder_path2%"), Zstr("%parent_path2%")); @@ -1967,16 +2018,23 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) //TODO: remove after migration! 2020-06-13 if (formatVer < 18) - for (ExternalApp& item : cfg.gui.externalApps) + for (ExternalApp& item : cfg.externalApps) { trim(item.cmdLine); if (item.cmdLine == "xdg-open \"%parent_path%\"") item.cmdLine = "xdg-open \"$(dirname \"%local_path%\")\""; } - //last update check - inGui["LastOnlineCheck" ](cfg.gui.lastUpdateCheck); - inGui["LastOnlineVersion"](cfg.gui.lastOnlineVersion); + if (formatVer < 20) //TODO: remove old parameter after migration! 2020-12-03 + { + in["Gui"]["LastOnlineCheck" ](cfg.lastUpdateCheck); + in["Gui"]["LastOnlineVersion"](cfg.lastOnlineVersion); + } + else + { + in["LastOnlineCheck" ](cfg.lastUpdateCheck); + in["LastOnlineVersion"](cfg.lastOnlineVersion); + } //batch specific global settings //XmlIn inBatch = in["Batch"]; @@ -1987,12 +2045,12 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) if (fastFromDIP(96) > 96) //high-DPI monitor => one-time migration { const XmlGlobalSettings defaultCfg; - cfg.gui.mainDlg.dlgSize = defaultCfg.gui.mainDlg.dlgSize; - cfg.gui.mainDlg.guiPerspectiveLast = defaultCfg.gui.mainDlg.guiPerspectiveLast; - cfg.gui.mainDlg.cfgGridColumnAttribs = defaultCfg.gui.mainDlg.cfgGridColumnAttribs; - cfg.gui.mainDlg.treeGridColumnAttribs = defaultCfg.gui.mainDlg.treeGridColumnAttribs; - cfg.gui.mainDlg.columnAttribLeft = defaultCfg.gui.mainDlg.columnAttribLeft; - cfg.gui.mainDlg.columnAttribRight = defaultCfg.gui.mainDlg.columnAttribRight; + cfg.mainDlg.dlgSize = defaultCfg.mainDlg.dlgSize; + cfg.mainDlg.guiPerspectiveLast = defaultCfg.mainDlg.guiPerspectiveLast; + cfg.mainDlg.cfgGridColumnAttribs = defaultCfg.mainDlg.cfgGridColumnAttribs; + cfg.mainDlg.treeGridColumnAttribs = defaultCfg.mainDlg.treeGridColumnAttribs; + cfg.mainDlg.columnAttribLeft = defaultCfg.mainDlg.columnAttribLeft; + cfg.mainDlg.columnAttribRight = defaultCfg.mainDlg.columnAttribRight; } } @@ -2253,8 +2311,8 @@ void writeConfig(const MainConfiguration& mainCfg, XmlOut& out) writeConfig(lpc, mainCfg.deviceParallelOps, outFp); outMain["Errors"].attribute("Ignore", mainCfg.ignoreErrors); - outMain["Errors"].attribute("Retry", mainCfg.automaticRetryCount); - outMain["Errors"].attribute("Delay", mainCfg.automaticRetryDelay); + outMain["Errors"].attribute("Retry", mainCfg.autoRetryCount); + outMain["Errors"].attribute("Delay", mainCfg.autoRetryDelay); outMain["PostSyncCommand"](mainCfg.postSyncCommand); outMain["PostSyncCommand"].attribute("Condition", mainCfg.postSyncCondition); @@ -2297,24 +2355,26 @@ void writeConfig(const XmlBatchConfig& cfg, XmlOut& out) void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out) { - XmlOut outGeneral = out["General"]; - - outGeneral["Language"].attribute("Name", cfg.programLanguage); - - outGeneral["FailSafeFileCopy" ].attribute("Enabled", cfg.failSafeFileCopy); - outGeneral["CopyLockedFiles" ].attribute("Enabled", cfg.copyLockedFiles); - outGeneral["CopyFilePermissions" ].attribute("Enabled", cfg.copyFilePermissions); - outGeneral["FileTimeTolerance" ].attribute("Seconds", cfg.fileTimeTolerance); - outGeneral["RunWithBackgroundPriority"].attribute("Enabled", cfg.runWithBackgroundPriority); - outGeneral["LockDirectoriesDuringSync"].attribute("Enabled", cfg.createLockFile); - outGeneral["VerifyCopiedFiles" ].attribute("Enabled", cfg.verifyFileCopy); - outGeneral["LogFiles" ].attribute("MaxAge", cfg.logfilesMaxAgeDays); - outGeneral["LogFiles" ].attribute("Format", cfg.logFormat); - outGeneral["NotificationSound" ].attribute("CompareFinished", substituteFfsResourcePath(cfg.soundFileCompareFinished)); - outGeneral["NotificationSound" ].attribute("SyncFinished", substituteFfsResourcePath(cfg.soundFileSyncFinished)); - outGeneral["ProgressDialog" ].attribute("AutoClose", cfg.autoCloseProgressDialog); - - XmlOut outOpt = outGeneral["OptionalDialogs"]; + out["Language"].attribute("Name", cfg.programLanguage); + + out["FailSafeFileCopy" ].attribute("Enabled", cfg.failSafeFileCopy); + out["CopyLockedFiles" ].attribute("Enabled", cfg.copyLockedFiles); + out["CopyFilePermissions" ].attribute("Enabled", cfg.copyFilePermissions); + out["FileTimeTolerance" ].attribute("Seconds", cfg.fileTimeTolerance); + out["RunWithBackgroundPriority"].attribute("Enabled", cfg.runWithBackgroundPriority); + out["LockDirectoriesDuringSync"].attribute("Enabled", cfg.createLockFile); + out["VerifyCopiedFiles" ].attribute("Enabled", cfg.verifyFileCopy); + out["LogFiles" ].attribute("MaxAge", cfg.logfilesMaxAgeDays); + out["LogFiles" ].attribute("Format", cfg.logFormat); + out["NotificationSound" ].attribute("CompareFinished", substituteFfsResourcePath(cfg.soundFileCompareFinished)); + out["NotificationSound" ].attribute("SyncFinished", substituteFfsResourcePath(cfg.soundFileSyncFinished)); + + out["ProgressDialog"].attribute("Width", cfg.progressDlg.dlgSize.x); + out["ProgressDialog"].attribute("Height", cfg.progressDlg.dlgSize.y); + out["ProgressDialog"].attribute("Maximized", cfg.progressDlg.isMaximized); + out["ProgressDialog"].attribute("AutoClose", cfg.progressDlg.autoClose); + + XmlOut outOpt = out["OptionalDialogs"]; outOpt["ConfirmStartSync" ].attribute("Show", cfg.confirmDlgs.confirmSyncStart); outOpt["ConfirmSaveConfig" ].attribute("Show", cfg.confirmDlgs.confirmSaveConfig); outOpt["ConfirmCommandMassInvoke" ].attribute("Show", cfg.confirmDlgs.confirmCommandMassInvoke); @@ -2333,32 +2393,31 @@ void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out) outOpt["WarnVersioningFolderPartOfSync"].attribute("Show", cfg.warnDlgs.warnVersioningFolderPartOfSync); //gui specific global settings (optional) - XmlOut outGui = out["Gui"]; - XmlOut outWnd = outGui["MainDialog"]; + XmlOut outMainWin = out["MainDialog"]; //write application window size and position - outWnd.attribute("Width", cfg.gui.mainDlg.dlgSize.x); - outWnd.attribute("Height", cfg.gui.mainDlg.dlgSize.y); - outWnd.attribute("PosX", cfg.gui.mainDlg.dlgPos.x); - outWnd.attribute("PosY", cfg.gui.mainDlg.dlgPos.y); - outWnd.attribute("Maximized", cfg.gui.mainDlg.isMaximized); + outMainWin.attribute("Width", cfg.mainDlg.dlgSize.x); + outMainWin.attribute("Height", cfg.mainDlg.dlgSize.y); + outMainWin.attribute("PosX", cfg.mainDlg.dlgPos.x); + outMainWin.attribute("PosY", cfg.mainDlg.dlgPos.y); + outMainWin.attribute("Maximized", cfg.mainDlg.isMaximized); //########################################################### - outWnd["SearchPanel"].attribute("CaseSensitive", cfg.gui.mainDlg.textSearchRespectCase); + outMainWin["SearchPanel"].attribute("CaseSensitive", cfg.mainDlg.textSearchRespectCase); //########################################################### - XmlOut outConfig = outWnd["ConfigPanel"]; - outConfig.attribute("ScrollPos", cfg.gui.mainDlg.cfgGridTopRowPos); - outConfig.attribute("SyncOverdue", cfg.gui.mainDlg.cfgGridSyncOverdueDays); - outConfig.attribute("SortByColumn", cfg.gui.mainDlg.cfgGridLastSortColumn); - outConfig.attribute("SortAscending", cfg.gui.mainDlg.cfgGridLastSortAscending); + XmlOut outConfig = outMainWin["ConfigPanel"]; + outConfig.attribute("ScrollPos", cfg.mainDlg.cfgGridTopRowPos); + outConfig.attribute("SyncOverdue", cfg.mainDlg.cfgGridSyncOverdueDays); + outConfig.attribute("SortByColumn", cfg.mainDlg.cfgGridLastSortColumn); + outConfig.attribute("SortAscending", cfg.mainDlg.cfgGridLastSortAscending); - outConfig["Columns"](cfg.gui.mainDlg.cfgGridColumnAttribs); - outConfig["Configurations"].attribute("MaxSize", cfg.gui.mainDlg.cfgHistItemsMax); - outConfig["Configurations"].attribute("LastSelected", cfg.gui.mainDlg.cfgFileLastSelected); - outConfig["Configurations"](cfg.gui.mainDlg.cfgFileHistory); + outConfig["Columns"](cfg.mainDlg.cfgGridColumnAttribs); + outConfig["Configurations"].attribute("MaxSize", cfg.mainDlg.cfgHistItemsMax); + outConfig["Configurations"].attribute("LastSelected", cfg.mainDlg.cfgFileLastSelected); + outConfig["Configurations"](cfg.mainDlg.cfgFileHistory); { - std::vector<Zstring> cfgPaths = cfg.gui.mainDlg.cfgFilesLastUsed; + std::vector<Zstring> cfgPaths = cfg.mainDlg.cfgFilesLastUsed; for (Zstring& filePath : cfgPaths) filePath = substituteFreeFileSyncDriveLetter(filePath); @@ -2367,72 +2426,72 @@ void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out) //########################################################### - XmlOut outOverview = outWnd["OverviewPanel"]; - outOverview.attribute("ShowPercentage", cfg.gui.mainDlg.treeGridShowPercentBar); - outOverview.attribute("SortByColumn", cfg.gui.mainDlg.treeGridLastSortColumn); - outOverview.attribute("SortAscending", cfg.gui.mainDlg.treeGridLastSortAscending); + XmlOut outOverview = outMainWin["OverviewPanel"]; + outOverview.attribute("ShowPercentage", cfg.mainDlg.treeGridShowPercentBar); + outOverview.attribute("SortByColumn", cfg.mainDlg.treeGridLastSortColumn); + outOverview.attribute("SortAscending", cfg.mainDlg.treeGridLastSortAscending); //write column attributes XmlOut outColTree = outOverview["Columns"]; - outColTree(cfg.gui.mainDlg.treeGridColumnAttribs); + outColTree(cfg.mainDlg.treeGridColumnAttribs); - XmlOut outFileGrid = outWnd["FilePanel"]; - outFileGrid.attribute("ShowIcons", cfg.gui.mainDlg.showIcons); - outFileGrid.attribute("IconSize", cfg.gui.mainDlg.iconSize); - outFileGrid.attribute("SashOffset", cfg.gui.mainDlg.sashOffset); - outFileGrid.attribute("FolderPairsMax", cfg.gui.mainDlg.folderPairsVisibleMax); + XmlOut outFileGrid = outMainWin["FilePanel"]; + outFileGrid.attribute("ShowIcons", cfg.mainDlg.showIcons); + outFileGrid.attribute("IconSize", cfg.mainDlg.iconSize); + outFileGrid.attribute("SashOffset", cfg.mainDlg.sashOffset); + outFileGrid.attribute("FolderPairsMax", cfg.mainDlg.folderPairsVisibleMax); - outFileGrid["ColumnsLeft"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatLeftGrid); - outFileGrid["ColumnsLeft"](cfg.gui.mainDlg.columnAttribLeft); + outFileGrid["ColumnsLeft"].attribute("PathFormat", cfg.mainDlg.itemPathFormatLeftGrid); + outFileGrid["ColumnsLeft"](cfg.mainDlg.columnAttribLeft); - outFileGrid["ColumnsRight"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatRightGrid); - outFileGrid["ColumnsRight"](cfg.gui.mainDlg.columnAttribRight); + outFileGrid["ColumnsRight"].attribute("PathFormat", cfg.mainDlg.itemPathFormatRightGrid); + outFileGrid["ColumnsRight"](cfg.mainDlg.columnAttribRight); - outFileGrid["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); - outFileGrid["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); + outFileGrid["FolderHistoryLeft" ](cfg.mainDlg.folderHistoryLeft); + outFileGrid["FolderHistoryRight"](cfg.mainDlg.folderHistoryRight); - outFileGrid["FolderHistoryLeft" ].attribute("LastSelected", cfg.gui.mainDlg.folderLastSelectedLeft); - outFileGrid["FolderHistoryRight"].attribute("LastSelected", cfg.gui.mainDlg.folderLastSelectedRight); + outFileGrid["FolderHistoryLeft" ].attribute("LastSelected", cfg.mainDlg.folderLastSelectedLeft); + outFileGrid["FolderHistoryRight"].attribute("LastSelected", cfg.mainDlg.folderLastSelectedRight); //########################################################### - XmlOut outCopyTo = outWnd["ManualCopyTo"]; - outCopyTo.attribute("KeepRelativePaths", cfg.gui.mainDlg.copyToCfg.keepRelPaths); - outCopyTo.attribute("OverwriteIfExists", cfg.gui.mainDlg.copyToCfg.overwriteIfExists); + XmlOut outCopyTo = outMainWin["ManualCopyTo"]; + outCopyTo.attribute("KeepRelativePaths", cfg.mainDlg.copyToCfg.keepRelPaths); + outCopyTo.attribute("OverwriteIfExists", cfg.mainDlg.copyToCfg.overwriteIfExists); XmlOut outCopyToHistory = outCopyTo["FolderHistory"]; - outCopyToHistory(cfg.gui.mainDlg.copyToCfg.folderHistory); - outCopyToHistory.attribute("TargetFolder", cfg.gui.mainDlg.copyToCfg.targetFolderPath); - outCopyToHistory.attribute("LastSelected", cfg.gui.mainDlg.copyToCfg.targetFolderLastSelected); + outCopyToHistory(cfg.mainDlg.copyToCfg.folderHistory); + outCopyToHistory.attribute("TargetFolder", cfg.mainDlg.copyToCfg.targetFolderPath); + outCopyToHistory.attribute("LastSelected", cfg.mainDlg.copyToCfg.targetFolderLastSelected); //########################################################### - outWnd["DefaultViewFilter"](cfg.gui.mainDlg.viewFilterDefault); - outWnd["Perspective" ](cfg.gui.mainDlg.guiPerspectiveLast); + outMainWin["DefaultViewFilter"](cfg.mainDlg.viewFilterDefault); + outMainWin["Perspective" ](cfg.mainDlg.guiPerspectiveLast); - outGui["FolderHistory" ].attribute("MaxSize", cfg.gui.folderHistoryMax); + out["FolderHistory" ].attribute("MaxSize", cfg.folderHistoryMax); - outGui["CsvExport" ].attribute("LastSelected", cfg.gui.csvFileLastSelected); - outGui["SftpKeyFile"].attribute("LastSelected", cfg.gui.sftpKeyFileLastSelected); + out["CsvExport" ].attribute("LastSelected", cfg.csvFileLastSelected); + out["SftpKeyFile"].attribute("LastSelected", cfg.sftpKeyFileLastSelected); - outGui["DefaultExclusionFilter"](splitFilterByLines(cfg.gui.defaultExclusionFilter)); + out["DefaultExclusionFilter"](splitFilterByLines(cfg.defaultExclusionFilter)); - outGui["VersioningFolderHistory"](cfg.gui.versioningFolderHistory); - outGui["VersioningFolderHistory"].attribute("LastSelected", cfg.gui.versioningFolderLastSelected); + out["VersioningFolderHistory"](cfg.versioningFolderHistory); + out["VersioningFolderHistory"].attribute("LastSelected", cfg.versioningFolderLastSelected); - outGui["LogFolderHistory"](cfg.gui.logFolderHistory); - outGui["LogFolderHistory"].attribute("LastSelected", cfg.gui.logFolderLastSelected); + out["LogFolderHistory"](cfg.logFolderHistory); + out["LogFolderHistory"].attribute("LastSelected", cfg.logFolderLastSelected); - outGui["EmailHistory"](cfg.gui.emailHistory); - outGui["EmailHistory"].attribute("MaxSize", cfg.gui.emailHistoryMax); + out["EmailHistory"](cfg.emailHistory); + out["EmailHistory"].attribute("MaxSize", cfg.emailHistoryMax); - outGui["CommandHistory"](cfg.gui.commandHistory); - outGui["CommandHistory"].attribute("MaxSize", cfg.gui.commandHistoryMax); + out["CommandHistory"](cfg.commandHistory); + out["CommandHistory"].attribute("MaxSize", cfg.commandHistoryMax); //external applications - outGui["ExternalApps"](cfg.gui.externalApps); + out["ExternalApps"](cfg.externalApps); //last update check - outGui["LastOnlineCheck" ](cfg.gui.lastUpdateCheck); - outGui["LastOnlineVersion"](cfg.gui.lastOnlineVersion); + out["LastOnlineCheck" ](cfg.lastUpdateCheck); + out["LastOnlineVersion"](cfg.lastOnlineVersion); //batch specific global settings //XmlOut outBatch = out["Batch"]; diff --git a/FreeFileSync/Source/config.h b/FreeFileSync/Source/config.h index df7e0e3b..9c164c41 100644 --- a/FreeFileSync/Source/config.h +++ b/FreeFileSync/Source/config.h @@ -140,98 +140,100 @@ struct XmlGlobalSettings Zstring soundFileCompareFinished; Zstring soundFileSyncFinished; - bool autoCloseProgressDialog = false; ConfirmationDialogs confirmDlgs; WarningDialogs warnDlgs; //--------------------------------------------------------------------- - struct Gui + struct { - Gui() {} //clang needs this anyway + wxPoint dlgPos; + wxSize dlgSize; + bool isMaximized = false; + + bool textSearchRespectCase = false; //good default for Linux, too! + int folderPairsVisibleMax = 6; + + size_t cfgGridTopRowPos = 0; + int cfgGridSyncOverdueDays = 7; + ColumnTypeCfg cfgGridLastSortColumn = cfgGridLastSortColumnDefault; + bool cfgGridLastSortAscending = getDefaultSortDirection(cfgGridLastSortColumnDefault); + std::vector<ColAttributesCfg> cfgGridColumnAttribs = getCfgGridDefaultColAttribs(); + size_t cfgHistItemsMax = 100; + Zstring cfgFileLastSelected; + std::vector<ConfigFileItem> cfgFileHistory; + std::vector<Zstring> cfgFilesLastUsed; + + bool treeGridShowPercentBar = treeGridShowPercentageDefault; + ColumnTypeTree treeGridLastSortColumn = treeGridLastSortColumnDefault; //remember sort on overview panel + bool treeGridLastSortAscending = getDefaultSortDirection(treeGridLastSortColumnDefault); // + std::vector<ColAttributesTree> treeGridColumnAttribs = getTreeGridDefaultColAttribs(); + struct { - wxPoint dlgPos; - wxSize dlgSize; - bool isMaximized = false; - - bool textSearchRespectCase = false; //good default for Linux, too! - int folderPairsVisibleMax = 6; - - size_t cfgGridTopRowPos = 0; - int cfgGridSyncOverdueDays = 7; - ColumnTypeCfg cfgGridLastSortColumn = cfgGridLastSortColumnDefault; - bool cfgGridLastSortAscending = getDefaultSortDirection(cfgGridLastSortColumnDefault); - std::vector<ColAttributesCfg> cfgGridColumnAttribs = getCfgGridDefaultColAttribs(); - size_t cfgHistItemsMax = 100; - Zstring cfgFileLastSelected; - std::vector<ConfigFileItem> cfgFileHistory; - std::vector<Zstring> cfgFilesLastUsed; - - bool treeGridShowPercentBar = treeGridShowPercentageDefault; - ColumnTypeTree treeGridLastSortColumn = treeGridLastSortColumnDefault; //remember sort on overview panel - bool treeGridLastSortAscending = getDefaultSortDirection(treeGridLastSortColumnDefault); // - std::vector<ColAttributesTree> treeGridColumnAttribs = getTreeGridDefaultColAttribs(); - - struct - { - bool keepRelPaths = false; - bool overwriteIfExists = false; - Zstring targetFolderPath; - Zstring targetFolderLastSelected; - std::vector<Zstring> folderHistory; - } copyToCfg; - - std::vector<Zstring> folderHistoryLeft; - std::vector<Zstring> folderHistoryRight; - Zstring folderLastSelectedLeft; - Zstring folderLastSelectedRight; - - bool showIcons = true; - FileIconSize iconSize = FileIconSize::small; - int sashOffset = 0; - - ItemPathFormat itemPathFormatLeftGrid = defaultItemPathFormatLeftGrid; - ItemPathFormat itemPathFormatRightGrid = defaultItemPathFormatRightGrid; - - std::vector<ColAttributesRim> columnAttribLeft = getFileGridDefaultColAttribsLeft(); - std::vector<ColAttributesRim> columnAttribRight = getFileGridDefaultColAttribsRight(); - - ViewFilterDefault viewFilterDefault; - wxString guiPerspectiveLast; //for wxAuiManager - } mainDlg; - - Zstring defaultExclusionFilter = "*/.Trash-*/" "\n" - "*/.recycle/"; - size_t folderHistoryMax = 20; - - Zstring csvFileLastSelected; - Zstring sftpKeyFileLastSelected; - - std::vector<Zstring> versioningFolderHistory; - Zstring versioningFolderLastSelected; - - std::vector<Zstring> logFolderHistory; - Zstring logFolderLastSelected; - - std::vector<Zstring> emailHistory; - size_t emailHistoryMax = 10; - - std::vector<Zstring> commandHistory; - size_t commandHistoryMax = 10; - - std::vector<ExternalApp> externalApps - { - /* CONTRACT: first entry: show item in file browser - default external app descriptions will be translated "on the fly"!!! */ - //"xdg-open \"%parent_path%\"" -> not good enough: we need %local_path% for proper MTP/Google Drive handling - { L"Browse directory", "xdg-open \"$(dirname \"%local_path%\")\"" }, - { L"Open with default application", "xdg-open \"%local_path%\"" }, - //mark for extraction: _("Browse directory") Linux doesn't use the term "folder" - }; - - time_t lastUpdateCheck = 0; //number of seconds since 00:00 hours, Jan 1, 1970 UTC - std::string lastOnlineVersion; - } gui; + bool keepRelPaths = false; + bool overwriteIfExists = false; + Zstring targetFolderPath; + Zstring targetFolderLastSelected; + std::vector<Zstring> folderHistory; + } copyToCfg; + + std::vector<Zstring> folderHistoryLeft; + std::vector<Zstring> folderHistoryRight; + Zstring folderLastSelectedLeft; + Zstring folderLastSelectedRight; + + bool showIcons = true; + FileIconSize iconSize = FileIconSize::small; + int sashOffset = 0; + + ItemPathFormat itemPathFormatLeftGrid = defaultItemPathFormatLeftGrid; + ItemPathFormat itemPathFormatRightGrid = defaultItemPathFormatRightGrid; + + std::vector<ColAttributesRim> columnAttribLeft = getFileGridDefaultColAttribsLeft(); + std::vector<ColAttributesRim> columnAttribRight = getFileGridDefaultColAttribsRight(); + + ViewFilterDefault viewFilterDefault; + wxString guiPerspectiveLast; //for wxAuiManager + } mainDlg; + + struct + { + wxSize dlgSize; + bool isMaximized = false; + bool autoClose = false; + } progressDlg; + + Zstring defaultExclusionFilter = "*/.Trash-*/" "\n" + "*/.recycle/"; + size_t folderHistoryMax = 20; + + Zstring csvFileLastSelected; + Zstring sftpKeyFileLastSelected; + + std::vector<Zstring> versioningFolderHistory; + Zstring versioningFolderLastSelected; + + std::vector<Zstring> logFolderHistory; + Zstring logFolderLastSelected; + + std::vector<Zstring> emailHistory; + size_t emailHistoryMax = 10; + + std::vector<Zstring> commandHistory; + size_t commandHistoryMax = 10; + + std::vector<ExternalApp> externalApps + { + /* CONTRACT: first entry: show item in file browser + default external app descriptions will be translated "on the fly"!!! */ + //"xdg-open \"%parent_path%\"" -> not good enough: we need %local_path% for proper MTP/Google Drive handling + { L"Browse directory", "xdg-open \"$(dirname \"%local_path%\")\"" }, + { L"Open with default application", "xdg-open \"%local_path%\"" }, + //mark for extraction: _("Browse directory") Linux doesn't use the term "folder" + }; + + time_t lastUpdateCheck = 0; //number of seconds since 00:00 hours, Jan 1, 1970 UTC + std::string lastOnlineVersion; }; //read/write specific config types diff --git a/FreeFileSync/Source/ffs_paths.cpp b/FreeFileSync/Source/ffs_paths.cpp index e728c54b..3b058270 100644 --- a/FreeFileSync/Source/ffs_paths.cpp +++ b/FreeFileSync/Source/ffs_paths.cpp @@ -43,16 +43,11 @@ Zstring getProcessParentFolderPath() -namespace -{ -//don't make this a function-scope static (avoid code-gen for "magic static") //getFfsVolumeId() might be called during static destruction, e.g. async update check -std::once_flag onceFlagGetFfsVolumeId; -} - VolumeId fff::getFfsVolumeId() //throw FileError { static VolumeId volumeId; //POD => no "magic static" code gen + static constinit2 std::once_flag onceFlagGetFfsVolumeId; //=> no "magic static" code gen std::call_once(onceFlagGetFfsVolumeId, [] { volumeId = getVolumeId(getProcessPath()); }); //throw FileError return volumeId; } diff --git a/FreeFileSync/Source/icon_buffer.cpp b/FreeFileSync/Source/icon_buffer.cpp index 145178c9..bdd5eab2 100644 --- a/FreeFileSync/Source/icon_buffer.cpp +++ b/FreeFileSync/Source/icon_buffer.cpp @@ -425,7 +425,7 @@ wxImage IconBuffer::linkOverlayIcon(IconSize sz) if (iconSize >= fastFromDIP(128)) return "file_link_128"; if (iconSize >= fastFromDIP( 48)) return "file_link_48"; - if (iconSize >= fastFromDIP( 24)) return "file_link_24"; + if (iconSize >= fastFromDIP( 20)) return "file_link_20"; return "file_link_16"; }()); } @@ -440,7 +440,7 @@ wxImage IconBuffer::plusOverlayIcon(IconSize sz) if (iconSize >= fastFromDIP(128)) return "file_plus_128"; if (iconSize >= fastFromDIP( 48)) return "file_plus_48"; - if (iconSize >= fastFromDIP( 24)) return "file_plus_24"; + if (iconSize >= fastFromDIP( 20)) return "file_plus_20"; return "file_plus_16"; }()); } @@ -455,7 +455,7 @@ wxImage IconBuffer::minusOverlayIcon(IconSize sz) if (iconSize >= fastFromDIP(128)) return "file_minus_128"; if (iconSize >= fastFromDIP( 48)) return "file_minus_48"; - if (iconSize >= fastFromDIP( 24)) return "file_minus_24"; + if (iconSize >= fastFromDIP( 20)) return "file_minus_20"; return "file_minus_16"; }()); } diff --git a/FreeFileSync/Source/icon_buffer.h b/FreeFileSync/Source/icon_buffer.h index 3693281a..f38053e9 100644 --- a/FreeFileSync/Source/icon_buffer.h +++ b/FreeFileSync/Source/icon_buffer.h @@ -38,10 +38,10 @@ public: wxImage getIconByExtension(const Zstring& filePath); //...and add to buffer //retrieveFileIcon() + getIconByExtension() are safe to call from within WM_PAINT handler! no COM calls (...on calling thread) - static wxImage genericFileIcon(IconSize sz); - static wxImage genericDirIcon (IconSize sz); - static wxImage linkOverlayIcon(IconSize sz); - static wxImage plusOverlayIcon(IconSize sz); + static wxImage genericFileIcon (IconSize sz); + static wxImage genericDirIcon (IconSize sz); + static wxImage linkOverlayIcon (IconSize sz); + static wxImage plusOverlayIcon (IconSize sz); static wxImage minusOverlayIcon(IconSize sz); private: diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp index be80afb0..63db36d8 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.cpp +++ b/FreeFileSync/Source/ui/batch_status_handler.cpp @@ -23,19 +23,20 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, const std::wstring& jobName, const std::chrono::system_clock::time_point& startTime, bool ignoreErrors, - size_t automaticRetryCount, - std::chrono::seconds automaticRetryDelay, + size_t autoRetryCount, + std::chrono::seconds autoRetryDelay, const Zstring& soundFileSyncComplete, + wxSize progressDlgSize, bool dlgMaximize, bool autoCloseDialog, PostSyncAction postSyncAction, BatchErrorHandling batchErrorHandling) : jobName_(jobName), startTime_(startTime), - automaticRetryCount_(automaticRetryCount), - automaticRetryDelay_(automaticRetryDelay), + autoRetryCount_(autoRetryCount), + autoRetryDelay_(autoRetryDelay), soundFileSyncComplete_(soundFileSyncComplete), - progressDlg_(SyncProgressDialog::create([this] { userRequestAbort(); }, *this, nullptr /*parentWindow*/, showProgress, autoCloseDialog, -{ jobName }, startTime, ignoreErrors, automaticRetryCount, [&] + progressDlg_(SyncProgressDialog::create(progressDlgSize, dlgMaximize, [this] { userRequestAbort(); }, *this, nullptr /*parentWindow*/, showProgress, autoCloseDialog, +{ jobName }, startTime, ignoreErrors, autoRetryCount, [&] { switch (postSyncAction) { @@ -227,12 +228,12 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post auto errorLogFinal = makeSharedRef<const ErrorLog>(std::move(errorLog_)); - progressDlg_->destroy(autoClose, - true /*restoreParentFrame: n/a here*/, - syncResult, errorLogFinal); + const auto [autoCloseDialog, dlgSize, dlgIsMaximized] = progressDlg_->destroy(autoClose, + true /*restoreParentFrame: n/a here*/, + syncResult, errorLogFinal); progressDlg_ = nullptr; - return { syncResult, errorLogFinal.ref().getStats(), finalRequest, logFilePath }; + return { syncResult, errorLogFinal.ref().getStats(), finalRequest, logFilePath, dlgSize, dlgIsMaximized }; } @@ -313,11 +314,11 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& ms PauseTimers dummy(*progressDlg_); //auto-retry - if (retryNumber < automaticRetryCount_) + if (retryNumber < autoRetryCount_) { errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO); - delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)), - automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess + delayAndCountDown(_("Automatic retry") + (autoRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(autoRetryCount_)), + autoRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess return ProcessCallback::retry; } diff --git a/FreeFileSync/Source/ui/batch_status_handler.h b/FreeFileSync/Source/ui/batch_status_handler.h index c748720c..07e13dee 100644 --- a/FreeFileSync/Source/ui/batch_status_handler.h +++ b/FreeFileSync/Source/ui/batch_status_handler.h @@ -24,9 +24,10 @@ public: const std::wstring& jobName, //should not be empty for a batch job! const std::chrono::system_clock::time_point& startTime, bool ignoreErrors, - size_t automaticRetryCount, - std::chrono::seconds automaticRetryDelay, + size_t autoRetryCount, + std::chrono::seconds autoRetryDelay, const Zstring& soundFileSyncComplete, + wxSize progressDlgSize, bool dlgMaximize, bool autoCloseDialog, PostSyncAction postSyncAction, BatchErrorHandling batchErrorHandling); //noexcept!! @@ -53,6 +54,8 @@ public: zen::ErrorLog::Stats logStats; FinalRequest finalRequest; AbstractPath logFilePath; + wxSize dlgSize; + bool dlgIsMaximized; }; Result reportResults(const Zstring& postSyncCommand, PostSyncCondition postSyncCondition, const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, LogFileFormat logFormat, const std::set<AbstractPath>& logFilePathsToKeep, @@ -61,8 +64,8 @@ public: private: const std::wstring jobName_; const std::chrono::system_clock::time_point startTime_; - const size_t automaticRetryCount_; - const std::chrono::seconds automaticRetryDelay_; + const size_t autoRetryCount_; + const std::chrono::seconds autoRetryDelay_; const Zstring soundFileSyncComplete_; SyncProgressDialog* progressDlg_; //managed to have the same lifetime as this handler! diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp index 98f5b215..1cb984c7 100644 --- a/FreeFileSync/Source/ui/file_grid.cpp +++ b/FreeFileSync/Source/ui/file_grid.cpp @@ -804,15 +804,16 @@ private: if (const auto& [cudAction, cudSide] = getCudAction(syncOp); cudAction != CudAction::doNothing && side == cudSide) { - wxColor backCol = *wxWHITE; - dc.GetPixel(rectCud.GetTopRight(), &backCol); - rectCud.width = gapSize_ + IconBuffer::getSize(IconBuffer::SIZE_SMALL); //fixed-size looks fine for all icon sizes! use same width even if file icons are disabled! clearArea(dc, rectCud, getBackGroundColorSyncAction(syncOp)); rectCud.x += rectCud.width; rectCud.width = gapSize_ + fastFromDIP(2); + + wxColor backCol = *wxWHITE; + dc.GetPixel(rectCud.GetTopRight(), &backCol); + dc.GradientFillLinear(rectCud, getBackGroundColorSyncAction(syncOp), backCol, wxEAST); } }; @@ -849,7 +850,8 @@ private: drawIcon(getIconManager().getGenericDirIcon().ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3). //treat all channels equally! ConvertToDisabled(), rectIcon, true /*drawActive: [!]*/); //visual hint to distinguish file/folder creation - drawIcon(getIconManager().getPlusOverlayIcon(), rectIcon, true /*drawActive: [!] e.g. disabled folder, exists left only, where child item is copied*/); + //too much clutter? => drawIcon(getIconManager().getPlusOverlayIcon(), rectIcon, + // true /*drawActive: [!] e.g. disabled folder, exists left only, where child item is copied*/); break; case CudAction::destroy: drawIcon(getIconManager().getMinusOverlayIcon(), rectIcon, true /*drawActive: [!]*/); @@ -974,8 +976,9 @@ private: if (!groupParentFolder.empty() && (( stackedGroupRender && row == groupFirstRow + 1) || - (!stackedGroupRender && row == groupFirstRow)) && - (groupName.empty() || !pdi.folderGroupObj->isEmpty<side>())) + (!stackedGroupRender && row == groupFirstRow)) + //&& (groupName.empty() || !pdi.folderGroupObj->isEmpty<side>()) -> show unconditionally, even for missing folders + ) { wxRect rectGroupParentText = rectGroupParent; rectGroupParentText.x += gapSize_; @@ -2006,9 +2009,9 @@ void filegrid::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) gridCenter.setColumnConfig( { - { static_cast<ColumnType>(ColumnTypeCenter::checkbox), widthCheckbox, 0, true }, + { static_cast<ColumnType>(ColumnTypeCenter::checkbox), widthCheckbox, 0, true }, { static_cast<ColumnType>(ColumnTypeCenter::difference), widthDifference, 0, true }, - { static_cast<ColumnType>(ColumnTypeCenter::action), widthAction, 0, true }, + { static_cast<ColumnType>(ColumnTypeCenter::action), widthAction, 0, true }, }); } diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index 44328c95..061b603f 100644 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -258,11 +258,7 @@ void FolderSelector::onSelectAltFolder(wxCommandEvent& event) Zstring folderPathPhrase = getPath(); size_t parallelOps = getDeviceParallelOps_ ? getDeviceParallelOps_(folderPathPhrase) : 1; - std::optional<std::wstring> parallelOpsDisabledReason; - - parallelOpsDisabledReason = _("Requires FreeFileSync Donation Edition"); - - if (showCloudSetupDialog(parent_, folderPathPhrase, sftpKeyFileLastSelected_, parallelOps, get(parallelOpsDisabledReason)) != ConfirmationButton::accept) + if (showCloudSetupDialog(parent_, folderPathPhrase, sftpKeyFileLastSelected_, parallelOps, static_cast<bool>(setDeviceParallelOps_)) != ConfirmationButton::accept) return; setPath(folderPathPhrase); diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index dbb2a688..5c103ed6 100644 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -11,7 +11,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style ) { - this->SetSizeHints( wxSize( 640, 400 ), wxDefaultSize ); + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); m_menubar = new wxMenuBar( 0 ); m_menuFile = new wxMenu(); @@ -382,7 +382,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer1601->Add( bSizer91, 0, wxEXPAND, 5 ); m_scrolledWindowFolderPairs = new wxScrolledWindow( m_panelDirectoryPairs, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ), wxHSCROLL|wxVSCROLL ); - m_scrolledWindowFolderPairs->SetScrollRate( 10, 10 ); + m_scrolledWindowFolderPairs->SetScrollRate( 5, 5 ); m_scrolledWindowFolderPairs->SetMinSize( wxSize( -1, 0 ) ); bSizerAddFolderPairs = new wxBoxSizer( wxVERTICAL ); @@ -433,7 +433,6 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer451; bSizer451 = new wxBoxSizer( wxHORIZONTAL ); - bSizer451->SetMinSize( wxSize( -1, 22 ) ); bSizerFileStatus = new wxBoxSizer( wxHORIZONTAL ); bSizerStatusLeft = new wxBoxSizer( wxHORIZONTAL ); @@ -1575,9 +1574,10 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizerPerformance = new wxBoxSizer( wxVERTICAL ); - m_staticTextPerfDeRequired = new wxStaticText( m_panelComparisonSettings, wxID_ANY, _("Requires FreeFileSync Donation Edition"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextPerfDeRequired->Wrap( -1 ); - bSizerPerformance->Add( m_staticTextPerfDeRequired, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); + m_hyperlinkPerfDeRequired = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("Requires FreeFileSync Donation Edition"), wxT("https://freefilesync.org/faq.php#donation-edition"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlinkPerfDeRequired->SetToolTip( _("https://freefilesync.org/faq.php#donation-edition") ); + + bSizerPerformance->Add( m_hyperlinkPerfDeRequired, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_staticlinePerfDeRequired = new wxStaticLine( m_panelComparisonSettings, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizerPerformance->Add( m_staticlinePerfDeRequired, 0, wxEXPAND, 5 ); @@ -1646,7 +1646,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_panelCompSettingsTab->SetSizer( bSizer275 ); m_panelCompSettingsTab->Layout(); bSizer275->Fit( m_panelCompSettingsTab ); - m_notebook->AddPage( m_panelCompSettingsTab, _("dummy"), false ); + m_notebook->AddPage( m_panelCompSettingsTab, _("dummy"), true ); m_panelFilterSettingsTab = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelFilterSettingsTab->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -2363,9 +2363,10 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer292->Add( bSizer287, 0, wxEXPAND, 5 ); - m_staticTextPerfDeRequired2 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Requires FreeFileSync Donation Edition"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextPerfDeRequired2->Wrap( -1 ); - bSizer292->Add( m_staticTextPerfDeRequired2, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP, 10 ); + m_hyperlinkPerfDeRequired2 = new wxHyperlinkCtrl( m_panelSyncSettings, wxID_ANY, _("Requires FreeFileSync Donation Edition"), wxT("https://freefilesync.org/faq.php#donation-edition"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlinkPerfDeRequired2->SetToolTip( _("https://freefilesync.org/faq.php#donation-edition") ); + + bSizer292->Add( m_hyperlinkPerfDeRequired2, 0, wxALL, 5 ); bSizerSyncMisc->Add( bSizer292, 0, wxEXPAND|wxALL, 10 ); @@ -2457,7 +2458,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w m_panelSyncSettingsTab->SetSizer( bSizer276 ); m_panelSyncSettingsTab->Layout(); bSizer276->Fit( m_panelSyncSettingsTab ); - m_notebook->AddPage( m_panelSyncSettingsTab, _("dummy"), true ); + m_notebook->AddPage( m_panelSyncSettingsTab, _("dummy"), false ); bSizer190->Add( m_notebook, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); @@ -2960,6 +2961,11 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, bSizer300->Add( m_staticTextConnectionCountDescr, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + m_hyperlinkDeRequired = new wxHyperlinkCtrl( m_panel411, wxID_ANY, _("Requires FreeFileSync Donation Edition"), wxT("https://freefilesync.org/faq.php#donation-edition"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlinkDeRequired->SetToolTip( _("https://freefilesync.org/faq.php#donation-edition") ); + + bSizer300->Add( m_hyperlinkDeRequired, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + fgSizer1611->Add( bSizer300, 0, wxALIGN_CENTER_VERTICAL, 5 ); @@ -3551,6 +3557,9 @@ SyncProgressPanelGenerated::SyncProgressPanelGenerated( wxWindow* parent, wxWind m_panel53 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel53->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxBoxSizer* bSizer301; + bSizer301 = new wxBoxSizer( wxVERTICAL ); + bSizer42 = new wxBoxSizer( wxHORIZONTAL ); @@ -3580,25 +3589,25 @@ SyncProgressPanelGenerated::SyncProgressPanelGenerated( wxWindow* parent, wxWind bSizer42->Add( bSizer247, 1, wxALIGN_CENTER_VERTICAL, 5 ); - m_panel53->SetSizer( bSizer42 ); - m_panel53->Layout(); - bSizer42->Fit( m_panel53 ); - bSizerRoot->Add( m_panel53, 0, wxEXPAND, 5 ); + bSizer301->Add( bSizer42, 0, wxEXPAND, 5 ); bSizerStatusText = new wxBoxSizer( wxVERTICAL ); + m_staticTextStatus = new wxStaticText( m_panel53, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextStatus->Wrap( -1 ); + bSizerStatusText->Add( m_staticTextStatus, 0, wxEXPAND|wxLEFT, 15 ); - bSizerStatusText->Add( 0, 5, 0, 0, 5 ); - 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, 10, 0, 0, 5 ); - bSizerStatusText->Add( 0, 5, 0, 0, 5 ); + bSizer301->Add( bSizerStatusText, 0, wxEXPAND, 5 ); - bSizerRoot->Add( bSizerStatusText, 0, wxEXPAND, 5 ); + m_panel53->SetSizer( bSizer301 ); + m_panel53->Layout(); + bSizer301->Fit( m_panel53 ); + bSizerRoot->Add( m_panel53, 0, wxEXPAND, 5 ); m_panelProgress = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panelProgress->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); @@ -3828,7 +3837,7 @@ SyncProgressPanelGenerated::SyncProgressPanelGenerated( wxWindow* parent, wxWind bSizer161->Add( bSizerProgressFooter, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 10 ); - bSizer173->Add( bSizer161, 1, wxEXPAND|wxLEFT, 10 ); + bSizer173->Add( bSizer161, 1, wxEXPAND|wxLEFT, 5 ); m_panelProgress->SetSizer( bSizer173 ); @@ -5302,20 +5311,14 @@ ActivationDlgGenerated::ActivationDlgGenerated( wxWindow* parent, wxWindowID id, m_bitmapActivation = new wxStaticBitmap( m_panel35, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); bSizer165->Add( m_bitmapActivation, 0, wxALL, 10 ); - - bSizer165->Add( 0, 120, 0, 0, 5 ); - wxBoxSizer* bSizer16; bSizer16 = new wxBoxSizer( wxVERTICAL ); bSizer16->Add( 0, 10, 0, 0, 5 ); - m_textCtrlLastError = new wxTextCtrl( m_panel35, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxBORDER_NONE ); - bSizer16->Add( m_textCtrlLastError, 1, wxEXPAND, 5 ); - - - bSizer16->Add( 0, 5, 0, 0, 5 ); + m_richTextLastError = new wxRichTextCtrl( m_panel35, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY|wxBORDER_NONE|wxVSCROLL|wxWANTS_CHARS ); + bSizer16->Add( m_richTextLastError, 1, wxEXPAND, 5 ); bSizer165->Add( bSizer16, 1, wxEXPAND, 5 ); @@ -5323,9 +5326,12 @@ ActivationDlgGenerated::ActivationDlgGenerated( wxWindow* parent, wxWindowID id, bSizer172->Add( bSizer165, 1, wxEXPAND, 5 ); + m_staticline82 = new wxStaticLine( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer172->Add( m_staticline82, 0, wxEXPAND, 5 ); + m_staticTextMain = new wxStaticText( m_panel35, wxID_ANY, _("Activate the FreeFileSync Donation Edition by one of the following methods:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextMain->Wrap( -1 ); - bSizer172->Add( m_staticTextMain, 0, wxBOTTOM|wxRIGHT|wxLEFT, 10 ); + bSizer172->Add( m_staticTextMain, 0, wxALL, 10 ); m_panel35->SetSizer( bSizer172 ); @@ -5404,8 +5410,8 @@ ActivationDlgGenerated::ActivationDlgGenerated( wxWindow* parent, wxWindowID id, bSizer237->Add( bSizer236, 0, wxEXPAND|wxBOTTOM, 5 ); - m_textCtrlManualActivationUrl = new wxTextCtrl( m_panel351, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, 55 ), wxTE_MULTILINE|wxTE_READONLY ); - bSizer237->Add( m_textCtrlManualActivationUrl, 0, wxEXPAND|wxBOTTOM, 5 ); + m_richTextManualActivationUrl = new wxRichTextCtrl( m_panel351, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY|wxBORDER_NONE|wxVSCROLL|wxWANTS_CHARS ); + bSizer237->Add( m_richTextManualActivationUrl, 0, wxEXPAND|wxBOTTOM, 5 ); wxBoxSizer* bSizer235; bSizer235 = new wxBoxSizer( wxHORIZONTAL ); @@ -5414,7 +5420,7 @@ ActivationDlgGenerated::ActivationDlgGenerated( wxWindow* parent, wxWindowID id, m_staticText13611->Wrap( -1 ); bSizer235->Add( m_staticText13611, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - m_textCtrlOfflineActivationKey = new wxTextCtrl( m_panel351, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 250, -1 ), wxTE_PROCESS_ENTER ); + m_textCtrlOfflineActivationKey = new wxTextCtrl( m_panel351, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), wxTE_PROCESS_ENTER ); bSizer235->Add( m_textCtrlOfflineActivationKey, 1, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); m_buttonActivateOffline = new wxButton( m_panel351, wxID_ANY, _("Activate offline"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index 9c538ac1..a03c8883 100644 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -50,6 +50,7 @@ #include <wx/grid.h> #include <wx/calctrl.h> #include <wx/gauge.h> +#include <wx/richtext/richtextctrl.h> #include "zen/i18n.h" @@ -361,7 +362,7 @@ protected: wxStaticLine* m_staticline3311; wxStaticLine* m_staticline751; wxBoxSizer* bSizerPerformance; - wxStaticText* m_staticTextPerfDeRequired; + wxHyperlinkCtrl* m_hyperlinkPerfDeRequired; wxStaticLine* m_staticlinePerfDeRequired; wxPanel* m_panelPerfHeader; wxStaticBitmap* m_bitmapPerf; @@ -473,7 +474,7 @@ protected: wxBitmapButton* m_bpButtonEmailAlways; wxBitmapButton* m_bpButtonEmailErrorWarning; wxBitmapButton* m_bpButtonEmailErrorOnly; - wxStaticText* m_staticTextPerfDeRequired2; + wxHyperlinkCtrl* m_hyperlinkPerfDeRequired2; wxStaticLine* m_staticline57; wxPanel* m_panelLogfile; wxStaticBitmap* m_bitmapLogFile; @@ -622,6 +623,7 @@ protected: wxStaticText* m_staticTextConnectionsLabelSub; wxSpinCtrl* m_spinCtrlConnectionCount; wxStaticText* m_staticTextConnectionCountDescr; + wxHyperlinkCtrl* m_hyperlinkDeRequired; wxStaticText* m_staticTextChannelCountSftp; wxSpinCtrl* m_spinCtrlChannelCountSftp; wxButton* m_buttonChannelCountSftp; @@ -1239,7 +1241,8 @@ private: protected: wxPanel* m_panel35; wxStaticBitmap* m_bitmapActivation; - wxTextCtrl* m_textCtrlLastError; + wxRichTextCtrl* m_richTextLastError; + wxStaticLine* m_staticline82; wxStaticText* m_staticTextMain; wxStaticLine* m_staticline181; wxStaticLine* m_staticline18111; @@ -1253,7 +1256,7 @@ protected: wxStaticText* m_staticText175; wxStaticText* m_staticText1361; wxButton* m_buttonCopyUrl; - wxTextCtrl* m_textCtrlManualActivationUrl; + wxRichTextCtrl* m_richTextManualActivationUrl; wxStaticText* m_staticText13611; wxTextCtrl* m_textCtrlOfflineActivationKey; wxButton* m_buttonActivateOffline; diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index 4928a220..2411957c 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -28,15 +28,15 @@ const std::chrono::seconds TEMP_PANEL_DISPLAY_DELAY(1); StatusHandlerTemporaryPanel::StatusHandlerTemporaryPanel(MainDialog& dlg, const std::chrono::system_clock::time_point& startTime, bool ignoreErrors, - size_t automaticRetryCount, - std::chrono::seconds automaticRetryDelay) : + size_t autoRetryCount, + std::chrono::seconds autoRetryDelay) : mainDlg_(dlg), ignoreErrors_(ignoreErrors), - automaticRetryCount_(automaticRetryCount), - automaticRetryDelay_(automaticRetryDelay), + autoRetryCount_(autoRetryCount), + autoRetryDelay_(autoRetryDelay), startTime_(startTime) { - mainDlg_.compareStatus_->init(*this, ignoreErrors_, automaticRetryCount_); //clear old values before showing panel + mainDlg_.compareStatus_->init(*this, ignoreErrors_, autoRetryCount_); //clear old values before showing panel //showStatsPanel(); => delay and avoid GUI distraction for short-lived tasks @@ -230,11 +230,11 @@ ProcessCallback::Response StatusHandlerTemporaryPanel::reportError(const std::ws PauseTimers dummy(*mainDlg_.compareStatus_); //auto-retry - if (retryNumber < automaticRetryCount_) + if (retryNumber < autoRetryCount_) { errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO); - delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)), - automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess + delayAndCountDown(_("Automatic retry") + (autoRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(autoRetryCount_)), + autoRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess return ProcessCallback::retry; } @@ -338,18 +338,18 @@ StatusHandlerFloatingDialog::StatusHandlerFloatingDialog(wxFrame* parentDlg, const std::vector<std::wstring>& jobNames, const std::chrono::system_clock::time_point& startTime, bool ignoreErrors, - size_t automaticRetryCount, - std::chrono::seconds automaticRetryDelay, + size_t autoRetryCount, + std::chrono::seconds autoRetryDelay, const Zstring& soundFileSyncComplete, - bool& autoCloseDialog) : + const wxSize& progressDlgSize, bool dlgMaximize, + bool autoCloseDialog) : jobNames_(jobNames), startTime_(startTime), - automaticRetryCount_(automaticRetryCount), - automaticRetryDelay_(automaticRetryDelay), + autoRetryCount_(autoRetryCount), + autoRetryDelay_(autoRetryDelay), soundFileSyncComplete_(soundFileSyncComplete), - progressDlg_(SyncProgressDialog::create([this] { userRequestAbort(); }, *this, parentDlg, true /*showProgress*/, autoCloseDialog, -jobNames, startTime, ignoreErrors, automaticRetryCount, PostSyncAction2::none)), -autoCloseDialogOut_(autoCloseDialog) {} + progressDlg_(SyncProgressDialog::create(progressDlgSize, dlgMaximize, [this] { userRequestAbort(); }, *this, parentDlg, true /*showProgress*/, autoCloseDialog, +jobNames, startTime, ignoreErrors, autoRetryCount, PostSyncAction2::none)) {} StatusHandlerFloatingDialog::~StatusHandlerFloatingDialog() @@ -521,13 +521,12 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c auto errorLogFinal = makeSharedRef<const ErrorLog>(std::move(errorLog_)); - autoCloseDialogOut_ = //output parameter owned by SyncProgressDialog (evaluate *after* user closed the results dialog) - progressDlg_->destroy(autoClose, - finalRequest == FinalRequest::none /*restoreParentFrame*/, - syncResult, errorLogFinal).autoCloseDialog; + const auto [autoCloseDialog, dlgSize, dlgIsMaximized] = progressDlg_->destroy(autoClose, + finalRequest == FinalRequest::none /*restoreParentFrame*/, + syncResult, errorLogFinal); progressDlg_ = nullptr; - return { summary, errorLogFinal, finalRequest, logFilePath }; + return { summary, errorLogFinal, finalRequest, logFilePath, dlgSize, dlgIsMaximized, autoClose }; } @@ -585,11 +584,13 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws PauseTimers dummy(*progressDlg_); //auto-retry - if (retryNumber < automaticRetryCount_) + if (retryNumber < autoRetryCount_) { + warn_static("bug: autoRetryDelay_ should start counting after error occured!! not when its reported!") + errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO); - delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)), - automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess + delayAndCountDown(_("Automatic retry") + (autoRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(autoRetryCount_)), + autoRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess return ProcessCallback::retry; } diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h index e8ed01e4..b5c3cca1 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.h +++ b/FreeFileSync/Source/ui/gui_status_handler.h @@ -22,7 +22,7 @@ namespace fff class StatusHandlerTemporaryPanel : private wxEvtHandler, public StatusHandler { public: - StatusHandlerTemporaryPanel(MainDialog& dlg, const std::chrono::system_clock::time_point& startTime, bool ignoreErrors, size_t automaticRetryCount, std::chrono::seconds automaticRetryDelay); + StatusHandlerTemporaryPanel(MainDialog& dlg, const std::chrono::system_clock::time_point& startTime, bool ignoreErrors, size_t autoRetryCount, std::chrono::seconds autoRetryDelay); ~StatusHandlerTemporaryPanel(); void initNewPhase (int itemsTotal, int64_t bytesTotal, ProcessPhase phaseID) override; // @@ -48,8 +48,8 @@ private: MainDialog& mainDlg_; zen::ErrorLog errorLog_; const bool ignoreErrors_; - const size_t automaticRetryCount_; - const std::chrono::seconds automaticRetryDelay_; + const size_t autoRetryCount_; + const std::chrono::seconds autoRetryDelay_; const std::chrono::system_clock::time_point startTime_; const std::chrono::steady_clock::time_point startTimeSteady_ = std::chrono::steady_clock::now(); }; @@ -63,10 +63,11 @@ public: const std::vector<std::wstring>& jobNames, const std::chrono::system_clock::time_point& startTime, bool ignoreErrors, - size_t automaticRetryCount, - std::chrono::seconds automaticRetryDelay, + size_t autoRetryCount, + std::chrono::seconds autoRetryDelay, const Zstring& soundFileSyncComplete, - bool& autoCloseDialog); //noexcept! + const wxSize& progressDlgSize, bool dlgMaximize, + bool autoCloseDialog); //noexcept! ~StatusHandlerFloatingDialog(); void initNewPhase (int itemsTotal, int64_t bytesTotal, ProcessPhase phaseID) override; // @@ -90,6 +91,9 @@ public: zen::SharedRef<const zen::ErrorLog> errorLog; FinalRequest finalRequest; AbstractPath logFilePath; + wxSize dlgSize; + bool dlgIsMaximized; + bool autoCloseDialog; }; Result reportResults(const Zstring& postSyncCommand, PostSyncCondition postSyncCondition, const Zstring& altLogFolderPathPhrase, int logfilesMaxAgeDays, LogFileFormat logFormat, const std::set<AbstractPath>& logFilePathsToKeep, @@ -98,13 +102,12 @@ public: private: const std::vector<std::wstring> jobNames_; const std::chrono::system_clock::time_point startTime_; - const size_t automaticRetryCount_; - const std::chrono::seconds automaticRetryDelay_; + const size_t autoRetryCount_; + const std::chrono::seconds autoRetryDelay_; const Zstring soundFileSyncComplete_; SyncProgressDialog* progressDlg_; //managed to have the same lifetime as this handler! zen::ErrorLog errorLog_; - bool& autoCloseDialogOut_; //owned by SyncProgressDialog }; } diff --git a/FreeFileSync/Source/ui/log_panel.cpp b/FreeFileSync/Source/ui/log_panel.cpp index a357f657..3bea6506 100644 --- a/FreeFileSync/Source/ui/log_panel.cpp +++ b/FreeFileSync/Source/ui/log_panel.cpp @@ -6,7 +6,7 @@ #include "log_panel.h" #include <wx/clipbrd.h> -#include <wx+/focus.h> +#include <wx+/window_tools.h> #include <wx+/image_resources.h> #include <wx+/rtl.h> #include <wx+/context_menu.h> diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index 0fe2d02e..bece5c5e 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -16,7 +16,6 @@ #include <wx/wupdlock.h> #include <wx/sound.h> #include <wx/filedlg.h> -#include <wx/display.h> #include <wx/textdlg.h> #include <wx/valtext.h> #include <wx+/context_menu.h> @@ -27,7 +26,7 @@ #include <wx+/rtl.h> #include <wx+/font_size.h> #include <wx+/popup_dlg.h> -#include <wx+/focus.h> +#include <wx+/window_tools.h> #include <wx+/image_resources.h> #include "cfg_grid.h" #include "version_check.h" @@ -45,6 +44,7 @@ #include "../base/algorithm.h" #include "../base/resolve_path.h" #include "../base/lock_holder.h" +#include "../base/icon_loader.h" #include "../ffs_paths.h" #include "../localization.h" #include "../version/version.h" @@ -319,7 +319,7 @@ void MainDialog::create(const Zstring& globalConfigFilePath) { const XmlGlobalSettings globalSettings = tryLoadGlobalConfig(globalConfigFilePath); - std::vector<Zstring> cfgFilePaths = globalSettings.gui.mainDlg.cfgFilesLastUsed; + std::vector<Zstring> cfgFilePaths = globalSettings.mainDlg.cfgFilesLastUsed; //------------------------------------------------------------------------------------------ //check existence of all files in parallel: @@ -356,7 +356,7 @@ void MainDialog::create(const Zstring& globalConfigFilePath) Zstring& excludeFilter = guiCfg.mainCfg.globalFilter.excludeFilter; if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr('\n'))) excludeFilter += Zstr('\n'); - excludeFilter += globalSettings.gui.defaultExclusionFilter; + excludeFilter += globalSettings.defaultExclusionFilter; if (!cfgFilePaths.empty()) try @@ -410,9 +410,17 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, bool startComparison) : MainDialogGenerated(nullptr), globalConfigFilePath_(globalConfigFilePath), - folderHistoryLeft_ (std::make_shared<HistoryList>(globalSettings.gui.mainDlg.folderHistoryLeft, globalSettings.gui.folderHistoryMax)), - folderHistoryRight_(std::make_shared<HistoryList>(globalSettings.gui.mainDlg.folderHistoryRight, globalSettings.gui.folderHistoryMax)) + folderHistoryLeft_ (std::make_shared<HistoryList>(globalSettings.mainDlg.folderHistoryLeft, globalSettings.folderHistoryMax)), + folderHistoryRight_(std::make_shared<HistoryList>(globalSettings.mainDlg.folderHistoryRight, globalSettings.folderHistoryMax)), + imgTrashSmall_([] { + try { return extractWxImage(fff::getTrashIcon(getDefaultMenuIconSize())); /*throw SysError*/ } + catch (SysError&) { assert(false); return loadImage("delete_recycler", getDefaultMenuIconSize()); } +} +()) +{ + SetSizeHints(fastFromDIP(640), fastFromDIP(400)); + //setup sash: detach + reparent: m_splitterMain->SetSizer(nullptr); //alas wxFormbuilder doesn't allow us to have child windows without a sizer, so we have to remove it here m_splitterMain->setupWindows(m_gridMainL, m_gridMainC, m_gridMainR); @@ -728,9 +736,9 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, //init handling of first folder pair firstFolderPair_ = std::make_unique<FolderPairFirst>(*this, - globalCfg_.gui.mainDlg.folderLastSelectedLeft, - globalCfg_.gui.mainDlg.folderLastSelectedRight, - globalCfg_.gui.sftpKeyFileLastSelected); + globalCfg_.mainDlg.folderLastSelectedLeft, + globalCfg_.mainDlg.folderLastSelectedRight, + globalCfg_.sftpKeyFileLastSelected); //init grid settings filegrid::init(*m_gridMainL, *m_gridMainC, *m_gridMainR); @@ -770,6 +778,9 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, onResizeConfigPanel (dummy3); //call once on window creation onResizeViewPanel (dummy3); // + const int scrollDelta = m_buttonSelectFolderLeft->GetSize().y; //more approriate than GetCharHeight() here + m_scrolledWindowFolderPairs->SetScrollRate(scrollDelta, scrollDelta); + //event handler for manual (un-)checking of rows and setting of sync direction m_gridMainC->Bind(EVENT_GRID_CHECK_ROWS, [this](CheckRowsEvent& event) { onCheckRows (event); }); m_gridMainC->Bind(EVENT_GRID_SYNC_DIRECTION, [this](SyncDirectionEvent& event) { onSetSyncDirection(event); }); @@ -789,7 +800,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, //1. setConfig() indirectly calls cfggrid::addAndSelect() which changes cfg history scroll position //2. Grid::makeRowVisible() requires final window height! => do this after window resizing is complete if (m_gridCfgHistory->getRowCount() > 0) - m_gridCfgHistory->scrollTo(std::clamp<size_t>(globalSettings.gui.mainDlg.cfgGridTopRowPos, //must be set *after* wxAuiManager::LoadPerspective() to have any effect + m_gridCfgHistory->scrollTo(std::clamp<size_t>(globalSettings.mainDlg.cfgGridTopRowPos, //must be set *after* wxAuiManager::LoadPerspective() to have any effect 0, m_gridCfgHistory->getRowCount() - 1)); //first selected item should always be visible: @@ -914,7 +925,7 @@ void MainDialog::onClose(wxCloseEvent& event) if (event.CanVeto()) { //=> veto all attempts to close the main window while comparison or synchronization are running: - if (!allowMainDialogClose_) + if (operationInProgress_) { event.Veto(); Raise(); //=what Windows does when vetoing a close (via middle mouse on taskbar preview) while showing a modal dialog @@ -939,76 +950,37 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings) globalCfg_ = globalSettings; //caveat: set/get language asymmmetry! setLanguage(globalSettings.programLanguage); //throw FileError - //we need to set langugabe before creating this class! - - wxSize newSize(fastFromDIP(900), fastFromDIP(600)); //default window size - std::optional<wxPoint> newPos; - //set dialog size and position: - // - width/height are invalid if the window is minimized (eg x,y = -32000; width = 160, height = 28) - // - multi-monitor setup: dialog may be placed on second monitor which is currently turned off - if (globalSettings.gui.mainDlg.dlgSize.GetWidth () > 0 && - globalSettings.gui.mainDlg.dlgSize.GetHeight() > 0) - { - //calculate how much of the dialog will be visible on screen - const int dlgArea = globalSettings.gui.mainDlg.dlgSize.GetWidth() * - globalSettings.gui.mainDlg.dlgSize.GetHeight(); - int dlgAreaMaxVisible = 0; - - const int monitorCount = wxDisplay::GetCount(); - for (int i = 0; i < monitorCount; ++i) - { - wxRect intersection = wxDisplay(i).GetClientArea().Intersect(wxRect(globalSettings.gui.mainDlg.dlgPos, globalSettings.gui.mainDlg.dlgSize)); - dlgAreaMaxVisible = std::max(dlgAreaMaxVisible, intersection.GetWidth() * intersection.GetHeight()); - } + //we need to set language before creating this class! - if (dlgAreaMaxVisible > 0.1 * dlgArea //at least 10% of the dialog should be visible! - ) - { - newSize = globalSettings.gui.mainDlg.dlgSize; - newPos = globalSettings.gui.mainDlg.dlgPos; - } - } - - //old comment: "wxGTK's wxWindow::SetSize seems unreliable and behaves like a wxWindow::SetClientSize - // => use wxWindow::SetClientSize instead (for the record: no such issue on Windows/macOS) - //2018-10-15: Weird new problem on CentOS/Ubuntu: SetClientSize() + SetPosition() fail to set correct dialog *position*, but SetSize() + SetPosition() do! - // => old issues with SetSize() seem to be gone... => revert to SetSize() - if (newPos) - SetSize(wxRect(*newPos, newSize)); - else - { - SetSize(newSize); - Center(); - } - - if (globalSettings.gui.mainDlg.isMaximized) //no real need to support both maximize and full screen functions - { - Maximize(true); - } + setInitialWindowSize(*this, + globalSettings.mainDlg.dlgSize, + globalSettings.mainDlg.dlgPos, + globalSettings.mainDlg.isMaximized, + wxSize{fastFromDIP(900), fastFromDIP(600)} /*defaultSize*/); //set column attributes - m_gridMainL ->setColumnConfig(convertColAttributes(globalSettings.gui.mainDlg.columnAttribLeft, getFileGridDefaultColAttribsLeft())); - m_gridMainR ->setColumnConfig(convertColAttributes(globalSettings.gui.mainDlg.columnAttribRight, getFileGridDefaultColAttribsLeft())); - m_splitterMain->setSashOffset(globalSettings.gui.mainDlg.sashOffset); + m_gridMainL ->setColumnConfig(convertColAttributes(globalSettings.mainDlg.columnAttribLeft, getFileGridDefaultColAttribsLeft())); + m_gridMainR ->setColumnConfig(convertColAttributes(globalSettings.mainDlg.columnAttribRight, getFileGridDefaultColAttribsLeft())); + m_splitterMain->setSashOffset(globalSettings.mainDlg.sashOffset); - m_gridOverview->setColumnConfig(convertColAttributes(globalSettings.gui.mainDlg.treeGridColumnAttribs, getTreeGridDefaultColAttribs())); - treegrid::setShowPercentage(*m_gridOverview, globalSettings.gui.mainDlg.treeGridShowPercentBar); + m_gridOverview->setColumnConfig(convertColAttributes(globalSettings.mainDlg.treeGridColumnAttribs, getTreeGridDefaultColAttribs())); + treegrid::setShowPercentage(*m_gridOverview, globalSettings.mainDlg.treeGridShowPercentBar); - treegrid::getDataView(*m_gridOverview).setSortDirection(globalSettings.gui.mainDlg.treeGridLastSortColumn, globalSettings.gui.mainDlg.treeGridLastSortAscending); + treegrid::getDataView(*m_gridOverview).setSortDirection(globalSettings.mainDlg.treeGridLastSortColumn, globalSettings.mainDlg.treeGridLastSortAscending); //-------------------------------------------------------------------------------- //load list of configuration files - cfggrid::getDataView(*m_gridCfgHistory).set(globalSettings.gui.mainDlg.cfgFileHistory); + cfggrid::getDataView(*m_gridCfgHistory).set(globalSettings.mainDlg.cfgFileHistory); - //globalSettings.gui.mainDlg.cfgGridTopRowPos => defer evaluation until later within MainDialog constructor - m_gridCfgHistory->setColumnConfig(convertColAttributes(globalSettings.gui.mainDlg.cfgGridColumnAttribs, getCfgGridDefaultColAttribs())); - cfggrid::getDataView(*m_gridCfgHistory).setSortDirection(globalSettings.gui.mainDlg.cfgGridLastSortColumn, globalSettings.gui.mainDlg.cfgGridLastSortAscending); - cfggrid::setSyncOverdueDays(*m_gridCfgHistory, globalSettings.gui.mainDlg.cfgGridSyncOverdueDays); + //globalSettings.mainDlg.cfgGridTopRowPos => defer evaluation until later within MainDialog constructor + m_gridCfgHistory->setColumnConfig(convertColAttributes(globalSettings.mainDlg.cfgGridColumnAttribs, getCfgGridDefaultColAttribs())); + cfggrid::getDataView(*m_gridCfgHistory).setSortDirection(globalSettings.mainDlg.cfgGridLastSortColumn, globalSettings.mainDlg.cfgGridLastSortAscending); + cfggrid::setSyncOverdueDays(*m_gridCfgHistory, globalSettings.mainDlg.cfgGridSyncOverdueDays); //m_gridCfgHistory->Refresh(); <- implicit in last call //remove non-existent items (we need this only on startup) std::vector<Zstring> cfgFilePaths; - for (const ConfigFileItem& item : globalSettings.gui.mainDlg.cfgFileHistory) + for (const ConfigFileItem& item : globalSettings.mainDlg.cfgFileHistory) cfgFilePaths.push_back(item.cfgFilePath); cfgHistoryRemoveObsolete(cfgFilePaths); @@ -1019,13 +991,13 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings) m_folderPathRight->setHistory(folderHistoryRight_); //show/hide file icons - filegrid::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalSettings.gui.mainDlg.showIcons, convert(globalSettings.gui.mainDlg.iconSize)); + filegrid::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalSettings.mainDlg.showIcons, convert(globalSettings.mainDlg.iconSize)); - filegrid::setItemPathForm(*m_gridMainL, globalSettings.gui.mainDlg.itemPathFormatLeftGrid); - filegrid::setItemPathForm(*m_gridMainR, globalSettings.gui.mainDlg.itemPathFormatRightGrid); + filegrid::setItemPathForm(*m_gridMainL, globalSettings.mainDlg.itemPathFormatLeftGrid); + filegrid::setItemPathForm(*m_gridMainR, globalSettings.mainDlg.itemPathFormatRightGrid); //-------------------------------------------------------------------------------- - m_checkBoxMatchCase->SetValue(globalCfg_.gui.mainDlg.textSearchRespectCase); + m_checkBoxMatchCase->SetValue(globalCfg_.mainDlg.textSearchRespectCase); //wxAuiManager erroneously loads panel captions, we don't want that std::vector<std::pair<wxAuiPaneInfo*, wxString>> paneCaptions; @@ -1044,7 +1016,7 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings) preserveConstraint(auiMgr_.GetPane(m_panelViewFilter)); preserveConstraint(auiMgr_.GetPane(m_panelConfig)); - auiMgr_.LoadPerspective(globalSettings.gui.mainDlg.guiPerspectiveLast, false /*don't call wxAuiManager::Update() yet*/); + auiMgr_.LoadPerspective(globalSettings.mainDlg.guiPerspectiveLast, false /*update: don't call wxAuiManager::Update() yet*/); //restore original captions for (const auto& [paneInfo, caption] : paneCaptions) @@ -1064,7 +1036,7 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings) auiMgr_.GetPane(m_panelSearch).Hide(); //no need to show it on startup auiMgr_.GetPane(m_panelLog ).Hide(); // - m_menuItemCheckVersionAuto->Check(updateCheckActive(globalCfg_.gui.lastUpdateCheck)); + m_menuItemCheckVersionAuto->Check(updateCheckActive(globalCfg_.lastUpdateCheck)); auiMgr_.Update(); } @@ -1080,16 +1052,16 @@ XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit() globalSettings.programLanguage = getLanguage(); //retrieve column attributes - globalSettings.gui.mainDlg.columnAttribLeft = convertColAttributes<ColAttributesRim>(m_gridMainL->getColumnConfig()); - globalSettings.gui.mainDlg.columnAttribRight = convertColAttributes<ColAttributesRim>(m_gridMainR->getColumnConfig()); - globalSettings.gui.mainDlg.sashOffset = m_splitterMain->getSashOffset(); + globalSettings.mainDlg.columnAttribLeft = convertColAttributes<ColAttributesRim>(m_gridMainL->getColumnConfig()); + globalSettings.mainDlg.columnAttribRight = convertColAttributes<ColAttributesRim>(m_gridMainR->getColumnConfig()); + globalSettings.mainDlg.sashOffset = m_splitterMain->getSashOffset(); - globalSettings.gui.mainDlg.treeGridColumnAttribs = convertColAttributes<ColAttributesTree>(m_gridOverview->getColumnConfig()); - globalSettings.gui.mainDlg.treeGridShowPercentBar = treegrid::getShowPercentage(*m_gridOverview); + globalSettings.mainDlg.treeGridColumnAttribs = convertColAttributes<ColAttributesTree>(m_gridOverview->getColumnConfig()); + globalSettings.mainDlg.treeGridShowPercentBar = treegrid::getShowPercentage(*m_gridOverview); const auto [sortCol, ascending] = treegrid::getDataView(*m_gridOverview).getSortConfig(); - globalSettings.gui.mainDlg.treeGridLastSortColumn = sortCol; - globalSettings.gui.mainDlg.treeGridLastSortAscending = ascending; + globalSettings.mainDlg.treeGridLastSortColumn = sortCol; + globalSettings.mainDlg.treeGridLastSortAscending = ascending; //-------------------------------------------------------------------------------- //write list of configuration files @@ -1107,24 +1079,24 @@ XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit() cfgHistory.push_back(item); //trim excess elements (oldest first) - if (cfgHistory.size() > globalSettings.gui.mainDlg.cfgHistItemsMax) - cfgHistory.resize(globalSettings.gui.mainDlg.cfgHistItemsMax); + if (cfgHistory.size() > globalSettings.mainDlg.cfgHistItemsMax) + cfgHistory.resize(globalSettings.mainDlg.cfgHistItemsMax); - globalSettings.gui.mainDlg.cfgFileHistory = std::move(cfgHistory); - globalSettings.gui.mainDlg.cfgGridTopRowPos = m_gridCfgHistory->getTopRow(); - globalSettings.gui.mainDlg.cfgGridColumnAttribs = convertColAttributes<ColAttributesCfg>(m_gridCfgHistory->getColumnConfig()); - globalSettings.gui.mainDlg.cfgGridSyncOverdueDays = cfggrid::getSyncOverdueDays(*m_gridCfgHistory); + globalSettings.mainDlg.cfgFileHistory = std::move(cfgHistory); + globalSettings.mainDlg.cfgGridTopRowPos = m_gridCfgHistory->getTopRow(); + globalSettings.mainDlg.cfgGridColumnAttribs = convertColAttributes<ColAttributesCfg>(m_gridCfgHistory->getColumnConfig()); + globalSettings.mainDlg.cfgGridSyncOverdueDays = cfggrid::getSyncOverdueDays(*m_gridCfgHistory); - std::tie(globalSettings.gui.mainDlg.cfgGridLastSortColumn, - globalSettings.gui.mainDlg.cfgGridLastSortAscending) = cfggrid::getDataView(*m_gridCfgHistory).getSortDirection(); + std::tie(globalSettings.mainDlg.cfgGridLastSortColumn, + globalSettings.mainDlg.cfgGridLastSortAscending) = cfggrid::getDataView(*m_gridCfgHistory).getSortDirection(); //-------------------------------------------------------------------------------- - globalSettings.gui.mainDlg.cfgFilesLastUsed = activeConfigFiles_; + globalSettings.mainDlg.cfgFilesLastUsed = activeConfigFiles_; //write list of last used folders - globalSettings.gui.mainDlg.folderHistoryLeft = folderHistoryLeft_ ->getList(); - globalSettings.gui.mainDlg.folderHistoryRight = folderHistoryRight_->getList(); + globalSettings.mainDlg.folderHistoryLeft = folderHistoryLeft_ ->getList(); + globalSettings.mainDlg.folderHistoryRight = folderHistoryRight_->getList(); - globalSettings.gui.mainDlg.textSearchRespectCase = m_checkBoxMatchCase->GetValue(); + globalSettings.mainDlg.textSearchRespectCase = m_checkBoxMatchCase->GetValue(); wxAuiPaneInfo& logPane = auiMgr_.GetPane(m_panelLog); if (logPane.IsShown()) @@ -1138,31 +1110,15 @@ XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit() else //wxAUI does not store size of hidden panels => show it (properly!) showLogPanel(true /*show*/); - globalSettings.gui.mainDlg.guiPerspectiveLast = auiMgr_.SavePerspective(); - - //we need to portably retrieve non-iconized, non-maximized size and position (non-portable: GetWindowPlacement()) - //call *after* wxAuiManager::SavePerspective()! - if (IsIconized()) - Iconize(false); - - globalSettings.gui.mainDlg.isMaximized = false; - if (IsMaximized()) //evaluate AFTER uniconizing! - { - globalSettings.gui.mainDlg.isMaximized = true; - Maximize(false); - } + globalSettings.mainDlg.guiPerspectiveLast = auiMgr_.SavePerspective(); - globalSettings.gui.mainDlg.dlgSize = GetSize(); - globalSettings.gui.mainDlg.dlgPos = GetPosition(); + const auto& [size, pos, isMaximized] = getWindowSizeBeforeClose(*this); //call *after* wxAuiManager::SavePerspective()! + if (size) + globalSettings.mainDlg.dlgSize = *size; + if (pos) + globalSettings.mainDlg.dlgPos = *pos; + globalSettings.mainDlg.isMaximized = isMaximized; - //wxGTK: returns full screen size and strange position (65/-4) - //OS X 10.9 (but NO issue on 10.11!) returns full screen size and strange position (0/-22) - if (globalSettings.gui.mainDlg.isMaximized) - if (globalSettings.gui.mainDlg.dlgPos.y < 0) - { - globalSettings.gui.mainDlg.dlgSize = wxSize(); - globalSettings.gui.mainDlg.dlgPos = wxPoint(); - } return globalSettings; } @@ -1353,30 +1309,30 @@ void MainDialog::copyToAlternateFolder(const std::vector<FileSystemObject*>& sel if (showCopyToDialog(this, selectionLeft, selectionRight, - globalCfg_.gui.mainDlg.copyToCfg.targetFolderPath, - globalCfg_.gui.mainDlg.copyToCfg.targetFolderLastSelected, - globalCfg_.gui.mainDlg.copyToCfg.folderHistory, globalCfg_.gui.folderHistoryMax, - globalCfg_.gui.sftpKeyFileLastSelected, - globalCfg_.gui.mainDlg.copyToCfg.keepRelPaths, - globalCfg_.gui.mainDlg.copyToCfg.overwriteIfExists) != ConfirmationButton::accept) + globalCfg_.mainDlg.copyToCfg.targetFolderPath, + globalCfg_.mainDlg.copyToCfg.targetFolderLastSelected, + globalCfg_.mainDlg.copyToCfg.folderHistory, globalCfg_.folderHistoryMax, + globalCfg_.sftpKeyFileLastSelected, + globalCfg_.mainDlg.copyToCfg.keepRelPaths, + globalCfg_.mainDlg.copyToCfg.overwriteIfExists) != ConfirmationButton::accept) return; - disableAllElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! + disableGuiElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! 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 + ZEN_ON_SCOPE_EXIT(app->Yield(); enableGuiElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks const auto& guiCfg = getConfig(); StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/, false /*ignoreErrors*/, - guiCfg.mainCfg.automaticRetryCount, - guiCfg.mainCfg.automaticRetryDelay); //handle status display and error messages + guiCfg.mainCfg.autoRetryCount, + guiCfg.mainCfg.autoRetryDelay); //handle status display and error messages try { fff::copyToAlternateFolder(selectionLeft, selectionRight, - globalCfg_.gui.mainDlg.copyToCfg.targetFolderPath, - globalCfg_.gui.mainDlg.copyToCfg.keepRelPaths, - globalCfg_.gui.mainDlg.copyToCfg.overwriteIfExists, + globalCfg_.mainDlg.copyToCfg.targetFolderPath, + globalCfg_.mainDlg.copyToCfg.keepRelPaths, + globalCfg_.mainDlg.copyToCfg.overwriteIfExists, globalCfg_.warnDlgs, statusHandler); //throw AbortProcess @@ -1385,7 +1341,6 @@ void MainDialog::copyToAlternateFolder(const std::vector<FileSystemObject*>& sel catch (AbortProcess&) {} const StatusHandlerTemporaryPanel::Result r = statusHandler.reportResults(); //noexcept - setLastOperationLog(r.summary, r.errorLog); //updateGui(); -> not needed @@ -1405,17 +1360,17 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec moveToRecycler) != ConfirmationButton::accept) return; - disableAllElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! + disableGuiElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! 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 + ZEN_ON_SCOPE_EXIT(app->Yield(); enableGuiElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks //wxBusyCursor dummy; -> redundant: progress already shown in status bar! const auto& guiCfg = getConfig(); StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/, false /*ignoreErrors*/, - guiCfg.mainCfg.automaticRetryCount, - guiCfg.mainCfg.automaticRetryDelay); //handle status display and error messages + guiCfg.mainCfg.autoRetryCount, + guiCfg.mainCfg.autoRetryDelay); //handle status display and error messages try { deleteFromGridAndHD(selectionLeft, selectionRight, @@ -1427,7 +1382,6 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec catch (AbortProcess&) {} const StatusHandlerTemporaryPanel::Result r = statusHandler.reportResults(); //noexcept - setLastOperationLog(r.summary, r.errorLog); //remove rows that are empty: just a beautification, invalid rows shouldn't cause issues @@ -1539,7 +1493,7 @@ void invokeCommandLine(const Zstring& commandLinePhrase, //throw FileError { std::optional<int> timeoutMs; if (selection.size() <= EXT_APP_MASS_INVOKE_THRESHOLD) - timeoutMs = EXT_APP_MAX_TOTAL_WAIT_TIME_MS / EXT_APP_MASS_INVOKE_THRESHOLD; //run async, but give consoleExecute() some "time to fail" + timeoutMs = EXT_APP_MAX_TOTAL_WAIT_TIME_MS / selection.size(); //run async, but give consoleExecute() some "time to fail" //else: run synchronously if (const auto& [exitCode, output] = consoleExecute(cmdLine, timeoutMs); //throw SysError, SysErrorTimeOut @@ -1557,7 +1511,7 @@ void MainDialog::openExternalApplication(const Zstring& commandLinePhrase, bool const std::vector<FileSystemObject*>& selectionLeft, const std::vector<FileSystemObject*>& selectionRight) { - const XmlGlobalSettings::Gui defaultCfg; + const XmlGlobalSettings defaultCfg; assert(defaultCfg.externalApps.size() >= 2); const bool showInFileBrowserRequested = commandLinePhrase == defaultCfg.externalApps[0].cmdLine; const bool openWithDefaultAppRequested = commandLinePhrase == defaultCfg.externalApps[1].cmdLine; @@ -1639,16 +1593,16 @@ void MainDialog::openExternalApplication(const Zstring& commandLinePhrase, bool { FocusPreserver fp; - disableAllElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! + disableGuiElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! 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 + ZEN_ON_SCOPE_EXIT(app->Yield(); enableGuiElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks const auto& guiCfg = getConfig(); StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/, false /*ignoreErrors*/, - guiCfg.mainCfg.automaticRetryCount, - guiCfg.mainCfg.automaticRetryDelay); //handle status display and error messages + guiCfg.mainCfg.autoRetryCount, + guiCfg.mainCfg.autoRetryDelay); //handle status display and error messages try { tempFileBuf_.createTempFiles(nonNativeFiles, statusHandler); //throw AbortProcess @@ -1657,7 +1611,6 @@ void MainDialog::openExternalApplication(const Zstring& commandLinePhrase, bool catch (AbortProcess&) {} const StatusHandlerTemporaryPanel::Result r = statusHandler.reportResults(); //noexcept - setLastOperationLog(r.summary, r.errorLog); if (r.summary.syncResult == SyncResult::aborted) @@ -1711,8 +1664,10 @@ void MainDialog::flashStatusInformation(const wxString& text) } -void MainDialog::disableAllElements(bool enableAbort) +void MainDialog::disableGuiElements(bool enableAbort) { + assert(!operationInProgress_); + //disables all elements (except abort button) that might receive user input during long-running processes: //when changing consider: comparison, synchronization, manual deletion @@ -1724,7 +1679,7 @@ void MainDialog::disableAllElements(bool enableAbort) //- OS X: Quit/Preferences menu items still enabled during sync, // ([[m_macWindow standardWindowButton:NSWindowCloseButton] setEnabled:enable]) does not stick after calling Maximize() ([m_macWindow zoom:nil]) //- Linux: it just works! :) - allowMainDialogClose_ = false; + operationInProgress_ = true; localKeyEventsEnabled_ = false; @@ -1764,12 +1719,14 @@ void MainDialog::disableAllElements(bool enableAbort) } -void MainDialog::enableAllElements() +void MainDialog::enableGuiElements() { + assert(operationInProgress_ && !localKeyEventsEnabled_); //disableGuiElements() not called? => WTF! + //wxGTK, yet another QOI issue: some stupid bug keeps moving main dialog to top!! EnableCloseButton(true); - allowMainDialogClose_ = true; + operationInProgress_ = false; localKeyEventsEnabled_ = true; @@ -2000,9 +1957,9 @@ void MainDialog::onGridKeyEvent(wxKeyEvent& event, Grid& grid, bool leftSide) return static_cast<size_t>(-1); }(); - if (extAppPos < globalCfg_.gui.externalApps.size()) + if (extAppPos < globalCfg_.externalApps.size()) { - openExternalApplication(globalCfg_.gui.externalApps[extAppPos].cmdLine, leftSide, selectionLeft, selectionRight); + openExternalApplication(globalCfg_.externalApps[extAppPos].cmdLine, leftSide, selectionLeft, selectionRight); return; } @@ -2303,7 +2260,7 @@ void MainDialog::onTreeGridContext(GridContextMenuEvent& event) //menu.addItem(_("&Copy to...") + L"\tCtrl+T", [&] { copyToAlternateFolder(selection, selection); }, wxNullImage, haveNonEmptyItems); //---------------------------------------------------------------------------------------------------- menu.addSeparator(); - menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selection, selection, true /*moveToRecycler*/); }, wxNullImage, haveNonEmptyItems); + menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selection, selection, true /*moveToRecycler*/); }, imgTrashSmall_, haveNonEmptyItems); menu.popup(*m_gridOverview); } @@ -2449,22 +2406,22 @@ void MainDialog::onGridContextRim(const std::vector<FileSystemObject*>& selectio menu.addSeparator(); menu.addItem(_("&Synchronize selection") + L"\tEnter", [&] { startSyncForSelecction(selection); }, loadImage("start_sync_selection_sicon"), selectionContainsItemsToSync); //---------------------------------------------------------------------------------------------------- - if (!globalCfg_.gui.externalApps.empty()) + if (!globalCfg_.externalApps.empty()) { menu.addSeparator(); - for (auto it = globalCfg_.gui.externalApps.begin(); - it != globalCfg_.gui.externalApps.end(); + for (auto it = globalCfg_.externalApps.begin(); + it != globalCfg_.externalApps.end(); ++it) { - //translate default external apps on the fly: 1. "open in explorer" 2. "start directly" + //translate default external apps on the fly: 1. "Show in Explorer" 2. "Open with default application" wxString description = translate(it->description); if (description.empty()) description = L' '; //wxWidgets doesn't like empty labels auto openApp = [this, command = it->cmdLine, leftSide, &selectionLeft, &selectionRight] { openExternalApplication(command, leftSide, selectionLeft, selectionRight); }; - const size_t pos = it - globalCfg_.gui.externalApps.begin(); + const size_t pos = it - globalCfg_.externalApps.begin(); if (pos == 0) description += L"\tD-Click, 0"; @@ -2482,7 +2439,7 @@ void MainDialog::onGridContextRim(const std::vector<FileSystemObject*>& selectio menu.addItem(_("&Copy to...") + L"\tCtrl+T", [&] { copyToAlternateFolder(selectionLeft, selectionRight); }, wxNullImage, haveNonEmptyItemsL || haveNonEmptyItemsR); //---------------------------------------------------------------------------------------------------- menu.addSeparator(); - menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selectionLeft, selectionRight, true /*moveToRecycler*/); }, wxNullImage, haveNonEmptyItemsL || haveNonEmptyItemsR); + menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selectionLeft, selectionRight, true /*moveToRecycler*/); }, imgTrashSmall_, haveNonEmptyItemsL || haveNonEmptyItemsR); menu.popup(leftSide ? *m_gridMainL : *m_gridMainR); } @@ -2629,7 +2586,7 @@ void MainDialog::onGridLabelContextRim(GridLabelClickEvent& event, bool leftSide //---------------------------------------------------------------------------------------------- menu.addSeparator(); - auto& itemPathFormat = leftSide ? globalCfg_.gui.mainDlg.itemPathFormatLeftGrid : globalCfg_.gui.mainDlg.itemPathFormatRightGrid; + auto& itemPathFormat = leftSide ? globalCfg_.mainDlg.itemPathFormatLeftGrid : globalCfg_.mainDlg.itemPathFormatRightGrid; auto setItemPathFormat = [&](ItemPathFormat fmt) { @@ -2649,29 +2606,29 @@ void MainDialog::onGridLabelContextRim(GridLabelClickEvent& event, bool leftSide auto setIconSize = [&](FileIconSize sz, bool showIcons) { - globalCfg_.gui.mainDlg.iconSize = sz; - globalCfg_.gui.mainDlg.showIcons = showIcons; - filegrid::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg_.gui.mainDlg.showIcons, convert(globalCfg_.gui.mainDlg.iconSize)); + globalCfg_.mainDlg.iconSize = sz; + globalCfg_.mainDlg.showIcons = showIcons; + filegrid::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg_.mainDlg.showIcons, convert(globalCfg_.mainDlg.iconSize)); }; auto setDefault = [&] { const XmlGlobalSettings defaultCfg; - grid.setColumnConfig(convertColAttributes(leftSide ? defaultCfg.gui.mainDlg.columnAttribLeft : defaultCfg.gui.mainDlg.columnAttribRight, defaultCfg.gui.mainDlg.columnAttribLeft)); + grid.setColumnConfig(convertColAttributes(leftSide ? defaultCfg.mainDlg.columnAttribLeft : defaultCfg.mainDlg.columnAttribRight, defaultCfg.mainDlg.columnAttribLeft)); - setItemPathFormat(leftSide ? defaultCfg.gui.mainDlg.itemPathFormatLeftGrid : defaultCfg.gui.mainDlg.itemPathFormatRightGrid); + setItemPathFormat(leftSide ? defaultCfg.mainDlg.itemPathFormatLeftGrid : defaultCfg.mainDlg.itemPathFormatRightGrid); - setIconSize(defaultCfg.gui.mainDlg.iconSize, defaultCfg.gui.mainDlg.showIcons); + setIconSize(defaultCfg.mainDlg.iconSize, defaultCfg.mainDlg.showIcons); }; menu.addItem(_("&Default"), setDefault); //'&' -> reuse text from "default" buttons elsewhere //---------------------------------------------------------------------------------------------- menu.addSeparator(); - menu.addCheckBox(_("Show icons:"), [&] { setIconSize(globalCfg_.gui.mainDlg.iconSize, !globalCfg_.gui.mainDlg.showIcons); }, globalCfg_.gui.mainDlg.showIcons); + menu.addCheckBox(_("Show icons:"), [&] { setIconSize(globalCfg_.mainDlg.iconSize, !globalCfg_.mainDlg.showIcons); }, globalCfg_.mainDlg.showIcons); auto addSizeEntry = [&](const wxString& label, FileIconSize sz) { - menu.addRadio(label, [sz, &setIconSize] { setIconSize(sz, true /*showIcons*/); }, globalCfg_.gui.mainDlg.iconSize == sz, globalCfg_.gui.mainDlg.showIcons); + menu.addRadio(label, [sz, &setIconSize] { setIconSize(sz, true /*showIcons*/); }, globalCfg_.mainDlg.iconSize == sz, globalCfg_.mainDlg.showIcons); }; addSizeEntry(L" " + _("Small" ), FileIconSize::small ); addSizeEntry(L" " + _("Medium"), FileIconSize::medium); @@ -2972,7 +2929,7 @@ bool MainDialog::trySaveConfig(const Zstring* guiCfgPath) //return true if saved std::optional<Zstring> defaultFolderPath = getParentFolderPath(activeCfgFilePath); if (!defaultFolderPath) - defaultFolderPath = getParentFolderPath(globalCfg_.gui.mainDlg.cfgFileLastSelected); + defaultFolderPath = getParentFolderPath(globalCfg_.mainDlg.cfgFileLastSelected); Zstring defaultFileName = afterLast(activeCfgFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); if (defaultFileName.empty()) @@ -2986,7 +2943,7 @@ bool MainDialog::trySaveConfig(const Zstring* guiCfgPath) //return true if saved wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (fileSelector.ShowModal() != wxID_OK) return false; - cfgFilePath = globalCfg_.gui.mainDlg.cfgFileLastSelected = utfTo<Zstring>(fileSelector.GetPath()); + cfgFilePath = globalCfg_.mainDlg.cfgFileLastSelected = utfTo<Zstring>(fileSelector.GetPath()); } const XmlGuiConfig guiCfg = getConfig(); @@ -3058,7 +3015,7 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchCfgPath) std::optional<Zstring> defaultFolderPath = getParentFolderPath(activeCfgFilePath); if (!defaultFolderPath) - defaultFolderPath = getParentFolderPath(globalCfg_.gui.mainDlg.cfgFileLastSelected); + defaultFolderPath = getParentFolderPath(globalCfg_.mainDlg.cfgFileLastSelected); Zstring defaultFileName = afterLast(activeCfgFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); if (defaultFileName.empty()) @@ -3072,7 +3029,7 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchCfgPath) wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (fileSelector.ShowModal() != wxID_OK) return false; - cfgFilePath = globalCfg_.gui.mainDlg.cfgFileLastSelected = utfTo<Zstring>(fileSelector.GetPath()); + cfgFilePath = globalCfg_.mainDlg.cfgFileLastSelected = utfTo<Zstring>(fileSelector.GetPath()); } const XmlGuiConfig guiCfg = getConfig(); @@ -3158,7 +3115,7 @@ bool MainDialog::saveOldConfig() //return false on user abort void MainDialog::onConfigLoad(wxCommandEvent& event) { - std::optional<Zstring> defaultFolderPath = getParentFolderPath(globalCfg_.gui.mainDlg.cfgFileLastSelected); + std::optional<Zstring> defaultFolderPath = getParentFolderPath(globalCfg_.mainDlg.cfgFileLastSelected); wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), wxString() /*default file name*/, wxString(L"FreeFileSync (*.ffs_gui; *.ffs_batch)|*.ffs_gui;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", @@ -3174,7 +3131,7 @@ void MainDialog::onConfigLoad(wxCommandEvent& event) filePaths.push_back(utfTo<Zstring>(path)); if (!filePaths.empty()) - globalCfg_.gui.mainDlg.cfgFileLastSelected = filePaths[0]; + globalCfg_.mainDlg.cfgFileLastSelected = filePaths[0]; assert(!filePaths.empty()); loadConfiguration(filePaths); @@ -3229,7 +3186,7 @@ bool MainDialog::loadConfiguration(const std::vector<Zstring>& filePaths) Zstring& excludeFilter = newGuiCfg.mainCfg.globalFilter.excludeFilter; if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr('\n'))) excludeFilter += Zstr('\n'); - excludeFilter += globalCfg_.gui.defaultExclusionFilter; + excludeFilter += globalCfg_.defaultExclusionFilter; if (!filePaths.empty()) //empty cfg file list means "use default" try @@ -3403,7 +3360,7 @@ void MainDialog::onCfgGridContext(GridContextMenuEvent& event) { if (!selectedRows.empty()) if (const ConfigView::Details* cfg = cfggrid::getDataView(*m_gridCfgHistory).getItem(selectedRows[0])) - return !cfg->isLastRunCfg; + return !cfg->isLastRunCfg; return false; }(); menu.addItem(_("&Rename...") + L"\tF2", [this] { renameSelectedCfgHistoryItem (); }, wxNullImage, renameEnabled); @@ -3447,7 +3404,39 @@ void MainDialog::onCfgGridContext(GridContextMenuEvent& event) addColorOption({ 0xdd, 0xdd, 0xdd }, _("Grey")); menu.addSubmenu(_("Background color"), submenu, loadImage("color_sicon"), !selectedRows.empty()); + menu.addSeparator(); //-------------------------------------------------------------------------------------------------------- + warn_static("review + add option to delete/trash?") + const XmlGlobalSettings defaultCfg; + assert(!defaultCfg.externalApps.empty()); + + auto showInFileManager = [&] + { + if (!selectedRows.empty()) + if (const ConfigView::Details* cfg = cfggrid::getDataView(*m_gridCfgHistory).getItem(selectedRows[0])) + { + const Zstring commandLinePhrase = defaultCfg.externalApps[0].cmdLine; + const Zstring cmdLine = replaceCpy(expandMacros(commandLinePhrase), Zstr("%local_path%"), cfg->cfgItem.cfgFilePath); + try + { + if (const auto& [exitCode, output] = consoleExecute(cmdLine, EXT_APP_MAX_TOTAL_WAIT_TIME_MS); //throw SysError, SysErrorTimeOut + exitCode != 0) + throw SysError(formatSystemError(utfTo<std::string>(commandLinePhrase), replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output)); + } + catch (SysErrorTimeOut&) {} //child process not failed yet => probably fine :> + catch (const SysError& e) + { + const std::wstring errorMsg = replaceCpy(_("Command %x failed."), L"%x", fmtPath(cmdLine)) + L"\n\n" + e.toString(); + showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(errorMsg)); + } + return; + } + assert(false); + }; + menu.addItem(translate(defaultCfg.externalApps[0].description), //translate default external apps on the fly: "Show in Explorer" + showInFileManager, wxNullImage, !selectedRows.empty()); + //-------------------------------------------------------------------------------------------------------- + menu.addSeparator(); menu.addItem(_("Hide configuration") + L"\tDel", [this] { deleteSelectedCfgHistoryItems(); }, wxNullImage, !selectedRows.empty()); //-------------------------------------------------------------------------------------------------------- menu.popup(*m_gridCfgHistory); @@ -3497,7 +3486,7 @@ void MainDialog::onCfgGridLabelContext(GridLabelClickEvent& event) auto setDefault = [&] { const XmlGlobalSettings defaultCfg; - m_gridCfgHistory->setColumnConfig(convertColAttributes(defaultCfg.gui.mainDlg.cfgGridColumnAttribs, getCfgGridDefaultColAttribs())); + m_gridCfgHistory->setColumnConfig(convertColAttributes(defaultCfg.mainDlg.cfgGridColumnAttribs, getCfgGridDefaultColAttribs())); }; menu.addItem(_("&Default"), setDefault); //'&' -> reuse text from "default" buttons elsewhere //-------------------------------------------------------------------------------------------------------- @@ -3653,8 +3642,8 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde globalPairCfg.miscCfg.deviceParallelOps = currentCfg_.mainCfg.deviceParallelOps; globalPairCfg.miscCfg.ignoreErrors = currentCfg_.mainCfg.ignoreErrors; - globalPairCfg.miscCfg.automaticRetryCount = currentCfg_.mainCfg.automaticRetryCount; - globalPairCfg.miscCfg.automaticRetryDelay = currentCfg_.mainCfg.automaticRetryDelay; + globalPairCfg.miscCfg.autoRetryCount = currentCfg_.mainCfg.autoRetryCount; + globalPairCfg.miscCfg.autoRetryDelay = currentCfg_.mainCfg.autoRetryDelay; globalPairCfg.miscCfg.postSyncCommand = currentCfg_.mainCfg.postSyncCommand; globalPairCfg.miscCfg.postSyncCondition = currentCfg_.mainCfg.postSyncCondition; globalPairCfg.miscCfg.altLogFolderPathPhrase = currentCfg_.mainCfg.altLogFolderPathPhrase; @@ -3686,12 +3675,12 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde showMultipleCfgs, globalPairCfg, localCfgs, - globalCfg_.gui.versioningFolderHistory, globalCfg_.gui.versioningFolderLastSelected, - globalCfg_.gui.logFolderHistory, globalCfg_.gui.logFolderLastSelected, - globalCfg_.gui.folderHistoryMax, - globalCfg_.gui.sftpKeyFileLastSelected, - globalCfg_.gui.emailHistory, globalCfg_.gui.emailHistoryMax, - globalCfg_.gui.commandHistory, globalCfg_.gui.commandHistoryMax) != ConfirmationButton::accept) + globalCfg_.versioningFolderHistory, globalCfg_.versioningFolderLastSelected, + globalCfg_.logFolderHistory, globalCfg_.logFolderLastSelected, + globalCfg_.folderHistoryMax, + globalCfg_.sftpKeyFileLastSelected, + globalCfg_.emailHistory, globalCfg_.emailHistoryMax, + globalCfg_.commandHistory, globalCfg_.commandHistoryMax) != ConfirmationButton::accept) return; assert(localCfgs.size() == localPairCfgOld.size()); @@ -3702,8 +3691,8 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde currentCfg_.mainCfg.deviceParallelOps = globalPairCfg.miscCfg.deviceParallelOps; currentCfg_.mainCfg.ignoreErrors = globalPairCfg.miscCfg.ignoreErrors; - currentCfg_.mainCfg.automaticRetryCount = globalPairCfg.miscCfg.automaticRetryCount; - currentCfg_.mainCfg.automaticRetryDelay = globalPairCfg.miscCfg.automaticRetryDelay; + currentCfg_.mainCfg.autoRetryCount = globalPairCfg.miscCfg.autoRetryCount; + currentCfg_.mainCfg.autoRetryDelay = globalPairCfg.miscCfg.autoRetryDelay; currentCfg_.mainCfg.postSyncCommand = globalPairCfg.miscCfg.postSyncCommand; currentCfg_.mainCfg.postSyncCondition = globalPairCfg.miscCfg.postSyncCondition; currentCfg_.mainCfg.altLogFolderPathPhrase = globalPairCfg.miscCfg.altLogFolderPathPhrase; @@ -3745,8 +3734,8 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde //const bool miscConfigChanged = globalPairCfg.miscCfg.deviceParallelOps != globalPairCfgOld.miscCfg.deviceParallelOps || // globalPairCfg.miscCfg.ignoreErrors != globalPairCfgOld.miscCfg.ignoreErrors || - // globalPairCfg.miscCfg.automaticRetryCount != globalPairCfgOld.miscCfg.automaticRetryCount || - // globalPairCfg.miscCfg.automaticRetryDelay != globalPairCfgOld.miscCfg.automaticRetryDelay || + // globalPairCfg.miscCfg.autoRetryCount != globalPairCfgOld.miscCfg.autoRetryCount || + // globalPairCfg.miscCfg.autoRetryDelay != globalPairCfgOld.miscCfg.autoRetryDelay || // globalPairCfg.miscCfg.postSyncCommand != globalPairCfgOld.miscCfg.postSyncCommand || // globalPairCfg.miscCfg.postSyncCondition != globalPairCfgOld.miscCfg.postSyncCondition || // globalPairCfg.miscCfg.altLogFolderPathPhrase != globalPairCfgOld.miscCfg.altLogFolderPathPhrase || @@ -3820,7 +3809,7 @@ void MainDialog::setViewFilterDefault() { auto setButton = [](ToggleButton& tb, bool value) { tb.setActive(value); }; - const auto& def = globalCfg_.gui.mainDlg.viewFilterDefault; + const auto& def = globalCfg_.mainDlg.viewFilterDefault; setButton(*m_bpButtonShowExcluded, def.excluded); setButton(*m_bpButtonShowEqual, def.equal); setButton(*m_bpButtonShowConflict, def.conflict); @@ -3865,7 +3854,7 @@ void MainDialog::onViewFilterContext(wxEvent& event) auto saveDefault = [&] { - auto& def = globalCfg_.gui.mainDlg.viewFilterDefault; + auto& def = globalCfg_.mainDlg.viewFilterDefault; saveButtonDefault(*m_bpButtonShowExcluded, def.excluded); saveButtonDefault(*m_bpButtonShowEqual, def.equal); saveButtonDefault(*m_bpButtonShowConflict, def.conflict); @@ -3917,11 +3906,11 @@ void MainDialog::onCompare(wxCommandEvent& event) m_gridMainR->Scroll(scrollPosX, scrollPosY); //restore m_gridMainC->Scroll(-1, scrollPosY); ); // - clearGrid(); //avoid memory peak by clearing old data first - - disableAllElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! + disableGuiElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! 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 + ZEN_ON_SCOPE_EXIT(app->Yield(); enableGuiElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks + + clearGrid(); //avoid memory peak by clearing old data first const auto& guiCfg = getConfig(); const std::chrono::system_clock::time_point startTime = std::chrono::system_clock::now(); @@ -3931,8 +3920,8 @@ void MainDialog::onCompare(wxCommandEvent& event) //handle status display and error messages StatusHandlerTemporaryPanel statusHandler(*this, startTime, guiCfg.mainCfg.ignoreErrors, - guiCfg.mainCfg.automaticRetryCount, - guiCfg.mainCfg.automaticRetryDelay); + guiCfg.mainCfg.autoRetryCount, + guiCfg.mainCfg.autoRetryDelay); try { //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization @@ -4167,17 +4156,18 @@ void MainDialog::onStartSync(wxCommandEvent& event) using FinalRequest = StatusHandlerFloatingDialog::FinalRequest; FinalRequest finalRequest = FinalRequest::none; { - disableAllElements(false /*enableAbort*/); //StatusHandlerFloatingDialog will internally process Window messages, so avoid unexpected callbacks! - ZEN_ON_SCOPE_EXIT(enableAllElements()); - //run this->enableAllElements() BEFORE "exitRequest" buf AFTER StatusHandlerFloatingDialog::reportResults() + disableGuiElements(false /*enableAbort*/); //StatusHandlerFloatingDialog will internally process Window messages, so avoid unexpected callbacks! + ZEN_ON_SCOPE_EXIT(enableGuiElements()); + //run this->enableGuiElements() BEFORE "finalRequest" buf AFTER StatusHandlerFloatingDialog::reportResults() //class handling status updates and error messages StatusHandlerFloatingDialog statusHandler(this, jobNames, syncStartTime, guiCfg.mainCfg.ignoreErrors, - guiCfg.mainCfg.automaticRetryCount, - guiCfg.mainCfg.automaticRetryDelay, + guiCfg.mainCfg.autoRetryCount, + guiCfg.mainCfg.autoRetryDelay, globalCfg_.soundFileSyncFinished, - globalCfg_.autoCloseProgressDialog); + globalCfg_.progressDlg.dlgSize, globalCfg_.progressDlg.isMaximized, + globalCfg_.progressDlg.autoClose); try { //PERF_START; @@ -4223,9 +4213,12 @@ void MainDialog::onStartSync(wxCommandEvent& event) guiCfg.mainCfg.altLogFolderPathPhrase, globalCfg_.logfilesMaxAgeDays, globalCfg_.logFormat, logFilePathsToKeep, guiCfg.mainCfg.emailNotifyAddress, guiCfg.mainCfg.emailNotifyCondition); //noexcept //--------------------------------------------------------------------------- - setLastOperationLog(r.summary, r.errorLog.ptr()); + globalCfg_.progressDlg.autoClose = r.autoCloseDialog; + globalCfg_.progressDlg.dlgSize = r.dlgSize; + globalCfg_.progressDlg.isMaximized = r.dlgIsMaximized; + //update last sync stats for the selected cfg files updateConfigLastRunStats(std::chrono::system_clock::to_time_t(syncStartTime), r.summary.syncResult, r.logFilePath); @@ -4375,14 +4368,14 @@ void MainDialog::startSyncForSelecction(const std::vector<FileSystemObject*>& se //last sync log file? => let's go without; same behavior as manual deletion - disableAllElements(true /*enableAbort*/); //StatusHandlerFloatingDialog will internally process Window messages, so avoid unexpected callbacks! + disableGuiElements(true /*enableAbort*/); //StatusHandlerFloatingDialog will internally process Window messages, so avoid unexpected callbacks! 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 + ZEN_ON_SCOPE_EXIT(app->Yield(); enableGuiElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks StatusHandlerTemporaryPanel statusHandler(*this, syncStartTime, guiCfg.mainCfg.ignoreErrors, - guiCfg.mainCfg.automaticRetryCount, - guiCfg.mainCfg.automaticRetryDelay); //handle status display and error messages + guiCfg.mainCfg.autoRetryCount, + guiCfg.mainCfg.autoRetryDelay); //handle status display and error messages try { //let's report here rather than before comparison (user might have changed global settings in the meantime!) @@ -4565,14 +4558,14 @@ void MainDialog::showLogPanel(bool show) void MainDialog::onGridDoubleClickRim(GridClickEvent& event, bool leftSide) { - if (!globalCfg_.gui.externalApps.empty()) + if (!globalCfg_.externalApps.empty()) { std::vector<FileSystemObject*> selectionLeft; std::vector<FileSystemObject*> selectionRight; if (FileSystemObject* fsObj = filegrid::getDataView(*m_gridMainC).getFsObject(event.row_)) //selection must be a list of BOUND pointers! (leftSide ? selectionLeft : selectionRight) = { fsObj }; - openExternalApplication(globalCfg_.gui.externalApps[0].cmdLine, leftSide, selectionLeft, selectionRight); + openExternalApplication(globalCfg_.externalApps[0].cmdLine, leftSide, selectionLeft, selectionRight); } } @@ -4588,7 +4581,7 @@ void MainDialog::onGridLabelLeftClickRim(GridLabelClickEvent& event, bool leftSi if (*sortType == colType && sortInfo->onLeft == leftSide) sortAscending = !sortInfo->ascending; - const ItemPathFormat itemPathFormat = leftSide ? globalCfg_.gui.mainDlg.itemPathFormatLeftGrid : globalCfg_.gui.mainDlg.itemPathFormatRightGrid; + const ItemPathFormat itemPathFormat = leftSide ? globalCfg_.mainDlg.itemPathFormatLeftGrid : globalCfg_.mainDlg.itemPathFormatRightGrid; filegrid::getDataView(*m_gridMainC).sortView(colType, itemPathFormat, leftSide, sortAscending); @@ -4681,16 +4674,16 @@ void MainDialog::onSwapSides(wxCommandEvent& event) { FocusPreserver fp; - disableAllElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! + disableGuiElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! 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 + ZEN_ON_SCOPE_EXIT(app->Yield(); enableGuiElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks const auto& guiCfg = getConfig(); StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/, false /*ignoreErrors*/, - guiCfg.mainCfg.automaticRetryCount, - guiCfg.mainCfg.automaticRetryDelay); //handle status display and error messages + guiCfg.mainCfg.autoRetryCount, + guiCfg.mainCfg.autoRetryDelay); //handle status display and error messages try { statusHandler.initNewPhase(-1, -1, ProcessPhase::none); @@ -4928,17 +4921,17 @@ void MainDialog::applySyncDirections() { FocusPreserver fp; - disableAllElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! + disableGuiElements(true /*enableAbort*/); //StatusHandlerTemporaryPanel will internally process Window messages, so avoid unexpected callbacks! 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 + ZEN_ON_SCOPE_EXIT(app->Yield(); enableGuiElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks const auto& guiCfg = getConfig(); const auto& directCfgs = extractDirectionCfg(folderCmp_, getConfig().mainCfg); StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/, false /*ignoreErrors*/, - guiCfg.mainCfg.automaticRetryCount, - guiCfg.mainCfg.automaticRetryDelay); //handle status display and error messages + guiCfg.mainCfg.autoRetryCount, + guiCfg.mainCfg.autoRetryDelay); //handle status display and error messages try { statusHandler.initNewPhase(-1, -1, ProcessPhase::none); @@ -5232,7 +5225,7 @@ void MainDialog::updateGuiForFolderPair() m_panelDirectoryPairs->ClientToWindowSize(m_panelTopCenter->GetSize()).y); // const int addPairHeight = !additionalFolderPairs_.empty() ? additionalFolderPairs_[0]->GetSize().y : 0; - const double addPairCountMax = std::max(globalCfg_.gui.mainDlg.folderPairsVisibleMax - 1 + 0.5, 1.5); + const double addPairCountMax = std::max(globalCfg_.mainDlg.folderPairsVisibleMax - 1 + 0.5, 1.5); const double addPairCountMin = std::min<double>(1.5, additionalFolderPairs_.size()); //add 0.5 to indicate additional folders const double addPairCountOpt = std::min<double>(addPairCountMax, additionalFolderPairs_.size()); // @@ -5273,7 +5266,7 @@ void MainDialog::recalcMaxFolderPairsVisible() if (numeric::dist(addPairCountCurrent, *addPairCountLast_) > 0.4) //=> presumely changed by user! { - globalCfg_.gui.mainDlg.folderPairsVisibleMax = numeric::round(addPairCountCurrent) + 1; + globalCfg_.mainDlg.folderPairsVisibleMax = numeric::round(addPairCountCurrent) + 1; } } } @@ -5296,9 +5289,9 @@ void MainDialog::insertAddFolderPair(const std::vector<LocalPairConfig>& newPair else { newPair = new FolderPairPanel(m_scrolledWindowFolderPairs, *this, - globalCfg_.gui.mainDlg.folderLastSelectedLeft, - globalCfg_.gui.mainDlg.folderLastSelectedRight, - globalCfg_.gui.sftpKeyFileLastSelected); + globalCfg_.mainDlg.folderLastSelectedLeft, + globalCfg_.mainDlg.folderLastSelectedRight, + globalCfg_.sftpKeyFileLastSelected); //setHistory dropdown history newPair->m_folderPathLeft ->setHistory(folderHistoryLeft_ ); @@ -5415,9 +5408,9 @@ void MainDialog::onMenuOptions(wxCommandEvent& event) void MainDialog::onMenuExportFileList(wxCommandEvent& event) { - std::optional<Zstring> defaultFolderPath = getParentFolderPath(globalCfg_.gui.csvFileLastSelected); + std::optional<Zstring> defaultFolderPath = getParentFolderPath(globalCfg_.csvFileLastSelected); - Zstring defaultFileName = afterLast(globalCfg_.gui.csvFileLastSelected, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + Zstring defaultFileName = afterLast(globalCfg_.csvFileLastSelected, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); if (defaultFileName.empty()) defaultFileName = Zstr("FileList.csv"); @@ -5426,7 +5419,7 @@ void MainDialog::onMenuExportFileList(wxCommandEvent& event) wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (fileSelector.ShowModal() != wxID_OK) return; - const Zstring csvFilePath = globalCfg_.gui.csvFileLastSelected = utfTo<Zstring>(fileSelector.GetPath()); + const Zstring csvFilePath = globalCfg_.csvFileLastSelected = utfTo<Zstring>(fileSelector.GetPath()); //--------------------------------------------------------------------- wxBusyCursor dummy; @@ -5547,24 +5540,24 @@ void MainDialog::onMenuExportFileList(wxCommandEvent& event) void MainDialog::onMenuCheckVersion(wxCommandEvent& event) { - checkForUpdateNow(*this, globalCfg_.gui.lastOnlineVersion); + checkForUpdateNow(*this, globalCfg_.lastOnlineVersion); } void MainDialog::onMenuCheckVersionAutomatically(wxCommandEvent& event) { - if (updateCheckActive(globalCfg_.gui.lastUpdateCheck)) - disableUpdateCheck(globalCfg_.gui.lastUpdateCheck); + if (updateCheckActive(globalCfg_.lastUpdateCheck)) + disableUpdateCheck(globalCfg_.lastUpdateCheck); else - globalCfg_.gui.lastUpdateCheck = 0; //reset to GlobalSettings.xml default value! + globalCfg_.lastUpdateCheck = 0; //reset to GlobalSettings.xml default value! - m_menuItemCheckVersionAuto->Check(updateCheckActive(globalCfg_.gui.lastUpdateCheck)); + m_menuItemCheckVersionAuto->Check(updateCheckActive(globalCfg_.lastUpdateCheck)); - if (shouldRunAutomaticUpdateCheck(globalCfg_.gui.lastUpdateCheck)) + if (shouldRunAutomaticUpdateCheck(globalCfg_.lastUpdateCheck)) { flashStatusInformation(_("Searching for program updates...")); //synchronous update check is sufficient here: - automaticUpdateCheckEval(this, globalCfg_.gui.lastUpdateCheck, globalCfg_.gui.lastOnlineVersion, + automaticUpdateCheckEval(this, globalCfg_.lastUpdateCheck, globalCfg_.lastOnlineVersion, automaticUpdateCheckRunAsync(automaticUpdateCheckPrepare(*this).get()).get()); } } @@ -5578,20 +5571,20 @@ void MainDialog::onStartupUpdateCheck(wxIdleEvent& event) auto showNewVersionReminder = [this] { - if (!globalCfg_.gui.lastOnlineVersion.empty() && haveNewerVersionOnline(globalCfg_.gui.lastOnlineVersion)) + if (!globalCfg_.lastOnlineVersion.empty() && haveNewerVersionOnline(globalCfg_.lastOnlineVersion)) { auto menu = new wxMenu(); wxMenuItem* newItem = new wxMenuItem(menu, wxID_ANY, _("&Show details")); - Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent&) { checkForUpdateNow(*this, globalCfg_.gui.lastOnlineVersion); }, newItem->GetId()); + Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent&) { checkForUpdateNow(*this, globalCfg_.lastOnlineVersion); }, newItem->GetId()); //show changelog + handle Donation Edition auto-updater (including expiration) menu->Append(newItem); //pass ownership const std::wstring& blackStar = utfTo<std::wstring>("★"); - m_menubar->Append(menu, blackStar + L' ' + replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo<std::wstring>(globalCfg_.gui.lastOnlineVersion)) + L' ' + blackStar); + m_menubar->Append(menu, blackStar + L' ' + replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo<std::wstring>(globalCfg_.lastOnlineVersion)) + L' ' + blackStar); } }; - if (shouldRunAutomaticUpdateCheck(globalCfg_.gui.lastUpdateCheck)) + if (shouldRunAutomaticUpdateCheck(globalCfg_.lastUpdateCheck)) { flashStatusInformation(_("Searching for program updates...")); @@ -5600,7 +5593,7 @@ void MainDialog::onStartupUpdateCheck(wxIdleEvent& event) guiQueue_.processAsync([resultPrep] { return automaticUpdateCheckRunAsync(resultPrep.get()); }, //run on worker thread: (long-running part of the check) [this, showNewVersionReminder] (std::shared_ptr<const UpdateCheckResult>&& resultAsync) { - automaticUpdateCheckEval(this, globalCfg_.gui.lastUpdateCheck, globalCfg_.gui.lastOnlineVersion, + automaticUpdateCheckEval(this, globalCfg_.lastUpdateCheck, globalCfg_.lastOnlineVersion, resultAsync.get()); //run on main thread: showNewVersionReminder(); }); diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index 26cb167f..ba95fed7 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -46,11 +46,7 @@ public: const std::vector<Zstring>& referenceFiles, bool startComparison); - void disableAllElements(bool enableAbort); //dis-/enables all elements (except abort button) that might receive user input - void enableAllElements(); //during long-running processes: comparison, deletion - void onQueryEndSession(); //last chance to do something useful before killing the application! - private: MainDialog(const Zstring& globalConfigFilePath, const XmlGuiConfig& guiCfg, @@ -67,6 +63,10 @@ private: friend class FolderPairCallback; friend class PanelMoveWindow; + //mitigate potential reentrancy in during window message pumping: + void disableGuiElements(bool enableAbort); //dis-/enables all elements (except abort button) that might receive user input + void enableGuiElements(); //during long-running processes: comparison, deletion + //configuration load/save void setLastUsedConfig(const XmlGuiConfig& guiConfig, const std::vector<Zstring>& cfgFilePaths); @@ -349,10 +349,13 @@ private: wxWindowID focusIdAfterSearch_ = wxID_ANY; //used to restore focus after search panel is closed + //mitigate reentrancy: bool localKeyEventsEnabled_ = true; - bool allowMainDialogClose_ = true; //e.g. do NOT allow close while sync is running => crash!!! + bool operationInProgress_ = false; //e.g. do NOT allow dialog exit while sync is running => crash!!! TempFileBuffer tempFileBuf_; //buffer temporary copies of non-native files for %local_path% + + const wxImage imgTrashSmall_; }; } diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index 54ab9c42..66b3dd56 100644 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -23,6 +23,7 @@ #include <zen/perf.h> #include <wx+/choice_enum.h> #include "wx+/taskbar.h" +#include "wx+/window_tools.h" #include "gui_generated.h" #include "tray_icon.h" #include "log_panel.h" @@ -52,11 +53,14 @@ inline wxColor getColorItems() { return { 127, 147, 255 }; } //light blue inline wxColor getColorBytesRim() { return { 20, 200, 0 }; } //medium green inline wxColor getColorItemsRim() { return { 90, 120, 255 }; } //medium blue -inline wxColor getColorBytesBackground() { return { 205, 255, 202 }; } //faint green -inline wxColor getColorItemsBackground() { return { 198, 206, 255 }; } //faint blue +//inline wxColor getColorBytesFaint() { return { 205, 255, 202 }; } //faint green +//inline wxColor getColorItemsFaint() { return { 198, 206, 255 }; } //faint blue -inline wxColor getColorBytesBackgroundRim() { return { 12, 128, 0 }; } //dark green -inline wxColor getColorItemsBackgroundRim() { return { 53, 25, 255 }; } //dark blue +inline wxColor getColorBytesDark() { return { 12, 128, 0 }; } //dark green +inline wxColor getColorItemsDark() { return { 53, 25, 255 }; } //dark blue + +inline wxColor getColorLightGrey() { return {0xf2, 0xf2, 0xf2}; } +inline wxColor getColorDarkGrey () { return {0x8f, 0x8f, 0x8f}; } std::wstring getDialogPhaseText(const Statistics& syncStat, bool paused) @@ -132,8 +136,8 @@ class CompareProgressPanel::Impl : public CompareProgressDlgGenerated public: Impl(wxFrame& parentWindow); - void init(const Statistics& syncStat, bool ignoreErrors, size_t automaticRetryCount); //constructor/destructor semantics, but underlying Window is reused - void teardown(); // + void init(const Statistics& syncStat, bool ignoreErrors, size_t autoRetryCount); //constructor/destructor semantics, but underlying Window is reused + void teardown(); // void initNewPhase(); void updateProgressGui(); @@ -194,10 +198,10 @@ CompareProgressPanel::Impl::Impl(wxFrame& parentWindow) : //init graph m_panelProgressGraph->setAttributes(Graph2D::MainAttributes().setMinY(0).setMaxY(2). - setLabelX(Graph2D::LABEL_X_NONE). - setLabelY(Graph2D::LABEL_Y_NONE). + setLabelX(XLabelPos::none). + setLabelY(YLabelPos::none). setBaseColors(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)). - setSelectionMode(Graph2D::SELECT_NONE)); + setSelectionMode(GraphSelMode::none)); m_panelProgressGraph->addCurve(curveDataBytes_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(1)).fillPolygonArea(getColorBytes()).setColor(Graph2D::getBorderColor())); m_panelProgressGraph->addCurve(curveDataItems_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(1)).fillPolygonArea(getColorItems()).setColor(Graph2D::getBorderColor())); @@ -213,7 +217,7 @@ CompareProgressPanel::Impl::Impl(wxFrame& parentWindow) : } -void CompareProgressPanel::Impl::init(const Statistics& syncStat, bool ignoreErrors, size_t automaticRetryCount) +void CompareProgressPanel::Impl::init(const Statistics& syncStat, bool ignoreErrors, size_t autoRetryCount) { syncStat_ = &syncStat; parentTitleBackup_ = parentWindow_.GetTitle(); @@ -226,8 +230,8 @@ void CompareProgressPanel::Impl::init(const Statistics& syncStat, bool ignoreErr stopWatch_.restart(); //measure total time - setText(*m_staticTextRetryCount, L'(' + formatNumber(automaticRetryCount) + MULT_SIGN + L')'); - bSizerErrorsRetry->Show(automaticRetryCount > 0); + setText(*m_staticTextRetryCount, L'(' + formatNumber(autoRetryCount) + MULT_SIGN + L')'); + bSizerErrorsRetry->Show(autoRetryCount > 0); //allow changing a few options dynamically during sync ignoreErrors_ = ignoreErrors; @@ -365,8 +369,8 @@ void CompareProgressPanel::Impl::updateProgressGui() //current speed -> Win 7 copy uses 1 sec update interval instead std::optional<std::wstring> bps = perf_.getBytesPerSecond(); std::optional<std::wstring> ips = perf_.getItemsPerSecond(); - m_panelProgressGraph->setAttributes(m_panelProgressGraph->getAttributes().setCornerText(bps ? *bps : L"", Graph2D::CORNER_TOP_LEFT)); - m_panelProgressGraph->setAttributes(m_panelProgressGraph->getAttributes().setCornerText(ips ? *ips : L"", Graph2D::CORNER_BOTTOM_LEFT)); + m_panelProgressGraph->setAttributes(m_panelProgressGraph->getAttributes().setCornerText(bps ? *bps : L"", GraphCorner::topL)); + m_panelProgressGraph->setAttributes(m_panelProgressGraph->getAttributes().setCornerText(ips ? *ips : L"", GraphCorner::bottomL)); //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 @@ -394,7 +398,7 @@ void CompareProgressPanel::Impl::updateProgressGui() //redirect to implementation CompareProgressPanel::CompareProgressPanel(wxFrame& parentWindow) : pimpl_(new Impl(parentWindow)) {} //owned by parentWindow wxWindow* CompareProgressPanel::getAsWindow() { return pimpl_; } -void CompareProgressPanel::init(const Statistics& syncStat, bool ignoreErrors, size_t automaticRetryCount) { pimpl_->init(syncStat, ignoreErrors, automaticRetryCount); } +void CompareProgressPanel::init(const Statistics& syncStat, bool ignoreErrors, size_t autoRetryCount) { pimpl_->init(syncStat, ignoreErrors, autoRetryCount); } void CompareProgressPanel::teardown() { pimpl_->teardown(); } void CompareProgressPanel::initNewPhase() { pimpl_->initNewPhase(); } void CompareProgressPanel::updateGui() { pimpl_->updateProgressGui(); } @@ -492,10 +496,10 @@ private: }; -class CurveDataTotalBlock : public CurveData +class CurveDataEstimate : public CurveData { public: - void setValue(double x1, double x2, double y) { x1_ = x1; x2_ = x2; y_ = y; } + void setValue(double x1, double x2, double y1, double y2) { x1_ = x1; x2_ = x2; y1_ = y1; y2_ = y2; } void setTotalTime(double x2) { x2_ = x2; } double getTotalTime() const { return x2_; } @@ -506,43 +510,38 @@ private: { return { - { x1_, 0 }, - { x1_, y_ }, - { x2_, y_ }, - { x2_, 0 }, + { x1_, y1_ }, + { x2_, y2_ }, }; } double x1_ = 0; //elapsed time [s] double x2_ = 0; //total time [s] (estimated) - double y_ = 0; //items/bytes total + double y1_ = 0; //items/bytes processed + double y2_ = 0; //items/bytes total }; -class CurveDataProcessedBlock : public CurveData +class CurveDataTimeMarker : public CurveData { public: - void setValue(double x1, double x2, double y1, double y2) { x1_ = x1; x2_ = x2; y1_ = y1; y2_ = y2; } - void setTotalTime(double x2) { x2_ = x2; } + void setValue(double x, double y) { x_ = x; y_ = y; } + void setTime(double x) { x_ = x; } private: - std::pair<double, double> getRangeX() const override { return { x1_, x2_ }; } + std::pair<double, double> getRangeX() const override { return { x_, x_ }; } std::vector<CurvePoint> getPoints(double minX, double maxX, const wxSize& areaSizePx) const override { return { - { x1_, 0 }, - { x1_, y2_ }, - { x1_, y1_ }, - { x2_, y1_ }, + { x_, y_ }, + { x_, 0 }, }; } - double x1_ = 0; //elapsed time [s] - double x2_ = 0; //total time [s] (estimated) - double y1_ = 0; //items/bytes processed - double y2_ = 0; //items/bytes total + double x_ = 0; //time [s] + double y_ = 0; //items/bytes }; @@ -641,6 +640,7 @@ class SyncProgressDialogImpl : public TopLevelDialog, public SyncProgressDialog { public: SyncProgressDialogImpl(long style, //wxFrame/wxDialog style + wxSize dlgSize, bool dlgMaximize, const std::function<void()>& userRequestAbort, const Statistics& syncStat, wxFrame* parentFrame, @@ -649,7 +649,7 @@ public: const std::vector<std::wstring>& jobNames, const std::chrono::system_clock::time_point& syncStartTime, bool ignoreErrors, - size_t automaticRetryCount, + size_t autoRetryCount, PostSyncAction2 postSyncAction); Result destroy(bool autoClose, bool restoreParentFrame, SyncResult syncResult, const SharedRef<const zen::ErrorLog>& log) override; @@ -686,7 +686,6 @@ private: void onCancel (wxCommandEvent& event); void onClose(wxCloseEvent& event); void onIconize(wxIconizeEvent& event); - void onMinimizeToTray(wxCommandEvent& event) { minimizeToTray(); } //void onToggleIgnoreErrors(wxCommandEvent& event) { updateStaticGui(); } void showSummary(SyncResult syncResult, const SharedRef<const ErrorLog>& log); @@ -722,12 +721,14 @@ private: //help calculate total speed std::chrono::nanoseconds phaseStart_{}; //begin of current phase - std::shared_ptr<CurveDataStatistics > curveDataBytes_ { std::make_shared<CurveDataStatistics>() }; - std::shared_ptr<CurveDataStatistics > curveDataItems_ { std::make_shared<CurveDataStatistics>() }; - std::shared_ptr<CurveDataProcessedBlock> curveDataBytesCurrent_{ std::make_shared<CurveDataProcessedBlock>() }; - std::shared_ptr<CurveDataProcessedBlock> curveDataItemsCurrent_{ std::make_shared<CurveDataProcessedBlock>() }; - std::shared_ptr<CurveDataTotalBlock > curveDataBytesTotal_ { std::make_shared<CurveDataTotalBlock>() }; - std::shared_ptr<CurveDataTotalBlock > curveDataItemsTotal_ { std::make_shared<CurveDataTotalBlock>() }; + std::shared_ptr<CurveDataStatistics> curveBytes_ = std::make_shared<CurveDataStatistics>(); + std::shared_ptr<CurveDataStatistics> curveItems_ = std::make_shared<CurveDataStatistics>(); + std::shared_ptr<CurveDataEstimate > curveBytesEstim_ = std::make_shared<CurveDataEstimate> (); + std::shared_ptr<CurveDataEstimate > curveItemsEstim_ = std::make_shared<CurveDataEstimate> (); + std::shared_ptr<CurveDataTimeMarker> curveBytesTimeNow_ = std::make_shared<CurveDataTimeMarker>(); + std::shared_ptr<CurveDataTimeMarker> curveItemsTimeNow_ = std::make_shared<CurveDataTimeMarker>(); + std::shared_ptr<CurveDataTimeMarker> curveBytesTimeEstim_ = std::make_shared<CurveDataTimeMarker>(); + std::shared_ptr<CurveDataTimeMarker> curveItemsTimeEstim_ = std::make_shared<CurveDataTimeMarker>(); wxString parentTitleBackup_; std::unique_ptr<FfsTrayIcon> trayIcon_; //optional: if filled all other windows should be hidden and conversely @@ -735,11 +736,14 @@ private: bool ignoreErrors_ = false; EnumDescrList<PostSyncAction2> enumPostSyncAction_; + + wxSize dlgSizeBuf_; }; template <class TopLevelDialog> SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxFrame/wxDialog style + wxSize dlgSize, bool dlgMaximize, const std::function<void()>& userRequestAbort, const Statistics& syncStat, wxFrame* parentFrame, @@ -748,7 +752,7 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF const std::vector<std::wstring>& jobNames, const std::chrono::system_clock::time_point& syncStartTime, bool ignoreErrors, - size_t automaticRetryCount, + size_t autoRetryCount, PostSyncAction2 postSyncAction) : TopLevelDialog(parentFrame, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, style), //title is overwritten anyway in setExternalStatus() pnl_(*new SyncProgressPanelGenerated(this)), //ownership passed to "this" @@ -767,7 +771,8 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF ()), parentFrame_(parentFrame), userRequestAbort_(userRequestAbort), -syncStat_(&syncStat) +syncStat_(&syncStat), +dlgSizeBuf_(dlgSize) { static_assert(std::is_same_v<TopLevelDialog, wxFrame > || std::is_same_v<TopLevelDialog, wxDialog>); @@ -788,7 +793,7 @@ syncStat_(&syncStat) pnl_.m_buttonClose ->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { closePressed_ = true; }); pnl_.m_buttonPause ->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onPause(event); }); pnl_.m_buttonStop ->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onCancel(event); }); - pnl_.m_bpButtonMinimizeToTray->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onMinimizeToTray(event); }); + pnl_.m_bpButtonMinimizeToTray->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { minimizeToTray(); }); if (parentFrame_) parentFrame_->Bind(wxEVT_CHAR_HOOK, &SyncProgressDialogImpl::onParentKeyEvent, this); @@ -836,25 +841,28 @@ syncStat_(&syncStat) const int xLabelHeight = this->GetCharHeight() + fastFromDIP(2) /*margin*/; //use same height for both graphs to make sure they stretch evenly const int yLabelWidth = fastFromDIP(70); pnl_.m_panelGraphBytes->setAttributes(Graph2D::MainAttributes(). - setLabelX(Graph2D::LABEL_X_TOP, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(true)). - setLabelY(Graph2D::LABEL_Y_RIGHT, yLabelWidth, std::make_shared<LabelFormatterBytes>()). - setBaseColors(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)). - setSelectionMode(Graph2D::SELECT_NONE)); + setLabelX(XLabelPos::top, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(true)). + setLabelY(YLabelPos::right, yLabelWidth, std::make_shared<LabelFormatterBytes>()). + setBaseColors(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)). + setSelectionMode(GraphSelMode::none)); pnl_.m_panelGraphItems->setAttributes(Graph2D::MainAttributes(). - setLabelX(Graph2D::LABEL_X_BOTTOM, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(true)). - setLabelY(Graph2D::LABEL_Y_RIGHT, yLabelWidth, std::make_shared<LabelFormatterItemCount>()). - setBaseColors(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)). - setSelectionMode(Graph2D::SELECT_NONE)); + setLabelX(XLabelPos::bottom, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(true)). + setLabelY(YLabelPos::right, yLabelWidth, std::make_shared<LabelFormatterItemCount>()). + setBaseColors(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)). + setSelectionMode(GraphSelMode::none)); + + pnl_.m_panelGraphBytes->addCurve(curveBytes_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(2)).fillCurveArea(getColorBytes()).setColor(getColorBytesRim())); + pnl_.m_panelGraphItems->addCurve(curveItems_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(2)).fillCurveArea(getColorItems()).setColor(getColorItemsRim())); - pnl_.m_panelGraphBytes->setCurve(curveDataBytesTotal_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(1)).fillCurveArea(*wxWHITE).setColor(wxColor(192, 192, 192))); //medium grey - pnl_.m_panelGraphItems->setCurve(curveDataItemsTotal_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(1)).fillCurveArea(*wxWHITE).setColor(wxColor(192, 192, 192))); //medium grey + pnl_.m_panelGraphBytes->addCurve(curveBytesEstim_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(1)).fillCurveArea(getColorLightGrey()).setColor(getColorDarkGrey())); + pnl_.m_panelGraphItems->addCurve(curveItemsEstim_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(1)).fillCurveArea(getColorLightGrey()).setColor(getColorDarkGrey())); - pnl_.m_panelGraphBytes->addCurve(curveDataBytes_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(2)).fillCurveArea(getColorBytes()).setColor(getColorBytesRim())); - pnl_.m_panelGraphItems->addCurve(curveDataItems_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(2)).fillCurveArea(getColorItems()).setColor(getColorItemsRim())); + pnl_.m_panelGraphBytes->addCurve(curveBytesTimeNow_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(2)).setColor(getColorBytesDark())); + pnl_.m_panelGraphItems->addCurve(curveItemsTimeNow_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(2)).setColor(getColorItemsDark())); - pnl_.m_panelGraphBytes->addCurve(curveDataBytesCurrent_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(2)).fillCurveArea(getColorBytesBackground()).setColor(getColorBytesBackgroundRim())); - pnl_.m_panelGraphItems->addCurve(curveDataItemsCurrent_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(2)).fillCurveArea(getColorItemsBackground()).setColor(getColorItemsBackgroundRim())); + pnl_.m_panelGraphBytes->addCurve(curveBytesTimeEstim_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(2)).setColor(getColorBytesDark())); + pnl_.m_panelGraphItems->addCurve(curveItemsTimeEstim_, Graph2D::CurveAttributes().setLineWidth(fastFromDIP(2)).setColor(getColorItemsDark())); //graph legend: auto generateSquareBitmap = [&](const wxColor& fillCol, const wxColor& borderCol) @@ -872,8 +880,8 @@ syncStat_(&syncStat) pnl_.bSizerDynSpace->SetMinSize(yLabelWidth, -1); //ensure item/time stats are nicely centered - setText(*pnl_.m_staticTextRetryCount, L'(' + formatNumber(automaticRetryCount) + MULT_SIGN + L')'); - pnl_.bSizerErrorsRetry->Show(automaticRetryCount > 0); + setText(*pnl_.m_staticTextRetryCount, L'(' + formatNumber(autoRetryCount) + MULT_SIGN + L')'); + pnl_.bSizerErrorsRetry->Show(autoRetryCount > 0); //allow changing a few options dynamically during sync ignoreErrors_ = ignoreErrors; @@ -896,6 +904,8 @@ syncStat_(&syncStat) pnl_.Layout(); this->Center(); //call *after* dialog layout update and *before* wxWindow::Show()! + setInitialWindowSize(*this, dlgSizeBuf_, std::nullopt/*pos*/, dlgMaximize, this->GetSize() /*defaultSize*/); + if (showProgress) { this->Show(); @@ -951,12 +961,14 @@ void SyncProgressDialogImpl<TopLevelDialog>::initNewPhase() updateStaticGui(); //evaluates "syncStat_->currentPhase()" //reset graphs (e.g. after binary comparison) - curveDataBytesTotal_ ->setValue(0, 0, 0); - curveDataItemsTotal_ ->setValue(0, 0, 0); - curveDataBytesCurrent_->setValue(0, 0, 0, 0); - curveDataItemsCurrent_->setValue(0, 0, 0, 0); - curveDataBytes_ ->clear(); - curveDataItems_ ->clear(); + curveBytes_ ->clear(); + curveItems_ ->clear(); + curveBytesEstim_ ->setValue(0, 0, 0, 0); + curveItemsEstim_ ->setValue(0, 0, 0, 0); + curveBytesTimeNow_ ->setValue(0, 0); + curveItemsTimeNow_ ->setValue(0, 0); + curveBytesTimeEstim_->setValue(0, 0); + curveItemsTimeEstim_->setValue(0, 0); notifyProgressChange(); //make sure graphs get initial values @@ -975,8 +987,8 @@ void SyncProgressDialogImpl<TopLevelDialog>::notifyProgressChange() //noexcept! if (syncStat_) //sync running { const ProgressStats stats = syncStat_->getStatsCurrent(); - curveDataBytes_->addRecord(stopWatch_.elapsed(), stats.bytes); - curveDataItems_->addRecord(stopWatch_.elapsed(), stats.items); + curveBytes_->addRecord(stopWatch_.elapsed(), stats.bytes); + curveItems_->addRecord(stopWatch_.elapsed(), stats.items); } } @@ -1077,21 +1089,23 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateProgressGui(bool allowYield) if (trayIcon_.get()) trayIcon_->setProgress(fractionTotal); if (taskbar_ .get()) taskbar_ ->setProgress(fractionTotal); - const double timeTotalSecTentative = bytesCurrent == bytesTotal ? timeElapsedDouble : std::max(curveDataBytesTotal_->getTotalTime(), timeElapsedDouble); + const double timeTotalSecTentative = bytesCurrent == bytesTotal ? timeElapsedDouble : std::max(curveBytesEstim_->getTotalTime(), timeElapsedDouble); - //constant line graph - curveDataBytesCurrent_->setValue(timeElapsedDouble, timeTotalSecTentative, bytesCurrent, bytesTotal); - curveDataItemsCurrent_->setValue(timeElapsedDouble, timeTotalSecTentative, itemsCurrent, itemsTotal); + curveBytesEstim_->setValue(timeElapsedDouble, timeTotalSecTentative, bytesCurrent, bytesTotal); + curveItemsEstim_->setValue(timeElapsedDouble, timeTotalSecTentative, itemsCurrent, itemsTotal); //tentatively update total time, may be improved on below: - curveDataBytesTotal_->setValue(timeElapsedDouble, timeTotalSecTentative, bytesTotal); - curveDataItemsTotal_->setValue(timeElapsedDouble, timeTotalSecTentative, itemsTotal); + curveBytesTimeNow_ ->setValue(timeElapsedDouble, bytesCurrent); + curveItemsTimeNow_ ->setValue(timeElapsedDouble, itemsCurrent); + + curveBytesTimeEstim_->setValue(timeTotalSecTentative, bytesTotal); + curveItemsTimeEstim_->setValue(timeTotalSecTentative, itemsTotal); } //even though notifyProgressChange() already set the latest data, let's add another sample to have all curves consider "timeNowMs" //no problem with adding too many records: CurveDataStatistics will remove duplicate entries! - curveDataBytes_->addRecord(timeElapsed, bytesCurrent); - curveDataItems_->addRecord(timeElapsed, itemsCurrent); + curveBytes_->addRecord(timeElapsed, bytesCurrent); + curveItems_->addRecord(timeElapsed, itemsCurrent); //item and data stats if (!haveTotalStats) @@ -1130,8 +1144,8 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateProgressGui(bool allowYield) //current speed -> Win 7 copy uses 1 sec update interval instead std::optional<std::wstring> bps = perf_.getBytesPerSecond(); std::optional<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)); + pnl_.m_panelGraphBytes->setAttributes(pnl_.m_panelGraphBytes->getAttributes().setCornerText(bps ? *bps : L"", GraphCorner::topL)); + pnl_.m_panelGraphItems->setAttributes(pnl_.m_panelGraphItems->getAttributes().setCornerText(ips ? *ips : L"", GraphCorner::topL)); //remaining time if (!haveTotalStats) @@ -1149,7 +1163,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateProgressGui(bool allowYield) const double timeRemainingSec = remTimeSec ? *remTimeSec : 0; const double timeTotalSec = timeElapsedDouble + timeRemainingSec; //update estimated total time marker only with precision of "15% remaining time" to avoid needless jumping around: - if (numeric::dist(curveDataBytesTotal_->getTotalTime(), timeTotalSec) > 0.15 * timeRemainingSec) + if (numeric::dist(curveBytesEstim_->getTotalTime(), timeTotalSec) > 0.15 * timeRemainingSec) { //avoid needless flicker and don't update total time graph too often: static_assert(std::chrono::duration_cast<std::chrono::milliseconds>(GRAPH_TOTAL_TIME_UPDATE_INTERVAL).count() % SPEED_ESTIMATE_UPDATE_INTERVAL.count() == 0); @@ -1157,11 +1171,11 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateProgressGui(bool allowYield) { timeLastGraphTotalUpdate_ = timeElapsed; - curveDataBytesTotal_->setTotalTime(timeTotalSec); - curveDataItemsTotal_->setTotalTime(timeTotalSec); - //don't forget to update these, too: - curveDataBytesCurrent_->setTotalTime(timeTotalSec); - curveDataItemsCurrent_->setTotalTime(timeTotalSec); + curveBytesEstim_->setTotalTime(timeTotalSec); + curveItemsEstim_->setTotalTime(timeTotalSec); + + curveBytesTimeEstim_->setTime(timeTotalSec); + curveItemsTimeEstim_->setTime(timeTotalSec); } } } @@ -1294,8 +1308,8 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult syncResult, const wxString overallItemsPerSecond = numeric::isNull(timeDelta) ? std::wstring() : replaceCpy(_("%x/sec"), L"%x", replaceCpy(_("%x items"), L"%x", formatThreeDigitPrecision(itemsProcessed / timeDelta))); - 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)); + pnl_.m_panelGraphBytes->setAttributes(pnl_.m_panelGraphBytes->getAttributes().setCornerText(overallBytesPerSecond, GraphCorner::topL)); + pnl_.m_panelGraphItems->setAttributes(pnl_.m_panelGraphItems->getAttributes().setCornerText(overallItemsPerSecond, GraphCorner::topL)); //...if everything was processed successfully if (itemsTotal >= 0 && bytesTotal >= 0 && //itemsTotal < 0 && bytesTotal < 0 => e.g. cancel during folder comparison @@ -1491,9 +1505,13 @@ auto SyncProgressDialogImpl<TopLevelDialog>::destroy(bool autoClose, bool restor //------------------------------------------------------------------------ const bool autoCloseDialog = getOptionAutoCloseDialog(); + const auto& [size, pos, isMaximized] = getWindowSizeBeforeClose(*this); + if (size) + dlgSizeBuf_ = *size; + this->Destroy(); //wxWidgets macOS: simple "delete"!!!!!!! - return { autoCloseDialog }; + return { autoCloseDialog, dlgSizeBuf_, isMaximized }; } @@ -1613,7 +1631,8 @@ void SyncProgressDialogImpl<TopLevelDialog>::resumeFromSystray() //######################################################################################## -SyncProgressDialog* SyncProgressDialog::create(const std::function<void()>& userRequestAbort, +SyncProgressDialog* SyncProgressDialog::create(wxSize dlgSize, bool dlgMaximize, + const std::function<void()>& userRequestAbort, const Statistics& syncStat, wxFrame* parentWindow, //may be nullptr bool showProgress, @@ -1621,7 +1640,7 @@ SyncProgressDialog* SyncProgressDialog::create(const std::function<void()>& user const std::vector<std::wstring>& jobNames, const std::chrono::system_clock::time_point& syncStartTime, bool ignoreErrors, - size_t automaticRetryCount, + size_t autoRetryCount, PostSyncAction2 postSyncAction) { if (parentWindow) //sync from GUI @@ -1629,12 +1648,12 @@ SyncProgressDialog* SyncProgressDialog::create(const std::function<void()>& user //due to usual "wxBugs", wxDialog on OS X does not float on its parent; wxFrame OTOH does => hack! //https://groups.google.com/forum/#!topic/wx-users/J5SjjLaBOQE return new SyncProgressDialogImpl<wxDialog>(wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxMINIMIZE_BOX | wxRESIZE_BORDER, - userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, jobNames, syncStartTime, ignoreErrors, automaticRetryCount, postSyncAction); + dlgSize, dlgMaximize, userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, jobNames, syncStartTime, ignoreErrors, autoRetryCount, postSyncAction); } else //FFS batch job { auto dlg = new SyncProgressDialogImpl<wxFrame>(wxDEFAULT_FRAME_STYLE, - userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, jobNames, syncStartTime, ignoreErrors, automaticRetryCount, postSyncAction); + dlgSize, dlgMaximize, userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, jobNames, syncStartTime, ignoreErrors, autoRetryCount, postSyncAction); dlg->SetIcon(getFfsIcon()); //only top level windows should have an icon return dlg; } diff --git a/FreeFileSync/Source/ui/progress_indicator.h b/FreeFileSync/Source/ui/progress_indicator.h index 1be7defc..1a8e73f4 100644 --- a/FreeFileSync/Source/ui/progress_indicator.h +++ b/FreeFileSync/Source/ui/progress_indicator.h @@ -25,7 +25,7 @@ public: wxWindow* getAsWindow(); //convenience! don't abuse! - void init(const Statistics& syncStat, bool ignoreErrors, size_t automaticRetryCount); //begin of sync: make visible, set pointer to "syncStat", initialize all status values + void init(const Statistics& syncStat, bool ignoreErrors, size_t autoRetryCount); //begin of sync: make visible, set pointer to "syncStat", initialize all status values void teardown(); //end of sync: hide again, clear pointer to "syncStat" void initNewPhase(); //call after "StatusHandler::initNewPhase" @@ -57,7 +57,8 @@ enum class PostSyncAction2 struct SyncProgressDialog { - static SyncProgressDialog* create(const std::function<void()>& userRequestAbort, + static SyncProgressDialog* create(wxSize dlgSize, bool dlgMaximize, + const std::function<void()>& userRequestAbort, const Statistics& syncStat, wxFrame* parentWindow, //may be nullptr bool showProgress, @@ -65,9 +66,9 @@ struct SyncProgressDialog const std::vector<std::wstring>& jobNames, const std::chrono::system_clock::time_point& syncStartTime, bool ignoreErrors, - size_t automaticRetryCount, + size_t autoRetryCount, PostSyncAction2 postSyncAction); - struct Result { bool autoCloseDialog; }; + struct Result { bool autoCloseDialog; wxSize dlgSize; bool dlgIsMaximized; }; virtual Result destroy(bool autoClose, bool restoreParentFrame, SyncResult syncResult, const zen::SharedRef<const zen::ErrorLog>& log) = 0; //--------------------------------------------------------------------------- diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index bf41479f..fd50c971 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -38,6 +38,7 @@ #include "../base/algorithm.h" #include "../base/synchronization.h" #include "../base/path_filter.h" +#include "../base/icon_loader.h" #include "../status_handler.h" //uiUpdateDue() #include "../version/version.h" #include "../log_file.h" @@ -135,6 +136,9 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) m_staticTextThanksForLoc->SetMinSize({fastFromDIP(200), -1}); m_staticTextThanksForLoc->Wrap(fastFromDIP(200)); + const int scrollDelta = GetCharHeight(); + m_scrolledWindowTranslators->SetScrollRate(scrollDelta, scrollDelta); + for (const TranslationInfo& ti : getExistingTranslations()) { //country flag @@ -181,7 +185,7 @@ namespace class CloudSetupDlg : public CloudSetupDlgGenerated { public: - CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstring& sftpKeyFileLastSelected, size_t& parallelOps, const std::wstring* parallelOpsDisabledReason); + CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstring& sftpKeyFileLastSelected, size_t& parallelOps, bool canChangeParallelOp); private: void onOkay (wxCommandEvent& event) override; @@ -242,7 +246,7 @@ private: }; -CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstring& sftpKeyFileLastSelected, size_t& parallelOps, const std::wstring* parallelOpsDisabledReason) : +CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstring& sftpKeyFileLastSelected, size_t& parallelOps, bool canChangeParallelOp) : CloudSetupDlgGenerated(parent), sftpKeyFileLastSelected_(sftpKeyFileLastSelected), folderPathPhraseOut_(folderPathPhrase), @@ -366,18 +370,11 @@ CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstrin m_spinCtrlConnectionCount->SetValue(parallelOps); - if (parallelOpsDisabledReason) - { - //m_staticTextConnectionsLabel ->Disable(); - //m_staticTextConnectionsLabelSub->Disable(); - m_spinCtrlChannelCountSftp ->Disable(); - m_buttonChannelCountSftp ->Disable(); - m_spinCtrlConnectionCount ->Disable(); - m_staticTextConnectionCountDescr->SetLabel(*parallelOpsDisabledReason); - //m_staticTextConnectionCountDescr->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); - } - else - m_staticTextConnectionCountDescr->SetLabel(_("Recommended range:") + L" [1" + EN_DASH + L"10]"); //no spaces! + m_spinCtrlConnectionCount->Disable(); + m_staticTextConnectionCountDescr->Hide(); + + m_spinCtrlChannelCountSftp->Disable(); + m_buttonChannelCountSftp ->Disable(); //--------------------------------------------------------- //set up default view for dialog size calculation @@ -749,9 +746,9 @@ void CloudSetupDlg::onOkay(wxCommandEvent& event) } } -ConfirmationButton fff::showCloudSetupDialog(wxWindow* parent, Zstring& folderPathPhrase, Zstring& sftpKeyFileLastSelected, size_t& parallelOps, const std::wstring* parallelOpsDisabledReason) +ConfirmationButton fff::showCloudSetupDialog(wxWindow* parent, Zstring& folderPathPhrase, Zstring& sftpKeyFileLastSelected, size_t& parallelOps, bool canChangeParallelOp) { - CloudSetupDlg dlg(parent, folderPathPhrase, sftpKeyFileLastSelected, parallelOps, parallelOpsDisabledReason); + CloudSetupDlg dlg(parent, folderPathPhrase, sftpKeyFileLastSelected, parallelOps, canChangeParallelOp); return static_cast<ConfirmationButton>(dlg.ShowModal()); } @@ -910,8 +907,7 @@ private: void updateGui(); - const std::span<const FileSystemObject* const> rowsToDeleteOnLeft_; - const std::span<const FileSystemObject* const> rowsToDeleteOnRight_; + int itemCount_ = 0; const std::chrono::steady_clock::time_point dlgStartTime_ = std::chrono::steady_clock::now(); //output-only parameters: @@ -924,8 +920,6 @@ DeleteDialog::DeleteDialog(wxWindow* parent, std::span<const FileSystemObject* const> rowsOnRight, bool& useRecycleBin) : DeleteDlgGenerated(parent), - rowsToDeleteOnLeft_(rowsOnLeft), - rowsToDeleteOnRight_(rowsOnRight), useRecycleBinOut_(useRecycleBin) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOK).setCancel(m_buttonCancel)); @@ -934,6 +928,16 @@ DeleteDialog::DeleteDialog(wxWindow* parent, m_textCtrlFileList->SetMinSize({fastFromDIP(500), fastFromDIP(200)}); + std::wstring itemList; + std::tie(itemList, itemCount_) = getSelectedItemsAsString(rowsOnLeft, rowsOnRight); + trim(itemList); //remove trailing newline + m_textCtrlFileList->ChangeValue(itemList); + /* 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" */ + m_checkBoxUseRecycler->SetValue(useRecycleBin); updateGui(); @@ -950,34 +954,27 @@ DeleteDialog::DeleteDialog(wxWindow* parent, void DeleteDialog::updateGui() { - const auto& [itemList, itemCount] = getSelectedItemsAsString(rowsToDeleteOnLeft_, rowsToDeleteOnRight_); - wxString header; if (m_checkBoxUseRecycler->GetValue()) { - header = _P("Do you really want to move the following item to the recycle bin?", - "Do you really want to move the following %x items to the recycle bin?", itemCount); - m_bitmapDeleteType->SetBitmap(loadImage("delete_recycler")); + wxImage imgTrash = loadImage("delete_recycler"); + //use system icon if available (can fail on Linux??) + try { imgTrash = extractWxImage(fff::getTrashIcon(imgTrash.GetHeight())); /*throw SysError*/ } + catch (SysError&) { assert(false); } + + m_bitmapDeleteType->SetBitmap(imgTrash); + m_staticTextHeader->SetLabel(_P("Do you really want to move the following item to the recycle bin?", + "Do you really want to move the following %x items to the recycle bin?", itemCount_)); m_buttonOK->SetLabel(_("Move")); //no access key needed: use ENTER! } else { - header = _P("Do you really want to delete the following item?", - "Do you really want to delete the following %x items?", itemCount); m_bitmapDeleteType->SetBitmap(loadImage("delete_permanently")); + m_staticTextHeader->SetLabel(_P("Do you really want to delete the following item?", + "Do you really want to delete the following %x items?", itemCount_)); m_buttonOK->SetLabel(replaceCpy(_("&Delete"), L"&", L"")); } - m_staticTextHeader->SetLabel(header); m_staticTextHeader->Wrap(fastFromDIP(460)); //needs to be reapplied after SetLabel() - m_textCtrlFileList->ChangeValue(itemList); - /* - 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(); Refresh(); //needed after m_buttonOK label change } @@ -1185,7 +1182,7 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) : OptionsDlgGenerated(parent), confirmDlgs_(globalSettings.confirmDlgs), warnDlgs_ (globalSettings.warnDlgs), - autoCloseProgressDialog_(globalSettings.autoCloseProgressDialog), + autoCloseProgressDialog_(globalSettings.progressDlg.autoClose), globalCfgOut_(globalSettings) { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonOkay).setCancel(m_buttonCancel)); @@ -1241,10 +1238,10 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) : m_gridCustomCommand->SetMargins(0, 0); //temporarily set dummy value for window height calculations: - setExtApp(std::vector<ExternalApp>(globalSettings.gui.externalApps.size() + 1)); - confirmDlgs_ = defaultCfg_.confirmDlgs; // - warnDlgs_ = defaultCfg_.warnDlgs; //make sure m_staticTextAllDialogsShown is shown - autoCloseProgressDialog_ = defaultCfg_.autoCloseProgressDialog; // + setExtApp(std::vector<ExternalApp>(globalSettings.externalApps.size() + 1)); + confirmDlgs_ = defaultCfg_.confirmDlgs; // + warnDlgs_ = defaultCfg_.warnDlgs; //make sure m_staticTextAllDialogsShown is shown + autoCloseProgressDialog_ = defaultCfg_.progressDlg.autoClose; // updateGui(); GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() @@ -1252,10 +1249,10 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) : Center(); //needs to be re-applied after a dialog size change! //restore actual value: - setExtApp(globalSettings.gui.externalApps); + setExtApp(globalSettings.externalApps); confirmDlgs_ = globalSettings.confirmDlgs; warnDlgs_ = globalSettings.warnDlgs; - autoCloseProgressDialog_ = globalSettings.autoCloseProgressDialog; + autoCloseProgressDialog_ = globalSettings.progressDlg.autoClose; updateGui(); //automatically fit column width to match total grid width @@ -1287,7 +1284,7 @@ void OptionsDlg::updateGui() { const bool haveHiddenDialogs = confirmDlgs_ != defaultCfg_.confirmDlgs || warnDlgs_ != defaultCfg_.warnDlgs || - autoCloseProgressDialog_ != defaultCfg_.autoCloseProgressDialog; + autoCloseProgressDialog_ != defaultCfg_.progressDlg.autoClose; m_buttonRestoreDialogs->Enable(haveHiddenDialogs); m_staticTextAllDialogsShown->Show(!haveHiddenDialogs); @@ -1304,7 +1301,7 @@ void OptionsDlg::onRestoreDialogs(wxCommandEvent& event) { confirmDlgs_ = defaultCfg_.confirmDlgs; warnDlgs_ = defaultCfg_.warnDlgs; - autoCloseProgressDialog_ = defaultCfg_.autoCloseProgressDialog; + autoCloseProgressDialog_ = defaultCfg_.progressDlg.autoClose; updateGui(); } @@ -1363,7 +1360,7 @@ void OptionsDlg::onDefault(wxCommandEvent& event) m_textCtrlSoundPathCompareDone->ChangeValue(utfTo<wxString>(defaultCfg_.soundFileCompareFinished)); m_textCtrlSoundPathSyncDone ->ChangeValue(utfTo<wxString>(defaultCfg_.soundFileSyncFinished)); - setExtApp(defaultCfg_.gui.externalApps); + setExtApp(defaultCfg_.externalApps); updateGui(); } @@ -1382,11 +1379,11 @@ void OptionsDlg::onOkay(wxCommandEvent& event) globalCfgOut_.soundFileCompareFinished = utfTo<Zstring>(trimCpy(m_textCtrlSoundPathCompareDone->GetValue())); globalCfgOut_.soundFileSyncFinished = utfTo<Zstring>(trimCpy(m_textCtrlSoundPathSyncDone ->GetValue())); - globalCfgOut_.gui.externalApps = getExtApp(); + globalCfgOut_.externalApps = getExtApp(); globalCfgOut_.confirmDlgs = confirmDlgs_; globalCfgOut_.warnDlgs = warnDlgs_; - globalCfgOut_.autoCloseProgressDialog = autoCloseProgressDialog_; + globalCfgOut_.progressDlg.autoClose = autoCloseProgressDialog_; EndModal(static_cast<int>(ConfirmationButton::accept)); } @@ -1678,13 +1675,17 @@ ActivationDlg::ActivationDlg(wxWindow* parent, SetTitle(L"FreeFileSync " + utfTo<std::wstring>(ffsVersion) + L" [" + _("Donation Edition") + L']'); + m_richTextLastError ->SetMinSize({-1, m_richTextLastError ->GetCharHeight() * 7}); + m_richTextManualActivationUrl->SetMinSize({-1, m_richTextManualActivationUrl->GetCharHeight() * 4}); + m_textCtrlOfflineActivationKey->SetMinSize({fastFromDIP(260), -1}); //setMainInstructionFont(*m_staticTextMain); m_bitmapActivation->SetBitmap(loadImage("internet")); m_textCtrlOfflineActivationKey->ForceUpper(); - m_textCtrlLastError ->ChangeValue(lastErrorMsg); - m_textCtrlManualActivationUrl ->ChangeValue(manualActivationUrl); + setTextWithUrls(*m_richTextLastError, lastErrorMsg); + setTextWithUrls(*m_richTextManualActivationUrl, manualActivationUrl); + m_textCtrlOfflineActivationKey->ChangeValue(manualActivationKey); GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() @@ -1700,10 +1701,10 @@ void ActivationDlg::onCopyUrl(wxCommandEvent& event) if (wxClipboard::Get()->Open()) { ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close()); - wxClipboard::Get()->SetData(new wxTextDataObject(m_textCtrlManualActivationUrl->GetValue())); //ownership passed + wxClipboard::Get()->SetData(new wxTextDataObject(m_richTextManualActivationUrl->GetValue())); //ownership passed - m_textCtrlManualActivationUrl->SetFocus(); //[!] otherwise selection is lost - m_textCtrlManualActivationUrl->SelectAll(); //some visual feedback + m_richTextManualActivationUrl->SetFocus(); //[!] otherwise selection is lost + m_richTextManualActivationUrl->SelectAll(); //some visual feedback } } diff --git a/FreeFileSync/Source/ui/small_dlgs.h b/FreeFileSync/Source/ui/small_dlgs.h index 7b051085..3b0ec004 100644 --- a/FreeFileSync/Source/ui/small_dlgs.h +++ b/FreeFileSync/Source/ui/small_dlgs.h @@ -47,7 +47,7 @@ zen::ConfirmationButton showSelectTimespanDlg(wxWindow* parent, time_t& timeFrom zen::ConfirmationButton showCfgHighlightDlg(wxWindow* parent, int& cfgHistSyncOverdueDays); zen::ConfirmationButton showCloudSetupDialog(wxWindow* parent, Zstring& folderPathPhrase, Zstring& sftpKeyFileLastSelected, - size_t& parallelOps, const std::wstring* parallelOpsDisabledReason /*optional: disable control + show text*/); + size_t& parallelOps, bool canChangeParallelOp); enum class ActivationDlgButton { diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index f2200e2e..46b9852d 100644 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -17,12 +17,13 @@ #include <wx+/std_button_layout.h> #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> -#include <wx+/focus.h> +#include <wx+/window_tools.h> #include "gui_generated.h" #include "command_box.h" #include "folder_selector.h" #include "../base/norm_filter.h" #include "../base/file_hierarchy.h" +#include "../base/icon_loader.h" #include "../log_file.h" #include "../afs/concrete.h" #include "../base_tools.h" @@ -47,6 +48,11 @@ void initBitmapRadioButtons(const std::vector<std::pair<ToggleButton*, std::stri wxImage imgTxt = createImageFromText(btn.GetLabel(), btn.GetFont(), btn.GetForegroundColour()); wxImage imgIco = mirrorIfRtl(loadImage(imgName, -1 /*maxWidth*/, getDefaultMenuIconSize())); + + if (imgName == "delete_recycler") //use system icon if available (can fail on Linux??) + try { imgIco = extractWxImage(fff::getTrashIcon(getDefaultMenuIconSize())); /*throw SysError*/ } + catch (SysError&) { assert(false); } + if (!selected) imgIco = greyScale(imgIco); @@ -381,6 +387,9 @@ showMultipleCfgs_(showMultipleCfgs) m_bitmapPerf->SetBitmap(greyScaleIfDisabled(loadImage("speed"), enableExtraFeatures_)); m_panelPerfHeader->Enable(enableExtraFeatures_); + const int scrollDelta = GetCharHeight(); + m_scrolledWindowPerf->SetScrollRate(scrollDelta, scrollDelta); + m_spinCtrlAutoRetryCount->SetMinSize({fastFromDIP(60), -1}); //Hack: set size (why does wxWindow::Size() not work?) m_spinCtrlAutoRetryDelay->SetMinSize({fastFromDIP(60), -1}); // @@ -447,8 +456,8 @@ showMultipleCfgs_(showMultipleCfgs) initBitmapRadioButtons( { - {m_buttonRecycler, "delete_recycler" }, - {m_buttonPermanent, "delete_permanently"}, + {m_buttonRecycler, "delete_recycler" }, + {m_buttonPermanent, "delete_permanently"}, {m_buttonVersioning, "delete_versioning" }, }, true /*alignLeft*/); @@ -1099,9 +1108,16 @@ void ConfigDialog::updateSyncGui() switch (handleDeletion_) //unconditionally update image, including "local options off" { case DeletionPolicy::recycler: - m_bitmapDeletionType->SetBitmap(greyScaleIfDisabled(loadImage("delete_recycler"), syncOptionsEnabled)); + { + wxImage imgTrash = loadImage("delete_recycler"); + //use system icon if available (can fail on Linux??) + try { imgTrash = extractWxImage(fff::getTrashIcon(imgTrash.GetHeight())); /*throw SysError*/ } + catch (SysError&) { assert(false); } + + m_bitmapDeletionType->SetBitmap(greyScaleIfDisabled(imgTrash, syncOptionsEnabled)); setText(*m_staticTextDeletionTypeDescription, _("Retain deleted and overwritten files in the recycle bin")); - break; + } + break; case DeletionPolicy::permanent: m_bitmapDeletionType->SetBitmap(greyScaleIfDisabled(loadImage("delete_permanently"), syncOptionsEnabled)); setText(*m_staticTextDeletionTypeDescription, _("Delete and overwrite files permanently")); @@ -1194,8 +1210,8 @@ MiscSyncConfig ConfigDialog::getMiscSyncOptions() const } //---------------------------------------------------------------------------- miscCfg.ignoreErrors = m_checkBoxIgnoreErrors ->GetValue(); - miscCfg.automaticRetryCount = m_checkBoxAutoRetry ->GetValue() ? m_spinCtrlAutoRetryCount->GetValue() : 0; - miscCfg.automaticRetryDelay = std::chrono::seconds(m_spinCtrlAutoRetryDelay->GetValue()); + miscCfg.autoRetryCount = m_checkBoxAutoRetry ->GetValue() ? m_spinCtrlAutoRetryCount->GetValue() : 0; + miscCfg.autoRetryDelay = std::chrono::seconds(m_spinCtrlAutoRetryDelay->GetValue()); //---------------------------------------------------------------------------- miscCfg.postSyncCommand = m_comboBoxPostSyncCommand->getValue(); miscCfg.postSyncCondition = getEnumVal(enumPostSyncCondition_, *m_choicePostSyncCondition); @@ -1259,9 +1275,9 @@ void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg) //---------------------------------------------------------------------------- m_checkBoxIgnoreErrors ->SetValue(miscCfg.ignoreErrors); - m_checkBoxAutoRetry ->SetValue(miscCfg.automaticRetryCount > 0); - m_spinCtrlAutoRetryCount->SetValue(std::max<size_t>(miscCfg.automaticRetryCount, 0)); - m_spinCtrlAutoRetryDelay->SetValue(miscCfg.automaticRetryDelay.count()); + m_checkBoxAutoRetry ->SetValue(miscCfg.autoRetryCount > 0); + m_spinCtrlAutoRetryCount->SetValue(std::max<size_t>(miscCfg.autoRetryCount, 0)); + m_spinCtrlAutoRetryDelay->SetValue(miscCfg.autoRetryDelay.count()); //---------------------------------------------------------------------------- m_comboBoxPostSyncCommand->setValue(miscCfg.postSyncCommand); setEnumVal(enumPostSyncCondition_, *m_choicePostSyncCondition, miscCfg.postSyncCondition); @@ -1288,9 +1304,9 @@ void ConfigDialog::updateMiscGui() const MiscSyncConfig miscCfg = getMiscSyncOptions(); m_bitmapIgnoreErrors->SetBitmap(greyScaleIfDisabled(loadImage("error_ignore_active"), miscCfg.ignoreErrors)); - m_bitmapRetryErrors ->SetBitmap(greyScaleIfDisabled(loadImage("error_retry"), miscCfg.automaticRetryCount > 0 )); + m_bitmapRetryErrors ->SetBitmap(greyScaleIfDisabled(loadImage("error_retry"), miscCfg.autoRetryCount > 0 )); - fgSizerAutoRetry->Show(miscCfg.automaticRetryCount > 0); + fgSizerAutoRetry->Show(miscCfg.autoRetryCount > 0); m_panelComparisonSettings->Layout(); //showing "retry count" can affect bSizerPerformance! //---------------------------------------------------------------------------- @@ -1336,7 +1352,7 @@ void ConfigDialog::updateMiscGui() updateButton(*m_bpButtonEmailErrorWarning, ResultsNotification::errorWarning); updateButton(*m_bpButtonEmailErrorOnly, ResultsNotification::errorOnly); - m_staticTextPerfDeRequired2->Show(!enableExtraFeatures_); //required after each bSizerSyncMisc->Show() + m_hyperlinkPerfDeRequired2->Show(!enableExtraFeatures_); //required after each bSizerSyncMisc->Show() //---------------------------------------------------------------------------- m_bitmapLogFile->SetBitmap(greyScaleIfDisabled(loadImage("log_file", fastFromDIP(20)), m_checkBoxOverrideLogPath->GetValue())); @@ -1379,7 +1395,7 @@ void ConfigDialog::selectFolderPairConfig(int newPairIndexToShow) bSizerCompMisc ->Show(mainConfigSelected); bSizerSyncMisc ->Show(mainConfigSelected); - if (mainConfigSelected) m_staticTextPerfDeRequired->Show(!enableExtraFeatures_); //keep after bSizerPerformance->Show() + if (mainConfigSelected) m_hyperlinkPerfDeRequired ->Show(!enableExtraFeatures_); //keep after bSizerPerformance->Show() if (mainConfigSelected) m_staticlinePerfDeRequired->Show(!enableExtraFeatures_); // m_panelCompSettingsTab ->Layout(); //fix comp panel glitch on Win 7 125% font size + perf panel diff --git a/FreeFileSync/Source/ui/sync_cfg.h b/FreeFileSync/Source/ui/sync_cfg.h index b853ad2d..bc9d4f09 100644 --- a/FreeFileSync/Source/ui/sync_cfg.h +++ b/FreeFileSync/Source/ui/sync_cfg.h @@ -25,8 +25,8 @@ struct MiscSyncConfig { std::map<AfsDevice, size_t> deviceParallelOps; bool ignoreErrors = false; - size_t automaticRetryCount = 0; - std::chrono::seconds automaticRetryDelay{0}; + size_t autoRetryCount = 0; + std::chrono::seconds autoRetryDelay{0}; Zstring postSyncCommand; PostSyncCondition postSyncCondition = PostSyncCondition::completion; diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index d2124cae..a704c018 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace fff { -const char ffsVersion[] = "11.3"; //internal linkage! +const char ffsVersion[] = "11.4"; //internal linkage! const char FFS_VERSION_SEPARATOR = '.'; } diff --git a/wx+/graph.cpp b/wx+/graph.cpp index 7bd67504..bb320db6 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -11,7 +11,6 @@ #include <zen/basic_math.h> #include <zen/scope_guard.h> #include <zen/perf.h> -#include "dc.h" using namespace zen; @@ -196,7 +195,7 @@ void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const Conver } -void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Graph2D::PosCorner pos, const wxColor& colorText, const wxColor& colorBack) +void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, GraphCorner pos, const wxColor& colorText, const wxColor& colorBack) { if (txt.empty()) return; @@ -208,15 +207,15 @@ void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Grap wxPoint drawPos = graphArea.GetTopLeft(); switch (pos) { - case Graph2D::CORNER_TOP_LEFT: + case GraphCorner::topL: break; - case Graph2D::CORNER_TOP_RIGHT: + case GraphCorner::topR: drawPos.x += graphArea.width - boxExtent.GetWidth(); break; - case Graph2D::CORNER_BOTTOM_LEFT: + case GraphCorner::bottomL: drawPos.y += graphArea.height - boxExtent.GetHeight(); break; - case Graph2D::CORNER_BOTTOM_RIGHT: + case GraphCorner::bottomR: drawPos.x += graphArea.width - boxExtent.GetWidth(); drawPos.y += graphArea.height - boxExtent.GetHeight(); break; @@ -512,13 +511,6 @@ void Graph2D::onMouseCaptureLost(wxMouseCaptureLostEvent& event) } -void Graph2D::setCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca) -{ - curves_.clear(); - addCurve(data, ca); -} - - void Graph2D::addCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca) { CurveAttributes newAttr = ca; @@ -553,35 +545,35 @@ void Graph2D::render(wxDC& dc) const int xLabelPosY = clientRect.y; int yLabelPosX = clientRect.x; - switch (attr_.labelposX) + switch (attr_.xLabelpos) { - case LABEL_X_TOP: + case XLabelPos::none: + break; + case XLabelPos::top: graphArea.y += xLabelHeight; graphArea.height -= xLabelHeight; break; - case LABEL_X_BOTTOM: + case XLabelPos::bottom: xLabelPosY += clientRect.height - xLabelHeight; graphArea.height -= xLabelHeight; break; - case LABEL_X_NONE: - break; } - switch (attr_.labelposY) + switch (attr_.yLabelpos) { - case LABEL_Y_LEFT: + case YLabelPos::none: + break; + case YLabelPos::left: graphArea.x += yLabelWidth; graphArea.width -= yLabelWidth; break; - case LABEL_Y_RIGHT: + case YLabelPos::right: yLabelPosX += clientRect.width - yLabelWidth; graphArea.width -= yLabelWidth; break; - case LABEL_Y_NONE: - break; } - assert(attr_.labelposX == LABEL_X_NONE || attr_.labelFmtX); - assert(attr_.labelposY == LABEL_Y_NONE || attr_.labelFmtY); + assert(attr_.xLabelpos == XLabelPos::none || attr_.labelFmtX); + assert(attr_.yLabelpos == YLabelPos::none || attr_.labelFmtY); //paint graph background (excluding label area) drawFilledRectangle(dc, graphArea, fastFromDIP(1), getBorderColor(), attr_.colorBack); @@ -614,7 +606,7 @@ void Graph2D::render(wxDC& dc) const int blockCountX = 0; //enlarge minX, maxX to a multiple of a "useful" block size - if (attr_.labelposX != LABEL_X_NONE && attr_.labelFmtX.get()) + if (attr_.xLabelpos != XLabelPos::none && attr_.labelFmtX.get()) blockCountX = widenRange(minX, maxX, //in/out graphArea.width, minimalBlockSizePx.GetWidth() * 7, @@ -638,7 +630,7 @@ void Graph2D::render(wxDC& dc) const if (!points.empty()) { //cut points outside visible x-range now in order to calculate height of visible line fragments only! - const bool doPolygonCut = curves_[index].second.fillMode == CurveAttributes::FILL_POLYGON; //impacts auto minY/maxY!! + const bool doPolygonCut = curves_[index].second.fillMode == CurveFillMode::polygon; //impacts auto minY/maxY!! cutPointsOutsideX(points, marker, minX, maxX, doPolygonCut); if (!attr_.minY || !attr_.maxY) @@ -656,7 +648,7 @@ void Graph2D::render(wxDC& dc) const { int blockCountY = 0; //enlarge minY, maxY to a multiple of a "useful" block size - if (attr_.labelposY != LABEL_Y_NONE && attr_.labelFmtY.get()) + if (attr_.yLabelpos != YLabelPos::none && attr_.labelFmtY.get()) blockCountY = widenRange(minY, maxY, //in/out graphArea.height, minimalBlockSizePx.GetHeight() * 3, @@ -676,7 +668,7 @@ void Graph2D::render(wxDC& dc) const auto& cp = curvePoints[index]; //add two artificial points to fill the curve area towards x-axis => do this before cutPointsOutsideY() to handle curve leaving upper bound - if (curves_[index].second.fillMode == CurveAttributes::FILL_CURVE) + if (curves_[index].second.fillMode == CurveFillMode::curve) if (!cp.empty()) { cp.emplace_back(CurvePoint{cp.back ().x, minY}); //add lower right and left corners @@ -689,7 +681,7 @@ void Graph2D::render(wxDC& dc) const //cut points outside visible y-range before calculating pixels: //1. realToScreenRound() deforms out-of-range values! //2. pixels that are grossly out of range can be a severe performance problem when drawing on the DC (Windows) - const bool doPolygonCut = curves_[index].second.fillMode != CurveAttributes::FILL_NONE; + const bool doPolygonCut = curves_[index].second.fillMode != CurveFillMode::none; cutPointsOutsideY(cp, oobMarker[index], minY, maxY, doPolygonCut); auto& dp = drawPoints[index]; @@ -731,7 +723,7 @@ void Graph2D::render(wxDC& dc) const //#################### begin drawing #################### //1. draw colored area under curves for (auto it = curves_.begin(); it != curves_.end(); ++it) - if (it->second.fillMode != CurveAttributes::FILL_NONE) + if (it->second.fillMode != CurveFillMode::none) if (const std::vector<wxPoint>& points = drawPoints[it - curves_.begin()]; points.size() >= 3) { @@ -746,6 +738,8 @@ void Graph2D::render(wxDC& dc) const std::vector<SelectionBlock> allSelections = oldSel_; if (activeSel_) allSelections.push_back(activeSel_->refSelection()); + + if (!allSelections.empty()) { //alpha channel not supported on wxMSW, so draw selection before curves wxDCBrushChanger dummy(dc, wxColor(168, 202, 236)); //light blue @@ -782,15 +776,15 @@ void Graph2D::render(wxDC& dc) const numeric::round(screenToY)) + graphAreaOrigin; switch (attr_.mouseSelMode) { - case SELECT_NONE: + case GraphSelMode::none: break; - case SELECT_RECTANGLE: - dc.DrawRectangle(wxRect(pixelFrom, pixelTo)); + case GraphSelMode::rect: + dc.DrawRectangle(wxRect(pixelFrom, pixelTo)); //wxRect considers area *including* both points break; - case SELECT_X_AXIS: + case GraphSelMode::x: dc.DrawRectangle(wxRect(wxPoint(pixelFrom.x, graphArea.y), wxPoint(pixelTo.x, graphArea.y + graphArea.height - 1))); break; - case SELECT_Y_AXIS: + case GraphSelMode::y: dc.DrawRectangle(wxRect(wxPoint(graphArea.x, pixelFrom.y), wxPoint(graphArea.x + graphArea.width - 1, pixelTo.y))); break; } diff --git a/wx+/graph.h b/wx+/graph.h index a6e99200..c843b09b 100644 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -14,6 +14,7 @@ #include <wx/settings.h> #include <wx/bitmap.h> #include <zen/string_tools.h> +#include "dc.h" //elegant 2D graph as wxPanel specialization @@ -143,6 +144,44 @@ struct GraphSelectEvent : public wxEvent SelectionBlock selectBlock_; }; + +//------------------------------------------------------------------------------------------------------------ +enum class XLabelPos +{ + none, + top, + bottom, +}; + +enum class YLabelPos +{ + none, + left, + right, +}; + +enum class CurveFillMode +{ + none, + curve, + polygon +}; + +enum class GraphCorner +{ + topL, + topR, + bottomL, + bottomR, +}; + +enum class GraphSelMode +{ + none, + rect, + x, + y, +}; //------------------------------------------------------------------------------------------------------------ class Graph2D : public wxPanel @@ -160,8 +199,8 @@ public: public: CurveAttributes() {} //required by GCC CurveAttributes& setColor (const wxColor& col) { color = col; autoColor = false; return *this; } - CurveAttributes& fillCurveArea (const wxColor& col) { fillColor = col; fillMode = FILL_CURVE; return *this; } - CurveAttributes& fillPolygonArea(const wxColor& col) { fillColor = col; fillMode = FILL_POLYGON; return *this; } + CurveAttributes& fillCurveArea (const wxColor& col) { fillColor = col; fillMode = CurveFillMode::curve; return *this; } + CurveAttributes& fillPolygonArea(const wxColor& col) { fillColor = col; fillMode = CurveFillMode::polygon; return *this; } CurveAttributes& setLineWidth(size_t width) { lineWidth = static_cast<int>(width); return *this; } private: @@ -170,54 +209,17 @@ public: bool autoColor = true; wxColor color; - enum FillMode - { - FILL_NONE, - FILL_CURVE, - FILL_POLYGON - }; - - FillMode fillMode = FILL_NONE; + CurveFillMode fillMode = CurveFillMode::none; wxColor fillColor; - int lineWidth = 2; + int lineWidth = fastFromDIP(2); }; - void setCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca = CurveAttributes()); void addCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca = CurveAttributes()); + void clearCurves() { curves_.clear(); } static wxColor getBorderColor() { return { 130, 135, 144 }; } //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal... - enum PosLabelY - { - LABEL_Y_LEFT, - LABEL_Y_RIGHT, - LABEL_Y_NONE - }; - - enum PosLabelX - { - LABEL_X_TOP, - LABEL_X_BOTTOM, - LABEL_X_NONE - }; - - enum PosCorner - { - CORNER_TOP_LEFT, - CORNER_TOP_RIGHT, - CORNER_BOTTOM_LEFT, - CORNER_BOTTOM_RIGHT, - }; - - enum SelMode - { - SELECT_NONE, - SELECT_RECTANGLE, - SELECT_X_AXIS, - SELECT_Y_AXIS, - }; - class MainAttributes { public: @@ -229,27 +231,27 @@ public: MainAttributes& setAutoSize() { minX = maxX = minY = maxY = {}; return *this; } - MainAttributes& setLabelX(PosLabelX posX, int height = -1, std::shared_ptr<LabelFormatter> newLabelFmt = nullptr) + MainAttributes& setLabelX(XLabelPos posX, int height = -1, std::shared_ptr<LabelFormatter> newLabelFmt = nullptr) { - labelposX = posX; + xLabelpos = posX; if (height >= 0) xLabelHeight = height; if (newLabelFmt) labelFmtX = newLabelFmt; return *this; } - MainAttributes& setLabelY(PosLabelY posY, int width = -1, std::shared_ptr<LabelFormatter> newLabelFmt = nullptr) + MainAttributes& setLabelY(YLabelPos posY, int width = -1, std::shared_ptr<LabelFormatter> newLabelFmt = nullptr) { - labelposY = posY; + yLabelpos = posY; if (width >= 0) yLabelWidth = width; if (newLabelFmt) labelFmtY = newLabelFmt; return *this; } - MainAttributes& setCornerText(const wxString& txt, PosCorner pos) { cornerTexts[pos] = txt; return *this; } + MainAttributes& setCornerText(const wxString& txt, GraphCorner pos) { cornerTexts[pos] = txt; return *this; } //accessibility: always set both colors MainAttributes& setBaseColors(const wxColor& text, const wxColor& back) { colorText = text; colorBack = back; return *this; } - MainAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; } + MainAttributes& setSelectionMode(GraphSelMode mode) { mouseSelMode = mode; return *this; } private: friend class Graph2D; @@ -260,25 +262,24 @@ public: std::optional<double> minY; //y-range to visualize std::optional<double> maxY; // - PosLabelX labelposX = LABEL_X_BOTTOM; + XLabelPos xLabelpos = XLabelPos::bottom; std::optional<int> xLabelHeight; std::shared_ptr<LabelFormatter> labelFmtX = std::make_shared<DecimalNumberFormatter>(); - PosLabelY labelposY = LABEL_Y_LEFT; + YLabelPos yLabelpos = YLabelPos::left; std::optional<int> yLabelWidth; std::shared_ptr<LabelFormatter> labelFmtY = std::make_shared<DecimalNumberFormatter>(); - std::map<PosCorner, wxString> cornerTexts; + std::map<GraphCorner, wxString> cornerTexts; //accessibility: consider system text and background colors; //small drawback: color of graphs is NOT connected to the background! => responsibility of client to use correct colors wxColor colorText = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); wxColor colorBack = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - SelMode mouseSelMode = SELECT_RECTANGLE; + GraphSelMode mouseSelMode = GraphSelMode::rect; }; - void setAttributes(const MainAttributes& newAttr) { attr_ = newAttr; Refresh(); } MainAttributes getAttributes() const { return attr_; } diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 6eb25ac9..f7e9736f 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -374,17 +374,23 @@ private: !event.IsPageScroll()) { mouseRotateRemainder_ += -event.GetWheelRotation(); - const int rotations = mouseRotateRemainder_ / event.GetWheelDelta(); + int rotations = mouseRotateRemainder_ / event.GetWheelDelta(); mouseRotateRemainder_ -= rotations * event.GetWheelDelta(); + if (rotations == 0) //macOS generates tiny GetWheelRotation()! => don't allow! Always scroll a single row at least! + { + rotations = -numeric::sign(event.GetWheelRotation()); + mouseRotateRemainder_ = 0; + } + const int rowsDelta = rotations * event.GetLinesPerAction(); parent_.scrollDelta(0, rowsDelta); } else parent_.HandleOnMouseWheel(event); - onMouseMovement(event); - event.Skip(false); + onMouseMovement(event); + event.Skip(false); //if (!sendEventToParent(event)) // event.Skip(); @@ -1013,7 +1019,7 @@ private: { if (auto prov = refParent().getDataProvider()) { - onMouseMovement(event); //update highlight in obscure cases (e.g. right-click while context menu is open) + onMouseMovement(event); //update highlight in obscure cases (e.g. right-click while context menu is open) const wxPoint mousePos = GetPosition() + event.GetPosition(); const ptrdiff_t rowCount = refParent().getRowCount(); @@ -1070,7 +1076,7 @@ private: //update mouse highlight (in case it was frozen above) event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)! - onMouseMovement(event); + onMouseMovement(event); } event.Skip(); //allow changing focus } @@ -1134,10 +1140,10 @@ private: sendEventToParent(GridClickEvent(EVENT_GRID_MOUSE_LEFT_UP, row, rowHover, mousePos)); } #endif - + //update mouse highlight and tooltip: macOS no mouse movement event is generated after a mouse button click (unlike on Windows) event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)! - onMouseMovement(event); + onMouseMovement(event); event.Skip(); //allow changing focus } diff --git a/wx+/image_tools.h b/wx+/image_tools.h index cd895c2e..5a799a8b 100644 --- a/wx+/image_tools.h +++ b/wx+/image_tools.h @@ -69,7 +69,7 @@ wxImage getTransparentPixel() inline int getDefaultMenuIconSize() { - return fastFromDIP(24); + return fastFromDIP(20); } diff --git a/wx+/no_flicker.h b/wx+/no_flicker.h index 03969c00..53e47bb5 100644 --- a/wx+/no_flicker.h +++ b/wx+/no_flicker.h @@ -7,13 +7,18 @@ #ifndef NO_FLICKER_H_893421590321532 #define NO_FLICKER_H_893421590321532 +#include <zen/string_tools.h> +#include <zen/scope_guard.h> #include <wx/textctrl.h> #include <wx/stattext.h> +#include <wx/richtext/richtextctrl.h> +#include <wx/wupdlock.h> namespace zen { -inline +namespace +{ void setText(wxTextCtrl& control, const wxString& newText, bool* additionalLayoutChange = nullptr) { const wxString& label = control.GetValue(); //perf: don't call twice! @@ -24,7 +29,7 @@ void setText(wxTextCtrl& control, const wxString& newText, bool* additionalLayou control.ChangeValue(newText); } -inline + void setText(wxStaticText& control, wxString newText, bool* additionalLayoutChange = nullptr) { @@ -35,6 +40,78 @@ void setText(wxStaticText& control, wxString newText, bool* additionalLayoutChan if (label != newText) control.SetLabel(newText); } + + +void setTextWithUrls(wxRichTextCtrl& richCtrl, const wxString& newText) +{ + enum class BlockType + { + text, + url, + }; + std::vector<std::pair<BlockType, wxString>> blocks; + + for (auto it = newText.begin();;) + { + const wchar_t urlPrefix[] = L"https://"; + const auto itUrl = std::search(it, newText.end(), + urlPrefix, urlPrefix + strLength(urlPrefix)); + if (it != itUrl) + blocks.emplace_back(BlockType::text, wxString(it, itUrl)); + + if (itUrl == newText.end()) + break; + + auto itUrlEnd = std::find_if(itUrl, newText.end(), [](wchar_t c) { return isWhiteSpace(c); }); + blocks.emplace_back(BlockType::url, wxString(itUrl, itUrlEnd)); + it = itUrlEnd; + } + richCtrl.BeginSuppressUndo(); + ZEN_ON_SCOPE_EXIT(richCtrl.EndSuppressUndo()); + + //fix mouse scroll speed: why the FUCK is this even necessary! + richCtrl.SetLineHeight(richCtrl.GetCharHeight()); + + //get rid of margins and space between text blocks/"paragraphs" + richCtrl.SetMargins({0, 0}); + richCtrl.BeginParagraphSpacing(0, 0); + ZEN_ON_SCOPE_EXIT(richCtrl.EndParagraphSpacing()); + + richCtrl.Clear(); + + if (std::any_of(blocks.begin(), blocks.end(), [](const auto& item) { return item.first == BlockType::url; })) + { + wxRichTextAttr urlStyle; + urlStyle.SetTextColour(*wxBLUE); + urlStyle.SetFontUnderlined(true); + + for (const auto& [type, text] : blocks) + switch (type) + { + case BlockType::text: + richCtrl.WriteText(text); + break; + + case BlockType::url: + richCtrl.BeginStyle(urlStyle); + ZEN_ON_SCOPE_EXIT(richCtrl.EndStyle()); + richCtrl.BeginURL(text); + ZEN_ON_SCOPE_EXIT(richCtrl.EndURL()); + richCtrl.WriteText(text); + break; + } + + //register only once! => use a global function pointer, so that Unbind() works correctly: + using LaunchUrlFun = void(*)(wxTextUrlEvent& event); + static const LaunchUrlFun launchUrl = [](wxTextUrlEvent& event) { wxLaunchDefaultBrowser(event.GetString()); }; + + [[maybe_unused]] const bool unbindOk = richCtrl.Unbind(wxEVT_TEXT_URL, launchUrl); + richCtrl.Bind(wxEVT_TEXT_URL, launchUrl); + } + else + richCtrl.WriteText(newText); +} +} } #endif //NO_FLICKER_H_893421590321532 diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp index 541a787c..0a1ff2a0 100644 --- a/wx+/popup_dlg.cpp +++ b/wx+/popup_dlg.cpp @@ -7,6 +7,7 @@ #include "popup_dlg.h" #include <wx/app.h> #include <wx/display.h> +#include "no_flicker.h" #include "font_size.h" #include "image_resources.h" #include "popup_dlg_generated.h" @@ -19,7 +20,7 @@ using namespace zen; namespace { -void setBestInitialSize(wxTextCtrl& ctrl, const wxString& text, wxSize maxSize) +void setBestInitialSize(wxRichTextCtrl& ctrl, const wxString& text, wxSize maxSize) { const int scrollbarWidth = fastFromDIP(25); /*not only scrollbar, but also left/right padding (on macOS)! better use slightly larger than exact value (Windows: 17, Linux(CentOS): 14, macOS: 25) @@ -27,19 +28,21 @@ void setBestInitialSize(wxTextCtrl& ctrl, const wxString& text, wxSize maxSize) if (maxSize.x <= scrollbarWidth) //implicitly checks for non-zero, too! return; - maxSize.x -= scrollbarWidth; - int bestWidth = 0; + int maxLineWidth = 0; int rowCount = 0; int rowHeight = 0; + bool haveLineWrap = false; auto evalLineExtent = [&](const wxSize& sz) -> bool //return true when done { - if (sz.x > bestWidth) - bestWidth = std::min(maxSize.x, sz.x); + maxLineWidth = std::max(maxLineWidth, sz.x); - rowCount += numeric::integerDivideRoundUp(sz.x, maxSize.x); //integer round up: consider line-wraps! + const int wrappedRows = numeric::integerDivideRoundUp(sz.x, maxSize.x - scrollbarWidth); //integer round up: consider line-wraps! + rowCount += wrappedRows; rowHeight = std::max(rowHeight, sz.y); //all rows *should* have same height + if (wrappedRows > 1) + haveLineWrap = true; return rowCount * rowHeight >= maxSize.y; }; @@ -60,8 +63,19 @@ void setBestInitialSize(wxTextCtrl& ctrl, const wxString& text, wxSize maxSize) it = itEnd + 1; } +#if 1 //wxRichTextCtrl const int rowGap = 0; - const wxSize bestSize(bestWidth + scrollbarWidth, std::min(rowCount * (rowHeight + rowGap), maxSize.y)); + const int extraHeight = 0; +#else //wxTextCtrl + const int rowGap = 0; + const int extraHeight = 0; +#endif + int extraWidth = 0; + if (haveLineWrap) //compensate for trivial integerDivideRoundUp() not + extraWidth += ctrl.GetTextExtent(L"FreeFileSync").x / 2; //understanding line wrap algorithm + + const wxSize bestSize(std::min(maxLineWidth, maxSize.x) + extraWidth, + std::min(rowCount * (rowHeight + rowGap) + extraHeight, maxSize.y)); ctrl.SetMinSize(bestSize); //alas, SetMinClientSize() is just not working! } } @@ -79,7 +93,6 @@ public: buttonToDisableWhenChecked_(cfg.buttonToDisableWhenChecked) { - if (type != DialogInfoType::info) try { @@ -163,11 +176,11 @@ public: text += L'\n'; text += trimCpy(cfg.textDetail) + L'\n'; //add empty top/bottom lines *instead* of using border space! - setBestInitialSize(*m_textCtrlTextDetail, text, wxSize(maxWidth, maxHeight)); - m_textCtrlTextDetail->ChangeValue(text); + setBestInitialSize(*m_richTextDetail, text, wxSize(maxWidth, maxHeight)); + setTextWithUrls(*m_richTextDetail, text); } else - m_textCtrlTextDetail->Hide(); + m_richTextDetail->Hide(); if (checkBoxValue_) { diff --git a/wx+/popup_dlg_generated.cpp b/wx+/popup_dlg_generated.cpp index 8933b135..22e3d02c 100644 --- a/wx+/popup_dlg_generated.cpp +++ b/wx+/popup_dlg_generated.cpp @@ -36,11 +36,8 @@ PopupDialogGenerated::PopupDialogGenerated( wxWindow* parent, wxWindowID id, con m_staticTextMain->Wrap( -1 ); bSizer16->Add( m_staticTextMain, 0, wxRIGHT, 10 ); - - bSizer16->Add( 0, 5, 0, 0, 5 ); - - m_textCtrlTextDetail = new wxTextCtrl( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxBORDER_NONE ); - bSizer16->Add( m_textCtrlTextDetail, 1, wxEXPAND, 5 ); + m_richTextDetail = new wxRichTextCtrl( m_panel33, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY|wxBORDER_NONE|wxVSCROLL|wxWANTS_CHARS ); + bSizer16->Add( m_richTextDetail, 1, wxEXPAND, 5 ); bSizer165->Add( bSizer16, 1, wxEXPAND, 5 ); diff --git a/wx+/popup_dlg_generated.h b/wx+/popup_dlg_generated.h index 87474708..93842d17 100644 --- a/wx+/popup_dlg_generated.h +++ b/wx+/popup_dlg_generated.h @@ -20,7 +20,7 @@ #include <wx/settings.h> #include <wx/string.h> #include <wx/stattext.h> -#include <wx/textctrl.h> +#include <wx/richtext/richtextctrl.h> #include <wx/sizer.h> #include <wx/panel.h> #include <wx/statline.h> @@ -44,7 +44,7 @@ protected: wxPanel* m_panel33; wxStaticBitmap* m_bitmapMsgType; wxStaticText* m_staticTextMain; - wxTextCtrl* m_textCtrlTextDetail; + wxRichTextCtrl* m_richTextDetail; wxStaticLine* m_staticline6; wxCheckBox* m_checkBoxCustom; wxBoxSizer* bSizerStdButtons; diff --git a/wx+/focus.h b/wx+/window_tools.h index 2920828f..73faf272 100644 --- a/wx+/focus.h +++ b/wx+/window_tools.h @@ -8,6 +8,7 @@ #define FOCUS_1084731021985757843 #include <wx/toplevel.h> +#include <wx/display.h> namespace zen @@ -87,6 +88,101 @@ private: //don't store wxWindow* which may be dangling during ~FocusPreserver()! //test: click on delete folder pair and immediately press F5 => focus window (= FP del button) is defer-deleted during sync }; + + +namespace +{ +void setInitialWindowSize(wxTopLevelWindow& topWin, wxSize size, std::optional<wxPoint> pos, bool isMaximized, wxSize defaultSize) +{ + wxSize newSize = defaultSize; + std::optional<wxPoint> newPos; + //set dialog size and position: + // - width/height are invalid if the window is minimized (eg x,y = -32000; width = 160, height = 28) + // - multi-monitor setup: dialog may be placed on second monitor which is currently turned off + if (size.GetWidth () > 0 && + size.GetHeight() > 0) + { + if (pos) + { + //calculate how much of the dialog will be visible on screen + const int dlgArea = size.GetWidth() * size.GetHeight(); + int dlgAreaMaxVisible = 0; + + const int monitorCount = wxDisplay::GetCount(); + for (int i = 0; i < monitorCount; ++i) + { + wxRect overlap = wxDisplay(i).GetClientArea().Intersect(wxRect(*pos, size)); + dlgAreaMaxVisible = std::max(dlgAreaMaxVisible, overlap.GetWidth() * overlap.GetHeight()); + } + + if (dlgAreaMaxVisible > 0.1 * dlgArea //at least 10% of the dialog should be visible! + ) + { + newSize = size; + newPos = pos; + } + } + else + newSize = size; + } + + //old comment: "wxGTK's wxWindow::SetSize seems unreliable and behaves like a wxWindow::SetClientSize + // => use wxWindow::SetClientSize instead (for the record: no such issue on Windows/macOS) + //2018-10-15: Weird new problem on CentOS/Ubuntu: SetClientSize() + SetPosition() fail to set correct dialog *position*, but SetSize() + SetPosition() do! + // => old issues with SetSize() seem to be gone... => revert to SetSize() + if (newPos) + topWin.SetSize(wxRect(*newPos, newSize)); + else + { + topWin.SetSize(newSize); + topWin.Center(); + } + + if (isMaximized) //no real need to support both maximize and full screen functions + { + topWin.Maximize(true); + } +} + + +struct WindowLayoutWeak +{ + std::optional<wxSize> size; + std::optional<wxPoint> pos; + bool isMaximized = false; +}; +//destructive! changes window size! +WindowLayoutWeak getWindowSizeBeforeClose(wxTopLevelWindow& topWin) +{ + //we need to portably retrieve non-iconized, non-maximized size and position + // non-portable: Win32 GetWindowPlacement(); wxWidgets take: wxTopLevelWindow::RestoreToGeometry() + if (topWin.IsIconized()) + topWin.Iconize(false); + + WindowLayoutWeak layout; + if (topWin.IsMaximized()) //evaluate AFTER uniconizing! + { + topWin.Maximize(false); + layout.isMaximized = true; + } + + layout.size = topWin.GetSize(); + layout.pos = topWin.GetPosition(); + + if (layout.isMaximized) + if (!topWin.IsShown() //=> Win: can't trust size GetSize()/GetPosition(): still at full screen size! + //wxGTK: returns full screen size and strange position (65/-4) + //OS X 10.9 (but NO issue on 10.11!) returns full screen size and strange position (0/-22) + || layout.pos->y < 0 + ) + { + layout.size = std::nullopt; + layout.pos = std::nullopt; + } + + return layout; +} +} } #endif //FOCUS_1084731021985757843 diff --git a/xBRZ/src/xbrz_tools.h b/xBRZ/src/xbrz_tools.h index 404849ca..98164678 100644 --- a/xBRZ/src/xbrz_tools.h +++ b/xBRZ/src/xbrz_tools.h @@ -182,10 +182,10 @@ void nearestNeighborScaleOverSource(const PixSrc* src, int srcWidth, int srcHeig /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/, int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/) { - static_assert(std::is_integral<PixSrc>::value, "PixSrc* is expected to be cast-able to char*"); - static_assert(std::is_integral<PixTrg>::value, "PixTrg* is expected to be cast-able to char*"); + static_assert(std::is_integral_v<PixSrc>, "PixSrc* is expected to be cast-able to char*"); + static_assert(std::is_integral_v<PixTrg>, "PixTrg* is expected to be cast-able to char*"); - static_assert(std::is_same<decltype(pixCvrt(PixSrc())), PixTrg>::value, "PixConverter returning wrong pixel format"); + static_assert(std::is_same_v<decltype(pixCvrt(PixSrc())), PixTrg>, "PixConverter returning wrong pixel format"); if (srcPitch < srcWidth * static_cast<int>(sizeof(PixSrc)) || trgPitch < trgWidth * static_cast<int>(sizeof(PixTrg))) diff --git a/zen/globals.h b/zen/globals.h index 635909f7..47da2ac4 100644 --- a/zen/globals.h +++ b/zen/globals.h @@ -50,7 +50,7 @@ template <class T> class Global //don't use for function-scope statics! { public: - consteval2 Global() {}; //demand static zero-initialization! + consteval Global() {}; //demand static zero-initialization! ~Global() { @@ -108,7 +108,7 @@ template <class T> class FunStatGlobal { public: - consteval2 FunStatGlobal() {}; //demand static zero-initialization! + consteval FunStatGlobal() {}; //demand static zero-initialization! //No ~FunStatGlobal()! @@ -211,9 +211,9 @@ void registerGlobalForDestruction(CleanUpEntry& entry) } //------------------------------------------------------------------------------------------ -#ifdef __cpp_lib_atomic_wait - #error implement + rewiew improvements -#endif + #ifdef __cpp_lib_atomic_wait + #error implement + rewiew improvements + #endif inline diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h index 0cbb830e..66b750c9 100644 --- a/zen/legacy_compiler.h +++ b/zen/legacy_compiler.h @@ -29,9 +29,8 @@ namespace std } //--------------------------------------------------------------------------------- -//constinit, consteval +//constinit #define constinit2 constinit //GCC, clang have it - #define consteval2 consteval // namespace zen diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp index 475e1c6c..70658a68 100644 --- a/zen/sys_info.cpp +++ b/zen/sys_info.cpp @@ -63,7 +63,7 @@ ComputerModel zen::getComputerModel() //throw FileError cm.vendor = tryGetInfo("/sys/devices/virtual/dmi/id/sys_vendor"); // //clean up: - cm.model = beforeFirst(cm.model , L'\u00ff', IfNotFoundReturn::all); //fix broken BIOS entries: + cm.model = beforeFirst(cm.model, L'\u00ff', IfNotFoundReturn::all); //fix broken BIOS entries: cm.vendor = beforeFirst(cm.vendor, L'\u00ff', IfNotFoundReturn::all); //0xff can be considered 0 for (const char* dummyModel : diff --git a/zen/zstring.h b/zen/zstring.h index 09aa43b9..234a398d 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -26,7 +26,7 @@ using Zstringc = zen::Zbase<char>; /* Caveat: don't expect input/output string sizes to match: - different UTF-8 encoding length of upper-case chars - - different number of upper case chars (e.g. "ߢ => "SS" on macOS) + - different number of upper case chars (e.g. ß => "SS" on macOS) - output is Unicode-normalized */ Zstring getUpperCase(const Zstring& str); @@ -144,6 +144,7 @@ const wchar_t RTL_MARK = L'\u200F'; //UTF-8: E2 80 8F const wchar_t* const ELLIPSIS = L"\u2026"; //"..." const wchar_t MULT_SIGN = L'\u00D7'; //fancy "x" //const wchar_t NOBREAK_SPACE = L'\u00A0'; +const wchar_t ZERO_WIDTH_SPACE = L'\u200B'; |