summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Bugs.txt4
-rw-r--r--Changelog.txt18
-rw-r--r--FreeFileSync/Build/Resources/Gtk3Styles.css27
-rw-r--r--FreeFileSync/Build/Resources/Gtk3Styles.old.css35
-rw-r--r--FreeFileSync/Build/Resources/Languages.zipbin504258 -> 509888 bytes
-rw-r--r--FreeFileSync/Source/RealTimeSync/application.cpp27
-rw-r--r--FreeFileSync/Source/RealTimeSync/application.h2
-rw-r--r--FreeFileSync/Source/RealTimeSync/gui_generated.cpp125
-rw-r--r--FreeFileSync/Source/RealTimeSync/gui_generated.h17
-rw-r--r--FreeFileSync/Source/RealTimeSync/main_dlg.cpp6
-rw-r--r--FreeFileSync/Source/RealTimeSync/tray_menu.cpp1
-rw-r--r--FreeFileSync/Source/afs/abstract.h2
-rw-r--r--FreeFileSync/Source/afs/ftp.cpp28
-rw-r--r--FreeFileSync/Source/afs/gdrive.cpp37
-rw-r--r--FreeFileSync/Source/afs/native.cpp2
-rw-r--r--FreeFileSync/Source/afs/sftp.cpp38
-rw-r--r--FreeFileSync/Source/application.cpp48
-rw-r--r--FreeFileSync/Source/application.h3
-rw-r--r--FreeFileSync/Source/base/algorithm.cpp4
-rw-r--r--FreeFileSync/Source/base/comparison.cpp96
-rw-r--r--FreeFileSync/Source/base/db_file.h2
-rw-r--r--FreeFileSync/Source/base/dir_exist_async.h2
-rw-r--r--FreeFileSync/Source/base/dir_lock.cpp2
-rw-r--r--FreeFileSync/Source/base/file_hierarchy.h90
-rw-r--r--FreeFileSync/Source/base/lock_holder.h2
-rw-r--r--FreeFileSync/Source/base/parallel_scan.cpp12
-rw-r--r--FreeFileSync/Source/base/structures.cpp4
-rw-r--r--FreeFileSync/Source/base/synchronization.cpp44
-rw-r--r--FreeFileSync/Source/base/versioning.cpp5
-rw-r--r--FreeFileSync/Source/base/versioning.h2
-rw-r--r--FreeFileSync/Source/config.cpp141
-rw-r--r--FreeFileSync/Source/config.h23
-rw-r--r--FreeFileSync/Source/localization.cpp4
-rw-r--r--FreeFileSync/Source/log_file.cpp10
-rw-r--r--FreeFileSync/Source/parse_lng.h73
-rw-r--r--FreeFileSync/Source/parse_plural.h2
-rw-r--r--FreeFileSync/Source/status_handler.cpp13
-rw-r--r--FreeFileSync/Source/ui/abstract_folder_picker.cpp5
-rw-r--r--FreeFileSync/Source/ui/batch_config.cpp7
-rw-r--r--FreeFileSync/Source/ui/batch_status_handler.cpp2
-rw-r--r--FreeFileSync/Source/ui/batch_status_handler.h4
-rw-r--r--FreeFileSync/Source/ui/cfg_grid.cpp55
-rw-r--r--FreeFileSync/Source/ui/command_box.cpp8
-rw-r--r--FreeFileSync/Source/ui/command_box.h2
-rw-r--r--FreeFileSync/Source/ui/file_grid.cpp43
-rw-r--r--FreeFileSync/Source/ui/gui_generated.cpp9
-rw-r--r--FreeFileSync/Source/ui/gui_status_handler.cpp4
-rw-r--r--FreeFileSync/Source/ui/gui_status_handler.h4
-rw-r--r--FreeFileSync/Source/ui/log_panel.cpp2
-rw-r--r--FreeFileSync/Source/ui/main_dlg.cpp61
-rw-r--r--FreeFileSync/Source/ui/progress_indicator.cpp67
-rw-r--r--FreeFileSync/Source/ui/progress_indicator.h4
-rw-r--r--FreeFileSync/Source/ui/small_dlgs.cpp83
-rw-r--r--FreeFileSync/Source/ui/sync_cfg.cpp25
-rw-r--r--FreeFileSync/Source/ui/tree_grid.cpp47
-rw-r--r--FreeFileSync/Source/ui/version_check.cpp4
-rw-r--r--FreeFileSync/Source/version/version.h2
-rw-r--r--libcurl/curl_wrap.cpp4
-rw-r--r--wx+/dc.h2
-rw-r--r--wx+/grid.cpp93
-rw-r--r--wx+/no_flicker.h5
-rw-r--r--wx+/popup_dlg.cpp10
-rw-r--r--wx+/tooltip.cpp17
-rw-r--r--wx+/window_layout.h (renamed from wx+/font_size.h)41
-rw-r--r--wx+/window_tools.h216
-rw-r--r--xBRZ/src/xbrz.cpp2
-rw-r--r--zen/argon2.cpp6
-rw-r--r--zen/basic_math.h20
-rw-r--r--zen/file_access.cpp52
-rw-r--r--zen/file_io.cpp8
-rw-r--r--zen/file_io.h2
-rw-r--r--zen/file_path.cpp78
-rw-r--r--zen/file_path.h4
-rw-r--r--zen/globals.h25
-rw-r--r--zen/http.cpp2
-rw-r--r--zen/resolve_path.cpp43
-rw-r--r--zen/serialize.h6
-rw-r--r--zen/socket.h4
-rw-r--r--zen/stream_buffer.h4
-rw-r--r--zen/string_base.h15
-rw-r--r--zen/string_tools.h4
-rw-r--r--zen/sys_error.cpp10
-rw-r--r--zen/sys_info.cpp34
-rw-r--r--zen/thread.h2
-rw-r--r--zen/time.h35
-rw-r--r--zen/utf.h2
-rw-r--r--zen/zlib_wrap.cpp4
87 files changed, 1219 insertions, 920 deletions
diff --git a/Bugs.txt b/Bugs.txt
index 7de9b78f..23ec71f4 100644
--- a/Bugs.txt
+++ b/Bugs.txt
@@ -5,7 +5,7 @@ the ones mentioned below. The remaining issues that are yet to be fixed are list
----------------
-| libcurl 7.87|
+| libcurl 7.88|
----------------
__________________________________________________________________________________________________________
/lib/ftp.c
@@ -71,7 +71,7 @@ ________________________________________________________________________________
-------------------
-| wxWidgets 3.2.1 |
+| wxWidgets 3.2.2 |
-------------------
__________________________________________________________________________________________________________
/include/wx/features.h
diff --git a/Changelog.txt b/Changelog.txt
index 601c33dd..9235d34a 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,4 +1,20 @@
-FreeFileSync 12.0 [2022-01-21]
+FreeFileSync 12.1 [2023-02-20]
+------------------------------
+First official build based on GTK3 (Linux)
+Allow cancel during folder path normalization (e.g. delay during HDD spin up)
+Fixed slow FTP comparison performance due to libcurl regression
+Open terminal with log messages on startup error (Linux)
+Preserve changed config during auto-update
+Save config during unexpected reboot (Linux)
+Preserve config upon SIGTERM (Linux, macOS)
+Fixed progress dialog z-order after switching windows (macOS)
+Removed packet size limit for SFTP directory reading
+Mouse hover effects for config and overview grid
+Always update existing shortcuts during installation (Windows, Linux)
+Fixed another "Some files will be synchronized as part of multiple base folders" false-negative
+
+
+FreeFileSync 12.0 [2023-01-21]
------------------------------
Don't save password and show prompt instead for (S)FTP
Fast path check failure on access errors
diff --git a/FreeFileSync/Build/Resources/Gtk3Styles.css b/FreeFileSync/Build/Resources/Gtk3Styles.css
index 027e7bc3..ca82fe1e 100644
--- a/FreeFileSync/Build/Resources/Gtk3Styles.css
+++ b/FreeFileSync/Build/Resources/Gtk3Styles.css
@@ -1,9 +1,8 @@
/* CSS format as required by CentOS (GTK 3.22.30)
-
- https://developer.gnome.org/gtk3/stable/chap-css-properties.html
- https://developer.gnome.org/gtk3/stable/gtk-migrating-GtkStyleContext-css.html
- pkg-config --modversion gtk+-3.0 */
-
+ pkg-config --modversion gtk+-3.0
+
+ https://docs.gtk.org/gtk3/css-overview.html
+ https://docs.gtk.org/gtk3/css-properties.html */
*
{
/* see wx+/grid.cpp: spacing wouldn't hurt, but let's be consistent */
@@ -12,8 +11,24 @@
button
{
- padding: 2px; /*remove excessive inner border from bitmap buttons*/
+ padding: 4px 5px; /*remove excessive inner border from bitmap buttons*/
min-width: 0;
min-height: 0;
/*border-radius: 5px;*/
}
+
+entry
+{
+ padding: 2px 5px; /*default is too small for text input*/
+}
+
+combobox entry
+{
+ padding: 0 5px;
+}
+
+spinbutton entry
+{
+ padding: 0 5px;
+ /*margin-right: -50px; possible hack! but not needed right now */
+}
diff --git a/FreeFileSync/Build/Resources/Gtk3Styles.old.css b/FreeFileSync/Build/Resources/Gtk3Styles.old.css
index a2c43ce7..ad11061f 100644
--- a/FreeFileSync/Build/Resources/Gtk3Styles.old.css
+++ b/FreeFileSync/Build/Resources/Gtk3Styles.old.css
@@ -1,9 +1,8 @@
/* CSS format as required by Debian (GTK 3.14.5)
+ pkg-config --modversion gtk+-3.0
- https://developer.gnome.org/gtk3/stable/chap-css-properties.html
- https://developer.gnome.org/gtk3/stable/gtk-migrating-GtkStyleContext-css.html
- pkg-config --modversion gtk+-3.0 */
-
+ https://docs.gtk.org/gtk3/css-overview.html
+ https://docs.gtk.org/gtk3/css-properties.html */
*
{
/* see wx+/grid.cpp: spacing wouldn't hurt, but let's be consistent */
@@ -12,7 +11,33 @@
GtkButton
{
- padding: 2px; /*remove excessive inner border from bitmap buttons*/
+ padding: 4px 5px; /*remove excessive inner border*/
/* min-width: 0; => Debian: Error code 3: Gtk3Styles.css:13:10'min-width' is not a valid property name [gtk_css_provider_load_from_path]
min-height: 0; */
}
+
+GtkPaned
+{
+ border: 10px solid #d0d0d0; /*hack wxAUI panel splitter: not sure why "color" and "background-color" are not working*/
+}
+
+GtkEntry
+{
+ padding: 2px 5px; /*fix excessive padding for text input fields*/
+}
+
+GtkComboBox GtkEntry
+{
+ padding: 4px 5px;
+}
+
+GtkSpinButton /*GtkEntry*/
+{
+ padding: 4px 5px;
+}
+
+.tooltip /* why not GtkTooltip!? */
+{
+ color: white;
+ background-color: #343434; /*fix "Adwaita" theme glitch (Debian): background is *light grey*, while text color is white!*/
+}
diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip
index c2293e8f..a6e9f1e8 100644
--- a/FreeFileSync/Build/Resources/Languages.zip
+++ b/FreeFileSync/Build/Resources/Languages.zip
Binary files differ
diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp
index a7289497..c3f8a59a 100644
--- a/FreeFileSync/Source/RealTimeSync/application.cpp
+++ b/FreeFileSync/Source/RealTimeSync/application.cpp
@@ -28,14 +28,15 @@ using namespace zen;
using namespace rts;
+#ifdef __WXGTK3__ //deprioritize Wayland: see FFS' application.cpp
+ GLOBAL_RUN_ONCE(::gdk_set_allowed_backends("x11,*")); //call *before* gtk_init()
+#endif
+
IMPLEMENT_APP(Application)
namespace
{
-wxDEFINE_EVENT(EVENT_ENTER_EVENT_LOOP, wxCommandEvent);
-
-
using fff::FfsExitCode;
void notifyAppError(const std::wstring& msg, FfsExitCode rc)
@@ -144,7 +145,7 @@ bool Application::OnInit()
catch (const FileError& e) { notifyAppError(e.toString(), FfsExitCode::warning); }
- auto onSystemShutdown = []
+ auto onSystemShutdown = [](int /*unused*/ = 0)
{
onSystemShutdownRunTasks();
@@ -153,20 +154,24 @@ bool Application::OnInit()
};
Bind(wxEVT_QUERY_END_SESSION, [onSystemShutdown](wxCloseEvent& event) { onSystemShutdown(); }); //can veto
Bind(wxEVT_END_SESSION, [onSystemShutdown](wxCloseEvent& event) { onSystemShutdown(); }); //can *not* veto
+ try
+ {
+ if (auto /*sighandler_t n.a. on macOS*/ oldHandler = ::signal(SIGTERM, onSystemShutdown);//"graceful" exit requested, unlike SIGKILL
+ oldHandler == SIG_ERR)
+ THROW_LAST_SYS_ERROR("signal(SIGTERM)");
+ else assert(!oldHandler);
+ }
+ catch (const SysError& e) { notifyAppError(e.toString(), FfsExitCode::warning); }
//Note: app start is deferred: -> see FreeFileSync
- Bind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this);
- wxCommandEvent scrollEvent(EVENT_ENTER_EVENT_LOOP);
- AddPendingEvent(scrollEvent);
+ CallAfter([&] { onEnterEventLoop(); });
+
return true; //true: continue processing; false: exit immediately.
}
-void Application::onEnterEventLoop(wxEvent& event)
+void Application::onEnterEventLoop()
{
- [[maybe_unused]] bool ubOk = Unbind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this);
- assert(ubOk);
-
//wxWidgets app exit handling is weird... we want to exit only if the logical main window is closed, not just *any* window!
wxTheApp->SetExitOnFrameDelete(false); //prevent popup-windows from becoming temporary top windows leading to program exit after closure
ZEN_ON_SCOPE_EXIT(if (!globalWindowWasSet()) wxTheApp->ExitMainLoop()); //quit application, if no main window was set (batch silent mode)
diff --git a/FreeFileSync/Source/RealTimeSync/application.h b/FreeFileSync/Source/RealTimeSync/application.h
index b2b52028..2a22b47e 100644
--- a/FreeFileSync/Source/RealTimeSync/application.h
+++ b/FreeFileSync/Source/RealTimeSync/application.h
@@ -22,7 +22,7 @@ private:
void OnUnhandledException () override;
wxLayoutDirection GetLayoutDirection() const override;
- void onEnterEventLoop(wxEvent& event);
+ void onEnterEventLoop();
};
}
diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp
index 0f91a13d..7ab659cd 100644
--- a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp
+++ b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp
@@ -56,49 +56,6 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
wxBoxSizer* bSizer161;
bSizer161 = new wxBoxSizer( wxVERTICAL );
- wxBoxSizer* bSizer16;
- bSizer16 = new wxBoxSizer( wxHORIZONTAL );
-
- m_staticText9 = new wxStaticText( this, wxID_ANY, _("Usage:"), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText9->Wrap( -1 );
- m_staticText9->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
-
- bSizer16->Add( m_staticText9, 0, wxALL, 5 );
-
- ffgSizer111 = new wxFlexGridSizer( 0, 2, 5, 5 );
- ffgSizer111->SetFlexibleDirection( wxBOTH );
- ffgSizer111->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
-
- m_staticText16 = new wxStaticText( this, wxID_ANY, _("1."), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText16->Wrap( -1 );
- ffgSizer111->Add( m_staticText16, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 );
-
- m_staticText3 = new wxStaticText( this, wxID_ANY, _("Select folders to watch."), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText3->Wrap( -1 );
- ffgSizer111->Add( m_staticText3, 0, wxALIGN_CENTER_VERTICAL, 5 );
-
- m_staticText17 = new wxStaticText( this, wxID_ANY, _("2."), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText17->Wrap( -1 );
- ffgSizer111->Add( m_staticText17, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 );
-
- m_staticText4 = new wxStaticText( this, wxID_ANY, _("Enter a command line."), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText4->Wrap( -1 );
- ffgSizer111->Add( m_staticText4, 0, wxALIGN_CENTER_VERTICAL, 5 );
-
- m_staticText18 = new wxStaticText( this, wxID_ANY, _("3."), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText18->Wrap( -1 );
- ffgSizer111->Add( m_staticText18, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 );
-
- m_staticText5 = new wxStaticText( this, wxID_ANY, _("Press 'Start'."), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText5->Wrap( -1 );
- ffgSizer111->Add( m_staticText5, 0, wxALIGN_CENTER_VERTICAL, 5 );
-
-
- bSizer16->Add( ffgSizer111, 0, wxALL, 5 );
-
-
- bSizer161->Add( bSizer16, 0, 0, 5 );
-
wxBoxSizer* bSizer152;
bSizer152 = new wxBoxSizer( wxHORIZONTAL );
@@ -152,7 +109,7 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
m_bitmapFolders = new wxStaticBitmap( m_panelMain, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
bSizer142->Add( m_bitmapFolders, 0, wxTOP|wxBOTTOM|wxLEFT, 5 );
- m_staticText7 = new wxStaticText( m_panelMain, wxID_ANY, _("Folders to watch:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText7 = new wxStaticText( m_panelMain, wxID_ANY, _("Folders to watch for changes:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText7->Wrap( -1 );
bSizer142->Add( m_staticText7, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
@@ -162,53 +119,31 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
m_panelMainFolder = new wxPanel( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panelMainFolder->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
- wxFlexGridSizer* fgSizer1;
- fgSizer1 = new wxFlexGridSizer( 0, 2, 0, 0 );
- fgSizer1->AddGrowableCol( 1 );
- fgSizer1->SetFlexibleDirection( wxBOTH );
- fgSizer1->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL );
-
-
- fgSizer1->Add( 0, 0, 1, wxEXPAND, 5 );
-
- m_staticTextFinalPath = new wxStaticText( m_panelMainFolder, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticTextFinalPath->Wrap( -1 );
- fgSizer1->Add( m_staticTextFinalPath, 0, wxALIGN_CENTER_VERTICAL|wxALL, 2 );
-
- wxBoxSizer* bSizer20;
- bSizer20 = new wxBoxSizer( wxHORIZONTAL );
+ wxBoxSizer* bSizer143;
+ bSizer143 = new wxBoxSizer( wxHORIZONTAL );
m_bpButtonAddFolder = new wxBitmapButton( m_panelMainFolder, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 );
m_bpButtonAddFolder->SetToolTip( _("Add folder") );
- bSizer20->Add( m_bpButtonAddFolder, 0, wxEXPAND, 5 );
+ bSizer143->Add( m_bpButtonAddFolder, 0, wxEXPAND, 5 );
m_bpButtonRemoveTopFolder = new wxBitmapButton( m_panelMainFolder, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 );
m_bpButtonRemoveTopFolder->SetToolTip( _("Remove folder") );
- bSizer20->Add( m_bpButtonRemoveTopFolder, 0, wxEXPAND, 5 );
-
-
- fgSizer1->Add( bSizer20, 0, wxEXPAND, 5 );
-
- wxBoxSizer* bSizer19;
- bSizer19 = new wxBoxSizer( wxHORIZONTAL );
+ bSizer143->Add( m_bpButtonRemoveTopFolder, 0, wxEXPAND, 5 );
m_txtCtrlDirectoryMain = new wxTextCtrl( m_panelMainFolder, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), 0 );
- bSizer19->Add( m_txtCtrlDirectoryMain, 1, wxALIGN_CENTER_VERTICAL, 5 );
+ bSizer143->Add( m_txtCtrlDirectoryMain, 1, wxALIGN_CENTER_VERTICAL, 5 );
m_buttonSelectFolderMain = new wxButton( m_panelMainFolder, wxID_ANY, _("Browse"), wxDefaultPosition, wxDefaultSize, 0 );
m_buttonSelectFolderMain->SetToolTip( _("Select a folder") );
- bSizer19->Add( m_buttonSelectFolderMain, 0, wxEXPAND, 5 );
+ bSizer143->Add( m_buttonSelectFolderMain, 0, wxEXPAND, 5 );
- fgSizer1->Add( bSizer19, 0, wxEXPAND, 5 );
-
-
- m_panelMainFolder->SetSizer( fgSizer1 );
+ m_panelMainFolder->SetSizer( bSizer143 );
m_panelMainFolder->Layout();
- fgSizer1->Fit( m_panelMainFolder );
+ bSizer143->Fit( m_panelMainFolder );
bSizer151->Add( m_panelMainFolder, 0, wxRIGHT|wxLEFT|wxEXPAND, 5 );
m_scrolledWinFolders = new wxScrolledWindow( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL );
@@ -224,29 +159,11 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
bSizer151->Add( m_scrolledWinFolders, 1, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
- bSizer1->Add( bSizer151, 1, wxALL|wxEXPAND, 5 );
+ bSizer1->Add( bSizer151, 1, wxALL|wxEXPAND, 10 );
m_staticline212 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
bSizer1->Add( m_staticline212, 0, wxEXPAND, 5 );
- wxBoxSizer* bSizer14;
- bSizer14 = new wxBoxSizer( wxHORIZONTAL );
-
- m_staticText8 = new wxStaticText( m_panelMain, wxID_ANY, _("Minimum idle time (in seconds):"), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText8->Wrap( -1 );
- bSizer14->Add( m_staticText8, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
-
- m_spinCtrlDelay = new wxSpinCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 2000000000, 0 );
- m_spinCtrlDelay->SetToolTip( _("Idle time between last detected change and execution of command") );
-
- bSizer14->Add( m_spinCtrlDelay, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 );
-
-
- bSizer1->Add( bSizer14, 0, wxALL|wxEXPAND, 5 );
-
- m_staticline211 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
- bSizer1->Add( m_staticline211, 0, wxEXPAND, 5 );
-
wxBoxSizer* bSizer141;
bSizer141 = new wxBoxSizer( wxVERTICAL );
@@ -256,7 +173,7 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
m_bitmapConsole = new wxStaticBitmap( m_panelMain, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
bSizer13->Add( m_bitmapConsole, 0, wxTOP|wxBOTTOM|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 );
- m_staticText6 = new wxStaticText( m_panelMain, wxID_ANY, _("Command line:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText6 = new wxStaticText( m_panelMain, wxID_ANY, _("Command line to run when changes are detected:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText6->Wrap( -1 );
bSizer13->Add( m_staticText6, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
@@ -269,7 +186,25 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
bSizer141->Add( m_textCtrlCommand, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
- bSizer1->Add( bSizer141, 0, wxALL|wxEXPAND, 5 );
+ bSizer1->Add( bSizer141, 0, wxALL|wxEXPAND, 10 );
+
+ m_staticline211 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer1->Add( m_staticline211, 0, wxEXPAND, 5 );
+
+ wxBoxSizer* bSizer14;
+ bSizer14 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_staticText8 = new wxStaticText( m_panelMain, wxID_ANY, _("Minimum idle time (in seconds) before running command:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText8->Wrap( -1 );
+ bSizer14->Add( m_staticText8, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
+
+ m_spinCtrlDelay = new wxSpinCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 2000000000, 0 );
+ m_spinCtrlDelay->SetToolTip( _("Idle time between last detected change and execution of command") );
+
+ bSizer14->Add( m_spinCtrlDelay, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 );
+
+
+ bSizer1->Add( bSizer14, 0, wxALL|wxEXPAND, 10 );
m_panelMain->SetSizer( bSizer1 );
diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.h b/FreeFileSync/Source/RealTimeSync/gui_generated.h
index 47514409..b3367031 100644
--- a/FreeFileSync/Source/RealTimeSync/gui_generated.h
+++ b/FreeFileSync/Source/RealTimeSync/gui_generated.h
@@ -22,9 +22,9 @@ namespace zen { class BitmapTextButton; }
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/stattext.h>
-#include <wx/sizer.h>
#include <wx/statbmp.h>
#include <wx/hyperlink.h>
+#include <wx/sizer.h>
#include <wx/statline.h>
#include <wx/bmpbuttn.h>
#include <wx/button.h>
@@ -52,14 +52,6 @@ protected:
wxMenu* m_menuHelp;
wxMenuItem* m_menuItemAbout;
wxBoxSizer* bSizerMain;
- wxStaticText* m_staticText9;
- wxFlexGridSizer* ffgSizer111;
- wxStaticText* m_staticText16;
- wxStaticText* m_staticText3;
- wxStaticText* m_staticText17;
- wxStaticText* m_staticText4;
- wxStaticText* m_staticText18;
- wxStaticText* m_staticText5;
wxStaticText* m_staticText811;
wxStaticText* m_staticText10;
wxStaticBitmap* m_bitmapBatch;
@@ -70,7 +62,6 @@ protected:
wxStaticBitmap* m_bitmapFolders;
wxStaticText* m_staticText7;
wxPanel* m_panelMainFolder;
- wxStaticText* m_staticTextFinalPath;
wxBitmapButton* m_bpButtonAddFolder;
wxBitmapButton* m_bpButtonRemoveTopFolder;
wxTextCtrl* m_txtCtrlDirectoryMain;
@@ -78,12 +69,12 @@ protected:
wxScrolledWindow* m_scrolledWinFolders;
wxBoxSizer* bSizerFolders;
wxStaticLine* m_staticline212;
- wxStaticText* m_staticText8;
- wxSpinCtrl* m_spinCtrlDelay;
- wxStaticLine* m_staticline211;
wxStaticBitmap* m_bitmapConsole;
wxStaticText* m_staticText6;
wxTextCtrl* m_textCtrlCommand;
+ wxStaticLine* m_staticline211;
+ wxStaticText* m_staticText8;
+ wxSpinCtrl* m_spinCtrlDelay;
wxStaticLine* m_staticline5;
zen::BitmapTextButton* m_buttonStart;
diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
index 707442fc..dbed8788 100644
--- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
+++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
@@ -9,7 +9,7 @@
#include <wx/filedlg.h>
#include <wx+/app_main.h>
#include <wx+/bitmap_button.h>
-#include <wx+/font_size.h>
+#include <wx+/window_layout.h>
#include <wx+/popup_dlg.h>
#include <wx+/image_resources.h>
#include <zen/file_access.h>
@@ -81,7 +81,7 @@ MainDialog::MainDialog(const Zstring& cfgFilePath) :
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?)
+ setDefaultWidth(*m_spinCtrlDelay);
m_bpButtonRemoveTopFolder->Hide();
m_panelMainFolder->Layout();
@@ -101,7 +101,7 @@ MainDialog::MainDialog(const Zstring& cfgFilePath) :
setGlobalWindow(this);
//prepare drag & drop
- firstFolderPanel_ = std::make_unique<FolderSelector2>(this, *m_panelMainFolder, *m_buttonSelectFolderMain, *m_txtCtrlDirectoryMain, folderLastSelected_, m_staticTextFinalPath);
+ firstFolderPanel_ = std::make_unique<FolderSelector2>(this, *m_panelMainFolder, *m_buttonSelectFolderMain, *m_txtCtrlDirectoryMain, folderLastSelected_, nullptr /*staticText*/);
//--------------------------- load config values ------------------------------------
XmlRealConfig newConfig;
diff --git a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
index a1d723e3..0caffe70 100644
--- a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
+++ b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
@@ -251,6 +251,7 @@ rts::AbortReason rts::runFolderMonitor(const XmlRealConfig& config, const wxStri
auto executeExternalCommand = [&](const Zstring& changedItemPath, const std::wstring& actionName) //throw FileError
{
+ warn_static("maybe not a good idea!? job for execve? https://rachelbythebay.com/w/2017/01/30/env/")
::wxSetEnv(L"change_path", utfTo<wxString>(changedItemPath)); //crude way to report changed file
::wxSetEnv(L"change_action", actionName); //
auto cmdLineExp = expandMacros(cmdLine);
diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h
index 2d43736a..c5e8305f 100644
--- a/FreeFileSync/Source/afs/abstract.h
+++ b/FreeFileSync/Source/afs/abstract.h
@@ -263,7 +263,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t
//Note: it MAY happen that copyFileTransactional() leaves temp files behind, e.g. temporary network drop.
// => clean them up at an appropriate time (automatically set sync directions to delete them). They have the following ending:
- static inline const Zchar* const TEMP_FILE_ENDING = Zstr(".ffs_tmp"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace!
+ static inline const ZstringView TEMP_FILE_ENDING = Zstr(".ffs_tmp"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace!
// caveat: ending is hard-coded by RealTimeSync
struct FileCopyResult
diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp
index ca3e147e..0bb9ee99 100644
--- a/FreeFileSync/Source/afs/ftp.cpp
+++ b/FreeFileSync/Source/afs/ftp.cpp
@@ -35,7 +35,7 @@ const size_t FTP_BLOCK_SIZE_UPLOAD = 64 * 1024; //libcurl requests blocks of 6
const size_t FTP_STREAM_BUFFER_SIZE = 1024 * 1024; //unit: [byte]
//stream buffer should be big enough to facilitate prefetching during alternating read/write operations => e.g. see serialize.h::unbufferedStreamCopy()
-const Zchar ftpPrefix[] = Zstr("ftp:");
+const ZstringView ftpPrefix = Zstr("ftp:");
enum class ServerEncoding
@@ -2038,7 +2038,7 @@ struct OutputStreamFtp : public AFS::OutputStreamImpl
AFS::FinalizeResult finalize(const IoCallback& notifyUnbufferedIO /*throw X*/) override //throw FileError, X
{
if (!asyncStreamOut_)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
asyncStreamOut_->closeStream();
@@ -2050,7 +2050,7 @@ struct OutputStreamFtp : public AFS::OutputStreamImpl
futUploadDone_.get(); //throw FileError
//asyncStreamOut_->checkReadErrors(); //throw FileError -> not needed after *successful* upload
- asyncStreamOut_.reset(); //do NOT reset on failure, so that ~OutputStreamFtp() will request worker thread to stop
+ asyncStreamOut_.reset(); //do NOT reset on error, so that ~OutputStreamFtp() will request worker thread to stop
//--------------------------------------------------------------------
AFS::FinalizeResult result;
@@ -2080,7 +2080,7 @@ private:
if (modTime_)
try
{
- const std::string isoTime = utfTo<std::string>(formatTime(Zstr("%Y%m%d%H%M%S"), getUtcTime(*modTime_))); //returns empty string on failure
+ const std::string isoTime = utfTo<std::string>(formatTime(Zstr("%Y%m%d%H%M%S"), getUtcTime(*modTime_))); //returns empty string on error
if (isoTime.empty())
throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(*modTime_) + L')');
@@ -2449,7 +2449,7 @@ private:
//caveat: connection phase only, so disable CURLOPT_SERVER_RESPONSE_TIMEOUT, or next access may fail with CURLE_OPERATION_TIMEDOUT!
}); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
};
-
+
try
{
const std::shared_ptr<FtpSessionManager> mgr = globalFtpSessionManager.get();
@@ -2465,16 +2465,17 @@ private:
connectServer(); //throw SysError, SysErrorPassword
return; //got new FtpSession (connected in constructor) or already connected session from cache
}
- catch (SysErrorPassword&) {}
-
- if (!requestPassword)
- throw SysError(_("Password prompt not permitted by current settings."));
+ catch (const SysErrorPassword& e)
+ {
+ if (!requestPassword)
+ throw SysError(e.toString() + L'\n' + _("Password prompt not permitted by current settings."));
+ }
std::wstring lastErrorMsg;
for (;;)
{
//2. request (new) password
- std::wstring msg = replaceCpy(_("Please enter your password to connect to %x"), L"%x", fmtPath(getDisplayPath(AfsPath())));
+ std::wstring msg = replaceCpy(_("Please enter your password to connect to %x."), L"%x", fmtPath(getDisplayPath(AfsPath())));
if (lastErrorMsg.empty())
msg += L"\n" + _("The password will only be remembered until FreeFileSync is closed.");
@@ -2650,16 +2651,17 @@ AbstractPath fff::createItemPathFtp(const Zstring& itemPathPhrase) //noexcept
const ZstringView port = afterLast(serverPort, Zstr(':'), IfNotFoundReturn::none);
login.portCfg = stringTo<int>(port); //0 if empty
- split(options, Zstr('|'), [&](const ZstringView optPhrase)
+ split(options, Zstr('|'), [&](ZstringView optPhrase)
{
+ optPhrase = trimCpy(optPhrase);
if (!optPhrase.empty())
{
if (startsWith(optPhrase, Zstr("timeout=")))
- login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none));
+ login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr('='), IfNotFoundReturn::none));
else if (optPhrase == Zstr("ssl"))
login.useTls = true;
else if (startsWith(optPhrase, Zstr("pass64=")))
- login.password = decodePasswordBase64(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none));
+ login.password = decodePasswordBase64(afterFirst(optPhrase, Zstr('='), IfNotFoundReturn::none));
else if (optPhrase == Zstr("pwprompt"))
login.password = std::nullopt;
else
diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp
index 33849de9..e82273cc 100644
--- a/FreeFileSync/Source/afs/gdrive.cpp
+++ b/FreeFileSync/Source/afs/gdrive.cpp
@@ -103,7 +103,7 @@ struct HttpSessionId
Zstring server;
};
-inline
+inline
bool operator==(const HttpSessionId& lhs, const HttpSessionId& rhs) { return equalAsciiNoCase(lhs.server, rhs.server); }
}
@@ -1392,7 +1392,7 @@ std::string /*fileId*/ gdriveCopyFile(const std::string& fileId, const std::stri
//more Google Drive peculiarities: changing the file name changes modifiedTime!!! => workaround:
//RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
- const std::string modTimeRfc = utfTo<std::string>(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime(newModTime))); //returns empty string on failure
+ const std::string modTimeRfc = utfTo<std::string>(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime(newModTime))); //returns empty string on error
if (modTimeRfc.empty())
throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(newModTime) + L')');
@@ -1442,7 +1442,7 @@ void gdriveMoveAndRenameItem(const std::string& itemId, const std::string& paren
//more Google Drive peculiarities: changing the file name changes modifiedTime!!! => workaround:
//RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
- const std::string modTimeRfc = utfTo<std::string>(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime(newModTime))); //returns empty string on failure
+ const std::string modTimeRfc = utfTo<std::string>(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime(newModTime))); //returns empty string on error
if (modTimeRfc.empty())
throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(newModTime) + L')');
@@ -1478,7 +1478,7 @@ void setModTime(const std::string& itemId, time_t modTime, const GdriveAccess& a
{
//https://developers.google.com/drive/api/v3/reference/files/update
//RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
- const std::string& modTimeRfc = formatTime<std::string>("%Y-%m-%dT%H:%M:%S.000Z", getUtcTime2(modTime)); //returns empty string on failure
+ const std::string& modTimeRfc = formatTime<std::string>("%Y-%m-%dT%H:%M:%S.000Z", getUtcTime2(modTime)); //returns empty string on error
if (modTimeRfc.empty())
throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(modTime) + L')');
@@ -1600,7 +1600,7 @@ std::string /*itemId*/ gdriveUploadSmallFile(const Zstring& fileName, const std:
postParams.objectVal.emplace("parents", std::vector<JsonValue> {JsonValue(parentId)});
if (modTime) //convert to RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
{
- const std::string& modTimeRfc = utfTo<std::string>(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime2(*modTime))); //returns empty string on failure
+ const std::string& modTimeRfc = utfTo<std::string>(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime2(*modTime))); //returns empty string on error
if (modTimeRfc.empty())
throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(*modTime) + L')');
@@ -1711,7 +1711,7 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri
postParams.objectVal.emplace("parents", std::vector<JsonValue> {JsonValue(parentId)});
if (modTime) //convert to RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
{
- const std::string& modTimeRfc = utfTo<std::string>(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime(*modTime))); //returns empty string on failure
+ const std::string& modTimeRfc = utfTo<std::string>(formatTime(Zstr("%Y-%m-%dT%H:%M:%S.000Z"), getUtcTime(*modTime))); //returns empty string on error
if (modTimeRfc.empty())
throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(*modTime) + L')');
@@ -1825,7 +1825,7 @@ public:
void update(const GdriveAccessInfo& accessInfo)
{
if (!equalAsciiNoCase(accessInfo.userInfo.email, accessInfo_.userInfo.email))
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
accessInfo_ = accessInfo;
}
@@ -1908,7 +1908,7 @@ public:
for (const auto& [folderId, content] : folderContents_)
if (folderId.empty())
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
else if (content.isKnownFolder)
writeContainer(stream, folderId);
writeContainer(stream, std::string()); //sentinel
@@ -1936,13 +1936,13 @@ public:
{
const auto& [itemId, details] = *itItem;
if (itemId.empty())
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
serializeItem(itemId, details);
if (details.type == GdriveItemType::shortcut)
{
if (details.targetId.empty())
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
if (auto it = itemDetails_.find(details.targetId);
it != itemDetails_.end())
@@ -2002,7 +2002,7 @@ public:
return *it;
//itemId was already found! => (must either be a location root) or buffered in itemDetails_
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
}
std::optional<GdriveItemDetails> tryGetBufferedItemDetails(const std::string& itemId) const
@@ -2190,7 +2190,7 @@ private:
itKnown = folderContents_.find(folderId);
assert(itKnown != folderContents_.end());
if (!itKnown->second.isKnownFolder)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
}
auto itFound = itemDetails_.cend();
@@ -2255,7 +2255,7 @@ private:
}
break;
}
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
}
}
@@ -2284,7 +2284,7 @@ private:
if (it != itemDetails_.end()) //update
{
if (it->second.type != details->type)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); //WTF!?
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!"); //WTF!?
std::vector<std::string> parentIdsNew = details->parentIds;
std::vector<std::string> parentIdsRemoved = it->second.parentIds;
@@ -3274,7 +3274,7 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl
AFS::FinalizeResult finalize(const IoCallback& notifyUnbufferedIO /*throw X*/) override //throw FileError, X
{
if (!asyncStreamOut_)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
asyncStreamOut_->closeStream();
@@ -3287,7 +3287,7 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl
result.filePrint = futFilePrint_.get(); //throw FileError
//asyncStreamOut_->checkReadErrors(); //throw FileError -> not needed after *successful* upload
- asyncStreamOut_.reset(); //do NOT reset on failure, so that ~OutputStreamGdrive() will request worker thread to stop
+ asyncStreamOut_.reset(); //do NOT reset on error, so that ~OutputStreamGdrive() will request worker thread to stop
//--------------------------------------------------------------------
//result.errorModTime -> already (successfully) set during file creation
@@ -4081,12 +4081,13 @@ AbstractPath fff::createItemPathGdrive(const Zstring& itemPathPhrase) //noexcept
.locationName = Zstring(afterFirst (emailAndDrive, Zstr(':'), IfNotFoundReturn::none)),
};
- split(options, Zstr('|'), [&](const ZstringView optPhrase)
+ split(options, Zstr('|'), [&](ZstringView optPhrase)
{
+ optPhrase = trimCpy(optPhrase);
if (!optPhrase.empty())
{
if (startsWith(optPhrase, Zstr("timeout=")))
- login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none));
+ login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr('='), IfNotFoundReturn::none));
else
assert(false);
}
diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp
index bbcd82e9..48fdcac2 100644
--- a/FreeFileSync/Source/afs/native.cpp
+++ b/FreeFileSync/Source/afs/native.cpp
@@ -711,7 +711,7 @@ void RecycleSessionNative::moveToRecycleBin(const AbstractPath& itemPath, const
{
const Zstring& itemPathNative = getNativeItemPath(itemPath);
if (itemPathNative.empty())
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
zen::moveToRecycleBin(itemPathNative); //throw FileError, RecycleBinUnavailable
}
diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp
index 86cc8d32..b284345d 100644
--- a/FreeFileSync/Source/afs/sftp.cpp
+++ b/FreeFileSync/Source/afs/sftp.cpp
@@ -47,7 +47,7 @@ OpenSSL supports the same ciphers like WinCNG plus the following:
cast128-cbc
blowfish-cbc */
-const Zchar sftpPrefix[] = Zstr("sftp:");
+const ZstringView sftpPrefix = Zstr("sftp:");
constexpr std::chrono::seconds SFTP_SESSION_MAX_IDLE_TIME (20);
constexpr std::chrono::seconds SFTP_SESSION_CLEANUP_INTERVAL (4); //facilitate default of 5-seconds delay for error retry
@@ -1050,8 +1050,8 @@ private:
struct SshSessionCache
{
//invariant: all cached sessions correspond to activeCfg at any time!
- std::vector<std::unique_ptr<SshSession>> idleSshSessions; //extract *temporarily* from this list during use
- std::map<std::thread::id, std::weak_ptr<SshSessionShared>> sshSessionsWithThreadAffinity; //Win32 thread IDs may be REUSED! still, shouldn't be a problem...
+ std::vector<std::unique_ptr<SshSession>> idleSshSessions; //extract *temporarily* from this list during use
+ std::unordered_map<std::thread::id, std::weak_ptr<SshSessionShared>> sshSessionsWithThreadAffinity; //Win32 thread IDs may be REUSED! still, shouldn't be a problem...
std::optional<SshSessionCfg> activeCfg;
@@ -1351,7 +1351,7 @@ struct InputStreamSftp : public AFS::InputStream
{
//libssh2_sftp_read has same semantics as Posix read:
if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
assert(bytesToRead % getBlockSize() == 0);
ssize_t bytesRead = 0;
@@ -1433,7 +1433,7 @@ struct OutputStreamSftp : public AFS::OutputStreamImpl
size_t tryWrite(const void* buffer, size_t bytesToWrite, const IoCallback& notifyUnbufferedIO /*throw X*/) override //throw FileError, X; may return short! CONTRACT: bytesToWrite > 0
{
if (bytesToWrite == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
assert(bytesToWrite % getBlockSize() == 0 || bytesToWrite < getBlockSize());
ssize_t bytesWritten = 0;
@@ -1484,10 +1484,10 @@ private:
void close() //throw FileError
{
if (!fileHandle_)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
try
{
- ZEN_ON_SCOPE_EXIT(fileHandle_ = nullptr); //reset on failure, too! there's no point in, calling libssh2_sftp_close() a second time in ~OutputStreamSftp()
+ ZEN_ON_SCOPE_EXIT(fileHandle_ = nullptr); //reset on error, too! there's no point in, calling libssh2_sftp_close() a second time in ~OutputStreamSftp()
session_->executeBlocking("libssh2_sftp_close", //throw SysError, SysErrorSftpProtocol
[&](const SshSession::Details& sd) { return ::libssh2_sftp_close(fileHandle_); }); //noexcept!
@@ -1889,17 +1889,18 @@ private:
{
/*auto session =*/ mgr->getSharedSession(login_); //throw SysError, SysErrorPassword
return; //got new SshSession (connected in constructor) or already connected session from cache
- }
- catch (SysErrorPassword&) {}
-
- if (!requestPassword)
- throw SysError(_("Password prompt not permitted by current settings."));
+ }
+ catch (const SysErrorPassword& e)
+ {
+ if (!requestPassword)
+ throw SysError(e.toString() + L'\n' + _("Password prompt not permitted by current settings."));
+ }
std::wstring lastErrorMsg;
for (;;)
{
//2. request (new) password
- std::wstring msg = replaceCpy(_("Please enter your password to connect to %x"), L"%x", fmtPath(getDisplayPath(AfsPath())));
+ std::wstring msg = replaceCpy(_("Please enter your password to connect to %x."), L"%x", fmtPath(getDisplayPath(AfsPath())));
if (lastErrorMsg.empty())
msg += L"\n" + _("The password will only be remembered until FreeFileSync is closed.");
@@ -2146,23 +2147,24 @@ AbstractPath fff::createItemPathSftp(const Zstring& itemPathPhrase) //noexcept
assert(login.allowZlib == false);
- split(options, Zstr('|'), [&](const ZstringView optPhrase)
+ split(options, Zstr('|'), [&](ZstringView optPhrase)
{
+ optPhrase = trimCpy(optPhrase);
if (!optPhrase.empty())
{
if (startsWith(optPhrase, Zstr("timeout=")))
- login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none));
+ login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr('='), IfNotFoundReturn::none));
else if (startsWith(optPhrase, Zstr("chan=")))
- login.traverserChannelsPerConnection = stringTo<int>(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none));
+ login.traverserChannelsPerConnection = stringTo<int>(afterFirst(optPhrase, Zstr('='), IfNotFoundReturn::none));
else if (startsWith(optPhrase, Zstr("keyfile=")))
{
login.authType = SftpAuthType::keyFile;
- login.privateKeyFilePath = getResolvedFilePath(Zstring(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none)));
+ login.privateKeyFilePath = getResolvedFilePath(Zstring(afterFirst(optPhrase, Zstr('='), IfNotFoundReturn::none)));
}
else if (optPhrase == Zstr("agent"))
login.authType = SftpAuthType::agent;
else if (startsWith(optPhrase, Zstr("pass64=")))
- login.password = decodePasswordBase64(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none));
+ login.password = decodePasswordBase64(afterFirst(optPhrase, Zstr('='), IfNotFoundReturn::none));
else if (optPhrase == Zstr("pwprompt"))
login.password = std::nullopt;
else if (optPhrase == Zstr("zlib"))
diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp
index 69d1c3fa..7a2d23fb 100644
--- a/FreeFileSync/Source/application.cpp
+++ b/FreeFileSync/Source/application.cpp
@@ -35,6 +35,16 @@ using namespace zen;
using namespace fff;
+#ifdef __WXGTK3__
+ /* Wayland backend used by GTK3 does not allow to move windows! (no such issue on GTK2)
+
+ "I'd really like to know if there is some deep technical reason for it or
+ if this is really as bloody stupid as it seems?" - vadz https://github.com/wxWidgets/wxWidgets/issues/18733#issuecomment-1011235902
+
+ => workaround: https://docs.gtk.org/gdk3/func.set_allowed_backends.html */
+ GLOBAL_RUN_ONCE(::gdk_set_allowed_backends("x11,*")); //call *before* gtk_init()
+#endif
+
IMPLEMENT_APP(Application)
@@ -52,8 +62,6 @@ std::vector<Zstring> getCommandlineArgs(const wxApp& app)
return args;
}
-wxDEFINE_EVENT(EVENT_ENTER_EVENT_LOOP, wxCommandEvent);
-
void showSyntaxHelp()
{
@@ -197,7 +205,7 @@ bool Application::OnInit()
- auto onSystemShutdown = []
+ auto onSystemShutdown = [](int /*unused*/ = 0)
{
onSystemShutdownRunTasks();
@@ -208,20 +216,24 @@ bool Application::OnInit()
};
Bind(wxEVT_QUERY_END_SESSION, [onSystemShutdown](wxCloseEvent& event) { onSystemShutdown(); }); //can veto
Bind(wxEVT_END_SESSION, [onSystemShutdown](wxCloseEvent& event) { onSystemShutdown(); }); //can *not* veto
+ //- log off: Windows/macOS generates wxEVT_QUERY_END_SESSION/wxEVT_END_SESSION
+ // Linux/macOS generates SIGTERM, which we handle below
+ //- Windows sends WM_QUERYENDSESSION, WM_ENDSESSION during log off, *not* WM_CLOSE https://devblogs.microsoft.com/oldnewthing/20080421-00/?p=22663
+ // => taskkill sending WM_CLOSE (without /f) is a misguided app simulating a button-click on X
+ // -> should send WM_QUERYENDSESSION instead!
+ try
+ {
+ if (auto /*sighandler_t n.a. on macOS*/ oldHandler = ::signal(SIGTERM, onSystemShutdown);//"graceful" exit requested, unlike SIGKILL
+ oldHandler == SIG_ERR)
+ THROW_LAST_SYS_ERROR("signal(SIGTERM)");
+ else assert(!oldHandler);
+ }
+ catch (const SysError& e) { notifyAppError(e.toString(), FfsExitCode::warning); }
//Note: app start is deferred: batch mode requires the wxApp eventhandler to be established for UI update events. This is not the case at the time of OnInit()!
- Bind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this);
- AddPendingEvent(wxCommandEvent(EVENT_ENTER_EVENT_LOOP));
- return true; //true: continue processing; false: exit immediately.
-}
-
+ CallAfter([&] { onEnterEventLoop(); });
-void Application::onEnterEventLoop(wxEvent& event)
-{
- [[maybe_unused]] const bool ubOk = Unbind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this);
- assert(ubOk);
-
- launch(getCommandlineArgs(*this)); //determine FFS mode of operation
+ return true; //true: continue processing; false: exit immediately.
}
@@ -267,8 +279,10 @@ void Application::OnUnhandledException() //handles both wxApp::OnInit() + wxApp:
-void Application::launch(const std::vector<Zstring>& commandArgs)
+void Application::onEnterEventLoop()
{
+ const std::vector<Zstring>& commandArgs = getCommandlineArgs(*this);
+
//wxWidgets app exit handling is weird... we want to exit only if the logical main window is closed, not just *any* window!
wxTheApp->SetExitOnFrameDelete(false); //prevent popup-windows from becoming temporary top windows leading to program exit after closure
ZEN_ON_SCOPE_EXIT(if (!globalWindowWasSet()) wxTheApp->ExitMainLoop()); //quit application, if no main window was set (batch silent mode)
@@ -548,7 +562,7 @@ void Application::runBatchMode(const Zstring& globalConfigFilePath, const XmlBat
batchCfg.mainCfg.autoRetryDelay,
globalCfg.soundFileSyncFinished,
globalCfg.soundFileAlertPending,
- globalCfg.dpiLayouts[getDpiScalePercent()].progressDlg.dlgSize,
+ globalCfg.dpiLayouts[getDpiScalePercent()].progressDlg.size,
globalCfg.dpiLayouts[getDpiScalePercent()].progressDlg.isMaximized,
batchCfg.batchExCfg.autoCloseSummary,
batchCfg.batchExCfg.postSyncAction,
@@ -621,7 +635,7 @@ void Application::runBatchMode(const Zstring& globalConfigFilePath, const XmlBat
//*INDENT-ON*
}
- globalCfg.dpiLayouts[getDpiScalePercent()].progressDlg.dlgSize = r.dlgSize;
+ globalCfg.dpiLayouts[getDpiScalePercent()].progressDlg.size = r.dlgSize;
globalCfg.dpiLayouts[getDpiScalePercent()].progressDlg.isMaximized = r.dlgIsMaximized;
//email sending, or saving log file failed? at the very least this should affect the exit code:
diff --git a/FreeFileSync/Source/application.h b/FreeFileSync/Source/application.h
index 3d8b75e6..1eaf8fe1 100644
--- a/FreeFileSync/Source/application.h
+++ b/FreeFileSync/Source/application.h
@@ -26,8 +26,7 @@ private:
void OnUnhandledException () override;
void notifyAppError(const std::wstring& msg, FfsExitCode rc);
wxLayoutDirection GetLayoutDirection() const override;
- void onEnterEventLoop(wxEvent& event);
- void launch(const std::vector<Zstring>& commandArgs);
+ void onEnterEventLoop();
void runGuiMode (const Zstring& globalConfigFile);
void runGuiMode (const Zstring& globalConfigFile, const XmlGuiConfig& guiCfg, const std::vector<Zstring>& cfgFilePaths, bool startComparison);
diff --git a/FreeFileSync/Source/base/algorithm.cpp b/FreeFileSync/Source/base/algorithm.cpp
index 49cc4883..3c371d34 100644
--- a/FreeFileSync/Source/base/algorithm.cpp
+++ b/FreeFileSync/Source/base/algorithm.cpp
@@ -698,7 +698,7 @@ std::vector<std::pair<BaseFolderPair*, SyncDirectionConfig>> fff::extractDirecti
mainCfg.additionalPairs.end());
if (folderCmp.size() != allPairs.size())
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
std::vector<std::pair<BaseFolderPair*, SyncDirectionConfig>> output;
@@ -998,7 +998,7 @@ void fff::applyFiltering(FolderComparison& folderCmp, const MainConfiguration& m
if (folderCmp.empty())
return;
else if (folderCmp.size() != mainCfg.additionalPairs.size() + 1)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
//merge first and additional pairs
std::vector<LocalPairConfig> allPairs;
diff --git a/FreeFileSync/Source/base/comparison.cpp b/FreeFileSync/Source/base/comparison.cpp
index 74035b80..240f31e4 100644
--- a/FreeFileSync/Source/base/comparison.cpp
+++ b/FreeFileSync/Source/base/comparison.cpp
@@ -8,7 +8,6 @@
#include <zen/process_priority.h>
#include <zen/perf.h>
#include <zen/time.h>
-#include <wx/datetime.h>
#include "algorithm.h"
#include "parallel_scan.h"
#include "dir_exist_async.h"
@@ -76,25 +75,55 @@ ResolvedBaseFolders initializeBaseFolders(const std::vector<FolderPairCfg>& fpCf
WarningDialogs& warnings,
PhaseCallback& callback /*throw X*/) //throw X
{
+ std::vector<Zstring> pathPhrases;
+ for (const FolderPairCfg& fpCfg : fpCfgList)
+ {
+ pathPhrases.push_back(fpCfg.folderPathPhraseLeft_);
+ pathPhrases.push_back(fpCfg.folderPathPhraseRight_);
+ }
+
ResolvedBaseFolders output;
std::set<AbstractPath> allFolders;
tryReportingError([&]
{
- //support "retry" for environment variable and variable driver letter resolution!
- allFolders.clear();
- output.resolvedPairs.clear();
- for (const FolderPairCfg& fpCfg : fpCfgList)
+ //createAbstractPath() -> tryExpandVolumeName() hangs for idle HDD! => run async to make it cancellable
+ auto protCurrentPhrase = makeSharedRef<Protected<Zstring>>();
+
+ std::future<std::vector<AbstractPath>> futFolderPaths = runAsync([pathPhrases, currentPhraseWeak = std::weak_ptr(protCurrentPhrase.ptr())]
{
- output.resolvedPairs.push_back(
+ setCurrentThreadName(Zstr("Normalizing folder paths"));
+
+ std::vector<AbstractPath> folderPaths;
+ for (const Zstring& pathPhrase : pathPhrases)
{
- createAbstractPath(fpCfg.folderPathPhraseLeft_),
- createAbstractPath(fpCfg.folderPathPhraseRight_)});
+ if (auto protCurrentPhrase2 = currentPhraseWeak.lock()) //[!] not owned by worker thread!
+ protCurrentPhrase2->access([&](Zstring& currentPathPhrase) { currentPathPhrase = pathPhrase; });
+ else
+ throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Caller context gone!");
- allFolders.insert(output.resolvedPairs.back().folderPathLeft);
- allFolders.insert(output.resolvedPairs.back().folderPathRight);
+ folderPaths.push_back(createAbstractPath(pathPhrase));
+ }
+ return folderPaths;
+ });
+
+ while (futFolderPaths.wait_for(UI_UPDATE_INTERVAL / 2) == std::future_status::timeout)
+ {
+ const Zstring pathPhrase = protCurrentPhrase.ref().access([](const Zstring& currentPathPhrase) { return currentPathPhrase; });
+ callback.updateStatus(_("Normalizing folder paths...") + L' ' + utfTo<std::wstring>(pathPhrase)); //throw X
}
+
+ const std::vector<AbstractPath>& folderPaths = futFolderPaths.get(); //throw (std::runtime_error)
+
+ //support "retry" for environment variable and variable driver letter resolution!
+ allFolders.clear();
+ allFolders.insert(folderPaths.begin(), folderPaths.end());
+
+ output.resolvedPairs.clear();
+ for (size_t i = 0; i < folderPaths.size(); i += 2)
+ output.resolvedPairs.push_back({folderPaths[i], folderPaths[i + 1]});
//---------------------------------------------------------------------------
+
output.baseFolderStatus = getFolderStatusParallel(allFolders,
true /*authenticateAccess*/, requestPassword, callback); //throw X
if (!output.baseFolderStatus.failedChecks.empty())
@@ -214,10 +243,9 @@ ComparisonBuffer::ComparisonBuffer(const std::set<DirectoryKey>& folderKeys,
UI_UPDATE_INTERVAL / 2); //every ~50 ms
const int64_t totalTimeSec = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - compareStartTime).count();
-
callback.logMessage(_("Comparison finished:") + L' ' +
_P("1 item found", "%x items found", itemsReported) + SPACED_DASH +
- _("Time elapsed:") + L' ' + copyStringTo<std::wstring>(wxTimeSpan::Seconds(totalTimeSec).Format()),
+ _("Time elapsed:") + L' ' + utfTo<std::wstring>(formatTimeSpan(totalTimeSec)),
PhaseCallback::MsgType::info); //throw X
//------------------------------------------------------------------
@@ -739,19 +767,19 @@ void MergeSides::fillOneSide(const FolderContainer& folderCont, const Zstringc*
{
forEachSorted(folderCont.files, [&](const Zstring& fileName, const FileAttributes& attrib)
{
- FilePair& newItem = output.addSubFile<side>(fileName, attrib);
+ FilePair& newItem = output.addFile<side>(fileName, attrib);
checkFailedRead(newItem, errorMsg);
});
forEachSorted(folderCont.symlinks, [&](const Zstring& linkName, const LinkAttributes& attrib)
{
- SymlinkPair& newItem = output.addSubLink<side>(linkName, attrib);
+ SymlinkPair& newItem = output.addLink<side>(linkName, attrib);
checkFailedRead(newItem, errorMsg);
});
forEachSorted(folderCont.folders, [&](const Zstring& folderName, const std::pair<FolderAttributes, FolderContainer>& attrib)
{
- FolderPair& newFolder = output.addSubFolder<side>(folderName, attrib.first);
+ FolderPair& newFolder = output.addFolder<side>(folderName, attrib.first);
const Zstringc* errorMsgNew = checkFailedRead(newFolder, errorMsg);
fillOneSide<side>(attrib.second, errorMsgNew, newFolder); //recurse
@@ -838,24 +866,24 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
matchFolders(lhs.files, rhs.files, [&](const FileData& fileLeft, const Zstringc* conflictMsg)
{
- FilePair& newItem = output.addSubFile<SelectSide::left >(fileLeft .first, fileLeft .second);
+ FilePair& newItem = output.addFile<SelectSide::left >(fileLeft .first, fileLeft .second);
checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg);
},
[&](const FileData& fileRight, const Zstringc* conflictMsg)
{
- FilePair& newItem = output.addSubFile<SelectSide::right>(fileRight.first, fileRight.second);
+ FilePair& newItem = output.addFile<SelectSide::right>(fileRight.first, fileRight.second);
checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg);
},
[&](const FileData& fileLeft, const FileData& fileRight)
{
- FilePair& newItem = output.addSubFile(fileLeft.first,
- fileLeft.second,
- FILE_CONFLICT, //dummy-value until categorization is finished later
- fileRight.first,
- fileRight.second);
+ FilePair& newItem = output.addFile(fileLeft.first,
+ fileLeft.second,
+ FILE_CONFLICT, //dummy-value until categorization is finished later
+ fileRight.first,
+ fileRight.second);
if (!checkFailedRead(newItem, errorMsg))
undefinedFiles_.push_back(&newItem);
- static_assert(std::is_same_v<ContainerObject::FileList, std::list<FilePair>>); //ContainerObject::addSubFile() must NOT invalidate references used in "undefinedFiles"!
+ static_assert(std::is_same_v<ContainerObject::FileList, std::list<FilePair>>); //ContainerObject::addFile() must NOT invalidate references used in "undefinedFiles"!
});
//-----------------------------------------------------------------------------------------------
@@ -863,21 +891,21 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
matchFolders(lhs.symlinks, rhs.symlinks, [&](const SymlinkData& symlinkLeft, const Zstringc* conflictMsg)
{
- SymlinkPair& newItem = output.addSubLink<SelectSide::left >(symlinkLeft .first, symlinkLeft .second);
+ SymlinkPair& newItem = output.addLink<SelectSide::left >(symlinkLeft .first, symlinkLeft .second);
checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg);
},
[&](const SymlinkData& symlinkRight, const Zstringc* conflictMsg)
{
- SymlinkPair& newItem = output.addSubLink<SelectSide::right>(symlinkRight.first, symlinkRight.second);
+ SymlinkPair& newItem = output.addLink<SelectSide::right>(symlinkRight.first, symlinkRight.second);
checkFailedRead(newItem, conflictMsg ? conflictMsg : errorMsg);
},
[&](const SymlinkData& symlinkLeft, const SymlinkData& symlinkRight) //both sides
{
- SymlinkPair& newItem = output.addSubLink(symlinkLeft.first,
- symlinkLeft.second,
- SYMLINK_CONFLICT, //dummy-value until categorization is finished later
- symlinkRight.first,
- symlinkRight.second);
+ SymlinkPair& newItem = output.addLink(symlinkLeft.first,
+ symlinkLeft.second,
+ SYMLINK_CONFLICT, //dummy-value until categorization is finished later
+ symlinkRight.first,
+ symlinkRight.second);
if (!checkFailedRead(newItem, errorMsg))
undefinedSymlinks_.push_back(&newItem);
});
@@ -887,19 +915,19 @@ void MergeSides::mergeTwoSides(const FolderContainer& lhs, const FolderContainer
matchFolders(lhs.folders, rhs.folders, [&](const FolderData& dirLeft, const Zstringc* conflictMsg)
{
- FolderPair& newFolder = output.addSubFolder<SelectSide::left>(dirLeft.first, dirLeft.second.first);
+ FolderPair& newFolder = output.addFolder<SelectSide::left>(dirLeft.first, dirLeft.second.first);
const Zstringc* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg);
this->fillOneSide<SelectSide::left>(dirLeft.second.second, errorMsgNew, newFolder); //recurse
},
[&](const FolderData& dirRight, const Zstringc* conflictMsg)
{
- FolderPair& newFolder = output.addSubFolder<SelectSide::right>(dirRight.first, dirRight.second.first);
+ FolderPair& newFolder = output.addFolder<SelectSide::right>(dirRight.first, dirRight.second.first);
const Zstringc* errorMsgNew = checkFailedRead(newFolder, conflictMsg ? conflictMsg : errorMsg);
this->fillOneSide<SelectSide::right>(dirRight.second.second, errorMsgNew, newFolder); //recurse
},
[&](const FolderData& dirLeft, const FolderData& dirRight)
{
- FolderPair& newFolder = output.addSubFolder(dirLeft.first, dirLeft.second.first, DIR_EQUAL, dirRight.first, dirRight.second.first);
+ FolderPair& newFolder = output.addFolder(dirLeft.first, dirLeft.second.first, DIR_EQUAL, dirRight.first, dirRight.second.first);
const Zstringc* errorMsgNew = checkFailedRead(newFolder, errorMsg);
if (!errorMsgNew)
@@ -1067,7 +1095,7 @@ FolderComparison fff::compare(WarningDialogs& warnings,
requestPassword, warnings, callback); //throw X
//directory existence only checked *once* to avoid race conditions!
if (resInfo.resolvedPairs.size() != fpCfgList.size())
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
std::vector<std::pair<ResolvedFolderPair, FolderPairCfg>> workLoad;
for (size_t i = 0; i < fpCfgList.size(); ++i)
diff --git a/FreeFileSync/Source/base/db_file.h b/FreeFileSync/Source/base/db_file.h
index 552cdfef..98d681b8 100644
--- a/FreeFileSync/Source/base/db_file.h
+++ b/FreeFileSync/Source/base/db_file.h
@@ -15,7 +15,7 @@
namespace fff
{
-const Zchar SYNC_DB_FILE_ENDING[] = Zstr(".ffs_db"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace!
+const ZstringView SYNC_DB_FILE_ENDING = Zstr(".ffs_db"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace!
struct InSyncDescrFile //subset of FileAttributes
{
diff --git a/FreeFileSync/Source/base/dir_exist_async.h b/FreeFileSync/Source/base/dir_exist_async.h
index a344cc06..f6af51fa 100644
--- a/FreeFileSync/Source/base/dir_exist_async.h
+++ b/FreeFileSync/Source/base/dir_exist_async.h
@@ -128,7 +128,7 @@ FolderStatus getFolderStatusParallel(const std::set<AbstractPath>& folderPaths,
if (protPromptsPending)
protPromptsPending->access([&](RingBuffer<AsyncPrompt>& promptsPending)
{
- //do work while holding Protected<> lock!? device authentication threads blocking doesn't matter because prompts are serialized to GUI anyway
+ //call back while holding Protected<> lock!? device authentication threads blocking doesn't matter because prompts are serialized to GUI anyway
if (!promptsPending.empty())
{
assert(requestPassword); //... in this context
diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp
index 0ab22486..543313bb 100644
--- a/FreeFileSync/Source/base/dir_lock.cpp
+++ b/FreeFileSync/Source/base/dir_lock.cpp
@@ -308,6 +308,8 @@ ProcessStatus getProcessStatus(const LockInformation& lockInfo) //throw FileErro
DEFINE_NEW_FILE_ERROR(ErrorFileNotExisting)
uint64_t getLockFileSize(const Zstring& filePath) //throw FileError, ErrorFileNotExisting
{
+ //yes, checking error ERROR_FILE_NOT_FOUND, ENOENT is not 100% reliable (e.g. might indicate missing parent folder)
+ //but good enough for our lock file!
struct stat fileInfo = {};
if (::stat(filePath.c_str(), &fileInfo) == 0)
return fileInfo.st_size;
diff --git a/FreeFileSync/Source/base/file_hierarchy.h b/FreeFileSync/Source/base/file_hierarchy.h
index 5709f9db..28c3f7ef 100644
--- a/FreeFileSync/Source/base/file_hierarchy.h
+++ b/FreeFileSync/Source/base/file_hierarchy.h
@@ -107,21 +107,21 @@ struct FolderContainer
SymlinkList symlinks; //non-followed symlinks
FolderList folders;
- void addSubFile(const Zstring& itemName, const FileAttributes& attr)
+ void addFile(const Zstring& itemName, const FileAttributes& attr)
{
const auto [it, inserted] = files.emplace(itemName, attr);
if (!inserted) //update entry if already existing (e.g. during folder traverser "retry")
it->second = attr;
}
- void addSubLink(const Zstring& itemName, const LinkAttributes& attr)
+ void addLink(const Zstring& itemName, const LinkAttributes& attr)
{
const auto [it, inserted] = symlinks.emplace(itemName, attr);
if (!inserted)
it->second = attr;
}
- FolderContainer& addSubFolder(const Zstring& itemName, const FolderAttributes& attr)
+ FolderContainer& addFolder(const Zstring& itemName, const FolderAttributes& attr)
{
auto& p = folders[itemName]; //value default-construction is okay here
p.first = attr;
@@ -189,35 +189,35 @@ public:
using SymlinkList = std::list<SymlinkPair>; //
using FolderList = std::list<FolderPair>;
- FolderPair& addSubFolder(const Zstring& itemNameL,
- const FolderAttributes& left, //file exists on both sides
- CompareDirResult defaultCmpResult,
- const Zstring& itemNameR,
- const FolderAttributes& right);
+ FolderPair& addFolder(const Zstring& itemNameL,
+ const FolderAttributes& left, //file exists on both sides
+ CompareDirResult defaultCmpResult,
+ const Zstring& itemNameR,
+ const FolderAttributes& right);
template <SelectSide side>
- FolderPair& addSubFolder(const Zstring& itemName, //dir exists on one side only
- const FolderAttributes& attr);
+ FolderPair& addFolder(const Zstring& itemName, //dir exists on one side only
+ const FolderAttributes& attr);
- FilePair& addSubFile(const Zstring& itemNameL,
- const FileAttributes& left, //file exists on both sides
- CompareFileResult defaultCmpResult,
- const Zstring& itemNameR,
- const FileAttributes& right);
+ FilePair& addFile(const Zstring& itemNameL,
+ const FileAttributes& left, //file exists on both sides
+ CompareFileResult defaultCmpResult,
+ const Zstring& itemNameR,
+ const FileAttributes& right);
template <SelectSide side>
- FilePair& addSubFile(const Zstring& itemName, //file exists on one side only
- const FileAttributes& attr);
+ FilePair& addFile(const Zstring& itemName, //file exists on one side only
+ const FileAttributes& attr);
- SymlinkPair& addSubLink(const Zstring& itemNameL,
- const LinkAttributes& left, //link exists on both sides
- CompareSymlinkResult defaultCmpResult,
- const Zstring& itemNameR,
- const LinkAttributes& right);
+ SymlinkPair& addLink(const Zstring& itemNameL,
+ const LinkAttributes& left, //link exists on both sides
+ CompareSymlinkResult defaultCmpResult,
+ const Zstring& itemNameR,
+ const LinkAttributes& right);
template <SelectSide side>
- SymlinkPair& addSubLink(const Zstring& itemName, //link exists on one side only
- const LinkAttributes& attr);
+ SymlinkPair& addLink(const Zstring& itemName, //link exists on one side only
+ const LinkAttributes& attr);
const FileList& refSubFiles() const { return subFiles_; }
/**/ FileList& refSubFiles() { return subFiles_; }
@@ -1009,11 +1009,11 @@ void ContainerObject::flip()
inline
-FolderPair& ContainerObject::addSubFolder(const Zstring& itemNameL,
- const FolderAttributes& left,
- CompareDirResult defaultCmpResult,
- const Zstring& itemNameR,
- const FolderAttributes& right)
+FolderPair& ContainerObject::addFolder(const Zstring& itemNameL,
+ const FolderAttributes& left,
+ CompareDirResult defaultCmpResult,
+ const Zstring& itemNameR,
+ const FolderAttributes& right)
{
subFolders_.emplace_back(itemNameL, left, defaultCmpResult, itemNameR, right, *this);
return subFolders_.back();
@@ -1021,7 +1021,7 @@ FolderPair& ContainerObject::addSubFolder(const Zstring& itemNameL,
template <> inline
-FolderPair& ContainerObject::addSubFolder<SelectSide::left>(const Zstring& itemName, const FolderAttributes& attr)
+FolderPair& ContainerObject::addFolder<SelectSide::left>(const Zstring& itemName, const FolderAttributes& attr)
{
subFolders_.emplace_back(itemName, attr, DIR_LEFT_SIDE_ONLY, Zstring(), FolderAttributes(), *this);
return subFolders_.back();
@@ -1029,7 +1029,7 @@ FolderPair& ContainerObject::addSubFolder<SelectSide::left>(const Zstring& itemN
template <> inline
-FolderPair& ContainerObject::addSubFolder<SelectSide::right>(const Zstring& itemName, const FolderAttributes& attr)
+FolderPair& ContainerObject::addFolder<SelectSide::right>(const Zstring& itemName, const FolderAttributes& attr)
{
subFolders_.emplace_back(Zstring(), FolderAttributes(), DIR_RIGHT_SIDE_ONLY, itemName, attr, *this);
return subFolders_.back();
@@ -1037,11 +1037,11 @@ FolderPair& ContainerObject::addSubFolder<SelectSide::right>(const Zstring& item
inline
-FilePair& ContainerObject::addSubFile(const Zstring& itemNameL,
- const FileAttributes& left, //file exists on both sides
- CompareFileResult defaultCmpResult,
- const Zstring& itemNameR,
- const FileAttributes& right)
+FilePair& ContainerObject::addFile(const Zstring& itemNameL,
+ const FileAttributes& left, //file exists on both sides
+ CompareFileResult defaultCmpResult,
+ const Zstring& itemNameR,
+ const FileAttributes& right)
{
subFiles_.emplace_back(itemNameL, left, defaultCmpResult, itemNameR, right, *this);
return subFiles_.back();
@@ -1049,7 +1049,7 @@ FilePair& ContainerObject::addSubFile(const Zstring& itemNameL,
template <> inline
-FilePair& ContainerObject::addSubFile<SelectSide::left>(const Zstring& itemName, const FileAttributes& attr)
+FilePair& ContainerObject::addFile<SelectSide::left>(const Zstring& itemName, const FileAttributes& attr)
{
subFiles_.emplace_back(itemName, attr, FILE_LEFT_SIDE_ONLY, Zstring(), FileAttributes(), *this);
return subFiles_.back();
@@ -1057,7 +1057,7 @@ FilePair& ContainerObject::addSubFile<SelectSide::left>(const Zstring& itemName,
template <> inline
-FilePair& ContainerObject::addSubFile<SelectSide::right>(const Zstring& itemName, const FileAttributes& attr)
+FilePair& ContainerObject::addFile<SelectSide::right>(const Zstring& itemName, const FileAttributes& attr)
{
subFiles_.emplace_back(Zstring(), FileAttributes(), FILE_RIGHT_SIDE_ONLY, itemName, attr, *this);
return subFiles_.back();
@@ -1065,11 +1065,11 @@ FilePair& ContainerObject::addSubFile<SelectSide::right>(const Zstring& itemName
inline
-SymlinkPair& ContainerObject::addSubLink(const Zstring& itemNameL,
- const LinkAttributes& left, //link exists on both sides
- CompareSymlinkResult defaultCmpResult,
- const Zstring& itemNameR,
- const LinkAttributes& right)
+SymlinkPair& ContainerObject::addLink(const Zstring& itemNameL,
+ const LinkAttributes& left, //link exists on both sides
+ CompareSymlinkResult defaultCmpResult,
+ const Zstring& itemNameR,
+ const LinkAttributes& right)
{
subLinks_.emplace_back(itemNameL, left, defaultCmpResult, itemNameR, right, *this);
return subLinks_.back();
@@ -1077,7 +1077,7 @@ SymlinkPair& ContainerObject::addSubLink(const Zstring& itemNameL,
template <> inline
-SymlinkPair& ContainerObject::addSubLink<SelectSide::left>(const Zstring& itemName, const LinkAttributes& attr)
+SymlinkPair& ContainerObject::addLink<SelectSide::left>(const Zstring& itemName, const LinkAttributes& attr)
{
subLinks_.emplace_back(itemName, attr, SYMLINK_LEFT_SIDE_ONLY, Zstring(), LinkAttributes(), *this);
return subLinks_.back();
@@ -1085,7 +1085,7 @@ SymlinkPair& ContainerObject::addSubLink<SelectSide::left>(const Zstring& itemNa
template <> inline
-SymlinkPair& ContainerObject::addSubLink<SelectSide::right>(const Zstring& itemName, const LinkAttributes& attr)
+SymlinkPair& ContainerObject::addLink<SelectSide::right>(const Zstring& itemName, const LinkAttributes& attr)
{
subLinks_.emplace_back(Zstring(), LinkAttributes(), SYMLINK_RIGHT_SIDE_ONLY, itemName, attr, *this);
return subLinks_.back();
diff --git a/FreeFileSync/Source/base/lock_holder.h b/FreeFileSync/Source/base/lock_holder.h
index 0b02b1c3..e7d28c6a 100644
--- a/FreeFileSync/Source/base/lock_holder.h
+++ b/FreeFileSync/Source/base/lock_holder.h
@@ -10,7 +10,7 @@
namespace fff
{
//intermediate locks created by DirLock use this extension, too:
-const Zchar LOCK_FILE_ENDING[] = Zstr(".ffs_lock"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace!
+const ZstringView LOCK_FILE_ENDING = Zstr(".ffs_lock"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace!
//Attention: 1. call after having checked directory existence!
// 2. perf: remove folder aliases (e.g. case differences) *before* calling this function!!!
diff --git a/FreeFileSync/Source/base/parallel_scan.cpp b/FreeFileSync/Source/base/parallel_scan.cpp
index 389c67bb..626a9371 100644
--- a/FreeFileSync/Source/base/parallel_scan.cpp
+++ b/FreeFileSync/Source/base/parallel_scan.cpp
@@ -195,7 +195,7 @@ private:
std::wstring currentFile_;
std::map<int /*threadIdx*/, size_t /*parallelOps*/> activeThreadIdxs_;
- std::atomic<int> notifyingThreadIdx_ {0}; //CAVEAT: do NOT use boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754
+ std::atomic<int> notifyingThreadIdx_{0}; //CAVEAT: do NOT use boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754
const std::chrono::milliseconds cbInterval_;
//---- status updates II (lock-free) ----
@@ -263,7 +263,7 @@ public:
output.failedItemReads,
acb,
threadIdx,
- lastReportTime
+ lastReportTime,
}
{
if (acb.mayReportCurrentFile(threadIdx, lastReportTime))
@@ -291,7 +291,7 @@ void DirCallback::onFile(const AFS::FileInfo& fi) //throw ThreadStopRequest
return;
//note: sync.ffs_db database and lock files are excluded via path filter!
- output_.addSubFile(fi.itemName, FileAttributes(fi.modTime, fi.fileSize, fi.filePrint, fi.isFollowedSymlink));
+ output_.addFile(fi.itemName, FileAttributes(fi.modTime, fi.fileSize, fi.filePrint, fi.isFollowedSymlink));
cfg_.acb.incItemsScanned(); //add 1 element to the progress indicator
}
@@ -315,13 +315,13 @@ std::shared_ptr<AFS::TraverserCallback> DirCallback::onFolder(const AFS::FolderI
return nullptr; //do NOT traverse subdirs
//else: ensure directory filtering is applied later to exclude actually filtered directories!!!
- FolderContainer& subFolder = output_.addSubFolder(fi.itemName, FolderAttributes(fi.isFollowedSymlink));
+ FolderContainer& subFolder = output_.addFolder(fi.itemName, FolderAttributes(fi.isFollowedSymlink));
if (passFilter)
cfg_.acb.incItemsScanned(); //add 1 element to the progress indicator
//------------------------------------------------------------------------------------
if (level_ > FOLDER_TRAVERSAL_LEVEL_MAX) //Win32 traverser: stack overflow approximately at level 1000
- //check after FolderContainer::addSubFolder()
+ //check after FolderContainer::addFolder()
for (size_t retryNumber = 0;; ++retryNumber)
switch (reportItemError({replaceCpy(_("Cannot read directory %x."), L"%x", AFS::getDisplayPath(AFS::appendRelPath(cfg_.baseFolderPath, relPath))) +
L"\n\n" L"Endless recursion.", std::chrono::steady_clock::now(), retryNumber}, fi.itemName)) //throw ThreadStopRequest
@@ -354,7 +354,7 @@ DirCallback::HandleLink DirCallback::onSymlink(const AFS::SymlinkInfo& si) //thr
case SymLinkHandling::asLink:
if (cfg_.filter.ref().passFileFilter(relPath)) //always use file filter: Link type may not be "stable" on Linux!
{
- output_.addSubLink(si.itemName, LinkAttributes(si.modTime));
+ output_.addLink(si.itemName, LinkAttributes(si.modTime));
cfg_.acb.incItemsScanned(); //add 1 element to the progress indicator
}
return HandleLink::skip;
diff --git a/FreeFileSync/Source/base/structures.cpp b/FreeFileSync/Source/base/structures.cpp
index fa6bb31e..b2e70d5e 100644
--- a/FreeFileSync/Source/base/structures.cpp
+++ b/FreeFileSync/Source/base/structures.cpp
@@ -75,7 +75,7 @@ DirectionSet fff::extractDirections(const SyncDirectionConfig& cfg)
switch (cfg.var)
{
case SyncVariant::twoWay:
- throw std::logic_error("there are no predefined directions for automatic mode! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] there are no predefined directions for automatic mode!");
case SyncVariant::mirror:
output.exLeftSideOnly = SyncDirection::right;
@@ -206,7 +206,7 @@ std::wstring fff::getSymbol(SyncOperation op)
case SO_COPY_METADATA_TO_RIGHT: return L"update ->";
case SO_DO_NOTHING: return L" -";
case SO_EQUAL: return L"'=="; //added quotation mark to avoid error in Excel cell when exporting to *.cvs
- case SO_UNRESOLVED_CONFLICT: return L"conflict";
+ case SO_UNRESOLVED_CONFLICT: return L"conflict"; //portable Unicode symbol: âš¡
//*INDENT-ON*
};
assert(false);
diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp
index 982fcf15..105ac8bc 100644
--- a/FreeFileSync/Source/base/synchronization.cpp
+++ b/FreeFileSync/Source/base/synchronization.cpp
@@ -12,7 +12,6 @@
#include <zen/crc.h>
#include "algorithm.h"
#include "db_file.h"
-//#include "dir_exist_async.h"
#include "status_handler_impl.h"
#include "versioning.h"
#include "binary.h"
@@ -447,12 +446,6 @@ bool plannedWriteAccess(const FileSystemObject& fsObj)
}
-bool plannedReadOrWrite(const FileSystemObject& fsObj)
-{
- return !!getTargetDirection(fsObj.getSyncOperation());
-}
-
-
inline
AbstractPath getAbstractPath(const FileSystemObject& fsObj, SelectSide side)
{
@@ -485,17 +478,17 @@ std::weak_ordering comparePathNoCase(const PathRaceItem& lhs, const PathRaceItem
std::wstring formatRaceItem(const PathRaceItem& item)
{
- const wchar_t arrowLeft [] = L" <- ";
- const wchar_t arrowRight[] = L" -> ";
-
- const SelectSide dir = *getTargetDirection(item.fsObj->getSyncOperation()); //non-null! see plannedReadOrWrite()
-
- return AFS::getDisplayPath(item.fsObj->base().getAbstractPath<SelectSide::left>()) +
- (dir == SelectSide::left ? arrowLeft : arrowRight) +
- AFS::getDisplayPath(item.fsObj->base().getAbstractPath<SelectSide::right>()) +
- (dir == item.side ? L" 💾 " : L" 👓 ") +
- utfTo<std::wstring>(item.side == SelectSide::left ? item.fsObj->getRelativePath<SelectSide::left>() : item.fsObj->getRelativePath<SelectSide::right>());
- //e.g. C:\Source -> D:\Target 💾 subfolder\file.txt
+ const std::optional<SelectSide> syncDir = getTargetDirection(item.fsObj->getSyncOperation());
+
+ return AFS::getDisplayPath(item.side == SelectSide::left ?
+ item.fsObj->base().getAbstractPath<SelectSide::left>() :
+ item.fsObj->base().getAbstractPath<SelectSide::right>()) +
+ (syncDir && *syncDir == item.side ? L" 💾 " : L" 👓 ") +
+ utfTo<std::wstring>(item.side == SelectSide::left ?
+ item.fsObj->getRelativePath<SelectSide::left>() :
+ item.fsObj->getRelativePath<SelectSide::right>());
+ //e.g. D:\folder 💾 subfolder\file.txt
+ // D:\folder\subfolder 👓 file.txt
}
@@ -530,20 +523,17 @@ private:
void recurse(const ContainerObject& hierObj, uint64_t parentPathHash)
{
for (const FilePair& file : hierObj.refSubFiles())
- if (plannedReadOrWrite(file))
- childPathRefs_.push_back({&file, getPathHash(file, parentPathHash)});
- //S1 -> T (update) is not a conflict (anymore) if S1, S2 contain different files https://freefilesync.org/forum/viewtopic.php?t=9365#p36466
- //S2 -> T (update) => ignore all items that are not physically read/written (e.g. categories "equal", "do nothing")
+ childPathRefs_.push_back({&file, getPathHash(file, parentPathHash)});
+ //S1 -> T (update) is not a conflict (anymore) if S1, S2 contain different files
+ //S2 -> T (update) https://freefilesync.org/forum/viewtopic.php?t=9365#p36466
for (const SymlinkPair& symlink : hierObj.refSubLinks())
- if (plannedReadOrWrite(symlink))
- childPathRefs_.push_back({&symlink, getPathHash(symlink, parentPathHash)});
+ childPathRefs_.push_back({&symlink, getPathHash(symlink, parentPathHash)});
for (const FolderPair& subFolder : hierObj.refSubFolders())
{
const uint64_t folderPathHash = getPathHash(subFolder, parentPathHash);
- if (plannedReadOrWrite(subFolder))
- childPathRefs_.push_back({&subFolder, folderPathHash});
+ childPathRefs_.push_back({&subFolder, folderPathHash});
recurse(subFolder, folderPathHash);
}
@@ -2471,7 +2461,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
//PERF_START;
if (syncConfig.size() != folderCmp.size())
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
//aggregate basic information
std::vector<SyncStatistics> folderPairStats;
diff --git a/FreeFileSync/Source/base/versioning.cpp b/FreeFileSync/Source/base/versioning.cpp
index 63af2cbb..3efe0788 100644
--- a/FreeFileSync/Source/base/versioning.cpp
+++ b/FreeFileSync/Source/base/versioning.cpp
@@ -458,9 +458,8 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& folderLimi
};
const std::map<DirectoryKey, DirectoryValue> folderBuf = parallelDeviceTraversal(foldersToRead,
- [&](const PhaseCallback::ErrorInfo& errorInfo) { return callback.reportError(errorInfo); }, //throw X
- onStatusUpdate, //throw X
- UI_UPDATE_INTERVAL / 2); //every ~50 ms
+ [&](const PhaseCallback::ErrorInfo& errorInfo) { return callback.reportError(errorInfo); } /*throw X*/,
+ onStatusUpdate /*throw X*/, UI_UPDATE_INTERVAL / 2); //every ~50 ms
//--------- group versions per (original) relative path ---------
std::map<AbstractPath, VersionInfoMap> versionDetails; //versioningFolderPath => <version details>
diff --git a/FreeFileSync/Source/base/versioning.h b/FreeFileSync/Source/base/versioning.h
index 10dec67d..49c864e9 100644
--- a/FreeFileSync/Source/base/versioning.h
+++ b/FreeFileSync/Source/base/versioning.h
@@ -42,7 +42,7 @@ public:
using namespace zen;
if (AbstractFileSystem::isNullPath(versioningFolderPath_))
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
if (timeStamp_.size() != 17) //formatTime() returns empty string on error; unexpected length: e.g. problem in year 10,000!
throw FileError(_("Unable to create time stamp for versioning:") + L" \"" + utfTo<std::wstring>(timeStamp_) + L'"');
diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp
index 02b21343..98ebfe4a 100644
--- a/FreeFileSync/Source/config.cpp
+++ b/FreeFileSync/Source/config.cpp
@@ -23,7 +23,7 @@ using namespace fff; //required for correct overload resolution!
namespace
{
//-------------------------------------------------------------------------------------------------------------------------------
-const int XML_FORMAT_GLOBAL_CFG = 25; //2022-08-26
+const int XML_FORMAT_GLOBAL_CFG = 26; //2023-02-18
const int XML_FORMAT_SYNC_CFG = 17; //2020-10-14
//-------------------------------------------------------------------------------------------------------------------------------
}
@@ -1474,8 +1474,9 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
//TODO: remove old parameter after migration! 2021-03-06
if (formatVer < 21)
{
- in2["ProgressDialog"].attribute("Width", cfg.dpiLayouts[getDpiScalePercent()].progressDlg.dlgSize.x);
- in2["ProgressDialog"].attribute("Height", cfg.dpiLayouts[getDpiScalePercent()].progressDlg.dlgSize.y);
+ cfg.dpiLayouts[getDpiScalePercent()].progressDlg.size = wxSize();
+ in2["ProgressDialog"].attribute("Width", cfg.dpiLayouts[getDpiScalePercent()].progressDlg.size->x);
+ in2["ProgressDialog"].attribute("Height", cfg.dpiLayouts[getDpiScalePercent()].progressDlg.size->y);
in2["ProgressDialog"].attribute("Maximized", cfg.dpiLayouts[getDpiScalePercent()].progressDlg.isMaximized);
}
@@ -1565,10 +1566,12 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
//TODO: remove old parameter after migration! 2021-03-06
if (formatVer < 21)
{
- inMainWin.attribute("Width", cfg.dpiLayouts[getDpiScalePercent()].mainDlg.dlgSize.x);
- inMainWin.attribute("Height", cfg.dpiLayouts[getDpiScalePercent()].mainDlg.dlgSize.y);
- inMainWin.attribute("PosX", cfg.dpiLayouts[getDpiScalePercent()].mainDlg.dlgPos.x);
- inMainWin.attribute("PosY", cfg.dpiLayouts[getDpiScalePercent()].mainDlg.dlgPos.y);
+ cfg.dpiLayouts[getDpiScalePercent()].mainDlg.size = wxSize();
+ inMainWin.attribute("Width", cfg.dpiLayouts[getDpiScalePercent()].mainDlg.size->x);
+ inMainWin.attribute("Height", cfg.dpiLayouts[getDpiScalePercent()].mainDlg.size->y);
+ cfg.dpiLayouts[getDpiScalePercent()].mainDlg.pos = wxPoint();
+ inMainWin.attribute("PosX", cfg.dpiLayouts[getDpiScalePercent()].mainDlg.pos->x);
+ inMainWin.attribute("PosY", cfg.dpiLayouts[getDpiScalePercent()].mainDlg.pos->y);
inMainWin.attribute("Maximized", cfg.dpiLayouts[getDpiScalePercent()].mainDlg.isMaximized);
}
@@ -1777,10 +1780,10 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
//TODO: remove old parameter after migration! 2018-01-16
if (formatVer < 7)
- inMainWin["Perspective5"](cfg.dpiLayouts[getDpiScalePercent()].mainDlg.panelLayout);
+ inMainWin["Perspective5"](cfg.dpiLayouts[getDpiScalePercent()].panelLayout);
//TODO: remove old parameter after migration! 2021-03-06
else if (formatVer < 21)
- inMainWin["Perspective"](cfg.dpiLayouts[getDpiScalePercent()].mainDlg.panelLayout);
+ inMainWin["Perspective"](cfg.dpiLayouts[getDpiScalePercent()].panelLayout);
//TODO: remove after migration! 2019-11-30
auto splitEditMerge = [](wxString& perspective, wchar_t delim, const std::function<void(wxString& item)>& editItem)
@@ -1801,7 +1804,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
//TODO: remove after migration! 2018-07-27
if (formatVer < 10)
- splitEditMerge(cfg.dpiLayouts[getDpiScalePercent()].mainDlg.panelLayout, L'|', [&](wxString& paneCfg)
+ splitEditMerge(cfg.dpiLayouts[getDpiScalePercent()].panelLayout, L'|', [&](wxString& paneCfg)
{
if (contains(paneCfg, L"name=TopPanel"))
replace(paneCfg, L";row=2;", L";row=3;");
@@ -1814,7 +1817,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
std::optional<int> tpDir;
std::optional<int> tpLayer;
std::optional<int> tpRow;
- splitEditMerge(cfg.dpiLayouts[getDpiScalePercent()].mainDlg.panelLayout, L'|', [&](wxString& paneCfg)
+ splitEditMerge(cfg.dpiLayouts[getDpiScalePercent()].panelLayout, L'|', [&](wxString& paneCfg)
{
if (contains(paneCfg, L"name=TopPanel"))
splitEditMerge(paneCfg, L';', [&](wxString& paneAttr)
@@ -1835,7 +1838,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
numberTo<wxString>(*tpLayer) + L"," +
numberTo<wxString>(*tpRow ) + L")=";
- splitEditMerge(cfg.dpiLayouts[getDpiScalePercent()].mainDlg.panelLayout, L'|', [&](wxString& paneCfg)
+ splitEditMerge(cfg.dpiLayouts[getDpiScalePercent()].panelLayout, L'|', [&](wxString& paneCfg)
{
if (startsWith(paneCfg, tpSize))
paneCfg = tpSize + L"0";
@@ -1970,7 +1973,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
in["LastOnlineVersion"](cfg.lastOnlineVersion);
}
- in["WelcomeShownVersion"](cfg.welcomeShownVersion);
+ in["WelcomeDialogVersion"](cfg.welcomeDialogLastVersion);
//cfg.dpiLayouts.clear(); -> NO: honor migration code above!
@@ -1981,23 +1984,68 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
const int scalePercent = stringTo<int>(beforeLast(scaleTxt, '%', IfNotFoundReturn::none));
DpiLayout layout;
- XmlIn inLayoutMain = inLayout["MainDialog"];
- inLayoutMain.attribute("Width", layout.mainDlg.dlgSize.x);
- inLayoutMain.attribute("Height", layout.mainDlg.dlgSize.y);
- inLayoutMain.attribute("PosX", layout.mainDlg.dlgPos.x);
- inLayoutMain.attribute("PosY", layout.mainDlg.dlgPos.y);
- inLayoutMain.attribute("Maximized", layout.mainDlg.isMaximized);
+ //TODO: remove parameter migration after some time! 2023-02-18
+ if (formatVer < 26)
+ {
+ XmlIn inLayoutMain = inLayout["MainDialog"];
+ layout.mainDlg.size = wxSize();
+ inLayoutMain.attribute("Width", layout.mainDlg.size->x);
+ inLayoutMain.attribute("Height", layout.mainDlg.size->y);
+
+ layout.mainDlg.pos = wxPoint();
+ inLayoutMain.attribute("PosX", layout.mainDlg.pos->x);
+ inLayoutMain.attribute("PosY", layout.mainDlg.pos->y);
+
+ inLayoutMain.attribute("Maximized", layout.mainDlg.isMaximized);
+
+ inLayoutMain["PanelLayout" ](layout.panelLayout);
+ inLayoutMain["ConfigPanel" ](layout.configColumnAttribs);
+ inLayoutMain["OverviewPanel" ](layout.overviewColumnAttribs);
+ inLayoutMain["FilePanelLeft" ](layout.fileColumnAttribsLeft);
+ inLayoutMain["FilePanelRight"](layout.fileColumnAttribsRight);
- inLayoutMain["PanelLayout" ](layout.mainDlg.panelLayout);
- inLayoutMain["ConfigPanel" ](layout.configColumnAttribs);
- inLayoutMain["OverviewPanel" ](layout.overviewColumnAttribs);
- inLayoutMain["FilePanelLeft" ](layout.fileColumnAttribsLeft);
- inLayoutMain["FilePanelRight"](layout.fileColumnAttribsRight);
+ XmlIn inLayoutProgress = inLayout["ProgressDialog"];
+ layout.progressDlg.size = wxSize();
+ inLayoutProgress.attribute("Width", layout.progressDlg.size->x);
+ inLayoutProgress.attribute("Height", layout.progressDlg.size->y);
- XmlIn inLayoutProgress = inLayout["ProgressDialog"];
- inLayoutProgress.attribute("Width", layout.progressDlg.dlgSize.x);
- inLayoutProgress.attribute("Height", layout.progressDlg.dlgSize.y);
- inLayoutProgress.attribute("Maximized", layout.progressDlg.isMaximized);
+ inLayoutProgress.attribute("Maximized", layout.progressDlg.isMaximized);
+ }
+ else
+ {
+ XmlIn inLayoutMain = inLayout["MainWindow"];
+ if (inLayoutMain.hasAttribute("Width") &&
+ inLayoutMain.hasAttribute("Height"))
+ {
+ layout.mainDlg.size = wxSize();
+ inLayoutMain.attribute("Width", layout.mainDlg.size->x);
+ inLayoutMain.attribute("Height", layout.mainDlg.size->y);
+ }
+ if (inLayoutMain.hasAttribute("PosX") &&
+ inLayoutMain.hasAttribute("PosY"))
+ {
+ layout.mainDlg.pos = wxPoint();
+ inLayoutMain.attribute("PosX", layout.mainDlg.pos->x);
+ inLayoutMain.attribute("PosY", layout.mainDlg.pos->y);
+ }
+ inLayoutMain.attribute("Maximized", layout.mainDlg.isMaximized);
+
+ XmlIn inLayoutProgress = inLayout["ProgressDialog"];
+ if (inLayoutProgress.hasAttribute("Width") &&
+ inLayoutProgress.hasAttribute("Height"))
+ {
+ layout.progressDlg.size = wxSize();
+ inLayoutProgress.attribute("Width", layout.progressDlg.size->x);
+ inLayoutProgress.attribute("Height", layout.progressDlg.size->y);
+ }
+ inLayoutProgress.attribute("Maximized", layout.progressDlg.isMaximized);
+
+ inLayout["Panels" ](layout.panelLayout);
+ inLayout["ConfigPanel" ](layout.configColumnAttribs);
+ inLayout["OverviewPanel" ](layout.overviewColumnAttribs);
+ inLayout["FilePanelLeft" ](layout.fileColumnAttribsLeft);
+ inLayout["FilePanelRight"](layout.fileColumnAttribsRight);
+ }
cfg.dpiLayouts.emplace(scalePercent, std::move(layout));
}
@@ -2435,7 +2483,7 @@ void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out)
out["LastOnlineCheck" ](cfg.lastUpdateCheck);
out["LastOnlineVersion"](cfg.lastOnlineVersion);
- out["WelcomeShownVersion"](cfg.welcomeShownVersion);
+ out["WelcomeDialogVersion"](cfg.welcomeDialogLastVersion);
for (const auto& [scalePercent, layout] : cfg.dpiLayouts)
@@ -2443,23 +2491,32 @@ void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out)
XmlOut outLayout = out["DpiLayouts"].addChild("Layout");
outLayout.attribute("Scale", numberTo<std::string>(scalePercent) + '%');
- XmlOut outLayoutMain = outLayout["MainDialog"];
- outLayoutMain.attribute("Width", layout.mainDlg.dlgSize.x);
- outLayoutMain.attribute("Height", layout.mainDlg.dlgSize.y);
- outLayoutMain.attribute("PosX", layout.mainDlg.dlgPos.x);
- outLayoutMain.attribute("PosY", layout.mainDlg.dlgPos.y);
+ XmlOut outLayoutMain = outLayout["MainWindow"];
+ if (layout.mainDlg.size)
+ {
+ outLayoutMain.attribute("Width", layout.mainDlg.size->x);
+ outLayoutMain.attribute("Height", layout.mainDlg.size->y);
+ }
+ if (layout.mainDlg.pos)
+ {
+ outLayoutMain.attribute("PosX", layout.mainDlg.pos->x);
+ outLayoutMain.attribute("PosY", layout.mainDlg.pos->y);
+ }
outLayoutMain.attribute("Maximized", layout.mainDlg.isMaximized);
- outLayoutMain["PanelLayout" ](layout.mainDlg.panelLayout);
- outLayoutMain["ConfigPanel" ](layout.configColumnAttribs);
- outLayoutMain["OverviewPanel" ](layout.overviewColumnAttribs);
- outLayoutMain["FilePanelLeft" ](layout.fileColumnAttribsLeft);
- outLayoutMain["FilePanelRight"](layout.fileColumnAttribsRight);
-
XmlOut outLayoutProgress = outLayout["ProgressDialog"];
- outLayoutProgress.attribute("Width", layout.progressDlg.dlgSize.x);
- outLayoutProgress.attribute("Height", layout.progressDlg.dlgSize.y);
+ if (layout.progressDlg.size)
+ {
+ outLayoutProgress.attribute("Width", layout.progressDlg.size->x);
+ outLayoutProgress.attribute("Height", layout.progressDlg.size->y);
+ }
outLayoutProgress.attribute("Maximized", layout.progressDlg.isMaximized);
+
+ outLayout["Panels" ](layout.panelLayout);
+ outLayout["ConfigPanel" ](layout.configColumnAttribs);
+ outLayout["OverviewPanel" ](layout.overviewColumnAttribs);
+ outLayout["FilePanelLeft" ](layout.fileColumnAttribsLeft);
+ outLayout["FilePanelRight"](layout.fileColumnAttribsRight);
}
}
diff --git a/FreeFileSync/Source/config.h b/FreeFileSync/Source/config.h
index faa14fbb..e19b4fa1 100644
--- a/FreeFileSync/Source/config.h
+++ b/FreeFileSync/Source/config.h
@@ -118,22 +118,23 @@ struct DpiLayout
{
struct
{
- wxPoint dlgPos;
- wxSize dlgSize;
+ std::optional<wxSize> size;
+ std::optional<wxPoint> pos;
bool isMaximized = false;
- wxString panelLayout; //for wxAuiManager::LoadPerspective
- } mainDlg;
-
- std::vector<ColAttributesCfg> configColumnAttribs = getCfgGridDefaultColAttribs();
- std::vector<ColumnAttribOverview> overviewColumnAttribs = getOverviewDefaultColAttribs();
- std::vector<ColAttributesRim> fileColumnAttribsLeft = getFileGridDefaultColAttribsLeft();
- std::vector<ColAttributesRim> fileColumnAttribsRight = getFileGridDefaultColAttribsRight();
+ } mainDlg; //WindowLayout::getBeforeClose()
struct
{
- wxSize dlgSize;
+ std::optional<wxSize> size;
bool isMaximized = false;
} progressDlg;
+
+ wxString panelLayout; //for wxAuiManager::LoadPerspective
+
+ std::vector<ColAttributesCfg> configColumnAttribs = getCfgGridDefaultColAttribs();
+ std::vector<ColumnAttribOverview> overviewColumnAttribs = getOverviewDefaultColAttribs();
+ std::vector<ColAttributesRim> fileColumnAttribsLeft = getFileGridDefaultColAttribsLeft();
+ std::vector<ColAttributesRim> fileColumnAttribsRight = getFileGridDefaultColAttribsRight();
};
@@ -246,7 +247,7 @@ struct XmlGlobalSettings
time_t lastUpdateCheck = 0; //number of seconds since Jan 1, 1970 GMT
std::string lastOnlineVersion;
- std::string welcomeShownVersion; //last FFS version for which the welcome dialog was shown
+ std::string welcomeDialogLastVersion;
std::unordered_map<int /*scale percent*/, DpiLayout> dpiLayouts;
};
diff --git a/FreeFileSync/Source/localization.cpp b/FreeFileSync/Source/localization.cpp
index 7c8bdd6b..d1de6201 100644
--- a/FreeFileSync/Source/localization.cpp
+++ b/FreeFileSync/Source/localization.cpp
@@ -248,11 +248,11 @@ public:
//https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
transMapping[""] = L"Content-Type: text/plain; charset=UTF-8\n";
- const int headerSize = 28;
+ const int headerSize = 7 * sizeof(uint32_t);
writeNumber<uint32_t>(moBuf_, 0x950412de); //magic number
writeNumber<uint32_t>(moBuf_, 0); //format version
writeNumber<uint32_t>(moBuf_, transMapping.size()); //string count
- writeNumber<uint32_t>(moBuf_, headerSize); //string references offset: original
+ writeNumber<uint32_t>(moBuf_, headerSize); //string references offset: original
writeNumber<uint32_t>(moBuf_, headerSize + (2 * sizeof(uint32_t)) * transMapping.size()); //string references offset: translation
writeNumber<uint32_t>(moBuf_, 0); //size of hashing table
writeNumber<uint32_t>(moBuf_, 0); //offset of hashing table
diff --git a/FreeFileSync/Source/log_file.cpp b/FreeFileSync/Source/log_file.cpp
index 5f268de0..dfa28ace 100644
--- a/FreeFileSync/Source/log_file.cpp
+++ b/FreeFileSync/Source/log_file.cpp
@@ -8,7 +8,7 @@
#include <zen/file_io.h>
#include <zen/http.h>
#include <zen/sys_info.h>
-#include <wx/datetime.h>
+//#include <wx/datetime.h>
#include "afs/concrete.h"
using namespace zen;
@@ -66,7 +66,7 @@ std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, i
L" (" + formatFilesizeShort(s.statsTotal.bytes - s.statsProcessed.bytes) + L')'));
const int64_t totalTimeSec = std::chrono::duration_cast<std::chrono::seconds>(s.totalTime).count();
- summary.push_back(tabSpace + utfTo<std::string>(_("Total time:")) + ' ' + utfTo<std::string>(wxTimeSpan::Seconds(totalTimeSec).Format()));
+ summary.push_back(tabSpace + utfTo<std::string>(_("Total time:")) + ' ' + utfTo<std::string>(formatTimeSpan(totalTimeSec)));
size_t sepLineLen = 0; //calculate max width (considering Unicode!)
for (const std::string& str : summary) sepLineLen = std::max(sepLineLen, unicodeLength(str));
@@ -293,7 +293,7 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log,
<tr>
<td>)" + htmlTxt(_("Total time:")) + R"(</td>
<td><img src="https://freefilesync.org/images/log/clock.png" width="24" height="24" alt=""></td>
- <td><span style="font-weight: 600;">)" + htmlTxt(wxTimeSpan::Seconds(totalTimeSec).Format()) + R"(</span></td>
+ <td><span style="font-weight: 600;">)" + htmlTxt(formatTimeSpan(totalTimeSec)) + R"(</span></td>
</tr>
</table>
</div>
@@ -350,7 +350,7 @@ std::string generateLogFooterHtml(const std::wstring& logFilePath /*optional*/,
output += R"(
<div style="border-bottom:1px solid #AAA; margin:5px 0;"></div>
- <div style="font-size:small;">
+ <div style="font-size:smaller;">
<img src="https://freefilesync.org/images/log/)" + osImage + R"(" width="24" height="24" alt="" style="vertical-align:middle;">
<span style="vertical-align:middle;">)" + htmlTxt(getOsDescription()) + /*throw FileError*/ +
" &ndash; " + htmlTxt(getUserDescription()) /*throw FileError*/ +
@@ -360,7 +360,7 @@ std::string generateLogFooterHtml(const std::wstring& logFilePath /*optional*/,
if (!logFilePath.empty())
output += R"(
- <div style="font-size:small;">
+ <div style="font-size:smaller;">
<img src="https://freefilesync.org/images/log/log.png" width="24" height="24" alt=")" + htmlTxt(_("Log file:")) + R"(" style="vertical-align:middle;">
<span style="font-family: Consolas,'Courier New',Courier,monospace; vertical-align:middle;">)" + htmlTxt(logFilePath) + R"(</span>
</div>)";
diff --git a/FreeFileSync/Source/parse_lng.h b/FreeFileSync/Source/parse_lng.h
index 7af4766d..2c1a7adb 100644
--- a/FreeFileSync/Source/parse_lng.h
+++ b/FreeFileSync/Source/parse_lng.h
@@ -21,7 +21,7 @@ using TranslationMap = std::unordered_map<std::string, std::string>; //orig |->
//plural forms
using SingularPluralPair = std::pair<std::string, std::string>; //1 house | %x houses
-using PluralForms = std::vector<std::string>; //1 dom | 2 domy | %x domów
+using PluralForms = std::vector<std::string>; //1 dom | 2 domy | %x domów
using TranslationPluralMap = std::unordered_map<SingularPluralPair, PluralForms>; //(sing/plu) |-> pluralforms
struct TransHeader
@@ -216,7 +216,7 @@ public:
private:
const TokenMap tokens_ =
{
- //header information
+ //header details
{TokenType::headerBegin, "<header>"},
{TokenType::headerEnd, "</header>"},
{TokenType::langNameBegin, "<language>"},
@@ -493,15 +493,15 @@ private:
if (endsWithSingleAmp(original) || endsWithSingleAmp(translation))
throw ParsingError({L"The & character to mark a menu item access key must not occur at the end of a string", scn_.posRow(), scn_.posCol()});
- //if source ends with colon, so must translation (note: character seems to be universally used, even for asian and arabic languages)
- if (endsWith(original, ':') && !endsWithColon(translation))
+ //if source ends with colon, so must translation
+ if (endsWithColon(original) && !endsWithColon(translation))
throw ParsingError({L"Source text ends with a colon character \":\", but translation does not", scn_.posRow(), scn_.posCol()});
- //if source ends with a period, so must translation (note: character seems to be universally used, even for asian and arabic languages)
+ //if source ends with period, so must translation
if (endsWithSingleDot(original) && !endsWithSingleDot(translation))
throw ParsingError({L"Source text ends with a punctuation mark character \".\", but translation does not", scn_.posRow(), scn_.posCol()});
- //if source ends with an ellipsis, so must translation (note: character seems to be universally used, even for asian and arabic languages)
+ //if source ends with ellipsis, so must translation
if (endsWithEllipsis(original) && !endsWithEllipsis(translation))
throw ParsingError({L"Source text ends with an ellipsis \"...\", but translation does not", scn_.posRow(), scn_.posCol()});
@@ -511,7 +511,7 @@ private:
throw ParsingError({replaceCpy<std::wstring>(L"Misspelled \"%x\" in translation", L"%x", utfTo<std::wstring>(fixedStr)), scn_.posRow(), scn_.posCol()});
//some languages (French!) put a space before punctuation mark => must be a no-brake space!
- for (const char punctChar : std::string(".!?:;$#"))
+ for (const char punctChar : std::string_view(".!?:;$#"))
if (contains(original, std::string(" ") + punctChar) ||
contains(translation, std::string(" ") + punctChar))
throw ParsingError({replaceCpy<std::wstring>(L"Text contains a space before the \"%x\" character. Are line-breaks really allowed here?"
@@ -644,7 +644,6 @@ private:
}
}
- //helper
static size_t ampersandTokenCount(const std::string& str)
{
using namespace zen;
@@ -749,34 +748,14 @@ std::string generateLng(const TranslationUnorderedList& in, const TransHeader& h
//header
out += tokens.text(TokenType::headerBegin) + '\n';
- out += '\t' + tokens.text(TokenType::langNameBegin);
- out += header.languageName;
- out += tokens.text(TokenType::langNameEnd) + '\n';
-
- out += '\t' + tokens.text(TokenType::transNameBegin);
- out += header.translatorName;
- out += tokens.text(TokenType::transNameEnd) + '\n';
-
- out += '\t' + tokens.text(TokenType::localeBegin);
- out += header.locale;
- out += tokens.text(TokenType::localeEnd) + '\n';
-
- out += '\t' + tokens.text(TokenType::flagFileBegin);
- out += header.flagFile;
- out += tokens.text(TokenType::flagFileEnd) + '\n';
-
- out += '\t' + tokens.text(TokenType::pluralCountBegin);
- out += zen::numberTo<std::string>(header.pluralCount);
- out += tokens.text(TokenType::pluralCountEnd) + '\n';
-
- out += '\t' + tokens.text(TokenType::pluralDefBegin);
- out += header.pluralDefinition;
- out += tokens.text(TokenType::pluralDefEnd) + '\n';
-
- out += tokens.text(TokenType::headerEnd) + '\n';
-
- out += '\n';
+ out += '\t' + tokens.text(TokenType::langNameBegin) + header.languageName + tokens.text(TokenType::langNameEnd) + '\n';
+ out += '\t' + tokens.text(TokenType::transNameBegin) + header.translatorName + tokens.text(TokenType::transNameEnd) + '\n';
+ out += '\t' + tokens.text(TokenType::localeBegin) + header.locale + tokens.text(TokenType::localeEnd) + '\n';
+ out += '\t' + tokens.text(TokenType::flagFileBegin) + header.flagFile + tokens.text(TokenType::flagFileEnd) + '\n';
+ out += '\t' + tokens.text(TokenType::pluralCountBegin) + zen::numberTo<std::string>(header.pluralCount) + tokens.text(TokenType::pluralCountEnd) + '\n';
+ out += '\t' + tokens.text(TokenType::pluralDefBegin) + header.pluralDefinition + tokens.text(TokenType::pluralDefEnd) + '\n';
+ out += tokens.text(TokenType::headerEnd) + "\n\n";
in.visitItems([&](const TranslationMap::value_type& trans)
{
@@ -786,13 +765,8 @@ std::string generateLng(const TranslationUnorderedList& in, const TransHeader& h
formatMultiLineText(original);
formatMultiLineText(translation);
- out += tokens.text(TokenType::srcBegin);
- out += original;
- out += tokens.text(TokenType::srcEnd) + '\n';
-
- out += tokens.text(TokenType::trgBegin);
- out += translation;
- out += tokens.text(TokenType::trgEnd) + '\n' + '\n';
+ out += tokens.text(TokenType::srcBegin) + original + tokens.text(TokenType::srcEnd) + '\n';
+ out += tokens.text(TokenType::trgBegin) + translation + tokens.text(TokenType::trgEnd) + "\n\n";
},
[&](const TranslationPluralMap::value_type& transPlural)
{
@@ -804,12 +778,8 @@ std::string generateLng(const TranslationUnorderedList& in, const TransHeader& h
formatMultiLineText(engPlural);
out += tokens.text(TokenType::srcBegin) + '\n';
- out += tokens.text(TokenType::pluralBegin);
- out += engSingular;
- out += tokens.text(TokenType::pluralEnd) + '\n';
- out += tokens.text(TokenType::pluralBegin);
- out += engPlural;
- out += tokens.text(TokenType::pluralEnd) + '\n';
+ out += '\t' + tokens.text(TokenType::pluralBegin) + engSingular + tokens.text(TokenType::pluralEnd) + '\n';
+ out += '\t' + tokens.text(TokenType::pluralBegin) + engPlural + tokens.text(TokenType::pluralEnd) + '\n';
out += tokens.text(TokenType::srcEnd) + '\n';
out += tokens.text(TokenType::trgBegin);
@@ -818,12 +788,9 @@ std::string generateLng(const TranslationUnorderedList& in, const TransHeader& h
for (std::string plForm : forms)
{
formatMultiLineText(plForm);
-
- out += tokens.text(TokenType::pluralBegin);
- out += plForm;
- out += tokens.text(TokenType::pluralEnd) + '\n';
+ out += '\t' + tokens.text(TokenType::pluralBegin) + plForm + tokens.text(TokenType::pluralEnd) + '\n';
}
- out += tokens.text(TokenType::trgEnd) + '\n' + '\n';
+ out += tokens.text(TokenType::trgEnd) + "\n\n";
});
assert(!zen::contains(out, "\r\n") && !zen::contains(out, "\r"));
diff --git a/FreeFileSync/Source/parse_plural.h b/FreeFileSync/Source/parse_plural.h
index 5f19c6b2..f5883135 100644
--- a/FreeFileSync/Source/parse_plural.h
+++ b/FreeFileSync/Source/parse_plural.h
@@ -27,7 +27,7 @@ class ParsingError {};
class PluralForm
{
public:
- PluralForm(const std::string& stream); //throw ParsingError
+ explicit PluralForm(const std::string& stream); //throw ParsingError
size_t getForm(int64_t n) const { n_ = std::abs(n) ; return static_cast<size_t>(expr_->eval()); }
private:
diff --git a/FreeFileSync/Source/status_handler.cpp b/FreeFileSync/Source/status_handler.cpp
index 4c1cacb3..6530ed48 100644
--- a/FreeFileSync/Source/status_handler.cpp
+++ b/FreeFileSync/Source/status_handler.cpp
@@ -56,14 +56,15 @@ void fff::runCommandAndLogErrors(const Zstring& cmdLine, ErrorLog& errorLog)
void fff::delayAndCountDown(std::chrono::steady_clock::time_point delayUntil, const std::function<void(const std::wstring& timeRemMsg)>& notifyStatus)
{
- for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now())
- {
- if (notifyStatus)
+ assert(notifyStatus);
+ if (notifyStatus)
+ for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now())
{
const auto timeRemMs = std::chrono::duration_cast<std::chrono::milliseconds>(delayUntil - now).count();
notifyStatus(_P("1 sec", "%x sec", numeric::intDivCeil(timeRemMs, 1000)));
- }
- std::this_thread::sleep_for(UI_UPDATE_INTERVAL / 2);
- }
+ std::this_thread::sleep_for(UI_UPDATE_INTERVAL / 2);
+ }
+ else
+ std::this_thread::sleep_until(delayUntil);
}
diff --git a/FreeFileSync/Source/ui/abstract_folder_picker.cpp b/FreeFileSync/Source/ui/abstract_folder_picker.cpp
index 20647e72..6ebb5024 100644
--- a/FreeFileSync/Source/ui/abstract_folder_picker.cpp
+++ b/FreeFileSync/Source/ui/abstract_folder_picker.cpp
@@ -127,7 +127,10 @@ AbstractFolderPickerDlg::AbstractFolderPickerDlg(wxWindow* parent, AbstractPath&
//----------------------------------------------------------------------
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //dialog-specific local key events
diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp
index 68d4dd88..f2cd35f5 100644
--- a/FreeFileSync/Source/ui/batch_config.cpp
+++ b/FreeFileSync/Source/ui/batch_config.cpp
@@ -7,7 +7,7 @@
#include "batch_config.h"
#include <wx/wupdlock.h>
//#include <wx+/std_button_layout.h>
-#include <wx+/font_size.h>
+#include <wx+/window_layout.h>
#include <wx+/image_resources.h>
#include <wx+/image_tools.h>
#include <wx+/choice_enum.h>
@@ -82,7 +82,10 @@ BatchDialog::BatchDialog(wxWindow* parent, BatchDialogConfig& dlgCfg) :
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //enable dialog-specific key events
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
m_buttonSaveAs->SetFocus();
diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp
index 16ca24ea..69e92ee8 100644
--- a/FreeFileSync/Source/ui/batch_status_handler.cpp
+++ b/FreeFileSync/Source/ui/batch_status_handler.cpp
@@ -26,7 +26,7 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress,
std::chrono::seconds autoRetryDelay,
const Zstring& soundFileSyncComplete,
const Zstring& soundFileAlertPending,
- wxSize progressDlgSize, bool dlgMaximize,
+ const std::optional<wxSize>& progressDlgSize, bool dlgMaximize,
bool autoCloseDialog,
PostSyncAction postSyncAction,
BatchErrorHandling batchErrorHandling) :
diff --git a/FreeFileSync/Source/ui/batch_status_handler.h b/FreeFileSync/Source/ui/batch_status_handler.h
index 02fa9ef4..e0ae7e22 100644
--- a/FreeFileSync/Source/ui/batch_status_handler.h
+++ b/FreeFileSync/Source/ui/batch_status_handler.h
@@ -29,7 +29,7 @@ public:
std::chrono::seconds autoRetryDelay,
const Zstring& soundFileSyncComplete,
const Zstring& soundFileAlertPending,
- wxSize progressDlgSize, bool dlgMaximize,
+ const std::optional<wxSize>& progressDlgSize, bool dlgMaximize,
bool autoCloseDialog,
PostSyncAction postSyncAction,
BatchErrorHandling batchErrorHandling); //noexcept!!
@@ -56,7 +56,7 @@ public:
zen::ErrorLogStats logStats;
FinalRequest finalRequest;
AbstractPath logFilePath;
- wxSize dlgSize;
+ std::optional<wxSize> dlgSize;
bool dlgIsMaximized;
};
Result reportResults(const Zstring& postSyncCommand, PostSyncCondition postSyncCondition,
diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp
index b50b114a..f38ec7d4 100644
--- a/FreeFileSync/Source/ui/cfg_grid.cpp
+++ b/FreeFileSync/Source/ui/cfg_grid.cpp
@@ -349,15 +349,14 @@ private:
//else: clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -> already the default
}
- enum class HoverAreaLog
+ enum class HoverAreaConfig
{
+ name,
link,
};
void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override
{
- wxRect rectTmp = rect;
-
wxDCTextColourChanger textColor(dc); //accessibility: always set both foreground AND background colors!
if (selected)
textColor.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
@@ -375,29 +374,32 @@ private:
if (backColor.IsOk())
{
- wxRect rectTmp2 = rectTmp;
+ wxRect rectTmp = rect;
if (!selected || item->cfgItem.backColorPreview.IsOk())
{
- rectTmp2.width = rectTmp.width * 2 / 3;
- clearArea(dc, rectTmp2, backColor); //accessibility: always set both foreground AND background colors!
+ rectTmp.width = rect.width * 2 / 3;
+ clearArea(dc, rectTmp, backColor); //accessibility: always set both foreground AND background colors!
textColor.Set(*wxBLACK); //
- rectTmp2.x += rectTmp2.width;
- rectTmp2.width = rectTmp.width - rectTmp2.width;
- dc.GradientFillLinear(rectTmp2, backColor, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), wxEAST);
+ rectTmp.x += rectTmp.width;
+ rectTmp.width = rect.width - rectTmp.width;
+ dc.GradientFillLinear(rectTmp, backColor, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), wxEAST);
}
else //always show a glimpse of the background color
{
- rectTmp2.width = getColumnGapLeft() + getDefaultMenuIconSize();
- clearArea(dc, rectTmp2, backColor);
+ rectTmp.width = getColumnGapLeft() + getDefaultMenuIconSize();
+ clearArea(dc, rectTmp, backColor);
- rectTmp2.x += rectTmp2.width;
- rectTmp2.width = getColumnGapLeft();
- dc.GradientFillLinear(rectTmp2, backColor, wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT), wxEAST);
+ rectTmp.x += rectTmp.width;
+ rectTmp.width = getColumnGapLeft();
+ dc.GradientFillLinear(rectTmp, backColor, wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT), wxEAST);
}
}
+ if (!selected && static_cast<HoverAreaConfig>(rowHover) == HoverAreaConfig::name)
+ drawInsetRectangle(dc, rect, fastFromDIP(1), wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
//-------------------------------------------------------------------------------------
+ wxRect rectTmp = rect;
rectTmp.x += getColumnGapLeft();
rectTmp.width -= getColumnGapLeft();
@@ -432,7 +434,7 @@ private:
if (getDaysPast(item->cfgItem.lastSyncTime) >= syncOverdueDays_)
textColor2.Set(*wxRED);
- drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_CENTER);
+ drawCellText(dc, rect, getValue(row, colType), wxALIGN_CENTER);
}
break;
@@ -455,10 +457,10 @@ private:
assert(false);
return wxNullImage;
}();
- drawBitmapRtlNoMirror(dc, enabled ? statusIcon : statusIcon.ConvertToDisabled(), rectTmp, wxALIGN_CENTER);
+ drawBitmapRtlNoMirror(dc, enabled ? statusIcon : statusIcon.ConvertToDisabled(), rect, wxALIGN_CENTER);
}
- if (static_cast<HoverAreaLog>(rowHover) == HoverAreaLog::link)
- drawBitmapRtlNoMirror(dc, loadImage("file_link_16"), rectTmp, wxALIGN_CENTER);
+ if (static_cast<HoverAreaConfig>(rowHover) == HoverAreaConfig::link)
+ drawBitmapRtlNoMirror(dc, loadImage("file_link_16"), rect, wxALIGN_CENTER);
break;
}
}
@@ -485,6 +487,7 @@ private:
HoverArea getMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override
{
if (const ConfigView::Details* item = cfgView_.getItem(row))
+ {
switch (static_cast<ColumnTypeCfg>(colType))
{
case ColumnTypeCfg::name:
@@ -493,9 +496,11 @@ private:
case ColumnTypeCfg::lastLog:
if (!item->isLastRunCfg && !getNativeItemPath(item->cfgItem.logFilePath).empty())
- return static_cast<HoverArea>(HoverAreaLog::link);
+ return static_cast<HoverArea>(HoverAreaConfig::link);
break;
}
+ return static_cast<HoverArea>(HoverAreaConfig::name);
+ }
return HoverArea::none;
}
@@ -593,9 +598,11 @@ private:
void onMouseLeft(GridClickEvent& event)
{
if (const ConfigView::Details* item = cfgView_.getItem(event.row_))
- switch (static_cast<HoverAreaLog>(event.hoverArea_))
+ switch (static_cast<HoverAreaConfig>(event.hoverArea_))
{
- case HoverAreaLog::link:
+ case HoverAreaConfig::name:
+ break;
+ case HoverAreaConfig::link:
try
{
if (const Zstring& nativePath = getNativeItemPath(item->cfgItem.logFilePath);
@@ -613,9 +620,11 @@ private:
void onMouseLeftDouble(GridClickEvent& event)
{
- switch (static_cast<HoverAreaLog>(event.hoverArea_))
+ switch (static_cast<HoverAreaConfig>(event.hoverArea_))
{
- case HoverAreaLog::link:
+ case HoverAreaConfig::name:
+ break;
+ case HoverAreaConfig::link:
return; //swallow event here before MainDialog considers it as a request to start comparison
}
event.Skip();
diff --git a/FreeFileSync/Source/ui/command_box.cpp b/FreeFileSync/Source/ui/command_box.cpp
index 018a8311..ca12ebe4 100644
--- a/FreeFileSync/Source/ui/command_box.cpp
+++ b/FreeFileSync/Source/ui/command_box.cpp
@@ -20,8 +20,6 @@ namespace
{
inline
wxString getSeparationLine() { return std::wstring(50, EM_DASH); } //no space between dashes!
-
-wxDEFINE_EVENT(EVENT_VALIDATE_USER_SELECTION, wxCommandEvent);
}
@@ -45,8 +43,6 @@ CommandBox::CommandBox(wxWindow* parent,
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) { onUpdateList(event); });
Bind(wxEVT_COMMAND_COMBOBOX_SELECTED, [this](wxCommandEvent& event) { onSelection (event); });
Bind(wxEVT_MOUSEWHEEL, [] (wxMouseEvent& event) {}); //swallow! this gives confusing UI feedback anyway
-
- Bind(EVENT_VALIDATE_USER_SELECTION, [this](wxCommandEvent& event) { onValidateSelection(event); });
}
@@ -129,13 +125,13 @@ void CommandBox::setValueAndUpdateList(const wxString& value)
void CommandBox::onSelection(wxCommandEvent& event)
{
//we cannot replace built-in commands at this position in call stack, so defer to a later time!
- GetEventHandler()->AddPendingEvent(wxCommandEvent(EVENT_VALIDATE_USER_SELECTION));
+ CallAfter([&] { onValidateSelection(); });
event.Skip();
}
-void CommandBox::onValidateSelection(wxCommandEvent& event)
+void CommandBox::onValidateSelection()
{
const wxString value = GetValue();
diff --git a/FreeFileSync/Source/ui/command_box.h b/FreeFileSync/Source/ui/command_box.h
index f780b380..02745be1 100644
--- a/FreeFileSync/Source/ui/command_box.h
+++ b/FreeFileSync/Source/ui/command_box.h
@@ -42,7 +42,7 @@ public:
private:
void onKeyEvent(wxKeyEvent& event);
void onSelection(wxCommandEvent& event);
- void onValidateSelection(wxCommandEvent& event);
+ void onValidateSelection();
void onUpdateList(wxEvent& event);
void setValueAndUpdateList(const wxString& value);
diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp
index 21114308..e81ec344 100644
--- a/FreeFileSync/Source/ui/file_grid.cpp
+++ b/FreeFileSync/Source/ui/file_grid.cpp
@@ -464,21 +464,24 @@ private:
std::wstring getValue(size_t row, ColumnType colType) const override
{
- std::wstring value;
if (const FileSystemObject* fsObj = getFsObject(row))
if (!fsObj->isEmpty<side>())
+ {
+ if (static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::path)
+ switch (itemPathFormat_)
+ {
+ case ItemPathFormat::name:
+ return utfTo<std::wstring>(fsObj->getItemName<side>());
+ case ItemPathFormat::relative:
+ return utfTo<std::wstring>(fsObj->getRelativePath<side>());
+ case ItemPathFormat::full:
+ return AFS::getDisplayPath(fsObj->getAbstractPath<side>());
+ }
+
+ std::wstring value; //dynamically allocates 16 byte memory! but why? shouldn't SSO make this superfluous?! or is it only in debug?
switch (static_cast<ColumnTypeRim>(colType))
{
case ColumnTypeRim::path:
- switch (itemPathFormat_)
- {
- case ItemPathFormat::name:
- return utfTo<std::wstring>(fsObj->getItemName<side>());
- case ItemPathFormat::relative:
- return utfTo<std::wstring>(fsObj->getRelativePath<side>());
- case ItemPathFormat::full:
- return AFS::getDisplayPath(fsObj->getAbstractPath<side>());
- }
assert(false);
break;
@@ -501,7 +504,9 @@ private:
[&](const SymlinkPair& symlink) { value = utfTo<std::wstring>(getFileExtension(symlink.getItemName<side>())); });
break;
}
- return value;
+ return value;
+ }
+ return {};
}
void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, HoverArea rowHover) override
@@ -787,9 +792,12 @@ private:
rectCud.x += rectCud.width;
rectCud.width = gapSize_ + fastFromDIP(2);
+#if 0 //wxDC::GetPixel() is broken in GTK3! https://github.com/wxWidgets/wxWidgets/issues/14067
wxColor backCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
dc.GetPixel(rectCud.GetTopRight(), &backCol);
-
+#else
+ const wxColor backCol = getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0);
+#endif
dc.GradientFillLinear(rectCud, getBackGroundColorSyncAction(syncOp), backCol, wxEAST);
}
};
@@ -807,9 +815,6 @@ private:
if (row == pdi.groupLastRow - 1 /*last group item*/) //preserve the group separation line!
rectNav.height -= fastFromDIP(1);
- //wxColor backCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
- //dc.GetPixel(rectNav.GetTopRight(), &backCol); //e.g. selected row!
-
dc.GradientFillLinear(rectNav, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST);
navMarkerDrawn = true;
}
@@ -1702,9 +1707,9 @@ public:
gridL_.Bind(EVENT_GRID_COL_RESIZE, [this](GridColumnResizeEvent& event) { onResizeColumn(event, gridL_, gridR_); });
gridR_.Bind(EVENT_GRID_COL_RESIZE, [this](GridColumnResizeEvent& event) { onResizeColumn(event, gridR_, gridL_); });
- gridL_.getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event, gridL_); });
- gridC_.getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event, gridC_); });
- gridR_.getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event, gridR_); });
+ gridL_.Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event, gridL_); });
+ gridC_.Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event, gridC_); });
+ gridR_.Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event, gridR_); });
gridC_.getMainWin().Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { onCenterMouseMovement(event); });
gridC_.getMainWin().Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { onCenterMouseLeave (event); });
@@ -1747,7 +1752,7 @@ public:
//=> Next keyboard input on left does *not* emit focus change event, but still "scrollMaster" needs to change
//=> hook keyboard input instead of focus event:
grid.getMainWin().Bind(wxEVT_CHAR, handler);
- grid.getMainWin().Bind(wxEVT_KEY_DOWN, handler);
+ grid.Bind(wxEVT_KEY_DOWN, handler);
//grid.getMainWin().Bind(wxEVT_KEY_UP, handler); -> superfluous?
grid.getMainWin().Bind(wxEVT_LEFT_DOWN, handler);
diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp
index 4b1ae973..d0fd57ee 100644
--- a/FreeFileSync/Source/ui/gui_generated.cpp
+++ b/FreeFileSync/Source/ui/gui_generated.cpp
@@ -1650,7 +1650,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"), true );
+ m_notebook->AddPage( m_panelCompSettingsTab, _("dummy"), false );
m_panelFilterSettingsTab = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panelFilterSettingsTab->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
@@ -1781,7 +1781,10 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizer162->Add( m_choiceUnitMinSize, 0, wxEXPAND, 5 );
- bSizer158->Add( bSizer162, 0, wxBOTTOM|wxEXPAND, 5 );
+ bSizer158->Add( bSizer162, 0, wxEXPAND, 5 );
+
+
+ bSizer158->Add( 0, 10, 0, 0, 5 );
wxBoxSizer* bSizer163;
bSizer163 = new wxBoxSizer( wxVERTICAL );
@@ -1877,7 +1880,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
m_panelFilterSettingsTab->SetSizer( bSizer278 );
m_panelFilterSettingsTab->Layout();
bSizer278->Fit( m_panelFilterSettingsTab );
- m_notebook->AddPage( m_panelFilterSettingsTab, _("dummy"), false );
+ m_notebook->AddPage( m_panelFilterSettingsTab, _("dummy"), true );
m_panelSyncSettingsTab = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panelSyncSettingsTab->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp
index 1cf637de..7b7adc95 100644
--- a/FreeFileSync/Source/ui/gui_status_handler.cpp
+++ b/FreeFileSync/Source/ui/gui_status_handler.cpp
@@ -361,7 +361,7 @@ StatusHandlerFloatingDialog::StatusHandlerFloatingDialog(wxFrame* parentDlg,
std::chrono::seconds autoRetryDelay,
const Zstring& soundFileSyncComplete,
const Zstring& soundFileAlertPending,
- const wxSize& progressDlgSize, bool dlgMaximize,
+ const std::optional<wxSize>& progressDlgSize, bool dlgMaximize,
bool autoCloseDialog,
const ErrorLog* errorLogStart) :
jobNames_(jobNames),
@@ -525,7 +525,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c
wxSound::Play(utfTo<wxString>(soundFileSyncComplete_), wxSOUND_ASYNC);
}
//if (::GetForegroundWindow() != GetHWND())
- // RequestUserAttention(); -> probably too much since task bar is already colorized with Taskbar::STATUS_ERROR or STATUS_NORMAL
+ // RequestUserAttention(); -> probably too much since task bar is already colorized with Taskbar::STATUS_ERROR or STATUS_NORMAL
}
//--------------------- save log file ----------------------
diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h
index beb4d558..c2929d59 100644
--- a/FreeFileSync/Source/ui/gui_status_handler.h
+++ b/FreeFileSync/Source/ui/gui_status_handler.h
@@ -73,7 +73,7 @@ public:
std::chrono::seconds autoRetryDelay,
const Zstring& soundFileSyncComplete,
const Zstring& soundFileAlertPending,
- const wxSize& progressDlgSize, bool dlgMaximize,
+ const std::optional<wxSize>& progressDlgSize, bool dlgMaximize,
bool autoCloseDialog,
const zen::ErrorLog* errorLogStart /*optional*/); //noexcept!
~StatusHandlerFloatingDialog();
@@ -99,7 +99,7 @@ public:
zen::SharedRef<const zen::ErrorLog> errorLog;
FinalRequest finalRequest;
AbstractPath logFilePath;
- wxSize dlgSize;
+ std::optional<wxSize> dlgSize;
bool dlgIsMaximized;
bool autoCloseDialog;
};
diff --git a/FreeFileSync/Source/ui/log_panel.cpp b/FreeFileSync/Source/ui/log_panel.cpp
index 9e839d4b..ff64044e 100644
--- a/FreeFileSync/Source/ui/log_panel.cpp
+++ b/FreeFileSync/Source/ui/log_panel.cpp
@@ -326,7 +326,7 @@ LogPanel::LogPanel(wxWindow* parent) : LogPanelGenerated(parent)
});
//support for CTRL + C
- m_gridMessages->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridButtonEvent(event); });
+ m_gridMessages->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridButtonEvent(event); });
m_gridMessages->Bind(EVENT_GRID_CONTEXT_MENU, [this](GridContextMenuEvent& event) { onMsgGridContext(event); });
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index 341cfec7..3ebebeba 100644
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -25,7 +25,7 @@
#include <wx+/toggle_button.h>
#include <wx+/no_flicker.h>
#include <wx+/rtl.h>
-#include <wx+/font_size.h>
+#include <wx+/window_layout.h>
#include <wx+/popup_dlg.h>
#include <wx+/window_tools.h>
#include <wx+/image_resources.h>
@@ -64,14 +64,14 @@ const int TOP_BUTTON_OPTIMAL_WIDTH_DIP = 170;
constexpr std::chrono::milliseconds LAST_USED_CFG_EXISTENCE_CHECK_TIME_MAX(500);
constexpr std::chrono::milliseconds FILE_GRID_POST_UPDATE_DELAY(400);
-const Zchar macroNameItemPath [] = Zstr("%item_path%");
-const Zchar macroNameItemPath2 [] = Zstr("%item_path2%");
-const Zchar macroNameLocalPath [] = Zstr("%local_path%");
-const Zchar macroNameLocalPath2 [] = Zstr("%local_path2%");
-const Zchar macroNameItemName [] = Zstr("%item_name%");
-const Zchar macroNameItemName2 [] = Zstr("%item_name2%");
-const Zchar macroNameParentPath [] = Zstr("%parent_path%");
-const Zchar macroNameParentPath2[] = Zstr("%parent_path2%");
+const ZstringView macroNameItemPath = Zstr("%item_path%");
+const ZstringView macroNameItemPath2 = Zstr("%item_path2%");
+const ZstringView macroNameLocalPath = Zstr("%local_path%");
+const ZstringView macroNameLocalPath2 = Zstr("%local_path2%");
+const ZstringView macroNameItemName = Zstr("%item_name%");
+const ZstringView macroNameItemName2 = Zstr("%item_name2%");
+const ZstringView macroNameParentPath = Zstr("%parent_path%");
+const ZstringView macroNameParentPath2 = Zstr("%parent_path2%");
bool containsFileItemMacro(const Zstring& commandLinePhrase)
{
@@ -436,10 +436,10 @@ void MainDialog::create(const Zstring& globalConfigFilePath,
//construction complete! trigger special events:
//------------------------------------------------------------------------------------------
- //show welcome screen after FreeFileSync update => show *before* any other dialogs
- if (mainDlg->globalCfg_.welcomeShownVersion != ffsVersion)
+ //show welcome dialog after FreeFileSync update => show *before* any other dialogs
+ if (mainDlg->globalCfg_.welcomeDialogLastVersion != ffsVersion)
{
- mainDlg->globalCfg_.welcomeShownVersion = ffsVersion;
+ mainDlg->globalCfg_.welcomeDialogLastVersion = ffsVersion;
showAboutDialog(mainDlg);
}
@@ -772,7 +772,7 @@ imgFileManagerSmall_([]
m_gridOverview->Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onTreeGridSelection(event); });
//cfg grid:
- m_gridCfgHistory->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onCfgGridKeyEvent(event); });
+ m_gridCfgHistory->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onCfgGridKeyEvent(event); });
m_gridCfgHistory->Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onCfgGridSelection (event); });
m_gridCfgHistory->Bind(EVENT_GRID_MOUSE_LEFT_DOUBLE, [this](GridClickEvent& event) { onCfgGridDoubleClick (event); });
m_gridCfgHistory->Bind(EVENT_GRID_CONTEXT_MENU, [this](GridContextMenuEvent& event) { onCfgGridContext (event); });
@@ -896,11 +896,11 @@ imgFileManagerSmall_([]
setConfig(guiCfg, referenceFiles); //expects auiMgr_.Update(): e.g. recalcMaxFolderPairsVisible()
//support for CTRL + C and DEL on grids
- m_gridMainL->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridKeyEvent(event, *m_gridMainL, true /*leftSide*/); });
- m_gridMainC->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridKeyEvent(event, *m_gridMainC, true /*leftSide*/); });
- m_gridMainR->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridKeyEvent(event, *m_gridMainR, false /*leftSide*/); });
+ m_gridMainL->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridKeyEvent(event, *m_gridMainL, true /*leftSide*/); });
+ m_gridMainC->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridKeyEvent(event, *m_gridMainC, true /*leftSide*/); });
+ m_gridMainR->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridKeyEvent(event, *m_gridMainR, false /*leftSide*/); });
- m_gridOverview->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onTreeKeyEvent(event); });
+ m_gridOverview->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onTreeKeyEvent(event); });
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //enable dialog-specific key events
@@ -1045,11 +1045,7 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings)
//caveat: set/get language asymmmetry! setLanguage(globalSettings.programLanguage); //throw FileError
//we need to set language before creating this class!
- setInitialWindowSize(*this,
- layout.mainDlg.dlgSize,
- layout.mainDlg.dlgPos,
- layout.mainDlg.isMaximized,
- wxSize{fastFromDIP(900), fastFromDIP(600)} /*defaultSize*/);
+ WindowLayout::setInitial(*this, {layout.mainDlg.size, layout.mainDlg.pos, layout.mainDlg.isMaximized}, {fastFromDIP(900), fastFromDIP(600)} /*defaultSize*/);
//set column attributes
m_gridMainL ->setColumnConfig(convertColAttributes(layout.fileColumnAttribsLeft, getFileGridDefaultColAttribsLeft()));
@@ -1110,7 +1106,7 @@ void MainDialog::setGlobalCfgOnInit(const XmlGlobalSettings& globalSettings)
preserveConstraint(auiMgr_.GetPane(m_panelViewFilter));
preserveConstraint(auiMgr_.GetPane(m_panelConfig));
- auiMgr_.LoadPerspective(layout.mainDlg.panelLayout, false /*update: don't call wxAuiManager::Update() yet*/);
+ auiMgr_.LoadPerspective(layout.panelLayout, false /*update: don't call wxAuiManager::Update() yet*/);
//restore original captions
for (const auto& [paneInfo, caption] : paneCaptions)
@@ -1204,14 +1200,10 @@ XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit()
//else: logPane.best_size already contains non-maximized value
//auiMgr_.Update(); //[!] not needed
- globalSettings.dpiLayouts[getDpiScalePercent()].mainDlg.panelLayout = auiMgr_.SavePerspective(); //does not need wxAuiManager::Update()!
+ globalSettings.dpiLayouts[getDpiScalePercent()].panelLayout = auiMgr_.SavePerspective(); //does not need wxAuiManager::Update()!
- const auto& [size, pos, isMaximized] = getWindowSizeBeforeClose(*this); //call *after* wxAuiManager::SavePerspective()!
- if (size)
- globalSettings.dpiLayouts[getDpiScalePercent()].mainDlg.dlgSize = *size;
- if (pos)
- globalSettings.dpiLayouts[getDpiScalePercent()].mainDlg.dlgPos = *pos;
- globalSettings.dpiLayouts[getDpiScalePercent()].mainDlg.isMaximized = isMaximized;
+ const auto& [size, pos, isMaximized] = WindowLayout::getBeforeClose(*this); //call *after* wxAuiManager::SavePerspective()!
+ globalSettings.dpiLayouts[getDpiScalePercent()].mainDlg = {size, pos, isMaximized};
return globalSettings;
}
@@ -4494,7 +4486,7 @@ void MainDialog::onStartSync(wxCommandEvent& event)
guiCfg.mainCfg.autoRetryDelay,
globalCfg_.soundFileSyncFinished,
globalCfg_.soundFileAlertPending,
- globalCfg_.dpiLayouts[getDpiScalePercent()].progressDlg.dlgSize,
+ globalCfg_.dpiLayouts[getDpiScalePercent()].progressDlg.size,
globalCfg_.dpiLayouts[getDpiScalePercent()].progressDlg.isMaximized,
globalCfg_.progressDlgAutoClose,
errorLogStart.get());
@@ -4555,7 +4547,7 @@ void MainDialog::onStartSync(wxCommandEvent& event)
setLastOperationLog(r.summary, r.errorLog.ptr());
globalCfg_.progressDlgAutoClose = r.autoCloseDialog;
- globalCfg_.dpiLayouts[getDpiScalePercent()].progressDlg.dlgSize = r.dlgSize;
+ globalCfg_.dpiLayouts[getDpiScalePercent()].progressDlg.size = r.dlgSize;
globalCfg_.dpiLayouts[getDpiScalePercent()].progressDlg.isMaximized = r.dlgIsMaximized;
//update last sync stats for the selected cfg files
@@ -4814,9 +4806,8 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s
}
const int64_t totalTimeSec = std::chrono::duration_cast<std::chrono::seconds>(summary.totalTime).count();
-
- m_staticTextTimeElapsed->SetLabelText(wxTimeSpan::Seconds(totalTimeSec).Format(L"%H:%M:%S"));
- //totalTimeSec < 3600 ? wxTimeSpan::Seconds(totalTimeSec).Format(L"%M:%S") -> let's use full precision for max. clarity: https://freefilesync.org/forum/viewtopic.php?t=6308
+ m_staticTextTimeElapsed->SetLabelText(utfTo<wxString>(formatTimeSpan(totalTimeSec)));
+ //hourOptional? -> let's use full precision for max. clarity: https://freefilesync.org/forum/viewtopic.php?t=6308
logPanel_->setLog(errorLog);
diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp
index d10a21db..21a94913 100644
--- a/FreeFileSync/Source/ui/progress_indicator.cpp
+++ b/FreeFileSync/Source/ui/progress_indicator.cpp
@@ -15,7 +15,7 @@
#include <wx+/image_tools.h>
#include <wx+/graph.h>
#include <wx+/no_flicker.h>
-#include <wx+/font_size.h>
+#include <wx+/window_layout.h>
#include <zen/file_access.h>
#include <zen/thread.h>
#include <zen/perf.h>
@@ -218,7 +218,10 @@ CompareProgressPanel::Impl::Impl(wxFrame& parentWindow) :
m_panelTimeStats->Layout();
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
}
@@ -362,9 +365,7 @@ void CompareProgressPanel::Impl::updateProgressGui()
//current time elapsed
const int64_t timeElapSec = std::chrono::duration_cast<std::chrono::seconds>(timeElapsed).count();
- setText(*m_staticTextTimeElapsed, timeElapSec < 3600 ?
- wxTimeSpan::Seconds(timeElapSec).Format( L"%M:%S") :
- wxTimeSpan::Seconds(timeElapSec).Format(L"%H:%M:%S"), &layoutChanged);
+ setText(*m_staticTextTimeElapsed, utfTo<wxString>(formatTimeSpan(timeElapSec, true /*hourOptional*/)), &layoutChanged);
if (haveTotalStats) //remaining time and speed: only visible during binary comparison
if (numeric::dist(timeLastSpeedEstimate_, timeElapsed) >= SPEED_ESTIMATE_UPDATE_INTERVAL)
@@ -618,12 +619,10 @@ struct LabelFormatterTimeElapsed : public LabelFormatter
wxString formatText(double timeElapsed, double optimalBlockSize) const override
{
const int64_t timeElapsedSec = std::round(timeElapsed);
+ if (timeElapsedSec < 60)
+ return _P("1 sec", "%x sec", timeElapsedSec);
- return timeElapsedSec < 60 ?
- wxString(_P("1 sec", "%x sec", timeElapsedSec)) :
- timeElapsedSec < 3600 ?
- wxTimeSpan::Seconds(timeElapsedSec).Format( L"%M:%S") :
- wxTimeSpan::Seconds(timeElapsedSec).Format(L"%H:%M:%S");
+ return utfTo<wxString>(formatTimeSpan(timeElapsedSec, true /*hourOptional*/));
}
};
}
@@ -638,7 +637,7 @@ class SyncProgressDialogImpl : public TopLevelDialog, public SyncProgressDialog
{
public:
SyncProgressDialogImpl(long style, //wxFrame/wxDialog style
- wxSize dlgSize, bool dlgMaximize,
+ const std::optional<wxSize>& dlgSize, bool dlgMaximize,
const std::function<void()>& userRequestAbort,
const Statistics& syncStat,
wxFrame* parentFrame,
@@ -738,14 +737,12 @@ 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::optional<wxSize>& dlgSize, bool dlgMaximize,
const std::function<void()>& userRequestAbort,
const Statistics& syncStat,
wxFrame* parentFrame,
@@ -773,8 +770,7 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF
()),
parentFrame_(parentFrame),
userRequestAbort_(userRequestAbort),
-syncStat_(&syncStat),
-dlgSizeBuf_(dlgSize)
+syncStat_(&syncStat)
{
static_assert(std::is_same_v<TopLevelDialog, wxFrame > ||
std::is_same_v<TopLevelDialog, wxDialog>);
@@ -903,10 +899,14 @@ dlgSizeBuf_(dlgSize)
//make sure that standard height matches ProcessPhase::comparingContent statistics layout (== largest)
this->GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
+#ifdef __WXGTK3__
+ this->Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ this->Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
pnl_.Layout();
this->Center(); //call *after* dialog layout update and *before* wxWindow::Show()!
- setInitialWindowSize(*this, dlgSizeBuf_, std::nullopt/*pos*/, dlgMaximize, this->GetSize() /*defaultSize*/);
+ WindowLayout::setInitial(*this, {dlgSize, std::nullopt/*pos*/, dlgMaximize}, this->GetSize() /*defaultSize*/);
if (showProgress)
{
@@ -1141,9 +1141,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateProgressGui(bool allowYield)
//current time elapsed
const int64_t timeElapSec = std::chrono::duration_cast<std::chrono::seconds>(timeElapsed).count();
- setText(*pnl_.m_staticTextTimeElapsed, timeElapSec < 3600 ?
- wxTimeSpan::Seconds(timeElapSec).Format( L"%M:%S") :
- wxTimeSpan::Seconds(timeElapSec).Format(L"%H:%M:%S"), &layoutChanged);
+ setText(*pnl_.m_staticTextTimeElapsed, utfTo<wxString>(formatTimeSpan(timeElapSec, true /*hourOptional*/)), &layoutChanged);
//remaining time and speed
if (numeric::dist(timeLastSpeedEstimate_, timeElapsed) >= SPEED_ESTIMATE_UPDATE_INTERVAL)
@@ -1340,8 +1338,9 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult syncResult,
pnl_.m_staticTextTimeRemaining->Hide();
const int64_t totalTimeSec = std::chrono::duration_cast<std::chrono::seconds>(stopWatch_.elapsed()).count();
- setText(*pnl_.m_staticTextTimeElapsed, wxTimeSpan::Seconds(totalTimeSec).Format(L"%H:%M:%S"));
- //totalTimeSec < 3600 ? wxTimeSpan::Seconds(totalTimeSec).Format(L"%M:%S") -> let's use full precision for max. clarity: https://freefilesync.org/forum/viewtopic.php?t=6308
+ pnl_.m_staticTextTimeElapsed->SetLabelText(utfTo<wxString>(formatTimeSpan(totalTimeSec)));
+ //hourOptional? -> let's use full precision for max. clarity: https://freefilesync.org/forum/viewtopic.php?t=6308
+
resumeFromSystray(false /*userRequested*/); //if in tray mode...
@@ -1519,13 +1518,11 @@ auto SyncProgressDialogImpl<TopLevelDialog>::destroy(bool autoClose, bool restor
//------------------------------------------------------------------------
const bool autoCloseDialog = getOptionAutoCloseDialog();
- const auto& [size, pos, isMaximized] = getWindowSizeBeforeClose(*this);
- if (size)
- dlgSizeBuf_ = *size;
+ const auto& [dlgSize, pos, isMaximized] = WindowLayout::getBeforeClose(*this);
this->Destroy(); //wxWidgets macOS: simple "delete"!!!!!!!
- return {autoCloseDialog, dlgSizeBuf_, isMaximized};
+ return {autoCloseDialog, dlgSize, isMaximized};
}
@@ -1646,7 +1643,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::resumeFromSystray(bool userRequeste
//########################################################################################
-SyncProgressDialog* SyncProgressDialog::create(wxSize dlgSize, bool dlgMaximize,
+SyncProgressDialog* SyncProgressDialog::create(const std::optional<wxSize>& dlgSize, bool dlgMaximize,
const std::function<void()>& userRequestAbort,
const Statistics& syncStat,
wxFrame* parentWindow, //may be nullptr
@@ -1658,21 +1655,15 @@ SyncProgressDialog* SyncProgressDialog::create(wxSize dlgSize, bool dlgMaximize,
size_t autoRetryCount,
PostSyncAction2 postSyncAction)
{
- if (parentWindow) //sync from GUI
- {
-#if 0 //macOS; update 08-2021: Bug seems to be fixed!?
- //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<wxFrame>(wxDEFAULT_FRAME_STYLE | wxFRAME_FLOAT_ON_PARENT,
- dlgSize, dlgMaximize, userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, jobNames, syncStartTime, ignoreErrors, autoRetryCount, postSyncAction);
-#else //GNOME bug: wxDialog seems to ignore wxMAXIMIZE_BOX | wxMINIMIZE_BOX! :( wxFrame OTOH has them, but adds an extra taskbar entry
+ if (parentWindow) //FFS GUI sync
return new SyncProgressDialogImpl<wxDialog>(wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxMINIMIZE_BOX | wxRESIZE_BORDER,
- dlgSize, dlgMaximize, userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, jobNames, syncStartTime, ignoreErrors, autoRetryCount, postSyncAction);
-#endif
- }
+ dlgSize, dlgMaximize, userRequestAbort, syncStat, parentWindow, showProgress,
+ autoCloseDialog, jobNames, syncStartTime, ignoreErrors, autoRetryCount, postSyncAction);
else //FFS batch job
{
auto dlg = new SyncProgressDialogImpl<wxFrame>(wxDEFAULT_FRAME_STYLE,
- dlgSize, dlgMaximize, userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, jobNames, syncStartTime, ignoreErrors, autoRetryCount, 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 a8536df7..fe63e9f5 100644
--- a/FreeFileSync/Source/ui/progress_indicator.h
+++ b/FreeFileSync/Source/ui/progress_indicator.h
@@ -55,7 +55,7 @@ enum class PostSyncAction2
struct SyncProgressDialog
{
- static SyncProgressDialog* create(wxSize dlgSize, bool dlgMaximize,
+ static SyncProgressDialog* create(const std::optional<wxSize>& dlgSize, bool dlgMaximize,
const std::function<void()>& userRequestAbort,
const Statistics& syncStat,
wxFrame* parentWindow, //may be nullptr
@@ -69,7 +69,7 @@ struct SyncProgressDialog
struct Result
{
bool autoCloseDialog;
- wxSize dlgSize;
+ std::optional<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 50713faa..6be25206 100644
--- a/FreeFileSync/Source/ui/small_dlgs.cpp
+++ b/FreeFileSync/Source/ui/small_dlgs.cpp
@@ -20,7 +20,7 @@
#include <wx+/rtl.h>
#include <wx+/no_flicker.h>
#include <wx+/image_tools.h>
-#include <wx+/font_size.h>
+#include <wx+/window_layout.h>
#include <wx+/popup_dlg.h>
#include <wx+/async_task.h>
#include <wx+/image_resources.h>
@@ -144,6 +144,10 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent)
//--------------------------------------------------------------------------
//have animal + text match *final* dialog width
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
{
const int imageWidth = (m_panelDonate->GetSize().GetWidth() - 5 - 5 - 5 /* grey border*/) / 2;
@@ -159,7 +163,10 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent)
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //enable dialog-specific key events
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
m_buttonClose->SetFocus(); //on GTK ESC is only associated with wxID_OK correctly if we set at least *any* focus at all!!!
@@ -278,10 +285,10 @@ CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstrin
m_textCtrlServer->SetHint(_("Example:") + L" website.com 66.198.240.22");
m_textCtrlServer->SetMinSize({fastFromDIP(260), -1});
- m_textCtrlPort ->SetMinSize({fastFromDIP(60), -1}); //
- m_spinCtrlConnectionCount ->SetMinSize({fastFromDIP(70), -1}); //Hack: set size (why does wxWindow::Size() not work?)
- m_spinCtrlChannelCountSftp->SetMinSize({fastFromDIP(70), -1}); //
- m_spinCtrlTimeout ->SetMinSize({fastFromDIP(70), -1}); //
+ m_textCtrlPort->SetMinSize({fastFromDIP(60), -1});
+ setDefaultWidth(*m_spinCtrlConnectionCount);
+ setDefaultWidth(*m_spinCtrlChannelCountSftp);
+ setDefaultWidth(*m_spinCtrlTimeout);
setupFileDrop(*m_panelAuth);
m_panelAuth->Bind(EVENT_DROP_FILE, [this](FileDropEvent& event) { onKeyFileDropped(event); });
@@ -393,7 +400,11 @@ CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, Zstrin
m_checkBoxPasswordPrompt->Hide();
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+ //=> works like a charm for GTK with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
updateGui(); //*after* SetSizeHints when standard dialog height has been calculated
@@ -527,7 +538,7 @@ void CloudSetupDlg::onDetectServerChannelLimit(wxCommandEvent& event)
m_spinCtrlChannelCountSftp->SetSelection(0, 0); //some visual feedback: clear selection
m_spinCtrlChannelCountSftp->Refresh(); //both needed for wxGTK: meh!
m_spinCtrlChannelCountSftp->Update(); //
-
+
AbstractPath folderPath = getFolderPath(); //noexcept
//-------------------------------------------------------------------
auto requestPassword = [&, password = Zstring()](const std::wstring& msg, const std::wstring& lastErrorMsg) mutable
@@ -546,7 +557,7 @@ void CloudSetupDlg::onDetectServerChannelLimit(wxCommandEvent& event)
m_spinCtrlChannelCountSftp->SetFocus(); //[!] otherwise selection is lost
m_spinCtrlChannelCountSftp->SetSelection(-1, -1); //some visual feedback: select all
}
- catch (AbortProcess&) { return; }
+ catch (AbortProcess&) { return; }
catch (const FileError& e)
{
showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString()));
@@ -823,7 +834,7 @@ void CloudSetupDlg::onBrowseCloudFolder(wxCommandEvent& event)
AbstractPath folderPath = getFolderPath(); //noexcept
try
{
- //-------------------------------------------------------------------
+ //-------------------------------------------------------------------
auto requestPassword = [&, password = Zstring()](const std::wstring& msg, const std::wstring& lastErrorMsg) mutable
{
assert(runningOnMainThread());
@@ -834,7 +845,7 @@ void CloudSetupDlg::onBrowseCloudFolder(wxCommandEvent& event)
AFS::authenticateAccess(folderPath.afsDevice, requestPassword); //throw FileError, AbortProcess
//caveat: this could block *indefinitely* for Google Drive, but luckily already authenticated in this context
//-------------------------------------------------------------------
- //
+ //
//for (S)FTP it makes more sense to start with the home directory rather than root (which often denies access!)
if (!AFS::getParentPath(folderPath))
{
@@ -961,7 +972,10 @@ CopyToDialog::CopyToDialog(wxWindow* parent,
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //enable dialog-specific key events
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
m_buttonOK->SetFocus();
@@ -1076,7 +1090,10 @@ DeleteDialog::DeleteDialog(wxWindow* parent,
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //enable dialog-specific key events
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
m_buttonOK->SetFocus();
@@ -1216,7 +1233,10 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent,
setIntValue(*m_staticTextDeleteRight, st.deleteCount<SelectSide::right>(), *m_bitmapDeleteRight, "so_delete_right_sicon");
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
m_buttonStartSync->SetFocus();
@@ -1380,7 +1400,7 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalCfg) :
logFolderSelector_.setPath(globalCfg.logFolderPhrase);
- m_spinCtrlLogFilesMaxAge->SetMinSize({fastFromDIP(70), -1}); //Hack: set size (why does wxWindow::Size() not work?)
+ setDefaultWidth(*m_spinCtrlLogFilesMaxAge);
setImage(*m_bitmapSettings, loadImage("settings"));
setImage(*m_bitmapWarnings, loadImage("msg_warning", fastFromDIP(20)));
@@ -1480,7 +1500,10 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalCfg) :
updateGui();
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
//restore actual value:
@@ -1800,7 +1823,10 @@ SelectTimespanDlg::SelectTimespanDlg(wxWindow* parent, time_t& timeFrom, time_t&
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //enable dialog-specific key events
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
m_buttonOkay->SetFocus();
@@ -1896,7 +1922,10 @@ PasswordPromptDlg::PasswordPromptDlg(wxWindow* parent, const std::wstring& msg,
m_textCtrlPasswordVisible->Hide();
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
updateGui(); //*after* SetSizeHints when standard dialog height has been calculated
@@ -1974,12 +2003,15 @@ CfgHighlightDlg::CfgHighlightDlg(wxWindow* parent, int& cfgHistSyncOverdueDays)
m_staticTextHighlight->Wrap(fastFromDIP(300));
- m_spinCtrlOverdueDays->SetMinSize({fastFromDIP(70), -1}); //Hack: set size (why does wxWindow::Size() not work?)
+ setDefaultWidth(*m_spinCtrlOverdueDays);
m_spinCtrlOverdueDays->SetValue(cfgHistSyncOverdueDays);
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
m_spinCtrlOverdueDays->SetFocus();
@@ -2045,7 +2077,10 @@ ActivationDlg::ActivationDlg(wxWindow* parent,
m_textCtrlOfflineActivationKey->ChangeValue(manualActivationKey);
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
m_buttonActivateOnline->SetFocus();
@@ -2150,8 +2185,12 @@ DownloadProgressWindow::Impl::Impl(wxWindow* parent, int64_t fileSizeTotal) :
updateGui();
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
+
Show();
//clear gui flicker: window must be visible to make this work!
diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp
index 66acc204..65b1b645 100644
--- a/FreeFileSync/Source/ui/sync_cfg.cpp
+++ b/FreeFileSync/Source/ui/sync_cfg.cpp
@@ -14,7 +14,7 @@
#include <wx+/context_menu.h>
#include <wx+/choice_enum.h>
#include <wx+/image_tools.h>
-#include <wx+/font_size.h>
+#include <wx+/window_layout.h>
#include <wx+/popup_dlg.h>
#include <wx+/image_resources.h>
#include <wx+/window_tools.h>
@@ -165,7 +165,7 @@ bool sanitizeFilter(FilterConfig& filterCfg, const std::vector<AbstractPath>& ba
replacements.emplace_back(phrase, relPath);
return; //... to next block
}
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
}
}
});
@@ -520,8 +520,8 @@ globalLogFolderPhrase_(globalLogFolderPhrase)
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}); //
+ setDefaultWidth(*m_spinCtrlAutoRetryCount);
+ setDefaultWidth(*m_spinCtrlAutoRetryDelay);
//ignore invalid input for time shift control:
wxTextValidator inputValidator(wxFILTER_DIGITS | wxFILTER_INCLUDE_CHAR_LIST);
@@ -536,6 +536,10 @@ globalLogFolderPhrase_(globalLogFolderPhrase)
m_textCtrlInclude->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onFilterKeyEvent(event); });
m_textCtrlExclude->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onFilterKeyEvent(event); });
+ setDefaultWidth(*m_spinCtrlMinSize);
+ setDefaultWidth(*m_spinCtrlMaxSize);
+ setDefaultWidth(*m_spinCtrlTimespan);
+
m_staticTextFilterDescr->Wrap(fastFromDIP(450));
setImage(*m_bpButtonDefaultContext, mirrorIfRtl(loadImage("button_arrow_right")));
@@ -598,9 +602,9 @@ globalLogFolderPhrase_(globalLogFolderPhrase)
add(VersioningStyle::timestampFolder, _("Time stamp") + L" [" + _("Folder") + L']', _("Move files into a time-stamped subfolder")).
add(VersioningStyle::timestampFile, _("Time stamp") + L" [" + _("File") + L']', _("Append a time stamp to each file name"));
- m_spinCtrlVersionMaxDays ->SetMinSize({fastFromDIP(60), -1}); //
- m_spinCtrlVersionCountMin->SetMinSize({fastFromDIP(60), -1}); //Hack: set size (why does wxWindow::Size() not work?)
- m_spinCtrlVersionCountMax->SetMinSize({fastFromDIP(60), -1}); //
+ setDefaultWidth(*m_spinCtrlVersionMaxDays );
+ setDefaultWidth(*m_spinCtrlVersionCountMin);
+ setDefaultWidth(*m_spinCtrlVersionCountMax);
m_versioningFolderPath->setHistory(std::make_shared<HistoryList>(versioningFolderHistory, folderHistoryMax));
@@ -674,7 +678,10 @@ globalLogFolderPhrase_(globalLogFolderPhrase)
selectFolderPairConfig(-1);
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!!
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
//keep stable sizer height: "two way" description is smaller than grid of sync directions
@@ -1421,7 +1428,7 @@ void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg)
for (int i = 0; i < rowsToCreate; ++i)
{
wxSpinCtrl* spinCtrlParallelOps = new wxSpinCtrl(m_scrolledWindowPerf, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 2000'000'000, 1);
- spinCtrlParallelOps->SetMinSize({fastFromDIP(60), -1}); //Hack: set size (why does wxWindow::Size() not work?)
+ setDefaultWidth(*spinCtrlParallelOps);
spinCtrlParallelOps->Enable(enableExtraFeatures_);
fgSizerPerf->Add(spinCtrlParallelOps, 0, wxALIGN_CENTER_VERTICAL);
diff --git a/FreeFileSync/Source/ui/tree_grid.cpp b/FreeFileSync/Source/ui/tree_grid.cpp
index f53b212f..c870be72 100644
--- a/FreeFileSync/Source/ui/tree_grid.cpp
+++ b/FreeFileSync/Source/ui/tree_grid.cpp
@@ -136,7 +136,7 @@ void TreeView::extractVisibleSubtree(ContainerObject& hierObj, //in
namespace
{
-//generate nice percentage numbers which precisely add up to 100
+//generate "nice" percentage numbers which precisely add up to 100
void calcPercentage(std::vector<std::pair<uint64_t, int*>>& workList)
{
uint64_t bytesTotal = 0;
@@ -693,7 +693,7 @@ public:
rootIcon_(loadImage("root_folder", widthNodeIcon_)),
grid_(grid)
{
- grid.getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event); });
+ grid.Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event); });
grid.Bind(EVENT_GRID_MOUSE_LEFT_DOWN, [this](GridClickEvent& event) { onMouseLeft (event); });
grid.Bind(EVENT_GRID_MOUSE_LEFT_DOUBLE, [this](GridClickEvent& event) { onMouseLeftDouble(event); });
grid.Bind(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, [this](GridLabelClickEvent& event) { onGridLabelContext (event); });
@@ -798,6 +798,7 @@ private:
enum class HoverAreaTree
{
node,
+ item,
};
void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override
@@ -819,13 +820,6 @@ private:
{
if (std::unique_ptr<TreeView::Node> node = getDataView().getLine(row))
{
- ////clear first section:
- //clearArea(dc, wxRect(rect.GetTopLeft(), wxSize(
- // node->level_ * widthLevelStep_ + gapSize_ + //width
- // (showPercentBar ? percentageBarWidth_ + 2 * gapSize_ : 0) + //
- // widthNodeStatus_ + gapSize_ + widthNodeIcon + gapSize_, //
- // rect.height)), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
-
auto drawIcon = [&](wxImage icon, const wxRect& rectIcon, bool drawActive)
{
if (!drawActive)
@@ -900,6 +894,9 @@ private:
drawIcon(nodeIcon, rectTmp, isActive);
+ if (static_cast<HoverAreaTree>(rowHover) == HoverAreaTree::item)
+ drawInsetRectangle(dc, rectTmp, fastFromDIP(1), *wxBLUE);
+
rectTmp.x += widthNodeIcon_ + gapSize_;
rectTmp.width -= widthNodeIcon_ + gapSize_;
@@ -956,26 +953,18 @@ private:
HoverArea getMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override
{
- switch (static_cast<ColumnTypeOverview>(colType))
- {
- case ColumnTypeOverview::folder:
- if (std::unique_ptr<TreeView::Node> node = getDataView().getLine(row))
- {
- const int tolerance = 2;
- const int nodeStatusXFirst = -tolerance + static_cast<int>(node->level_) * widthLevelStep_ + gapSize_ + (showPercentBar_ ? percentageBarWidth_ + 2 * gapSize_ : 0);
- const int nodeStatusXLast = (nodeStatusXFirst + tolerance) + widthNodeStatus_ + tolerance;
- // -> synchronize renderCell() <-> getBestSize() <-> getMouseHover()
-
- if (nodeStatusXFirst <= cellRelativePosX && cellRelativePosX < nodeStatusXLast)
- return static_cast<HoverArea>(HoverAreaTree::node);
- }
- break;
+ if (static_cast<ColumnTypeOverview>(colType) == ColumnTypeOverview::folder)
+ if (std::unique_ptr<TreeView::Node> node = getDataView().getLine(row))
+ {
+ const int nodeStatusXFirst = static_cast<int>(node->level_) * widthLevelStep_ + gapSize_ + (showPercentBar_ ? percentageBarWidth_ + 2 * gapSize_ : 0);
+ const int nodeStatusXLast = nodeStatusXFirst + widthNodeStatus_;
+ // -> synchronize renderCell() <-> getBestSize() <-> getMouseHover()
- case ColumnTypeOverview::itemCount:
- case ColumnTypeOverview::bytes:
- break;
- }
- return HoverArea::none;
+ const int tolerance = fastFromDIP(5);
+ if (nodeStatusXFirst - tolerance <= cellRelativePosX && cellRelativePosX < nodeStatusXLast + tolerance)
+ return static_cast<HoverArea>(HoverAreaTree::node);
+ }
+ return static_cast<HoverArea>(HoverAreaTree::item);
}
std::wstring getColumnLabel(ColumnType colType) const override
@@ -1007,6 +996,8 @@ private:
break;
}
break;
+ case HoverAreaTree::item:
+ break;
}
event.Skip();
}
diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp
index 543c9510..29b5ed2f 100644
--- a/FreeFileSync/Source/ui/version_check.cpp
+++ b/FreeFileSync/Source/ui/version_check.cpp
@@ -142,9 +142,9 @@ std::vector<std::pair<std::string, std::string>> geHttpPostParameters(wxWindow&
const char* osArch = cpuArchName;
params.emplace_back("os_arch", osArch);
-#if GTK_MAJOR_VERSION == 2
+#ifdef __WXGTK2__
//wxWindow::GetContentScaleFactor() requires GTK3 or later
-#elif GTK_MAJOR_VERSION == 3
+#elif defined __WXGTK3__
params.emplace_back("dip_scale", numberTo<std::string>(parent.GetContentScaleFactor()));
#else
#error unknown GTK version!
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index 4e3f0c59..87e6db77 100644
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace fff
{
-const char ffsVersion[] = "12.0"; //internal linkage!
+const char ffsVersion[] = "12.1"; //internal linkage!
const char FFS_VERSION_SEPARATOR = '.';
}
diff --git a/libcurl/curl_wrap.cpp b/libcurl/curl_wrap.cpp
index f9a60233..1d72dcac 100644
--- a/libcurl/curl_wrap.cpp
+++ b/libcurl/curl_wrap.cpp
@@ -223,11 +223,11 @@ HttpSession::Result HttpSession::perform(const std::string& serverRelPath,
//Contradicting options: CURLOPT_READFUNCTION, CURLOPT_POSTFIELDS:
if (std::any_of(extraOptions.begin(), extraOptions.end(), [](const CurlOption& o) { return o.option == CURLOPT_POSTFIELDS; }))
- /**/ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ /**/ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
}
if (std::any_of(extraOptions.begin(), extraOptions.end(), [](const CurlOption& o) { return o.option == CURLOPT_WRITEFUNCTION || o.option == CURLOPT_READFUNCTION; }))
- /**/ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); //Option already used here!
+ /**/ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!"); //Option already used here!
//---------------------------------------------------
curl_slist* headers = nullptr; //"libcurl will not copy the entire list so you must keep it!"
diff --git a/wx+/dc.h b/wx+/dc.h
index e0cb2c87..a169cffe 100644
--- a/wx+/dc.h
+++ b/wx+/dc.h
@@ -13,7 +13,7 @@
#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER
#include <wx/dcscreen.h>
#include <wx/bmpbndl.h>
- #include <gtk/gtk.h>
+// #include <gtk/gtk.h>
namespace zen
diff --git a/wx+/grid.cpp b/wx+/grid.cpp
index f997d72c..306d4847 100644
--- a/wx+/grid.cpp
+++ b/wx+/grid.cpp
@@ -152,7 +152,7 @@ void GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& te
- wxDC::DrawText also calls wxDC::GetTextExtent()!!
=> wxDC::DrawLabel() boils down to 3(!) calls to wxDC::GetTextExtent()!!!
- wxDC::DrawLabel results in GetTextExtent() call even for empty strings!!!
- => skip the wxDC::DrawLabel() cruft and directly call wxDC::DrawText()! */
+ => NEVER EVER call wxDC::DrawLabel() cruft and directly call wxDC::DrawText()! */
assert(!contains(text, L'\n'));
if (rect.width <= 0 || rect.height <= 0 || text.empty())
return;
@@ -284,7 +284,11 @@ public:
Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent& event) { onMouseWheel (event); });
Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent& event) { onMouseCaptureLost(event); });
- Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event); });
+ Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event)
+ {
+ if (!sendEventToParent(event)) //let parent collect all key events
+ event.Skip();
+ });
//Bind(wxEVT_KEY_UP, [this](wxKeyEvent& event) { onKeyUp (event); }); -> superfluous?
assert(GetClientAreaOrigin() == wxPoint()); //generally assumed when dealing with coordinates below
@@ -332,12 +336,6 @@ private:
virtual void onLeaveWindow (wxMouseEvent& event) { event.Skip(); }
virtual void onMouseCaptureLost(wxMouseCaptureLostEvent& event) { event.Skip(); }
- void onKeyDown(wxKeyEvent& event)
- {
- if (!sendEventToParent(event)) //let parent collect all key events
- event.Skip();
- }
-
void onMouseWheel(wxMouseEvent& event)
{
/* MSDN, WM_MOUSEWHEEL: "Sent to the focus window when the mouse wheel is rotated.
@@ -816,13 +814,6 @@ private:
void onMouseRightDown(wxMouseEvent& event) override
{
evalMouseMovement(event.GetPosition()); //update highlight in obscure cases (e.g. right-click while other context menu is open)
- freezeMouseHighlight_ = true; //e.g. while showing context menu
-
- ZEN_ON_SCOPE_EXIT(
- freezeMouseHighlight_ = false;
- //update mouse highlight (e.g. mouse position changed after showing context menu)
- evalMouseMovement(ScreenToClient(wxGetMousePosition()));
- );
const wxPoint mousePos = GetPosition() + event.GetPosition();
@@ -837,6 +828,9 @@ private:
if (fillGapAfterColumns)
sendEventToParent(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, ColumnType::none, mousePos));
+ //update mouse highlight (e.g. mouse position changed after showing context menu) => needed on Linux/macOS
+ evalMouseMovement(ScreenToClient(wxGetMousePosition()));
+
event.Skip();
}
@@ -883,7 +877,7 @@ private:
if (action->wantResize)
SetCursor(wxCURSOR_SIZEWE); //window-local only! :)
else
- SetCursor(*wxSTANDARD_CURSOR);
+ SetCursor(*wxSTANDARD_CURSOR); //NOOP when setting same cursor
}
else
{
@@ -926,7 +920,7 @@ private:
void setMouseHighlight(const std::optional<size_t>& hl)
{
- if (highlightCol_ != hl && !freezeMouseHighlight_)
+ if (highlightCol_ != hl)
{
highlightCol_ = hl;
Refresh();
@@ -935,8 +929,7 @@ private:
std::unique_ptr<ColumnResizing> activeResizing_;
std::unique_ptr<ColumnMove> activeClickOrMove_;
- std::optional<size_t> highlightCol_; //column during mouse-over
- bool freezeMouseHighlight_ = false;
+ std::optional<size_t> highlightCol_;
int colLabelHeight_ = 0;
const wxFont labelFont_;
@@ -966,9 +959,19 @@ public:
{
wxMouseCaptureLostEvent evt;
GetEventHandler()->ProcessEvent(evt); //better integrate into event handling rather than calling onMouseCaptureLost() directly!?
+ return;
}
- else
- event.Skip();
+
+ /* using keyboard: => clear distracting mouse highlights
+
+ wxEVT_KEY_DOWN evaluation order:
+ 1. this callback
+ 2. Grid::SubWindow ... sendEventToParent()
+ 3. clients binding to Grid wxEVT_KEY_DOWN
+ 4. Grid::onKeyDown() */
+ setMouseHighlight(std::nullopt);
+
+ event.Skip();
});
}
@@ -1050,8 +1053,11 @@ private:
if (activeSelection_->getFirstClick().row_ == row)
return activeSelection_->getFirstClick().hoverArea_;
}
- else if (highlight_.row == row)
- return highlight_.rowHover;
+ else if (highlight_)
+ {
+ if (makeSigned(highlight_->row) == row)
+ return highlight_->rowHover;
+ }
return HoverArea::none;
}
@@ -1110,13 +1116,6 @@ private:
if (auto prov = refParent().getDataProvider())
{
evalMouseMovement(event.GetPosition()); //update highlight in obscure cases (e.g. right-click while other context menu is open)
- freezeMouseHighlight_ = true; //e.g. while showing context menu
-
- ZEN_ON_SCOPE_EXIT(
- freezeMouseHighlight_ = false;
- //update mouse highlight (e.g. mouse position changed after showing context menu)
- evalMouseMovement(ScreenToClient(wxGetMousePosition()));
- );
const wxPoint mousePos = GetPosition() + event.GetPosition();
const ptrdiff_t rowCount = refParent().getRowCount();
@@ -1166,6 +1165,9 @@ private:
}
}
}
+
+ //update mouse highlight (e.g. mouse position changed after showing context menu) => needed on Linux/macOS
+ evalMouseMovement(ScreenToClient(wxGetMousePosition()));
}
event.Skip(); //allow changing focus
}
@@ -1272,7 +1274,7 @@ private:
if (activeSelection_)
activeSelection_->evalMousePos(); //call on both mouse movement + timer event!
else
- setMouseHighlight({row, rowHover});
+ setMouseHighlight(rowHover != HoverArea::none ? std::make_optional<MouseHighlight>({static_cast<size_t>(row), rowHover}) : std::nullopt);
}
}
@@ -1286,14 +1288,14 @@ private:
activeSelection_.reset();
Refresh();
}
- setMouseHighlight({-1, HoverArea::none});
+ setMouseHighlight(std::nullopt);
//event.Skip(); -> we DID handle it!
}
void onLeaveWindow(wxMouseEvent& event) override
{
if (!activeSelection_) //wxEVT_LEAVE_WINDOW does not respect mouse capture!
- setMouseHighlight({-1, HoverArea::none});
+ setMouseHighlight(std::nullopt);
//CAVEAT: we can get wxEVT_MOTION *after* wxEVT_LEAVE_WINDOW: see RowLabelWin::redirectMouseEvent()
// => therefore we also redirect wxEVT_LEAVE_WINDOW, but user will see a little flicker when moving between RowLabelWin and MainWin
@@ -1433,33 +1435,33 @@ private:
struct MouseHighlight
{
- ptrdiff_t row = -1;
+ size_t row = 0;
HoverArea rowHover = HoverArea::none;
bool operator==(const MouseHighlight&) const = default;
};
- void setMouseHighlight(const MouseHighlight& hl)
+ void setMouseHighlight(const std::optional<MouseHighlight>& hl)
{
- if (highlight_ != hl && !freezeMouseHighlight_)
+ assert(!hl || hl->row < refParent().getRowCount() && hl->rowHover != HoverArea::none);
+ if (highlight_ != hl)
{
- const ptrdiff_t rowCount = refParent().getRowCount();
- if (0 <= highlight_.row && highlight_.row < rowCount && highlight_.rowHover != HoverArea::none) //no highlight_? => NOP!
- refreshRow(highlight_.row);
+ if (highlight_)
+ refreshRow(highlight_->row);
highlight_ = hl;
- if (0 <= highlight_.row && highlight_.row < rowCount && highlight_.rowHover != HoverArea::none) //no highlight_? => NOP!
- refreshRow(highlight_.row);
+ if (highlight_)
+ refreshRow(highlight_->row);
}
}
+
RowLabelWin& rowLabelWin_;
ColLabelWin& colLabelWin_;
std::unique_ptr<MouseSelection> activeSelection_; //bound while user is selecting with mouse
- MouseHighlight highlight_; //current mouse highlight
- bool freezeMouseHighlight_ = false;
+ std::optional<MouseHighlight> highlight_;
size_t cursorRow_ = 0;
size_t selectionAnchor_ = 0;
@@ -1736,10 +1738,7 @@ void Grid::onKeyDown(wxKeyEvent& event)
auto moveCursorTo = [&](ptrdiff_t row)
{
if (rowCount > 0)
- {
- row = std::clamp<ptrdiff_t>(row, 0, rowCount - 1);
- setGridCursor(row, GridEventPolicy::allow);
- }
+ setGridCursor(std::clamp<ptrdiff_t>(row, 0, rowCount - 1), GridEventPolicy::allow);
};
auto selectWithCursorTo = [&](ptrdiff_t row)
diff --git a/wx+/no_flicker.h b/wx+/no_flicker.h
index 1c91bd48..9f91bbdb 100644
--- a/wx+/no_flicker.h
+++ b/wx+/no_flicker.h
@@ -55,9 +55,8 @@ void setTextWithUrls(wxRichTextCtrl& richCtrl, const wxString& newText)
for (auto it = newText.begin();;)
{
- const wchar_t urlPrefix[] = L"https://";
- const auto itUrl = std::search(it, newText.end(),
- urlPrefix, urlPrefix + strLength(urlPrefix));
+ const std::wstring_view urlPrefix = L"https://";
+ const auto itUrl = std::search(it, newText.end(), urlPrefix.begin(), urlPrefix.end());
if (it != itUrl)
blocks.emplace_back(BlockType::text, wxString(it, itUrl));
diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp
index c30426cb..b5a194ad 100644
--- a/wx+/popup_dlg.cpp
+++ b/wx+/popup_dlg.cpp
@@ -13,10 +13,9 @@
#include "app_main.h"
#include "bitmap_button.h"
#include "no_flicker.h"
-#include "font_size.h"
+#include "window_layout.h"
#include "image_resources.h"
#include "popup_dlg_generated.h"
-#include "std_button_layout.h"
#include "taskbar.h"
#include "window_tools.h"
@@ -273,12 +272,17 @@ public:
//set std order after button visibility was set
setStandardButtonLayout(*bSizerStdButtons, stdBtns);
-
updateGui();
+
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
+#ifdef __WXGTK3__
+ Show(); //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ Hide(); //avoid old position flash when Center() moves window (asynchronously?)
+#endif
Center(); //needs to be re-applied after a dialog size change!
+
Raise(); //[!] popup may be triggered by ffs_batch job running in the background!
if (m_buttonAccept->IsEnabled())
diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp
index c56d80a1..5ad5da31 100644
--- a/wx+/tooltip.cpp
+++ b/wx+/tooltip.cpp
@@ -13,7 +13,6 @@
#include "image_tools.h"
#include "bitmap_button.h"
#include "dc.h"
- #include <gtk/gtk.h>
using namespace zen;
@@ -77,11 +76,12 @@ void Tooltip::show(const wxString& text, wxPoint mousePos, const wxImage* img)
}
if (imgChanged || txtChanged)
- {
//tipWindow_->Layout(); -> apparently not needed!?
tipWindow_->GetSizer()->SetSizeHints(tipWindow_); //~=Fit() + SetMinSize()
- //Linux: Fit() seems to be broken => call EVERY time inside show, not only if text or bmp change -> still true?!?
- }
+#ifdef __WXGTK3__
+ //GTK3 size calculation requires visible window: https://github.com/wxWidgets/wxWidgets/issues/16088
+ //=> call wxWindow::Show() to "execute"
+#endif
const wxPoint newPos = wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ?
mousePos - wxPoint(fastFromDIP(TIP_WINDOW_OFFSET_DIP) + tipWindow_->GetSize().GetWidth(), 0) :
@@ -101,16 +101,13 @@ void Tooltip::hide()
{
if (tipWindow_)
{
-#if GTK_MAJOR_VERSION == 2 //the tooltip sometimes turns blank or is not shown again after it was hidden: e.g. drag-selection on middle grid
+#ifdef __WXGTK2__ //the tooltip sometimes turns blank or is not shown again after it was hidden: e.g. drag-selection on middle grid
+ //=> no such issues on GTK3!
tipWindow_->Destroy(); //apply brute force:
tipWindow_ = nullptr; //
lastUsedImg_ = wxNullImage;
-
-#elif GTK_MAJOR_VERSION == 3
- tipWindow_->Hide();
#else
-#error unknown GTK version!
+ tipWindow_->Hide();
#endif
-
}
}
diff --git a/wx+/font_size.h b/wx+/window_layout.h
index da74eada..8a86ec86 100644
--- a/wx+/font_size.h
+++ b/wx+/window_layout.h
@@ -4,12 +4,14 @@
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
-#ifndef FONT_SIZE_H_23849632846734343234532
-#define FONT_SIZE_H_23849632846734343234532
+#ifndef WINDOW_LAYOUT_H_23849632846734343234532
+#define WINDOW_LAYOUT_H_23849632846734343234532
#include <zen/basic_math.h>
#include <wx/window.h>
+#include <wx/spinctrl.h>
#include <zen/scope_guard.h>
+ #include <gtk/gtk.h>
#include "dc.h"
@@ -19,7 +21,7 @@ namespace zen
void setRelativeFontSize(wxWindow& control, double factor);
void setMainInstructionFont(wxWindow& control); //following Windows/Gnome/OS X guidelines
-
+void setDefaultWidth(wxSpinCtrl& m_spinCtrl);
@@ -47,6 +49,37 @@ void setMainInstructionFont(wxWindow& control)
control.SetFont(font);
}
+
+
+inline
+void setDefaultWidth(wxSpinCtrl& m_spinCtrl)
+{
+#ifdef __WXGTK3__
+ //there's no way to set width using GTK's CSS! =>
+ m_spinCtrl.InvalidateBestSize();
+ ::gtk_entry_set_width_chars(GTK_ENTRY(m_spinCtrl.m_widget), 3);
+
+#if 0 //apparently not needed!?
+ if (::gtk_check_version(3, 12, 0) == NULL)
+ ::gtk_entry_set_max_width_chars(GTK_ENTRY(m_spinCtrl.m_widget), 3);
+#endif
+
+ //get rid of excessive default width on old GTK3 3.14 (Debian);
+ //gtk_entry_set_width_chars() not working => mitigate
+ m_spinCtrl.SetMinSize({fastFromDIP(100), -1}); //must be wider than gtk_entry_set_width_chars(), or it breaks newer GTK e.g. 3.22!
+
+#if 0 //generic property syntax:
+ GValue bval = G_VALUE_INIT;
+ ::g_value_init(&bval, G_TYPE_BOOLEAN);
+ ::g_value_set_boolean(&bval, false);
+ ZEN_ON_SCOPE_EXIT(::g_value_unset(&bval));
+ ::g_object_set_property(G_OBJECT(m_spinCtrl.m_widget), "visibility", &bval);
+#endif
+#else
+ m_spinCtrl.SetMinSize({fastFromDIP(70), -1});
+#endif
+
+}
}
-#endif //FONT_SIZE_H_23849632846734343234532
+#endif //WINDOW_LAYOUT_H_23849632846734343234532
diff --git a/wx+/window_tools.h b/wx+/window_tools.h
index 73faf272..179508f8 100644
--- a/wx+/window_tools.h
+++ b/wx+/window_tools.h
@@ -92,96 +92,172 @@ private:
namespace
{
-void setInitialWindowSize(wxTopLevelWindow& topWin, wxSize size, std::optional<wxPoint> pos, bool isMaximized, wxSize defaultSize)
+class WindowLayout
{
- 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)
+public:
+ struct Layout
{
- if (pos)
+ std::optional<wxSize> size;
+ std::optional<wxPoint> pos;
+ bool isMaximized = false;
+ };
+ static void setInitial(wxTopLevelWindow& topWin, const Layout& layout, wxSize defaultSize)
+ {
+ initialLayouts_[&topWin] = layout;
+
+ 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 (layout.size &&
+ layout.size->GetWidth () > 0 &&
+ layout.size->GetHeight() > 0)
{
- //calculate how much of the dialog will be visible on screen
- const int dlgArea = size.GetWidth() * size.GetHeight();
- int dlgAreaMaxVisible = 0;
+ if (layout.pos)
+ {
+ //calculate how much of the dialog will be visible on screen
+ const int dlgArea = layout.size->GetWidth() * layout.size->GetHeight();
+ int dlgAreaMaxVisible = 0;
+
+ const int monitorCount = wxDisplay::GetCount();
+ for (int i = 0; i < monitorCount; ++i)
+ {
+ wxRect overlap = wxDisplay(i).GetClientArea().Intersect(wxRect(*layout.pos, *layout.size));
+ dlgAreaMaxVisible = std::max(dlgAreaMaxVisible, overlap.GetWidth() * overlap.GetHeight());
+ }
+
+ if (dlgAreaMaxVisible > 0.1 * dlgArea //at least 10% of the dialog should be visible!
+ )
+ {
+ newSize = *layout.size;
+ newPos = layout.pos;
+ }
+ }
+ else
+ newSize = *layout.size;
+ }
- const int monitorCount = wxDisplay::GetCount();
- for (int i = 0; i < monitorCount; ++i)
+ //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 (layout.isMaximized) //no real need to support both maximize and full screen functions
+ {
+ topWin.Maximize(true);
+ }
+
+
+#if 0 //wxWidgets alternative: apparently no benefits (not even on Wayland! but strange decisions: why restore the minimized state!???)
+ class GeoSerializer : public wxTopLevelWindow::GeometrySerializer
+ {
+ public:
+ GeoSerializer(const std::string& l)
{
- wxRect overlap = wxDisplay(i).GetClientArea().Intersect(wxRect(*pos, size));
- dlgAreaMaxVisible = std::max(dlgAreaMaxVisible, overlap.GetWidth() * overlap.GetHeight());
+ split(l, ' ', [&](const std::string_view phrase)
+ {
+ assert(phrase.empty() || contains(phrase, '='));
+ if (contains(phrase, '='))
+ valuesByName_[utfTo<wxString>(beforeFirst(phrase, '=', IfNotFoundReturn::none))] =
+ /**/ stringTo<int>(afterFirst(phrase, '=', IfNotFoundReturn::none));
+ });
}
- if (dlgAreaMaxVisible > 0.1 * dlgArea //at least 10% of the dialog should be visible!
- )
+ bool SaveField(const wxString& name, int value) const /*NO, this must not be const!*/ override { return false; }
+
+ bool RestoreField(const wxString& name, int* value) /*const: yes, this MAY(!) be const*/ override
{
- newSize = size;
- newPos = pos;
+ auto it = valuesByName_.find(name);
+ if (it == valuesByName_.end())
+ return false;
+ * value = it->second;
+ return true;
}
- }
- else
- newSize = size;
- }
+ private:
+ std::unordered_map<wxString, int> valuesByName_;
+ } serializer(layout);
- //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 (!topWin.RestoreToGeometry(serializer)) //apparently no-fail as long as GeometrySerializer::RestoreField is!
+ assert(false);
+#endif
}
- if (isMaximized) //no real need to support both maximize and full screen functions
+ //destructive! changes window size!
+ static Layout getBeforeClose(wxTopLevelWindow& topWin)
{
- topWin.Maximize(true);
- }
-}
-
+ //we need to portably retrieve non-iconized, non-maximized size and position
+ // non-portable: Win32 GetWindowPlacement(); wxWidgets take: wxTopLevelWindow::SaveGeometry/RestoreToGeometry()
+ if (topWin.IsIconized())
+ topWin.Iconize(false);
-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);
+ bool isMaximized = false;
+ if (topWin.IsMaximized()) //evaluate AFTER uniconizing!
+ {
+ topWin.Maximize(false);
+ isMaximized = true;
+ }
- WindowLayoutWeak layout;
- if (topWin.IsMaximized()) //evaluate AFTER uniconizing!
- {
- topWin.Maximize(false);
- layout.isMaximized = true;
- }
+ std::optional<wxSize> size = topWin.GetSize();
+ std::optional<wxPoint> pos = topWin.GetPosition();
- layout.size = topWin.GetSize();
- layout.pos = topWin.GetPosition();
+ if (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)
+ || pos->y < 0
+ )
+ {
+ size = std::nullopt;
+ pos = std::nullopt;
+ }
- 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
- )
+ //reuse previous values if current ones are not available:
+ if (const auto it = initialLayouts_.find(&topWin);
+ it != initialLayouts_.end())
{
- layout.size = std::nullopt;
- layout.pos = std::nullopt;
+ if (!size)
+ size = it->second.size;
+
+ if (!pos)
+ pos = it->second.pos;
}
- return layout;
-}
+ return {size, pos, isMaximized};
+
+#if 0 //wxWidgets alternative: apparently no benefits (not even on Wayland! but strange decisions: why restore the minimized state!???)
+ struct : wxTopLevelWindow::GeometrySerializer
+ {
+ bool SaveField(const wxString& name, int value) const /*NO, this must not be const!*/ override
+ {
+ layout_ += utfTo<std::string>(name) + '=' + numberTo<std::string>(value) + ' ';
+ return true;
+ }
+
+ bool RestoreField(const wxString& name, int* value) /*const: yes, this MAY(!) be const*/ override { return false; }
+
+ mutable //wxWidgets people: 1. learn when and when not to use const for input/output functions! see SaveField/RestoreField()
+ // 2. learn flexible software design: why are input/output tied up in a single GeometrySerializer implementation?
+ std::string layout_;
+ } serializer;
+
+ if (topWin.SaveGeometry(serializer)) //apparently no-fail as long as GeometrySerializer::SaveField is!
+ return serializer.layout_;
+ else
+ assert(false);
+#endif
+ }
+
+private:
+ inline static std::unordered_map<const wxTopLevelWindow* /*don't access! use as key only!*/, Layout> initialLayouts_;
+};
}
}
diff --git a/xBRZ/src/xbrz.cpp b/xBRZ/src/xbrz.cpp
index 448a4b74..6d2b7325 100644
--- a/xBRZ/src/xbrz.cpp
+++ b/xBRZ/src/xbrz.cpp
@@ -406,7 +406,7 @@ void blendPixel(const Kernel_3x3& ker,
return true;
//make sure there is no second blending in an adjacent rotation for this pixel: handles insular pixels, mario eyes
- if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90° corners
+ if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90° corners
return false;
if (getBottomL(blend) != BLEND_NONE && !eq(e, c))
return false;
diff --git a/zen/argon2.cpp b/zen/argon2.cpp
index d78dc26c..f48abe5e 100644
--- a/zen/argon2.cpp
+++ b/zen/argon2.cpp
@@ -4,9 +4,9 @@
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
-/* The code in this file, except for zen::zargon2() is from:
+/* The code in this file, except for zen::zargon2(), is from PuTTY:
- PuTTY is copyright 1997-2022 Simon Tatham.
+ PuTTY is copyright 1997-2022 Simon Tatham.
Portions copyright Robert de Bath, Joris van Rantwijk, Delian
Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry,
@@ -972,7 +972,7 @@ std::string zen::zargon2(zen::Argon2Flavor flavour, uint32_t mem, uint32_t passe
std::string output(taglen, '\0');
argon2_internal(parallel, taglen, mem, passes, static_cast<uint32_t>(flavour),
{.ptr = password.data(), .len = password.size()},
- {.ptr = salt.data(), .len = salt.size()},
+ {.ptr = salt .data(), .len = salt .size()},
{.ptr = "", .len = 0},
{.ptr = "", .len = 0}, reinterpret_cast<uint8_t*>(output.data()));
return output;
diff --git a/zen/basic_math.h b/zen/basic_math.h
index 7258128f..77ce7b7e 100644
--- a/zen/basic_math.h
+++ b/zen/basic_math.h
@@ -29,8 +29,8 @@ template <class N, class D> auto intDivFloor(N numerator, D denominator);
template <size_t N, class T>
T power(T value);
-double radToDeg(double rad); //convert unit [rad] into [°]
-double degToRad(double degree); //convert unit [°] into [rad]
+double radToDeg(double rad); //convert unit [rad] into [°]
+double degToRad(double degree); //convert unit [°] into [rad]
template <class InputIterator>
double arithmeticMean(InputIterator first, InputIterator last);
@@ -84,13 +84,13 @@ std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, Input
{
//by factor 1.5 to 3 faster than boost::minmax_element (=two-step algorithm) for built-in types!
- InputIterator lowest = first;
- InputIterator largest = first;
+ InputIterator itMin = first;
+ InputIterator itMax = first;
if (first != last)
{
- auto minVal = *lowest; //nice speedup on 64 bit!
- auto maxVal = *largest; //
+ auto minVal = *itMin; //nice speedup on 64 bit!
+ auto maxVal = *itMax; //
for (;;)
{
++first;
@@ -100,17 +100,17 @@ std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, Input
if (compLess(maxVal, val))
{
- largest = first;
+ itMax = first;
maxVal = val;
}
else if (compLess(val, minVal))
{
- lowest = first;
+ itMin = first;
minVal = val;
}
}
}
- return {lowest, largest};
+ return {itMin, itMax};
}
@@ -143,7 +143,7 @@ auto roundToGrid(T val, InputIterator first, InputIterator last)
template <class T> inline
bool isNull(T value)
{
- return abs(value) <= std::numeric_limits<T>::epsilon(); //epsilon is 0 für integral types => less-equal
+ return abs(value) <= std::numeric_limits<T>::epsilon(); //epsilon is 0 für integral types => less-equal
}
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index d06202ba..ef6cdc80 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -157,11 +157,15 @@ int64_t zen::getFreeDiskSpace(const Zstring& folderPath) //throw FileError
uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError
{
- struct stat fileInfo = {};
- if (::stat(filePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), "stat");
+ try
+ {
+ struct stat fileInfo = {};
+ if (::stat(filePath.c_str(), &fileInfo) != 0)
+ THROW_LAST_SYS_ERROR("stat");
- return fileInfo.st_size;
+ return fileInfo.st_size;
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), e.toString()); }
}
@@ -169,8 +173,8 @@ uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError
Zstring zen::getTempFolderPath() //throw FileError
{
- if (const char* tempPath = ::getenv("TMPDIR")) //no extended error reporting
- return tempPath;
+ if (const std::optional<Zstring> tempDirPath = getEnvironmentVar("TMPDIR"))
+ return *tempDirPath;
//TMPDIR not set on CentOS 7, WTF!
return P_tmpdir; //usually resolves to "/tmp"
}
@@ -268,20 +272,7 @@ namespace
//wrapper for file system rename function:
void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool replaceExisting) //throw FileError, ErrorMoveUnsupported, ErrorTargetExisting
{
- auto throwException = [&](int ec)
- {
- const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L'\n' + fmtPath(pathFrom)), L"%y", L'\n' + fmtPath(pathTo));
- const std::wstring errorDescr = formatSystemError("rename", ec);
-
- if (ec == EXDEV)
- throw ErrorMoveUnsupported(errorMsg, errorDescr);
-
- assert(!replaceExisting || ec != EEXIST);
- if (!replaceExisting && ec == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
-
- throw FileError(errorMsg, errorDescr);
- };
+ auto getErrorMsg = [&] { return replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L'\n' + fmtPath(pathFrom)), L"%y", L'\n' + fmtPath(pathTo)); };
//rename() will never fail with EEXIST, but always (atomically) overwrite!
//=> equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists or ::MoveFileEx() + MOVEFILE_REPLACE_EXISTING
@@ -291,22 +282,31 @@ void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool r
{
struct stat sourceInfo = {};
if (::lstat(pathFrom.c_str(), &sourceInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(pathFrom)), "stat");
+ throw FileError(getErrorMsg(), formatSystemError("lstat(source)", errno));
struct stat targetInfo = {};
- if (::lstat(pathTo.c_str(), &targetInfo) == 0)
+ if (::lstat(pathTo.c_str(), &targetInfo) != 0)
+ {
+ if (errno != ENOENT)
+ throw FileError(getErrorMsg(), formatSystemError("lstat(target)", errno));
+ }
+ else
{
if (sourceInfo.st_dev != targetInfo.st_dev ||
sourceInfo.st_ino != targetInfo.st_ino)
- throwException(EEXIST); //that's what we're really here for
+ throw ErrorTargetExisting(getErrorMsg(), replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(pathTo))));
//else: continue with a rename in case
//caveat: if we have a hardlink referenced by two different paths, the source one will be unlinked => fine, but not exactly a "rename"...
}
- //else: not existing or access error (hopefully ::rename will also fail!)
}
if (::rename(pathFrom.c_str(), pathTo.c_str()) != 0)
- throwException(errno);
+ {
+ if (errno == EXDEV)
+ throw ErrorMoveUnsupported(getErrorMsg(), formatSystemError("rename", errno));
+
+ throw FileError(getErrorMsg(), formatSystemError("rename", errno));
+ }
}
@@ -661,7 +661,7 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target
//close output file handle before setting file time; also good place to catch errors when closing stream!
fileOut.close(); //throw FileError
//==========================================================================================================
- //take over fileOut ownership => from this point on, WE are responsible for calling removeFilePlain() on failure!!
+ //take over fileOut ownership => from this point on, WE are responsible for calling removeFilePlain() on error!!
// not needed *currently*! see below: ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); } catch (FileError&) {});
//===========================================================================================================
std::optional<FileError> errorModTime;
diff --git a/zen/file_io.cpp b/zen/file_io.cpp
index 910b75e7..2e4ab60a 100644
--- a/zen/file_io.cpp
+++ b/zen/file_io.cpp
@@ -71,7 +71,7 @@ void FileBase::close() //throw FileError
throw SysError(L"Contract error: close() called more than once.");
if (::close(hFile_) != 0)
THROW_LAST_SYS_ERROR("close");
- hFile_ = invalidFileHandle; //do NOT set on failure! => ~FileOutputPlain() still wants to (try to) delete the file!
+ hFile_ = invalidFileHandle; //do NOT set on error! => ~FileOutputPlain() still wants to (try to) delete the file!
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), e.toString()); }
}
@@ -153,7 +153,7 @@ FileInputPlain::FileInputPlain(FileHandle handle, const Zstring& filePath) :
size_t FileInputPlain::tryRead(void* buffer, size_t bytesToRead) //throw FileError, ErrorFileLocked
{
if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
assert(bytesToRead % getBlockSize() == 0);
try
{
@@ -263,7 +263,7 @@ void FileOutputPlain::reserveSpace(uint64_t expectedSize) //throw FileError
size_t FileOutputPlain::tryWrite(const void* buffer, size_t bytesToWrite) //throw FileError
{
if (bytesToWrite == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
assert(bytesToWrite % getBlockSize() == 0 || bytesToWrite < getBlockSize());
try
{
@@ -306,7 +306,7 @@ std::string zen::getFileContent(const Zstring& filePath, const IoCallback& notif
}
-void zen::setFileContent(const Zstring& filePath, const std::string& byteStream, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X
+void zen::setFileContent(const Zstring& filePath, const std::string_view byteStream, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X
{
const Zstring tmpFilePath = getPathWithTempName(filePath);
diff --git a/zen/file_io.h b/zen/file_io.h
index 46ffa843..838b5021 100644
--- a/zen/file_io.h
+++ b/zen/file_io.h
@@ -175,7 +175,7 @@ Zstring getPathWithTempName(const Zstring& filePath) //generate (hopefully) uniq
[[nodiscard]] std::string getFileContent(const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X
//overwrites if existing + transactional! :)
-void setFileContent(const Zstring& filePath, const std::string& bytes, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X
+void setFileContent(const Zstring& filePath, const std::string_view bytes, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X
}
#endif //FILE_IO_H_89578342758342572345
diff --git a/zen/file_path.cpp b/zen/file_path.cpp
index d06ab6bd..7ef78569 100644
--- a/zen/file_path.cpp
+++ b/zen/file_path.cpp
@@ -36,13 +36,13 @@ std::optional<PathComponents> zen::parsePathComponents(const Zstring& itemPath)
pc = doParse(3 /*sepCountVolumeRoot*/, false /*rootWithSep*/);
if (!pc && startsWith(itemPath, "/media/")) //Ubuntu: e.g. /media/zenju/DEVICE_NAME
- if (const char* username = ::getenv("USER")) //no ownership transfer + no extended error reporting
- if (startsWith(itemPath, std::string("/media/") + username + "/"))
+ if (const std::optional<Zstring> username = getEnvironmentVar("USER"))
+ if (startsWith(itemPath, std::string("/media/") + *username + "/"))
pc = doParse(4 /*sepCountVolumeRoot*/, false /*rootWithSep*/);
if (!pc && startsWith(itemPath, "/run/media/")) //CentOS, Suse: e.g. /run/media/zenju/DEVICE_NAME
- if (const char* username = ::getenv("USER"))
- if (startsWith(itemPath, std::string("/run/media/") + username + "/"))
+ if (const std::optional<Zstring> username = getEnvironmentVar("USER"))
+ if (startsWith(itemPath, std::string("/run/media/") + *username + "/"))
pc = doParse(5 /*sepCountVolumeRoot*/, false /*rootWithSep*/);
if (!pc && startsWith(itemPath, "/run/user/")) //Ubuntu, e.g.: /run/user/1000/gvfs/smb-share:server=192.168.62.145,share=folder
@@ -154,3 +154,73 @@ std::weak_ordering zen::compareNativePath(const Zstring& lhs, const Zstring& rhs
}
+namespace
+{
+std::unordered_map<Zstring, Zstring> getAllEnvVars()
+{
+ assert(runningOnMainThread());
+
+ std::unordered_map<Zstring, Zstring> envVars;
+ if (char** line = environ)
+ for (; *line; ++line)
+ {
+ const std::string_view l(*line);
+ envVars.emplace(beforeFirst(l, '=', IfNotFoundReturn::all),
+ afterFirst(l, '=', IfNotFoundReturn::none));
+ }
+ return envVars;
+}
+
+constinit Global<std::unordered_map<Zstring, Zstring>> globalEnvVars;
+GLOBAL_RUN_ONCE(
+ //*INDENT-OFF*
+ //mitigate static initialization order fiasco: (whatever comes first)
+ if (!globalEnvVars.get())
+ globalEnvVars.set(std::make_unique<std::unordered_map<Zstring, Zstring>>(getAllEnvVars()))
+ //*INDENT-ON*
+);
+}
+
+
+std::optional<Zstring> zen::getEnvironmentVar(const ZstringView name)
+{
+ /* const char* buffer = ::getenv(name); => NO! *not* thread-safe: returns pointer to internal memory!
+ might change after setenv(), allegedly possible even after another getenv()!
+
+ getenv_s() to the rescue!? not implemented on GCC, apparently *still* not threadsafe!!!
+
+ => *eff* this: make a global copy during start up! */
+ std::shared_ptr<std::unordered_map<Zstring, Zstring>> envVars = globalEnvVars.get();
+ if (!envVars) //access during static init or shutdown?
+ {
+ if (globalEnvVars.wasDestroyed())
+ {
+ assert(false);
+ return {}; //SOL!
+ }
+ //mitigate static initialization order fiasco: (whatever comes first)
+ globalEnvVars.set(std::make_unique<std::unordered_map<Zstring, Zstring>>(getAllEnvVars()));
+ envVars = globalEnvVars.get();
+ }
+
+ auto it = envVars->find(name);
+ if (it == envVars->end())
+ return {};
+
+ Zstring value = it->second;
+
+ //some postprocessing (good idea!? Is this even needed!?
+ warn_static("let's find out!")
+#if 0
+ trim(value); //remove leading, trailing blanks
+
+ //remove leading, trailing double-quotes
+ if (startsWith(value, Zstr('"')) &&
+ endsWith (value, Zstr('"')) &&
+ value.length() >= 2)
+ value = Zstring(value.c_str() + 1, value.length() - 2);
+#endif
+ return value;
+}
+
+
diff --git a/zen/file_path.h b/zen/file_path.h
index d67a49d0..960ec52f 100644
--- a/zen/file_path.h
+++ b/zen/file_path.h
@@ -19,7 +19,7 @@ struct PathComponents
Zstring rootPath; //itemPath = rootPath + (FILE_NAME_SEPARATOR?) + relPath
Zstring relPath; //
};
-std::optional<PathComponents> parsePathComponents(const Zstring& itemPath); //no value on failure
+std::optional<PathComponents> parsePathComponents(const Zstring& itemPath); //no value on error
std::optional<Zstring> getParentFolderPath(const Zstring& itemPath);
inline Zstring getItemName(const Zstring& itemPath) { return afterLast(itemPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); }
@@ -44,6 +44,8 @@ inline bool equalNativePath(const Zstring& lhs, const Zstring& rhs) { return com
struct LessNativePath { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return compareNativePath(lhs, rhs) < 0; } };
//------------------------------------------------------------------------------------------
+std::optional<Zstring> getEnvironmentVar(const ZstringView name);
+
}
diff --git a/zen/globals.h b/zen/globals.h
index 9e22f56b..5d4a7041 100644
--- a/zen/globals.h
+++ b/zen/globals.h
@@ -9,6 +9,7 @@
#include <atomic>
#include <memory>
+#include <utility>
#include "scope_guard.h"
@@ -16,7 +17,7 @@ namespace zen
{
/* Solve static destruction order fiasco by providing shared ownership and serialized access to global variables
- => there may be accesses to "Global<T>::get()" during process shutdown e.g. _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message!
+ => e.g. accesses to "Global<T>::get()" during process shutdown: _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message!
=> use trivially-destructible POD only!!!
ATTENTION: function-static globals have the compiler generate "magic statics" == compiler-genenerated locking code which will crash or leak memory when accessed after global is "dead"
@@ -54,7 +55,13 @@ public:
~Global()
{
static_assert(std::is_trivially_destructible_v<Pod>, "this memory needs to live forever");
- set(nullptr);
+
+ pod_.spinLock.lock();
+ std::shared_ptr<T>* oldInst = std::exchange(pod_.inst, nullptr);
+ pod_.destroyed = true;
+ pod_.spinLock.unlock();
+
+ delete oldInst;
}
std::shared_ptr<T> get() //=> return std::shared_ptr to let instance life time be handled by caller (MT usage!)
@@ -76,17 +83,29 @@ public:
pod_.spinLock.lock();
ZEN_ON_SCOPE_EXIT(pod_.spinLock.unlock());
- std::swap(pod_.inst, tmpInst);
+ if (!pod_.destroyed)
+ std::swap(pod_.inst, tmpInst);
+ else
+ assert(false);
}
delete tmpInst;
}
+ bool wasDestroyed()
+ {
+ pod_.spinLock.lock();
+ ZEN_ON_SCOPE_EXIT(pod_.spinLock.unlock());
+
+ return pod_.destroyed;
+ }
+
private:
struct Pod
{
PodSpinMutex spinLock; //rely entirely on static zero-initialization! => avoid potential contention with worker thread during Global<> construction!
//serialize access: can't use std::mutex: has non-trival destructor
std::shared_ptr<T>* inst = nullptr;
+ bool destroyed = false;
} pod_;
};
diff --git a/zen/http.cpp b/zen/http.cpp
index 7eb3fb76..e1a828c1 100644
--- a/zen/http.cpp
+++ b/zen/http.cpp
@@ -499,7 +499,7 @@ bool zen::isValidEmail(const std::string_view& email)
return false;
//---------------------------------------------------------------------
- //not going to parse and validate this!
+ //we're not going to parse and validate this!
const bool quoted = (startsWith(local, '"') && endsWith(local, '"')) ||
contains(local, '\\'); //e.g. "t\@st@email.com"
if (!quoted)
diff --git a/zen/resolve_path.cpp b/zen/resolve_path.cpp
index 8b81e184..daaf91ff 100644
--- a/zen/resolve_path.cpp
+++ b/zen/resolve_path.cpp
@@ -10,48 +10,15 @@
#include "file_access.h"
#include <zen/sys_info.h>
- // #include <stdlib.h> //getenv()
- #include <unistd.h> //getuid()
- #include <pwd.h> //getpwuid_r()
+ #include <unistd.h> //getcwd()
using namespace zen;
namespace
{
-std::optional<Zstring> getEnvironmentVar(const Zchar* name)
-{
- assert(runningOnMainThread()); //getenv() is not thread-safe!
-
- const char* buffer = ::getenv(name); //no ownership transfer + no extended error reporting
- if (!buffer)
- return {};
- Zstring value(buffer);
-
- //some postprocessing:
- trim(value); //remove leading, trailing blanks
-
- //remove leading, trailing double-quotes
- if (startsWith(value, Zstr('"')) &&
- endsWith (value, Zstr('"')) &&
- value.length() >= 2)
- value = Zstring(value.c_str() + 1, value.length() - 2);
-
- return value;
-}
-
-
Zstring resolveRelativePath(const Zstring& relativePath)
{
- assert(runningOnMainThread());
- /* MSDN: "Multithreaded applications and shared library code should not use the GetFullPathName function
- and should avoid using relative path names. The current directory state written by the
- SetCurrentDirectory function is stored as a global variable in each process,
- therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads, [...]"
-
- => Just plain wrong, there is no data corruption. What MSDN really means: GetFullPathName() is *perfectly* thread-safe, but depends
- on the current directory, which is a process-scope global: https://devblogs.microsoft.com/oldnewthing/20210816-00/?p=105562 */
-
if (relativePath.empty())
return relativePath;
@@ -99,7 +66,7 @@ Zstring resolveRelativePath(const Zstring& relativePath)
//returns value if resolved
-std::optional<Zstring> tryResolveMacro(const Zstring& macro) //macro without %-characters
+std::optional<Zstring> tryResolveMacro(const ZstringView macro) //macro without %-characters
{
Zstring timeStr;
auto resolveTimePhrase = [&](const Zchar* phrase, const Zchar* format) -> bool
@@ -142,7 +109,7 @@ std::optional<Zstring> tryResolveMacro(const Zstring& macro) //macro without %-c
}
//try to resolve as environment variables
- if (std::optional<Zstring> value = getEnvironmentVar(macro.c_str()))
+ if (std::optional<Zstring> value = getEnvironmentVar(macro))
return *value;
return {};
@@ -201,14 +168,14 @@ std::vector<Zstring> zen::getPathPhraseAliases(const Zstring& itemPath)
{
//environment variables: C:\Users\<user> -> %UserProfile%
- auto substByMacro = [&](const Zchar* macroName, const Zstring& macroPath)
+ auto substByMacro = [&](const ZstringView macroName, const Zstring& macroPath)
{
//should use a replaceCpy() that considers "local path" case-sensitivity (if only we had one...)
if (contains(itemPath, macroPath))
pathAliases.push_back(makePathPhrase(replaceCpyAsciiNoCase(itemPath, macroPath, Zstring() + MACRO_SEP + macroName + MACRO_SEP)));
};
- for (const Zchar* envName :
+ for (const ZstringView envName :
{
"HOME", //Linux: /home/<user> Mac: /Users/<user>
//"USER", -> any benefit?
diff --git a/zen/serialize.h b/zen/serialize.h
index a996b118..8ccecd53 100644
--- a/zen/serialize.h
+++ b/zen/serialize.h
@@ -256,7 +256,7 @@ BinContainer unbufferedLoad(Function tryRead /*(void* buffer, size_t bytesToRead
{
static_assert(sizeof(typename BinContainer::value_type) == 1); //expect: bytes
if (blockSize == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
BinContainer buf;
for (;;)
@@ -285,7 +285,7 @@ void unbufferedSave(const BinContainer& cont,
{
static_assert(sizeof(typename BinContainer::value_type) == 1); //expect: bytes
if (blockSize == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
const size_t bufPosEnd = cont.size();
size_t bufPos = 0;
@@ -311,7 +311,7 @@ void unbufferedStreamCopy(Function1 tryRead /*(void* buffer, size_t bytesToRead)
blockSizeOut = std::bit_ceil(blockSizeOut);
#endif
if (blockSizeIn == 0 || blockSizeOut == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
const size_t bufCapacity = blockSizeOut - 1 + blockSizeIn;
const size_t alignment = ::sysconf(_SC_PAGESIZE); //-1 on error => posix_memalign() will fail
diff --git a/zen/socket.h b/zen/socket.h
index df8b768b..4ccde190 100644
--- a/zen/socket.h
+++ b/zen/socket.h
@@ -143,7 +143,7 @@ namespace
size_t tryReadSocket(SocketType socket, void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF!
{
if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
int bytesReceived = 0;
for (;;)
@@ -168,7 +168,7 @@ size_t tryReadSocket(SocketType socket, void* buffer, size_t bytesToRead) //thro
size_t tryWriteSocket(SocketType socket, const void* buffer, size_t bytesToWrite) //throw SysError; may return short! CONTRACT: bytesToWrite > 0
{
if (bytesToWrite == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
int bytesWritten = 0;
for (;;)
diff --git a/zen/stream_buffer.h b/zen/stream_buffer.h
index ee9e18fd..64cb76ca 100644
--- a/zen/stream_buffer.h
+++ b/zen/stream_buffer.h
@@ -149,7 +149,7 @@ private:
size_t tryReadImpl(std::unique_lock<std::mutex>& ul, void* buffer, size_t bytesToRead) //throw <write error>; may return short; only 0 means EOF! CONTRACT: bytesToRead > 0!
{
if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
assert(isLocked(lockStream_));
assert(!errorRead_);
@@ -170,7 +170,7 @@ private:
size_t tryWriteWhileImpl(std::unique_lock<std::mutex>& ul, const void* buffer, size_t bytesToWrite) //throw <read error>; may return short! CONTRACT: bytesToWrite > 0
{
if (bytesToWrite == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
assert(isLocked(lockStream_));
assert(!eof_ && !errorWrite_);
diff --git a/zen/string_base.h b/zen/string_base.h
index 98544ab3..b19b4851 100644
--- a/zen/string_base.h
+++ b/zen/string_base.h
@@ -663,7 +663,20 @@ void Zbase<Char, SP>::pop_back()
template <class Char, template <class> class SP>
struct std::hash<zen::Zbase<Char, SP>>
{
- size_t operator()(const zen::Zbase<Char, SP>& str) const { return zen::hashString<size_t>(str); }
+ using is_transparent = int; //allow heterogenous lookup!
+
+ template <class String>
+ size_t operator()(const String& str) const { return zen::hashString<size_t>(str); }
+};
+
+
+template <class Char, template <class> class SP>
+struct std::equal_to<zen::Zbase<Char, SP>>
+{
+ using is_transparent = int; //enable heterogenous lookup!
+
+ template <class String1, class String2>
+ bool operator()(const String1& lhs, const String2& rhs) const { return zen::equalString(lhs, rhs); }
};
#endif //STRING_BASE_H_083217454562342526
diff --git a/zen/string_tools.h b/zen/string_tools.h
index ca086efd..03563d41 100644
--- a/zen/string_tools.h
+++ b/zen/string_tools.h
@@ -624,13 +624,13 @@ namespace impl
template <class Num> inline
int saferPrintf(char* buffer, size_t bufferSize, const char* format, const Num& number) //there is no such thing as a "safe" printf ;)
{
- return std::snprintf(buffer, bufferSize, format, number); //C99: returns number of chars written if successful, < 0 or >= bufferSize on failure
+ return std::snprintf(buffer, bufferSize, format, number); //C99: returns number of chars written if successful, < 0 or >= bufferSize on error
}
template <class Num> inline
int saferPrintf(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const Num& number)
{
- return std::swprintf(buffer, bufferSize, format, number); //C99: returns number of chars written if successful, < 0 on failure (including buffer too small)
+ return std::swprintf(buffer, bufferSize, format, number); //C99: returns number of chars written if successful, < 0 on error (including buffer too small)
}
}
diff --git a/zen/sys_error.cpp b/zen/sys_error.cpp
index fa7352f0..90d9ee2e 100644
--- a/zen/sys_error.cpp
+++ b/zen/sys_error.cpp
@@ -248,13 +248,13 @@ std::wstring zen::formatGlibError(const std::string& functionName, GError* error
std::wstring zen::getSystemErrorDescription(ErrorCode ec) //return empty string on error
{
- const ErrorCode currentError = getLastError(); //not necessarily == ec
- ZEN_ON_SCOPE_EXIT(errno = currentError);
+ const ErrorCode ecCurrent = getLastError(); //not necessarily == ec
+ ZEN_ON_SCOPE_EXIT(errno = ecCurrent);
- std::wstring errorMsg;
- errorMsg = utfTo<std::wstring>(::g_strerror(ec)); //... vs strerror(): "marginally improves thread safety, and marginally improves consistency"
+ std::wstring errorMsg = utfTo<std::wstring>(::g_strerror(ec)); //... vs strerror(): "marginally improves thread safety, and marginally improves consistency"
- return trimCpy(errorMsg); //Windows messages seem to end with a space...
+ trim(errorMsg); //Windows messages seem to end with a space...
+ return errorMsg;
}
diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp
index 244343f2..55465711 100644
--- a/zen/sys_info.cpp
+++ b/zen/sys_info.cpp
@@ -26,12 +26,12 @@ using namespace zen;
Zstring zen::getLoginUser() //throw FileError
{
- auto tryGetNonRootUser = [](const char* varName) -> const char*
+ auto tryGetNonRootUser = [](const char* varName) -> std::optional<Zstring>
{
- if (const char* buf = ::getenv(varName)) //no ownership transfer + no extended error reporting
- if (strLength(buf) > 0 && !equalString(buf, "root"))
- return buf;
- return nullptr;
+ if (const std::optional<Zstring> username = getEnvironmentVar(varName))
+ if (!username->empty() && *username != "root")
+ return *username;
+ return {};
};
if (const uid_t userIdNo = ::getuid(); //never fails
@@ -65,9 +65,9 @@ Zstring zen::getLoginUser() //throw FileError
//BUT: getlogin() can fail with ENOENT on Linux Mint: https://freefilesync.org/forum/viewtopic.php?t=8181
//getting a little desperate: variables used by installer.sh
- if (const char* username = tryGetNonRootUser("USER")) return username;
- if (const char* username = tryGetNonRootUser("SUDO_USER")) return username;
- if (const char* username = tryGetNonRootUser("LOGNAME")) return username;
+ if (const std::optional<Zstring> username = tryGetNonRootUser("USER")) return *username;
+ if (const std::optional<Zstring> username = tryGetNonRootUser("SUDO_USER")) return *username;
+ if (const std::optional<Zstring> username = tryGetNonRootUser("LOGNAME")) return *username;
//apparently the current user really IS root: https://freefilesync.org/forum/viewtopic.php?t=8405
@@ -221,8 +221,8 @@ Zstring zen::getUserHome() //throw FileError
/* https://linux.die.net/man/3/getpwuid: An application that wants to determine its user's home directory
should inspect the value of HOME (rather than the value getpwuid(getuid())->pw_dir) since this allows
the user to modify their notion of "the home directory" during a login session. */
- if (const char* homePath = ::getenv("HOME")) //no ownership transfer + no extended error reporting
- return homePath;
+ if (const std::optional<Zstring> homeDirPath = getEnvironmentVar("HOME"))
+ return *homeDirPath;
//root(0) => consider as request for elevation, NOT impersonation!
//=> "HOME=/root" :(
@@ -234,10 +234,10 @@ Zstring zen::getUserHome() //throw FileError
passwd buf2 = {};
passwd* pwEntry = nullptr;
if (const int rv = ::getpwnam_r(loginUser.c_str(), //const char *name
- &buf2, //struct passwd* pwd
- buf.data(), //char* buf
- buf.size(), //size_t buflen
- &pwEntry); //struct passwd** result
+ &buf2, //struct passwd* pwd
+ buf.data(), //char* buf
+ buf.size(), //size_t buflen
+ &pwEntry); //struct passwd** result
rv != 0 || !pwEntry)
{
//"If an error occurs, errno is set appropriately" => why the fuck, then also return errno as return value!?
@@ -252,9 +252,9 @@ Zstring zen::getUserHome() //throw FileError
Zstring zen::getUserDataPath() //throw FileError
{
if (::getuid() != 0) //nofail; non-root
- if (const char* xdgCfgPath = ::getenv("XDG_CONFIG_HOME"); //no ownership transfer + no extended error reporting
- xdgCfgPath && xdgCfgPath[0] != 0)
- return xdgCfgPath;
+ if (const std::optional<Zstring> xdgCfgPath = getEnvironmentVar("XDG_CONFIG_HOME");
+ xdgCfgPath&& !xdgCfgPath->empty())
+ return *xdgCfgPath;
//root(0) => consider as request for elevation, NOT impersonation
return appendPath(getUserHome(), ".config"); //throw FileError
diff --git a/zen/thread.h b/zen/thread.h
index 25a6463a..2464f8be 100644
--- a/zen/thread.h
+++ b/zen/thread.h
@@ -153,7 +153,7 @@ class ThreadGroup
{
public:
ThreadGroup(size_t threadCountMax, const Zstring& groupName) : threadCountMax_(threadCountMax), groupName_(groupName)
- { if (threadCountMax == 0) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); }
+ { if (threadCountMax == 0) throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!"); }
ThreadGroup (ThreadGroup&& tmp) noexcept = default; //noexcept *required* to support move for reallocations in std::vector and std::swap!!!
ThreadGroup& operator=(ThreadGroup&& tmp) noexcept = default; //don't use swap() but end worker_ life time immediately
diff --git a/zen/time.h b/zen/time.h
index 6ca3be3b..ee43566b 100644
--- a/zen/time.h
+++ b/zen/time.h
@@ -41,7 +41,7 @@ TimeComp getCompileTime(); //returns TimeComp() on error
formatTime(Zstr("%Y|%m|%d")); -> "2011|10|29"
formatTime(formatDateTag); -> "2011-10-29"
formatTime(formatTimeTag); -> "17:55:34" */
-Zstring formatTime(const Zchar* format, const TimeComp& tc = getLocalTime()); //format as specified by "std::strftime", returns empty string on failure
+Zstring formatTime(const Zchar* format, const TimeComp& tc = getLocalTime()); //format as specified by "std::strftime", returns empty string on error
//the "format" parameter of formatTime() is partially specialized with the following type tags:
const Zchar* const formatDateTag = Zstr("%x"); //locale-dependent date representation: e.g. 8/23/2001
@@ -59,7 +59,8 @@ template <class String, class String2>
TimeComp parseTime(const String& format, const String2& str); //similar to ::strptime()
//----------------------------------------------------------------------------------------------------------------------------------
-
+//format: [-][[d.]HH:]MM:SS e.g. -1.23:45:67
+Zstring formatTimeSpan(int64_t timeInSec, bool hourOptional = false);
@@ -385,6 +386,36 @@ TimeComp parseTime(const String& format, const String2& str)
return output;
}
+
+
+inline
+Zstring formatTimeSpan(int64_t timeInSec, bool hourOptional)
+{
+ Zstring timespanStr;
+
+ if (timeInSec < 0)
+ {
+ timeInSec = -timeInSec; //need to fix LLONG_MIN?
+ timespanStr = Zstr('-');
+ }
+
+ //check *before* subtracting days!
+ const Zchar* timeSpanFmt = hourOptional && timeInSec < 3600 ? Zstr("%M:%S") : formatIsoTimeTag;
+
+ const int secsPerDay = 24 * 3600;
+ const int64_t days = numeric::intDivFloor(timeInSec, secsPerDay);
+ if (days > 0)
+ {
+ timeInSec -= days * secsPerDay;
+ timespanStr += numberTo<Zstring>(days) + Zstr("."); //don't need zen::formatNumber(), do we?
+ }
+
+ //format time span as if absolute UTC time
+ const TimeComp& tc = getUtcTime(timeInSec); //returns TimeComp() on error
+ timespanStr += formatTime(timeSpanFmt, tc); //returns empty string on error
+
+ return timespanStr;
+}
}
#endif //TIME_H_8457092814324342453627
diff --git a/zen/utf.h b/zen/utf.h
index 56b1ff55..6f7c39cc 100644
--- a/zen/utf.h
+++ b/zen/utf.h
@@ -16,7 +16,7 @@ namespace zen
template <class TargetString, class SourceString>
TargetString utfTo(const SourceString& str);
-const char BYTE_ORDER_MARK_UTF8[] = "\xEF\xBB\xBF";
+const std::string_view BYTE_ORDER_MARK_UTF8 = "\xEF\xBB\xBF";
template <class UtfString>
bool isValidUtf(const UtfString& str); //check for UTF-8 encoding errors
diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp
index 28b85c5c..7e680131 100644
--- a/zen/zlib_wrap.cpp
+++ b/zen/zlib_wrap.cpp
@@ -6,7 +6,7 @@
#include "zlib_wrap.h"
//Windows: use the SAME zlib version that wxWidgets is linking against! //C:\Data\Projects\wxWidgets\Source\src\zlib\zlib.h
-//Linux/macOS: use zlib system header for both wxWidgets and libcurl (zlib is required for HTTP, SFTP)
+//Linux/macOS: use zlib system header for wxWidgets, libcurl (HTTP), libssh2 (SFTP)
// => don't compile wxWidgets with: --with-zlib=builtin
#include <zlib.h>
#include "scope_guard.h"
@@ -178,7 +178,7 @@ public:
size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream!
{
if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
gzipStream_.next_out = static_cast<Bytef*>(buffer);
gzipStream_.avail_out = static_cast<uInt>(bytesToRead);
bgstack15