diff options
author | B Stack <bgstack15@gmail.com> | 2020-08-31 20:07:13 -0400 |
---|---|---|
committer | B Stack <bgstack15@gmail.com> | 2020-08-31 20:07:13 -0400 |
commit | 8a27fa9c617533e76673ce61a65e2ba869b52208 (patch) | |
tree | acfdfb3e1046db87040477033fda0df76d92916a | |
parent | Merge branch '11.0' into 'master' (diff) | |
download | FreeFileSync-8a27fa9c617533e76673ce61a65e2ba869b52208.tar.gz FreeFileSync-8a27fa9c617533e76673ce61a65e2ba869b52208.tar.bz2 FreeFileSync-8a27fa9c617533e76673ce61a65e2ba869b52208.zip |
add upstream 11.1
157 files changed, 4701 insertions, 4455 deletions
@@ -4,9 +4,9 @@ that affect FreeFileSync. Therefore it is NOT RECOMMENDED TO COMPILE AGAINST OLD the ones mentioned below. The remaining issues that are yet to be fixed are listed in the following: ------------------ -| libcurl 7.6.9 | ------------------ +---------------- +| libcurl 7.72 | +---------------- __________________________________________________________________________________________________________ /lib/ftp.c https://github.com/curl/curl/issues/1455 @@ -208,9 +208,20 @@ ________________________________________________________________________________ ------------------- -| wxWidgets 3.1.3 | +| wxWidgets 3.1.4 | ------------------- __________________________________________________________________________________________________________ +/include/wx/window.h +wxWidgets/GTK2 on some Linux systems incorrectly detects high DPI: https://freefilesync.org/forum/viewtopic.php?t=6114 +=> hack away high-DPI support for GTK2 (= pretend GTK2 has device independent pixels, which it clearly has not!) + + #include "wx/gtk/window.h" +- #ifdef __WXGTK3__ ++ //#ifdef __WXGTK3__ + #define wxHAVE_DPI_INDEPENDENT_PIXELS +- #endif ++ //#endif +__________________________________________________________________________________________________________ /src/aui/framemanager.cpp: Fix incorrect pane height calculations: @@ -300,14 +311,3 @@ Backspace not working in filter dialog: http://www.freefilesync.org/forum/viewto G_CALLBACK (gtk_window_key_press_callback), this); __________________________________________________________________________________________________________ -/include/wx/window.h -wxWidgets/GTK2 on some Linux systems incorrectly detects high DPI: https://freefilesync.org/forum/viewtopic.php?t=6114 -=> hack away high-DPI support for GTK2 (= pretend GTK2 has device independent pixels, which it clearly has not!) - - #include "wx/gtk/window.h" -- #ifdef __WXGTK3__ -+ //#ifdef __WXGTK3__ - #define wxHAVE_DPI_INDEPENDENT_PIXELS -- #endif -+ //#endif -__________________________________________________________________________________________________________ diff --git a/Changelog.txt b/Changelog.txt index 6591c551..e7c9cd75 100755 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,5 +1,25 @@ -FreeFileSync 11.1 ------------------ +FreeFileSync 11.1 [2020-08-31] +------------------------------ +New file group layout on main grid (reloaded) +Alternate colors for main grid folder groups +Added file group context menu +Quick selection of items in folder group +Fixed FTP access errors with Explicit SSL/TLS +Fixed Google Drive error when double quotes in file name +Fixed RTL layout bug with number input control +Fixed grid column default sizes +Fixed grid rendering performance during mouse scrolling +Update all config files transactionally +Respect user-preferred number/time format (Linux) +Fixed floating panels not being resizable (Linux) +Instantly open selection context menu on right mouse button down +Further improved high DPI support +Updated deprecated system API calls (requires macOS 10.10 or later) +Fixed crash when accessing Nexis storage (macOS) +Avoid buffer flush when aborting native file output +Clear preview after folder history selection +Pre-allocate target file without setting size +Unified system error message formatting FreeFileSync 11.0 [2020-07-21] diff --git a/FreeFileSync/Build/Resources/Gtk3Styles.css b/FreeFileSync/Build/Resources/Gtk3Styles.css index 6ebcf4c3..027e7bc3 100755 --- 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) +/* 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 */ * { diff --git a/FreeFileSync/Build/Resources/Gtk3Styles.old.css b/FreeFileSync/Build/Resources/Gtk3Styles.old.css index 24c559a6..a2c43ce7 100755 --- 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) +/* CSS format as required by Debian (GTK 3.14.5) 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 */ * { @@ -14,8 +13,6 @@ GtkButton { padding: 2px; /*remove excessive inner border from bitmap buttons*/ - /* - 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; - */ + /* 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; */ } diff --git a/FreeFileSync/Build/Resources/Icons.zip b/FreeFileSync/Build/Resources/Icons.zip Binary files differindex 9f81e6ec..79ea7657 100644 --- a/FreeFileSync/Build/Resources/Icons.zip +++ b/FreeFileSync/Build/Resources/Icons.zip diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip Binary files differindex a84ddb83..895ef3c0 100644 --- a/FreeFileSync/Build/Resources/Languages.zip +++ b/FreeFileSync/Build/Resources/Languages.zip diff --git a/FreeFileSync/Build/Resources/cacert.pem b/FreeFileSync/Build/Resources/cacert.pem index 651694e8..f9bd706b 100755 --- a/FreeFileSync/Build/Resources/cacert.pem +++ b/FreeFileSync/Build/Resources/cacert.pem @@ -1,7 +1,7 @@ ## ## Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Wed Jan 1 04:12:10 2020 GMT +## Certificate data from Mozilla as of: Wed Jul 22 03:12:14 2020 GMT ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates @@ -13,8 +13,8 @@ ## an Apache+mod_ssl webserver for SSL client authentication. ## Just configure this file as the SSLCACertificateFile. ## -## Conversion done with mk-ca-bundle.pl version 1.27. -## SHA256: f3bdcd74612952da8476a9d4147f50b29ad0710b7dd95b4c8690500209986d70 +## Conversion done with mk-ca-bundle.pl version 1.28. +## SHA256: cc6408bd4be7fbfb8699bdb40ccb7f6de5780d681d87785ea362646e4dad5e8e ## @@ -61,30 +61,6 @@ BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- -Verisign Class 3 Public Primary Certification Authority - G3 -============================================================ ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV -UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv -cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy -dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv -cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1 -EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc -cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw -EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj -055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA -ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f -j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0 -xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa -t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- - Entrust.net Premium 2048 Secure Server CA ========================================= -----BEGIN CERTIFICATE----- @@ -130,30 +106,6 @@ Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- -AddTrust External Root -====================== ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML -QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD -VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw -NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU -cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg -Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821 -+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw -Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo -aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy -2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7 -7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL -VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk -VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB -IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl -j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355 -e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u -G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- - Entrust Root Certification Authority ==================================== -----BEGIN CERTIFICATE----- @@ -1126,38 +1078,6 @@ NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- -Staat der Nederlanden Root CA - G2 -================================== ------BEGIN CERTIFICATE----- -MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE -CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g -Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC -TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l -ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ -5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn -vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj -CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil -e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR -OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI -CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65 -48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi -trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737 -qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB -AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC -ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA -A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz -+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj -f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN -kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk -CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF -URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb -CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h -oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV -IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm -66+KAQ== ------END CERTIFICATE----- - Hongkong Post Root CA 1 ======================= -----BEGIN CERTIFICATE----- @@ -2831,37 +2751,6 @@ MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== -----END CERTIFICATE----- -LuxTrust Global Root 2 -====================== ------BEGIN CERTIFICATE----- -MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQELBQAwRjELMAkG -A1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNVBAMMFkx1eFRydXN0IEdsb2Jh -bCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUwMzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEW -MBQGA1UECgwNTHV4VHJ1c3QgUy5BLjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCC -AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wm -Kb3FibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTemhfY7RBi2 -xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1EMShduxq3sVs35a0VkBC -wGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsnXpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm -1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkm -FRseTJIpgp7VkoGSQXAZ96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niF -wpN6cj5mj5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4gDEa/ -a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+8kPREd8vZS9kzl8U -ubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2jX5t/Lax5Gw5CMZdjpPuKadUiDTSQ -MC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmHhFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB -/zBCBgNVHSAEOzA5MDcGByuBKwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5 -Lmx1eHRydXN0Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT -+Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQELBQADggIBAGoZ -FO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9BzZAcg4atmpZ1gDlaCDdLnIN -H2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTOjFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW -7MM3LGVYvlcAGvI1+ut7MV3CwRI9loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIu -ZY+kt9J/Z93I055cqqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWA -VWe+2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/JEAdemrR -TxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKrezrnK+T+Tb/mjuuqlPpmt -/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQfLSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc -7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31I -iyBMz2TWuJdGsE7RKlY6oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr ------END CERTIFICATE----- - TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 ============================================= -----BEGIN CERTIFICATE----- @@ -3464,3 +3353,95 @@ JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G +TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT kcpG2om3PVODLAgfi49T3f+sHw== -----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile index d76274bc..caa599c1 100755 --- a/FreeFileSync/Source/Makefile +++ b/FreeFileSync/Source/Makefile @@ -92,7 +92,7 @@ cppFiles+=../../zen/process_priority.cpp cppFiles+=../../zen/shell_execute.cpp cppFiles+=../../zen/shutdown.cpp cppFiles+=../../zen/sys_error.cpp -cppFiles+=../../zen/system.cpp +cppFiles+=../../zen/sys_info.cpp cppFiles+=../../zen/thread.cpp cppFiles+=../../zen/zlib_wrap.cpp cppFiles+=../../wx+/file_drop.cpp diff --git a/FreeFileSync/Source/RealTimeSync/Makefile b/FreeFileSync/Source/RealTimeSync/Makefile index f2ea409d..3670af2e 100755 --- a/FreeFileSync/Source/RealTimeSync/Makefile +++ b/FreeFileSync/Source/RealTimeSync/Makefile @@ -25,6 +25,13 @@ cppFiles+=../base/resolve_path.cpp cppFiles+=../ffs_paths.cpp cppFiles+=../icon_buffer.cpp cppFiles+=../localization.cpp +cppFiles+=../../../wx+/file_drop.cpp +cppFiles+=../../../wx+/image_tools.cpp +cppFiles+=../../../wx+/image_resources.cpp +cppFiles+=../../../wx+/popup_dlg.cpp +cppFiles+=../../../wx+/popup_dlg_generated.cpp +cppFiles+=../../../wx+/taskbar.cpp +cppFiles+=../../../xBRZ/src/xbrz.cpp cppFiles+=../../../zen/dir_watcher.cpp cppFiles+=../../../zen/file_access.cpp cppFiles+=../../../zen/file_io.cpp @@ -34,15 +41,10 @@ cppFiles+=../../../zen/legacy_compiler.cpp cppFiles+=../../../zen/shell_execute.cpp cppFiles+=../../../zen/shutdown.cpp cppFiles+=../../../zen/sys_error.cpp +cppFiles+=../../../zen/sys_info.cpp +cppFiles+=../../../zen/sys_version.cpp cppFiles+=../../../zen/thread.cpp cppFiles+=../../../zen/zstring.cpp -cppFiles+=../../../wx+/file_drop.cpp -cppFiles+=../../../wx+/image_tools.cpp -cppFiles+=../../../wx+/image_resources.cpp -cppFiles+=../../../wx+/popup_dlg.cpp -cppFiles+=../../../wx+/popup_dlg_generated.cpp -cppFiles+=../../../wx+/taskbar.cpp -cppFiles+=../../../xBRZ/src/xbrz.cpp tmpPath = $(TMPDIR)/$(exeName)_Make diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp index 2804838c..7e408979 100644 --- a/FreeFileSync/Source/RealTimeSync/application.cpp +++ b/FreeFileSync/Source/RealTimeSync/application.cpp @@ -33,7 +33,7 @@ IMPLEMENT_APP(Application) namespace { -const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); +wxDEFINE_EVENT(EVENT_ENTER_EVENT_LOOP, wxCommandEvent); } @@ -65,9 +65,7 @@ bool Application::OnInit() (fff::getResourceDirPf() + fileName).c_str(), //const gchar* path, &error); //GError** error if (error) - throw SysError(formatSystemError("gtk_css_provider_load_from_path", - replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)), - utfTo<std::wstring>(error->message))); + throw SysError(formatGlibError("gtk_css_provider_load_from_path", error)); ::gtk_style_context_add_provider_for_screen(::gdk_screen_get_default(), //GdkScreen* screen, GTK_STYLE_PROVIDER(provider), //GtkStyleProvider* provider, @@ -93,7 +91,7 @@ bool Application::OnInit() //Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise: wxToolTip::Enable(true); //yawn, a wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it - wxToolTip::SetAutoPop(10000); //https://docs.microsoft.com/en-us/windows/win32/uxguide/ctrl-tooltips-and-infotips + wxToolTip::SetAutoPop(10'000); //https://docs.microsoft.com/en-us/windows/win32/uxguide/ctrl-tooltips-and-infotips SetAppName(L"RealTimeSync"); @@ -109,11 +107,11 @@ bool Application::OnInit() } - Connect(wxEVT_QUERY_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); - Connect(wxEVT_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); + Bind(wxEVT_QUERY_END_SESSION, [this](wxCloseEvent& event) { onQueryEndSession(event); }); //can veto + Bind(wxEVT_END_SESSION, [this](wxCloseEvent& event) { onQueryEndSession(event); }); //can *not* veto //Note: app start is deferred: -> see FreeFileSync - Connect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); + Bind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this); wxCommandEvent scrollEvent(EVENT_ENTER_EVENT_LOOP); AddPendingEvent(scrollEvent); return true; //true: continue processing; false: exit immediately. @@ -133,7 +131,8 @@ wxLayoutDirection Application::GetLayoutDirection() const { return fff::getLayou void Application::onEnterEventLoop(wxEvent& event) { - Disconnect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); + [[maybe_unused]] bool ubOk = Unbind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this); + assert(ubOk); //try to set config/batch- filepath set by %1 parameter std::vector<Zstring> commandArgs; @@ -175,7 +174,7 @@ void Application::OnUnhandledException() //handles both wxApp::OnInit() + wxApp: { try { - throw; //just re-throw and avoid display of additional messagebox + throw; //just re-throw } catch (const std::bad_alloc& e) //the only kind of exception we don't want crash dumps for { diff --git a/FreeFileSync/Source/RealTimeSync/config.cpp b/FreeFileSync/Source/RealTimeSync/config.cpp index 2454b941..478f67f8 100644 --- a/FreeFileSync/Source/RealTimeSync/config.cpp +++ b/FreeFileSync/Source/RealTimeSync/config.cpp @@ -72,7 +72,7 @@ void readConfig(const XmlIn& in, XmlRealConfig& cfg, int formatVer) if (formatVer < 2) if (startsWithAsciiNoCase(cfg.commandline, "cmd /c ") || startsWithAsciiNoCase(cfg.commandline, "cmd.exe /c ")) - cfg.commandline = afterFirst(cfg.commandline, Zstr("/c "), IF_MISSING_RETURN_ALL); + cfg.commandline = afterFirst(cfg.commandline, Zstr("/c "), IfNotFoundReturn::all); } diff --git a/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp b/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp index af93cea2..866f273e 100644 --- a/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp +++ b/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp @@ -59,22 +59,23 @@ FolderSelector2::FolderSelector2(wxWindow* parent, //prepare drag & drop setupFileDrop(dropWindow_); - dropWindow_.Connect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector2::onFilesDropped), nullptr, this); + dropWindow_.Bind(EVENT_DROP_FILE, &FolderSelector2::onFilesDropped, this); //keep dirPicker and dirpath synchronous - folderPathCtrl_.Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector2::onMouseWheel ), nullptr, this); - folderPathCtrl_.Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector2::onEditFolderPath), nullptr, this); - selectButton_ .Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector2::onSelectDir ), nullptr, this); + folderPathCtrl_.Bind(wxEVT_MOUSEWHEEL, &FolderSelector2::onMouseWheel, this); + folderPathCtrl_.Bind(wxEVT_COMMAND_TEXT_UPDATED, &FolderSelector2::onEditFolderPath, this); + selectButton_ .Bind(wxEVT_COMMAND_BUTTON_CLICKED, &FolderSelector2::onSelectDir, this); } FolderSelector2::~FolderSelector2() { - dropWindow_.Disconnect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector2::onFilesDropped), nullptr, this); + [[maybe_unused]] bool ubOk1 = dropWindow_.Unbind(EVENT_DROP_FILE, &FolderSelector2::onFilesDropped, this); - folderPathCtrl_.Disconnect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector2::onMouseWheel ), nullptr, this); - folderPathCtrl_.Disconnect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector2::onEditFolderPath), nullptr, this); - selectButton_ .Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector2::onSelectDir ), nullptr, this); + [[maybe_unused]] bool ubOk2 = folderPathCtrl_.Unbind(wxEVT_MOUSEWHEEL, &FolderSelector2::onMouseWheel, this); + [[maybe_unused]] bool ubOk3 = folderPathCtrl_.Unbind(wxEVT_COMMAND_TEXT_UPDATED, &FolderSelector2::onEditFolderPath, this); + [[maybe_unused]] bool ubOk4 = selectButton_ .Unbind(wxEVT_COMMAND_BUTTON_CLICKED, &FolderSelector2::onSelectDir, this); + assert(ubOk1 && ubOk2 && ubOk3 && ubOk4); } @@ -98,11 +99,10 @@ void FolderSelector2::onMouseWheel(wxMouseEvent& event) void FolderSelector2::onFilesDropped(FileDropEvent& event) { - const auto& itemPaths = event.getPaths(); - if (itemPaths.empty()) + if (event.itemPaths_.empty()) return; - Zstring itemPath = itemPaths[0]; + Zstring itemPath = event.itemPaths_[0]; try { if (getItemType(itemPath) == ItemType::file) //throw FileError @@ -145,7 +145,7 @@ void FolderSelector2::onSelectDir(wxCommandEvent& event) } Zstring newFolderPath; - wxDirDialog dirPicker(parent_, _("Select a folder"), utfTo<wxString>(defaultFolderPath)); //put modal wxWidgets dialogs on stack: creating on freestore leads to memleak! + wxDirDialog dirPicker(parent_, _("Select a folder"), utfTo<wxString>(defaultFolderPath), wxDD_DEFAULT_STYLE | wxDD_SHOW_HIDDEN); if (dirPicker.ShowModal() != wxID_OK) return; newFolderPath = utfTo<Zstring>(dirPicker.GetPath()); diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp index ee458580..4e0e24bd 100644 --- a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp +++ b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp @@ -293,17 +293,17 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MainDlgGenerated::OnClose ) ); - m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnConfigNew ), this, m_menuItem6->GetId()); - m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnConfigLoad ), this, m_menuItem13->GetId()); - m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnConfigSave ), this, m_menuItem14->GetId()); - m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnMenuQuit ), this, m_menuItemQuit->GetId()); - m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnShowHelp ), this, m_menuItemContent->GetId()); - m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::OnMenuAbout ), this, m_menuItemAbout->GetId()); - m_hyperlink243->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( MainDlgGenerated::OnHelpRealTimeSync ), NULL, this ); - m_bpButtonAddFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnAddFolder ), NULL, this ); - m_bpButtonRemoveTopFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnRemoveTopFolder ), NULL, this ); - m_buttonStart->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::OnStart ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MainDlgGenerated::onClose ) ); + m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::onConfigNew ), this, m_menuItem6->GetId()); + m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::onConfigLoad ), this, m_menuItem13->GetId()); + m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::onConfigSave ), this, m_menuItem14->GetId()); + m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::onMenuQuit ), this, m_menuItemQuit->GetId()); + m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::onShowHelp ), this, m_menuItemContent->GetId()); + m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDlgGenerated::onMenuAbout ), this, m_menuItemAbout->GetId()); + m_hyperlink243->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( MainDlgGenerated::onHelpRealTimeSync ), NULL, this ); + m_bpButtonAddFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::onAddFolder ), NULL, this ); + m_bpButtonRemoveTopFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::onRemoveTopFolder ), NULL, this ); + m_buttonStart->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDlgGenerated::onStart ), NULL, this ); } MainDlgGenerated::~MainDlgGenerated() diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.h b/FreeFileSync/Source/RealTimeSync/gui_generated.h index 5ecd49d3..51b92712 100644 --- a/FreeFileSync/Source/RealTimeSync/gui_generated.h +++ b/FreeFileSync/Source/RealTimeSync/gui_generated.h @@ -88,17 +88,17 @@ protected: zen::BitmapTextButton* m_buttonStart; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnConfigNew( wxCommandEvent& event ) { event.Skip(); } - virtual void OnConfigLoad( wxCommandEvent& event ) { event.Skip(); } - virtual void OnConfigSave( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuQuit( wxCommandEvent& event ) { event.Skip(); } - virtual void OnShowHelp( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuAbout( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpRealTimeSync( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnAddFolder( wxCommandEvent& event ) { event.Skip(); } - virtual void OnRemoveTopFolder( wxCommandEvent& event ) { event.Skip(); } - virtual void OnStart( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onConfigNew( wxCommandEvent& event ) { event.Skip(); } + virtual void onConfigLoad( wxCommandEvent& event ) { event.Skip(); } + virtual void onConfigSave( wxCommandEvent& event ) { event.Skip(); } + virtual void onMenuQuit( wxCommandEvent& event ) { event.Skip(); } + virtual void onShowHelp( wxCommandEvent& event ) { event.Skip(); } + virtual void onMenuAbout( wxCommandEvent& event ) { event.Skip(); } + virtual void onHelpRealTimeSync( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onAddFolder( wxCommandEvent& event ) { event.Skip(); } + virtual void onRemoveTopFolder( wxCommandEvent& event ) { event.Skip(); } + virtual void onStart( wxCommandEvent& event ) { event.Skip(); } public: diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index 4101adc6..a65dbb10 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -35,8 +35,8 @@ namespace std::wstring extractJobName(const Zstring& cfgFilePath) { - const Zstring fileName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); - const Zstring jobName = beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_ALL); + const Zstring fileName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + const Zstring jobName = beforeLast(fileName, Zstr('.'), IfNotFoundReturn::all); return utfTo<std::wstring>(jobName); } @@ -91,8 +91,7 @@ MainDialog::MainDialog(const Zstring& cfgFileName) : m_bpButtonRemoveTopFolder->SetBitmapLabel(loadImage("item_remove")); setBitmapTextLabel(*m_buttonStart, loadImage("startRts"), m_buttonStart->GetLabel(), fastFromDIP(5), fastFromDIP(8)); - //register key event - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::OnKeyPressed), nullptr, this); + Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //prepare drag & drop @@ -138,7 +137,7 @@ MainDialog::MainDialog(const Zstring& cfgFileName) : if (startWatchingImmediately) //start watch mode directly { wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); - this->OnStart(dummy2); + this->onStart(dummy2); //don't Show()! } else @@ -149,7 +148,7 @@ MainDialog::MainDialog(const Zstring& cfgFileName) : //drag and drop .ffs_real and .ffs_batch on main dialog setupFileDrop(*this); - Connect(EVENT_DROP_FILE, FileDropEventHandler(MainDialog::onFilesDropped), nullptr, this); + Bind(EVENT_DROP_FILE, [this](FileDropEvent& event) { onFilesDropped(event); }); } @@ -177,13 +176,13 @@ void MainDialog::onQueryEndSession() -void MainDialog::OnShowHelp(wxCommandEvent& event) +void MainDialog::onShowHelp(wxCommandEvent& event) { fff::displayHelpEntry(L"realtimesync", this); } -void MainDialog::OnMenuAbout(wxCommandEvent& event) +void MainDialog::onMenuAbout(wxCommandEvent& event) { wxString build = utfTo<wxString>(fff::ffsVersion); #ifndef wxUSE_UNICODE @@ -194,6 +193,9 @@ void MainDialog::OnMenuAbout(wxCommandEvent& event) build += SPACED_BULLET; build += LTR_MARK; //fix Arabic +#ifndef ZEN_BUILD_ARCH +#error include <zen/build_info.h> +#endif #if ZEN_BUILD_ARCH == ZEN_ARCH_32BIT build += L"32 Bit"; #else @@ -209,7 +211,7 @@ void MainDialog::OnMenuAbout(wxCommandEvent& event) } -void MainDialog::OnKeyPressed(wxKeyEvent& event) +void MainDialog::onLocalKeyEvent(wxKeyEvent& event) { const int keyCode = event.GetKeyCode(); if (keyCode == WXK_ESCAPE) @@ -221,7 +223,7 @@ void MainDialog::OnKeyPressed(wxKeyEvent& event) } -void MainDialog::OnStart(wxCommandEvent& event) +void MainDialog::onStart(wxCommandEvent& event) { Hide(); @@ -243,14 +245,14 @@ void MainDialog::OnStart(wxCommandEvent& event) } -void MainDialog::OnConfigSave(wxCommandEvent& event) +void MainDialog::onConfigSave(wxCommandEvent& event) { const Zstring defaultFilePath = !activeConfigFile_.empty() && !equalNativePath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstr("Realtime.ffs_real"); - auto defaultFolder = utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); - auto defaultFileName = utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)); + auto defaultFolder = utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); + auto defaultFileName = utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all)); //attention: currentConfigFileName may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config! - defaultFileName = beforeLast(defaultFileName, L'.', IF_MISSING_RETURN_ALL) + L".ffs_real"; + defaultFileName = beforeLast(defaultFileName, L'.', IfNotFoundReturn::all) + L".ffs_real"; wxFileDialog filePicker(this, wxString(), //message @@ -309,24 +311,18 @@ void MainDialog::setLastUsedConfig(const Zstring& filepath) if (!activeCfgFilePath.empty()) SetTitle(utfTo<wxString>(activeCfgFilePath)); else - SetTitle(wxString(L"RealTimeSync ") + fff::ffsVersion + SPACED_DASH + _("Automated Synchronization")); - -} + SetTitle(L"RealTimeSync " + utfTo<std::wstring>(fff::ffsVersion) + SPACED_DASH + _("Automated Synchronization")); - -void MainDialog::OnConfigNew(wxCommandEvent& event) -{ - loadConfig({}); } -void MainDialog::OnConfigLoad(wxCommandEvent& event) +void MainDialog::onConfigLoad(wxCommandEvent& event) { const Zstring activeCfgFilePath = !equalNativePath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstring(); wxFileDialog filePicker(this, wxString(), //message - utfTo<wxString>(beforeLast(activeCfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)), //default folder + utfTo<wxString>(beforeLast(activeCfgFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)), //default folder wxString(), //default file name wxString(L"RealTimeSync (*.ffs_real; *.ffs_batch)|*.ffs_real;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", wxFD_OPEN); @@ -337,9 +333,8 @@ void MainDialog::OnConfigLoad(wxCommandEvent& event) void MainDialog::onFilesDropped(FileDropEvent& event) { - const auto& filePaths = event.getPaths(); - if (!filePaths.empty()) - loadConfig(filePaths[0]); + if (!event.itemPaths_.empty()) + loadConfig(event.itemPaths_[0]); } @@ -378,7 +373,7 @@ XmlRealConfig MainDialog::getConfiguration() } -void MainDialog::OnAddFolder(wxCommandEvent& event) +void MainDialog::onAddFolder(wxCommandEvent& event) { const Zstring topFolder = firstFolderPanel_->getPath(); @@ -390,7 +385,7 @@ void MainDialog::OnAddFolder(wxCommandEvent& event) } -void MainDialog::OnRemoveFolder(wxCommandEvent& event) +void MainDialog::onRemoveFolder(wxCommandEvent& event) { //find folder pair originating the event @@ -404,7 +399,7 @@ void MainDialog::OnRemoveFolder(wxCommandEvent& event) } -void MainDialog::OnRemoveTopFolder(wxCommandEvent& event) +void MainDialog::onRemoveTopFolder(wxCommandEvent& event) { if (!additionalFolderPanels_.empty()) @@ -429,7 +424,7 @@ void MainDialog::insertAddFolder(const std::vector<Zstring>& newFolders, size_t additionalFolderPanels_.insert(additionalFolderPanels_.begin() + pos + i, newFolder); //register events - newFolder->m_bpButtonRemoveFolder->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolder), nullptr, this ); + newFolder->m_bpButtonRemoveFolder->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onRemoveFolder(event); }); //make sure panel has proper default height newFolder->GetSizer()->SetSizeHints(newFolder); //~=Fit() + SetMinSize() diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.h b/FreeFileSync/Source/RealTimeSync/main_dlg.h index 39052f93..bfe500c1 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.h +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.h @@ -36,19 +36,19 @@ private: void loadConfig(const Zstring& filepath); - void OnClose (wxCloseEvent& event ) override { Destroy(); } - void OnShowHelp (wxCommandEvent& event) override; - void OnHelpRealTimeSync(wxHyperlinkEvent& event) override { OnShowHelp(event); } - void OnMenuAbout (wxCommandEvent& event) override; - void OnAddFolder (wxCommandEvent& event) override; - void OnRemoveFolder (wxCommandEvent& event); - void OnRemoveTopFolder(wxCommandEvent& event) override; - void OnKeyPressed (wxKeyEvent& event); - void OnStart (wxCommandEvent& event) override; - void OnConfigNew (wxCommandEvent& event) override; - void OnConfigSave (wxCommandEvent& event) override; - void OnConfigLoad (wxCommandEvent& event) override; - void OnMenuQuit (wxCommandEvent& event) override { Close(); } + void onClose (wxCloseEvent& event ) override { Destroy(); } + void onShowHelp (wxCommandEvent& event) override; + void onHelpRealTimeSync(wxHyperlinkEvent& event) override { onShowHelp(event); } + void onMenuAbout (wxCommandEvent& event) override; + void onAddFolder (wxCommandEvent& event) override; + void onRemoveFolder (wxCommandEvent& event); + void onRemoveTopFolder(wxCommandEvent& event) override; + void onLocalKeyEvent (wxKeyEvent& event); + void onStart (wxCommandEvent& event) override; + void onConfigNew (wxCommandEvent& event) override { loadConfig({}); } + void onConfigSave (wxCommandEvent& event) override; + void onConfigLoad (wxCommandEvent& event) override; + void onMenuQuit (wxCommandEvent& event) override { Close(); } void onFilesDropped(zen::FileDropEvent& event); void setConfiguration(const XmlRealConfig& cfg); diff --git a/FreeFileSync/Source/RealTimeSync/monitor.cpp b/FreeFileSync/Source/RealTimeSync/monitor.cpp index b8d7650e..7bd4b662 100644 --- a/FreeFileSync/Source/RealTimeSync/monitor.cpp +++ b/FreeFileSync/Source/RealTimeSync/monitor.cpp @@ -56,7 +56,7 @@ std::set<Zstring, LessNativePath> waitForMissingDirs(const std::vector<Zstring>& { std::future<bool>& folderAvailable = folderInfo.folderAvailable; - while (folderAvailable.wait_for(cbInterval) != std::future_status::ready) + while (folderAvailable.wait_for(cbInterval) == std::future_status::timeout) requestUiUpdate(folderPath); //throw X if (folderAvailable.get()) @@ -88,7 +88,7 @@ std::set<Zstring, LessNativePath> waitForMissingDirs(const std::vector<Zstring>& return dirAvailable(folderPath); }); - while (folderAvailable.wait_for(cbInterval) != std::future_status::ready) + while (folderAvailable.wait_for(cbInterval) == std::future_status::timeout) requestUiUpdate(folderPath); //throw X if (folderAvailable.get()) diff --git a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp index 2e6feb38..e44123ba 100644 --- a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp +++ b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp @@ -47,9 +47,9 @@ bool uiUpdateDue() enum TrayMode { - TRAY_MODE_ACTIVE, - TRAY_MODE_WAITING, - TRAY_MODE_ERROR, + active, + waiting, + error, }; @@ -59,19 +59,21 @@ public: TrayIconObject(const wxString& jobname) : jobName_(jobname) { - Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxEventHandler(TrayIconObject::OnDoubleClick), nullptr, this); + Bind(wxEVT_TASKBAR_LEFT_DCLICK, [this](wxTaskBarIconEvent& event) { onDoubleClick(event); }); - assert(mode_ != TRAY_MODE_ACTIVE); //setMode() supports polling! - setMode(TRAY_MODE_ACTIVE, Zstring()); + assert(mode_ != TrayMode::active); //setMode() supports polling! + setMode(TrayMode::active, Zstring()); + + timer_.Bind(wxEVT_TIMER, [this](wxTimerEvent& event) { onErrorFlashIcon(event); }); } //require polling: bool resumeIsRequested() const { return resumeRequested_; } bool abortIsRequested () const { return abortRequested_; } - //during TRAY_MODE_ERROR those two functions are available: - void clearShowErrorRequested() { assert(mode_ == TRAY_MODE_ERROR); showErrorMsgRequested_ = false; } - bool getShowErrorRequested() const { assert(mode_ == TRAY_MODE_ERROR); return showErrorMsgRequested_; } + //during TrayMode::error those two functions are available: + void clearShowErrorRequested() { assert(mode_ == TrayMode::error); showErrorMsgRequested_ = false; } + bool getShowErrorRequested() const { assert(mode_ == TrayMode::error); return showErrorMsgRequested_; } void setMode(TrayMode m, const Zstring& missingFolderPath) { @@ -82,27 +84,25 @@ public: missingFolderPath_ = missingFolderPath; timer_.Stop(); - timer_.Disconnect(wxEVT_TIMER, wxEventHandler(TrayIconObject::OnErrorFlashIcon), nullptr, this); switch (m) { - case TRAY_MODE_ACTIVE: + case TrayMode::active: setTrayIcon(trayImg_, _("Directory monitoring active")); break; - case TRAY_MODE_WAITING: + case TrayMode::waiting: assert(!missingFolderPath.empty()); setTrayIcon(greyScale(trayImg_), _("Waiting until directory is available:") + L' ' + fmtPath(missingFolderPath)); break; - case TRAY_MODE_ERROR: - timer_.Connect(wxEVT_TIMER, wxEventHandler(TrayIconObject::OnErrorFlashIcon), nullptr, this); + case TrayMode::error: timer_.Start(500); //timer interval in [ms] break; } } private: - void OnErrorFlashIcon(wxEvent& event) + void onErrorFlashIcon(wxEvent& event) { iconFlashStatusLast_ = !iconFlashStatusLast_; setTrayIcon(greyScaleIfDisabled(trayImg_, iconFlashStatusLast_), _("Error")); @@ -118,13 +118,6 @@ private: SetIcon(realtimeIcon, tooltip); } - enum Selection - { - CONTEXT_CONFIGURE = 1, //wxWidgets: "A MenuItem ID of zero does not work under Mac" - CONTEXT_SHOW_ERROR, - CONTEXT_ABORT = wxID_EXIT - }; - wxMenu* CreatePopupMenu() override { wxMenu* contextMenu = new wxMenu; @@ -132,50 +125,36 @@ private: wxMenuItem* defaultItem = nullptr; switch (mode_) { - case TRAY_MODE_ACTIVE: - case TRAY_MODE_WAITING: - defaultItem = new wxMenuItem(contextMenu, CONTEXT_CONFIGURE, _("&Configure")); //better than "Restore"? https://freefilesync.org/forum/viewtopic.php?t=2044&p=20391#p20391 + case TrayMode::active: + case TrayMode::waiting: + defaultItem = new wxMenuItem(contextMenu, wxID_ANY, _("&Configure")); //better than "Restore"? https://freefilesync.org/forum/viewtopic.php?t=2044&p=20391#p20391 + contextMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& event) { resumeRequested_ = true; }, defaultItem->GetId()); break; - case TRAY_MODE_ERROR: - defaultItem = new wxMenuItem(contextMenu, CONTEXT_SHOW_ERROR, _("&Show error message")); + + case TrayMode::error: + defaultItem = new wxMenuItem(contextMenu, wxID_ANY, _("&Show error message")); + contextMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& event) { showErrorMsgRequested_ = true; }, defaultItem->GetId()); break; } contextMenu->Append(defaultItem); contextMenu->AppendSeparator(); - contextMenu->Append(CONTEXT_ABORT, _("&Quit")); - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TrayIconObject::OnContextMenuSelection), nullptr, this); - return contextMenu; //ownership transferred to caller - } + wxMenuItem* itemAbort = contextMenu->Append(wxID_ANY, _("&Quit")); + contextMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& event) { abortRequested_ = true; }, itemAbort->GetId()); - void OnContextMenuSelection(wxCommandEvent& event) - { - switch (static_cast<Selection>(event.GetId())) - { - case CONTEXT_ABORT: - abortRequested_ = true; - break; - - case CONTEXT_CONFIGURE: - resumeRequested_ = true; - break; - - case CONTEXT_SHOW_ERROR: - showErrorMsgRequested_ = true; - break; - } + return contextMenu; //ownership transferred to caller } - void OnDoubleClick(wxEvent& event) + void onDoubleClick(wxEvent& event) { switch (mode_) { - case TRAY_MODE_ACTIVE: - case TRAY_MODE_WAITING: + case TrayMode::active: + case TrayMode::waiting: resumeRequested_ = true; //never throw exceptions through a C-Layer call stack (GUI)! break; - case TRAY_MODE_ERROR: + case TrayMode::error: showErrorMsgRequested_ = true; break; } @@ -185,11 +164,11 @@ private: bool abortRequested_ = false; bool showErrorMsgRequested_ = false; - TrayMode mode_ = TRAY_MODE_WAITING; + TrayMode mode_ = TrayMode::waiting; Zstring missingFolderPath_; - bool iconFlashStatusLast_ = false; //flash try icon for TRAY_MODE_ERROR - wxTimer timer_; // + bool iconFlashStatusLast_ = false; //flash try icon for TrayMode::error + wxTimer timer_; // const wxString jobName_; //RTS job name, may be empty @@ -285,9 +264,9 @@ rts::AbortReason rts::runFolderMonitor(const XmlRealConfig& config, const wxStri auto requestUiUpdate = [&](const Zstring* missingFolderPath) { if (missingFolderPath) - trayIcon.setMode(TRAY_MODE_WAITING, *missingFolderPath); + trayIcon.setMode(TrayMode::waiting, *missingFolderPath); else - trayIcon.setMode(TRAY_MODE_ACTIVE, Zstring()); + trayIcon.setMode(TrayMode::active, Zstring()); if (uiUpdateDue()) trayIcon.doUiRefreshNow(); //throw AbortMonitoring @@ -295,7 +274,7 @@ rts::AbortReason rts::runFolderMonitor(const XmlRealConfig& config, const wxStri auto reportError = [&](const std::wstring& msg) { - trayIcon.setMode(TRAY_MODE_ERROR, Zstring()); + trayIcon.setMode(TrayMode::error, Zstring()); trayIcon.clearShowErrorRequested(); //wait for some time, then return to retry diff --git a/FreeFileSync/Source/afs/abstract.cpp b/FreeFileSync/Source/afs/abstract.cpp index d41996be..bcad6443 100644 --- a/FreeFileSync/Source/afs/abstract.cpp +++ b/FreeFileSync/Source/afs/abstract.cpp @@ -8,15 +8,13 @@ #include <zen/serialize.h> #include <zen/guid.h> #include <zen/crc.h> +#include <typeindex> using namespace zen; using namespace fff; using AFS = AbstractFileSystem; -const Zchar* AFS::TEMP_FILE_ENDING = Zstr(".ffs_tmp"); - - bool fff::isValidRelPath(const Zstring& relPath) { return !contains(relPath, '\\') && @@ -37,27 +35,15 @@ AfsPath fff::sanitizeDeviceRelativePath(Zstring relPath) int AFS::compareDevice(const AbstractFileSystem& lhs, const AbstractFileSystem& rhs) { //note: in worst case, order is guaranteed to be stable only during each program run - if (typeid(lhs).before(typeid(rhs))) - return -1; - if (typeid(rhs).before(typeid(lhs))) - return 1; - assert(typeid(lhs) == typeid(rhs)); //caveat: typeid returns static type for pointers, dynamic type for references!!! + if (const std::strong_ordering cmp = std::type_index(typeid(lhs)) <=> std::type_index(typeid(rhs)); + cmp != std::strong_ordering::equal) + return cmp < 0 ? -1 : 1; return lhs.compareDeviceSameAfsType(rhs); } -int AFS::comparePath(const AbstractPath& lhs, const AbstractPath& rhs) -{ - if (const int rv = compareDevice(lhs.afsDevice.ref(), rhs.afsDevice.ref()); - rv != 0) - return rv; - - return compareString(lhs.afsPath.value, rhs.afsPath.value); -} - - std::optional<AbstractPath> AFS::getParentPath(const AbstractPath& ap) { if (const std::optional<AfsPath> parentAfsPath = getParentPath(ap.afsPath)) @@ -72,7 +58,7 @@ std::optional<AfsPath> AFS::getParentPath(const AfsPath& afsPath) if (afsPath.value.empty()) return {}; - return AfsPath(beforeLast(afsPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); + return AfsPath(beforeLast(afsPath.value, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); } @@ -131,9 +117,10 @@ AFS::FileCopyResult AFS::copyFileAsStream(const AfsPath& afsSource, const Stream //try to get the most current attributes if possible (input file might have changed after comparison!) if (std::optional<StreamAttributes> attr = streamIn->getAttributesBuffered()) //throw FileError attrSourceNew = *attr; //Native/MTP/Google Drive - else //use more stale ones: + else //use possibly stale ones: attrSourceNew = attrSource; //SFTP/FTP //TODO: evaluate: consequences of stale attributes + warn_static("TODO") //already existing: undefined behavior! (e.g. fail/overwrite/auto-rename) auto streamOut = getOutputStream(apTarget, attrSourceNew.fileSize, attrSourceNew.modTime, notifyUnbufferedWrite); //throw FileError @@ -149,10 +136,10 @@ AFS::FileCopyResult AFS::copyFileAsStream(const AfsPath& afsSource, const Stream const FinalizeResult finResult = streamOut->finalize(); //throw FileError, X - //catch file I/O bugs + read/write conflicts: (note: different check than inside AbstractFileSystem::OutputStream::finalize() => checks notifyUnbufferedIO()!) ZEN_ON_SCOPE_FAIL(try { removeFilePlain(apTarget); /*throw FileError*/ } catch (FileError&) {}); //after finalize(): not guarded by ~AFS::OutputStream() anymore! + //catch file I/O bugs + read/write conflicts: (note: different check than inside AbstractFileSystem::OutputStream::finalize() => checks notifyUnbufferedIO()!) if (totalBytesWritten != totalBytesRead) throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getDisplayPath(apTarget))), replaceCpy(replaceCpy(_("Unexpected size of data stream.\nExpected: %x bytes\nActual: %y bytes"), @@ -209,7 +196,7 @@ AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& apSource, con //- generate (hopefully) unique file name to avoid clashing with some remnant ffs_tmp file //- do not loop: avoid pathological cases, e.g. https://freefilesync.org/forum/viewtopic.php?t=1592 - Zstring tmpName = beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_ALL); + Zstring tmpName = beforeLast(fileName, Zstr('.'), IfNotFoundReturn::all); //don't make the temp name longer than the original when hitting file system name length limitations: "lpMaximumComponentLength is commonly 255 characters" while (tmpName.size() > 200) //BUT don't trim short names! we want early failure on filename-related issues diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h index ff0a882e..6bf78631 100644 --- a/FreeFileSync/Source/afs/abstract.h +++ b/FreeFileSync/Source/afs/abstract.h @@ -30,6 +30,8 @@ struct AfsPath //= path relative to the file system root folder (no leading/tral AfsPath() {} explicit AfsPath(const Zstring& p) : value(p) { assert(isValidRelPath(value)); } Zstring value; + + std::strong_ordering operator<=>(const AfsPath&) const = default; }; struct AbstractPath //THREAD-SAFETY: like an int! @@ -48,7 +50,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t { //=============== convenience ================= static Zstring getItemName(const AbstractPath& ap) { assert(getParentPath(ap)); return getItemName(ap.afsPath); } - static Zstring getItemName(const AfsPath& afsPath) { using namespace zen; return afterLast(afsPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); } + static Zstring getItemName(const AfsPath& afsPath) { using namespace zen; return afterLast(afsPath.value, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); } static bool isNullPath(const AbstractPath& ap) { return isNullDevice(ap.afsDevice) /*&& ap.afsPath.value.empty()*/; } @@ -60,8 +62,6 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t static int compareDevice(const AbstractFileSystem& lhs, const AbstractFileSystem& rhs); - static int comparePath(const AbstractPath& lhs, const AbstractPath& rhs); - static bool isNullDevice(const AfsDevice& afsDevice) { return afsDevice.ref().isNullFileSystem(); } static std::wstring getDisplayPath(const AbstractPath& ap) { return ap.afsDevice.ref().getDisplayPath(ap.afsPath); } @@ -165,8 +165,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t virtual FinalizeResult finalize() = 0; //throw FileError, X }; - //TRANSACTIONAL output stream! => call finalize when done! - struct OutputStream + struct OutputStream //call finalize when done! { OutputStream(std::unique_ptr<OutputStreamImpl>&& outStream, const AbstractPath& filePath, std::optional<uint64_t> streamSize); ~OutputStream(); @@ -251,7 +250,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 const Zchar* TEMP_FILE_ENDING; //don't use Zstring as global constant: avoid static initialization order problem in global namespace! + 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! struct FileCopyResult { @@ -404,15 +403,21 @@ private: }; -inline bool operator< (const AfsDevice& lhs, const AfsDevice& rhs) { return AbstractFileSystem::compareDevice(lhs.ref(), rhs.ref()) < 0; } -inline bool operator==(const AfsDevice& lhs, const AfsDevice& rhs) { return AbstractFileSystem::compareDevice(lhs.ref(), rhs.ref()) == 0; } -inline bool operator!=(const AfsDevice& lhs, const AfsDevice& rhs) { return !(lhs == rhs); } +inline std::weak_ordering operator<=>(const AfsDevice& lhs, const AfsDevice& rhs) { return AbstractFileSystem::compareDevice(lhs.ref(), rhs.ref()) <=> 0; } -inline bool operator< (const AbstractPath& lhs, const AbstractPath& rhs) { return AbstractFileSystem::comparePath(lhs, rhs) < 0; } -inline bool operator==(const AbstractPath& lhs, const AbstractPath& rhs) { return AbstractFileSystem::comparePath(lhs, rhs) == 0; } -//inline bool operator==(const AbstractPath& lhs, const AbstractPath& rhs) { return lhs.afsPath.value == rhs.afsPath.value && lhs.afsDevice == rhs.afsDevice; } => premature optimization? -inline bool operator!=(const AbstractPath& lhs, const AbstractPath& rhs) { return !(lhs == rhs); } +inline bool operator==(const AfsDevice& lhs, const AfsDevice& rhs) { return lhs <=> rhs == std::strong_ordering::equal; } + +inline +std::weak_ordering operator<=>(const AbstractPath& lhs, const AbstractPath& rhs) +{ + if (const std::weak_ordering cmp = lhs.afsDevice <=> rhs.afsDevice; + cmp != std::weak_ordering::equivalent) + return cmp; + + return lhs.afsPath <=> rhs.afsPath; +} +inline bool operator==(const AbstractPath& lhs, const AbstractPath& rhs) { return lhs.afsPath == rhs.afsPath && lhs.afsDevice == rhs.afsDevice; } @@ -444,7 +449,8 @@ AbstractFileSystem::OutputStream::~OutputStream() outStream_.reset(); //close file handle *before* remove! if (!finalizeSucceeded_) //transactional output stream! => clean up! - //even needed for Google Drive: e.g. user might cancel during OutputStreamImpl::finalize(), just after file was written transactionally + //- needed for Google Drive: e.g. user might cancel during OutputStreamImpl::finalize(), just after file was written transactionally + //- also for Native: setFileTime() may fail *after* FileOutput::finalize() try { AbstractFileSystem::removeFilePlain(filePath_); /*throw FileError*/ } catch (zen::FileError&) {} } diff --git a/FreeFileSync/Source/afs/abstract_impl.h b/FreeFileSync/Source/afs/abstract_impl.h index a05843a8..f22985f2 100644 --- a/FreeFileSync/Source/afs/abstract_impl.h +++ b/FreeFileSync/Source/afs/abstract_impl.h @@ -58,55 +58,14 @@ bool tryReportingItemError(Command cmd, AbstractFileSystem::TraverserCallback& c } //========================================================================================== -/* -implement streaming API on top of libcurl's icky callback-based design - => support copying arbitrarily-large files: https://freefilesync.org/forum/viewtopic.php?t=4471 - => maximum performance through async processing (prefetching + output buffer!) - => cost per worker thread creation ~ 1/20 ms -*/ +/* implement streaming API on top of libcurl's icky callback-based design + => support copying arbitrarily-large files: https://freefilesync.org/forum/viewtopic.php?t=4471 + => maximum performance through async processing (prefetching + output buffer!) + => cost per worker thread creation ~ 1/20 ms */ class AsyncStreamBuffer { public: - AsyncStreamBuffer(size_t bufferSize) : bufferSize_(bufferSize) { ringBuf_.reserve(bufferSize); } - - //context of output thread, blocking - void write(const void* buffer, size_t bytesToWrite) //throw <read error> - { - totalBytesWritten_ += bytesToWrite; //bytes already processed as far as raw FTP access is concerned - - auto it = static_cast<const std::byte*>(buffer); - const auto itEnd = it + bytesToWrite; - - for (std::unique_lock dummy(lockStream_); it != itEnd;) - { - //=> can't use InterruptibleThread's interruptibleWait() :( - // -> AsyncStreamBuffer is used for input and output streaming - // => both AsyncStreamBuffer::write()/read() would have to implement interruptibleWait() - // => one of these usually called from main thread - // => but interruptibleWait() cannot be called from main thread! - conditionBytesRead_.wait(dummy, [this] { return errorRead_ || ringBuf_.size() < bufferSize_; }); - - if (errorRead_) - std::rethrow_exception(errorRead_); //throw <read error> - - const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufferSize_ - ringBuf_.size()); - ringBuf_.insert_back(it, it + junkSize); - it += junkSize; - - conditionBytesWritten_.notify_all(); - } - } - - //context of output thread - void closeStream() - { - { - std::lock_guard dummy(lockStream_); - assert(!eof_); - eof_ = true; - } - conditionBytesWritten_.notify_all(); - } + AsyncStreamBuffer(size_t bufferSize) : bufSize_(bufferSize) { ringBuf_.reserve(bufferSize); } //context of input thread, blocking //return "bytesToRead" bytes unless end of stream! @@ -120,6 +79,7 @@ public: for (std::unique_lock dummy(lockStream_); it != itEnd;) { + assert(!errorRead_); conditionBytesWritten_.wait(dummy, [this] { return errorWrite_ || !ringBuf_.empty() || eof_; }); if (errorWrite_) @@ -140,6 +100,46 @@ public: return bytesRead; } + //context of output thread, blocking + void write(const void* buffer, size_t bytesToWrite) //throw <read error> + { + totalBytesWritten_ += bytesToWrite; //bytes already processed as far as raw FTP access is concerned + + auto it = static_cast<const std::byte*>(buffer); + const auto itEnd = it + bytesToWrite; + + for (std::unique_lock dummy(lockStream_); it != itEnd;) + { + assert(!eof_ && !errorWrite_); + /* => can't use InterruptibleThread's interruptibleWait() :( + -> AsyncStreamBuffer is used for input and output streaming + => both AsyncStreamBuffer::write()/read() would have to implement interruptibleWait() + => one of these usually called from main thread + => but interruptibleWait() cannot be called from main thread! */ + conditionBytesRead_.wait(dummy, [this] { return errorRead_ || ringBuf_.size() < bufSize_; }); + + if (errorRead_) + std::rethrow_exception(errorRead_); //throw <read error> + + const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufSize_ - ringBuf_.size()); + ringBuf_.insert_back(it, it + junkSize); + it += junkSize; + + conditionBytesWritten_.notify_all(); + } + } + + //context of output thread + void closeStream() + { + { + std::lock_guard dummy(lockStream_); + assert(!eof_ && !errorWrite_); + eof_ = true; + } + conditionBytesWritten_.notify_all(); + } + //context of input thread void setReadError(const std::exception_ptr& error) { @@ -172,13 +172,14 @@ public: std::rethrow_exception(errorRead_); //throw <read error> } - // -> function not needed: when EOF is reached (without errors), reading is done => no further error can occur! - //void checkWriteErrors() //throw <write error> - //{ - // std::lock_guard dummy(lockStream_); - // if (errorWrite_) - // std::rethrow_exception(errorWrite_); //throw <write error> - //} +#if 0 //function not needed: when EOF is reached (without errors), reading is done => no further error can occur! + void checkWriteErrors() //throw <write error> + { + std::lock_guard dummy(lockStream_); + if (errorWrite_) + std::rethrow_exception(errorWrite_); //throw <write error> + } +#endif uint64_t getTotalBytesWritten() const { return totalBytesWritten_; } uint64_t getTotalBytesRead () const { return totalBytesRead_; } @@ -187,7 +188,7 @@ private: AsyncStreamBuffer (const AsyncStreamBuffer&) = delete; AsyncStreamBuffer& operator=(const AsyncStreamBuffer&) = delete; - const size_t bufferSize_; + const size_t bufSize_; std::mutex lockStream_; zen::RingBuffer<std::byte> ringBuf_; //prefetch/output buffer bool eof_ = false; diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp index ad2e5f29..73977021 100644 --- a/FreeFileSync/Source/afs/ftp.cpp +++ b/FreeFileSync/Source/afs/ftp.cpp @@ -60,30 +60,27 @@ struct FtpSessionId bool useTls = false; //timeoutSec => irrelevant for session equality }; - - -bool operator<(const FtpSessionId& lhs, const FtpSessionId& rhs) +std::weak_ordering operator<=>(const FtpSessionId& lhs, const FtpSessionId& rhs) { //exactly the type of case insensitive comparison we need for server names! - if (const int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs - rv != 0) - return rv < 0; + if (const int cmp = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs + cmp != 0) + return cmp <=> 0; if (lhs.port != rhs.port) - return lhs.port < rhs.port; + return lhs.port <=> rhs.port; - if (const int rv = compareString(lhs.username, rhs.username); //case sensitive! - rv != 0) - return rv < 0; + if (const std::strong_ordering cmp = lhs.username <=> rhs.username; //case sensitive! + cmp != std::strong_ordering::equal) + return cmp; - if (const int rv = compareString(lhs.password, rhs.password); //case sensitive! - rv != 0) - return rv < 0; + if (const std::strong_ordering cmp = lhs.password <=> rhs.password; //case sensitive! + cmp != std::strong_ordering::equal) + return cmp; - return lhs.useTls < rhs.useTls; + return lhs.useTls <=> rhs.useTls; } - Zstring ansiToUtfEncoding(const std::string& str) //throw SysError { gsize bytesWritten = 0; //not including the terminating null @@ -100,9 +97,7 @@ Zstring ansiToUtfEncoding(const std::string& str) //throw SysError &bytesWritten, //gsize* bytes_written, &error); //GError** error if (!utfStr) - throw SysError(formatSystemError("g_convert(" + utfTo<std::string>(str) + ')', - error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"", - error ? utfTo<std::wstring>(error->message) : L"Unknown error.")); + throw SysError(formatGlibError("g_convert(" + utfTo<std::string>(str) + ')', error)); ZEN_ON_SCOPE_EXIT(::g_free(utfStr)); return { utfStr, bytesWritten }; @@ -126,9 +121,7 @@ std::string utfToAnsiEncoding(const Zstring& str) //throw SysError &bytesWritten, //gsize* bytes_written, &error); //GError** error if (!ansiStr) - throw SysError(formatSystemError("g_convert(" + utfTo<std::string>(str) + ')', - error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"", - error ? utfTo<std::wstring>(error->message) : L"Unknown error.")); + throw SysError(formatGlibError("g_convert(" + utfTo<std::string>(str) + ')', error)); ZEN_ON_SCOPE_EXIT(::g_free(ansiStr)); return { ansiStr, bytesWritten }; @@ -282,7 +275,22 @@ std::wstring formatFtpStatus(int sc) //================================================================================================================ //================================================================================================================ -Global<UniSessionCounter> globalFtpSessionCount(createUniSessionCounter()); +constinit2 Global<UniSessionCounter> globalFtpSessionCount; +GLOBAL_RUN_ONCE(globalFtpSessionCount.set(createUniSessionCounter())); + + +//*currently* not needed => keep or let go? +struct SysErrorFtp : public SysError +{ + SysErrorFtp(const std::wstring& msg, + int statusCode, + std::string response) : SysError(msg), + ftpStatusCode(statusCode), + serverResponse(response) {} + + int ftpStatusCode = 0; + std::string serverResponse; +}; class FtpSession @@ -301,7 +309,7 @@ public: //returns server response (header data) std::string perform(const AfsPath& afsPath, bool isDir, curl_ftpmethod pathMethod, - const std::vector<CurlOption>& extraOptions, bool requiresUtf8, int timeoutSec) //throw SysError + const std::vector<CurlOption>& extraOptions, bool requiresUtf8, int timeoutSec) //throw SysError, SysErrorFtp { if (requiresUtf8) //avoid endless recursion sessionEnableUtf8(timeoutSec); //throw SysError @@ -480,14 +488,21 @@ public: { std::wstring errorMsg = trimCpy(utfTo<std::wstring>(curlErrorBuf)); //optional - if (rcPerf != CURLE_RECV_ERROR) + if (rcPerf == CURLE_RECV_ERROR) //failed to get server response { - const std::vector<std::string> headerLines = splitFtpResponse(headerData); - if (!headerLines.empty()) - errorMsg += (errorMsg.empty() ? L"" : L"\n") + trimCpy(utfTo<std::wstring>(headerLines.back())); //that *should* be the servers error response + if (ftpStatusCode != 0) //possible despite CURLE_RECV_ERROR! But: useful? + errorMsg += (errorMsg.empty() ? L"" : L" ") + formatFtpStatus(ftpStatusCode); + throw SysError(formatSystemError("curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg)); } - else //failed to get server response - errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatFtpStatus(ftpStatusCode); + + std::string serverResponse; + if (const std::vector<std::string> headerLines = splitFtpResponse(headerData); + !headerLines.empty()) + serverResponse = headerLines.back(); //that *should* be the server's error response + + if (const std::wstring responseFmt = trimCpy(utfTo<std::wstring>(serverResponse)); + !responseFmt.empty()) + errorMsg += (errorMsg.empty() ? L"" : L"\n") + responseFmt; #if 0 //utfTo<std::wstring>(::curl_easy_strerror(ec)) is uninteresting //use CURLINFO_OS_ERRNO ?? https://curl.haxx.se/libcurl/c/CURLINFO_OS_ERRNO.html @@ -496,7 +511,8 @@ public: if (nativeErrorCode != 0) errorMsg += (errorMsg.empty() ? L"" : L"\n") + std::wstring(L"Native error code: ") + numberTo<std::wstring>(nativeErrorCode); #endif - throw SysError(formatSystemError("curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg)); + throw SysErrorFtp(formatSystemError("curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg), + ftpStatusCode, serverResponse); } lastSuccessfulUseTime_ = std::chrono::steady_clock::now(); @@ -615,7 +631,7 @@ private: { std::string curlRelPath; //libcurl expects encoded paths (except for '/' char!!!) => bug: https://github.com/curl/curl/pull/4423 - for (const std::string& comp : split(getServerPathInternal(afsPath, timeoutSec), '/', SplitType::SKIP_EMPTY)) //throw SysError + for (const std::string& comp : split(getServerPathInternal(afsPath, timeoutSec), '/', SplitOnEmpty::skip)) //throw SysError { char* compFmt = ::curl_easy_escape(easyHandle_, comp.c_str(), static_cast<int>(comp.size())); if (!compFmt) @@ -699,7 +715,7 @@ private: { if (!featureCache_) { - static FunStatGlobal<Protected<FeatureList>> globalServerFeatures; + static constinit2 FunStatGlobal<Protected<FeatureList>> globalServerFeatures; globalServerFeatures.initOnce([] { return std::make_unique<Protected<FeatureList>>(); }); const auto sf = globalServerFeatures.get(); @@ -739,7 +755,7 @@ private: //suppport ProFTPD with "MultilineRFC2228 = on" https://freefilesync.org/forum/viewtopic.php?t=7243 if (startsWith(line, "211-")) - line = ' ' + afterFirst(line, '-', IF_MISSING_RETURN_NONE); + line = ' ' + afterFirst(line, '-', IfNotFoundReturn::none); //https://tools.ietf.org/html/rfc3659#section-7.8 //"a server-FTP process that supports MLST, and MLSD [...] MUST indicate that this support exists" @@ -789,14 +805,9 @@ class FtpSessionManager //reuse (healthy) FTP sessions globally public: FtpSessionManager() : sessionCleaner_([this] { - setCurrentThreadName("Session Cleaner[FTP]"); - runGlobalSessionCleanUp(); /*throw ThreadInterruption*/ + setCurrentThreadName(Zstr("Session Cleaner[FTP]")); + runGlobalSessionCleanUp(); /*throw ThreadStopRequest*/ }) {} - ~FtpSessionManager() - { - sessionCleaner_.interrupt(); - sessionCleaner_.join(); - } void access(const FtpLogin& login, const std::function<void(FtpSession& session)>& useFtpSession /*throw X*/) //throw SysError, X { @@ -845,7 +856,7 @@ private: //run a dedicated clean-up thread => it's unclear when the server let's a connection time out, so we do it preemptively //context of worker thread: - void runGlobalSessionCleanUp() //throw ThreadInterruption + void runGlobalSessionCleanUp() //throw ThreadStopRequest { std::chrono::steady_clock::time_point lastCleanupTime; for (;;) @@ -853,7 +864,7 @@ private: const auto now = std::chrono::steady_clock::now(); if (now < lastCleanupTime + FTP_SESSION_CLEANUP_INTERVAL) - interruptibleSleep(lastCleanupTime + FTP_SESSION_CLEANUP_INTERVAL - now); //throw ThreadInterruption + interruptibleSleep(lastCleanupTime + FTP_SESSION_CLEANUP_INTERVAL - now); //throw ThreadStopRequest lastCleanupTime = std::chrono::steady_clock::now(); @@ -891,7 +902,7 @@ private: //-------------------------------------------------------------------------------------- UniInitializer globalStartupInitFtp(*globalFtpSessionCount.get()); -Global<FtpSessionManager> globalFtpSessionManager; //caveat: life time must be subset of static UniInitializer! +constinit2 Global<FtpSessionManager> globalFtpSessionManager; //caveat: life time must be subset of static UniInitializer! //-------------------------------------------------------------------------------------- void accessFtpSession(const FtpLogin& login, const std::function<void(FtpSession& session)>& useFtpSession /*throw X*/) //throw SysError, X @@ -1026,18 +1037,18 @@ private: std::string typeFact; std::optional<uint64_t> fileSize; - for (const std::string& fact : split(facts, ';', SplitType::SKIP_EMPTY)) + for (const std::string& fact : split(facts, ';', SplitOnEmpty::skip)) if (startsWithAsciiNoCase(fact, "type=")) //must be case-insensitive!!! { - const std::string tmp = afterFirst(fact, '=', IF_MISSING_RETURN_NONE); - typeFact = beforeFirst(tmp, ':', IF_MISSING_RETURN_ALL); + const std::string tmp = afterFirst(fact, '=', IfNotFoundReturn::none); + typeFact = beforeFirst(tmp, ':', IfNotFoundReturn::all); } else if (startsWithAsciiNoCase(fact, "size=")) - fileSize = stringTo<uint64_t>(afterFirst(fact, '=', IF_MISSING_RETURN_NONE)); + fileSize = stringTo<uint64_t>(afterFirst(fact, '=', IfNotFoundReturn::none)); else if (startsWithAsciiNoCase(fact, "modify=")) { - std::string modifyFact = afterFirst(fact, '=', IF_MISSING_RETURN_NONE); - modifyFact = beforeLast(modifyFact, '.', IF_MISSING_RETURN_ALL); //truncate millisecond precision if available + std::string modifyFact = afterFirst(fact, '=', IfNotFoundReturn::none); + modifyFact = beforeLast(modifyFact, '.', IfNotFoundReturn::all); //truncate millisecond precision if available const TimeComp tc = parseTime("%Y%m%d%H%M%S", modifyFact); if (tc == TimeComp()) @@ -1227,8 +1238,8 @@ private: if (contains(timeOrYear, ':')) { - const int hour = stringTo<int>(beforeFirst(timeOrYear, ':', IF_MISSING_RETURN_NONE)); - const int minute = stringTo<int>(afterFirst (timeOrYear, ':', IF_MISSING_RETURN_NONE)); + const int hour = stringTo<int>(beforeFirst(timeOrYear, ':', IfNotFoundReturn::none)); + const int minute = stringTo<int>(afterFirst (timeOrYear, ':', IfNotFoundReturn::none)); if (hour < 0 || hour > 23 || minute < 0 || minute > 59) throw SysError(L"Failed to parse file time."); @@ -1266,7 +1277,7 @@ private: const std::string trail = parser.readRange([](char) { return true; }); //throw SysError std::string itemName; if (typeTag == "l") - itemName = beforeFirst(trail, " -> ", IF_MISSING_RETURN_NONE); + itemName = beforeFirst(trail, " -> ", IfNotFoundReturn::none); else itemName = trail; if (itemName.empty()) @@ -1630,25 +1641,24 @@ struct InputStreamFtp : public AbstractFileSystem::InputStream { worker_ = InterruptibleThread([asyncStreamOut = this->asyncStreamIn_, login, afsPath] { - setCurrentThreadName(("Istream[FTP] " + utfTo<std::string>(getCurlDisplayPath(login.server, afsPath))). c_str()); + setCurrentThreadName(Zstr("Istream[FTP] ") + utfTo<Zstring>(getCurlDisplayPath(login.server, afsPath))); try { auto writeBlock = [&](const void* buffer, size_t bytesToWrite) { - return asyncStreamOut->write(buffer, bytesToWrite); //throw ThreadInterruption + return asyncStreamOut->write(buffer, bytesToWrite); //throw ThreadStopRequest }; - ftpFileDownload(login, afsPath, writeBlock); //throw FileError, ThreadInterruption + ftpFileDownload(login, afsPath, writeBlock); //throw FileError, ThreadStopRequest asyncStreamOut->closeStream(); } - catch (FileError&) { asyncStreamOut->setWriteError(std::current_exception()); } //let ThreadInterruption pass through! + catch (FileError&) { asyncStreamOut->setWriteError(std::current_exception()); } //let ThreadStopRequest pass through! }); } ~InputStreamFtp() { - asyncStreamIn_->setReadError(std::make_exception_ptr(ThreadInterruption())); - worker_.join(); + asyncStreamIn_->setReadError(std::make_exception_ptr(ThreadStopRequest())); } size_t read(void* buffer, size_t bytesToRead) override //throw FileError, (ErrorFileLocked), X; return "bytesToRead" bytes unless end of stream! @@ -1698,30 +1708,39 @@ struct OutputStreamFtp : public AbstractFileSystem::OutputStreamImpl modTime_(modTime), notifyUnbufferedIO_(notifyUnbufferedIO) { - worker_ = InterruptibleThread([asyncStreamIn = this->asyncStreamOut_, login, afsPath] + std::promise<void> pUploadDone; + futUploadDone_ = pUploadDone.get_future(); + + worker_ = InterruptibleThread([login, afsPath, + asyncStreamIn = this->asyncStreamOut_, + pUploadDone = std::move(pUploadDone)]() mutable { - setCurrentThreadName(("Ostream[FTP] " + utfTo<std::string>(getCurlDisplayPath(login.server, afsPath))). c_str()); + setCurrentThreadName(Zstr("Ostream[FTP] ") + utfTo<Zstring>(getCurlDisplayPath(login.server, afsPath))); try { auto readBlock = [&](void* buffer, size_t bytesToRead) { //returns "bytesToRead" bytes unless end of stream! => maps nicely into Posix read() semantics expected by ftpFileUpload() - return asyncStreamIn->read(buffer, bytesToRead); //throw ThreadInterruption + return asyncStreamIn->read(buffer, bytesToRead); //throw ThreadStopRequest }; - ftpFileUpload(login, afsPath, readBlock); //throw FileError, ThreadInterruption + ftpFileUpload(login, afsPath, readBlock); //throw FileError, ThreadStopRequest assert(asyncStreamIn->getTotalBytesRead() == asyncStreamIn->getTotalBytesWritten()); + + pUploadDone.set_value(); } - catch (FileError&) { asyncStreamIn->setReadError(std::current_exception()); } //let ThreadInterruption pass through! + catch (FileError&) + { + const std::exception_ptr exptr = std::current_exception(); + asyncStreamIn->setReadError(exptr); //set both! + pUploadDone.set_exception(exptr); // + } + //let ThreadStopRequest pass through! }); } ~OutputStreamFtp() { - if (worker_.joinable()) - { - asyncStreamOut_->setWriteError(std::make_exception_ptr(ThreadInterruption())); - worker_.join(); - } + asyncStreamOut_->setWriteError(std::make_exception_ptr(ThreadStopRequest())); } void write(const void* buffer, size_t bytesToWrite) override //throw FileError, X @@ -1734,13 +1753,16 @@ struct OutputStreamFtp : public AbstractFileSystem::OutputStreamImpl { asyncStreamOut_->closeStream(); - while (!worker_.tryJoinFor(std::chrono::milliseconds(50))) + while (futUploadDone_.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout) reportBytesProcessed(); //throw X reportBytesProcessed(); //[!] once more, now that *all* bytes were written asyncStreamOut_->checkReadErrors(); //throw FileError //-------------------------------------------------------------------- + assert(isReady(futUploadDone_)); + futUploadDone_.get(); //throw FileError + AFS::FinalizeResult result; //result.fileId = ... -> not supported by FTP try @@ -1764,7 +1786,7 @@ private: void setModTimeIfAvailable() const //throw FileError, follows symlinks { - assert(!worker_.joinable()); + //assert(isReady(futUploadDone_)); => MUST NOT CALL *after* std::future<>::get()! if (modTime_) try { @@ -1795,6 +1817,7 @@ private: int64_t totalBytesReported_ = 0; std::shared_ptr<AsyncStreamBuffer> asyncStreamOut_ = std::make_shared<AsyncStreamBuffer>(FTP_STREAM_BUFFER_SIZE); InterruptibleThread worker_; + std::future<void> futUploadDone_; }; //--------------------------------------------------------------------------------------------------------------------------- @@ -2191,7 +2214,7 @@ AfsDevice fff::condenseToFtpDevice(const FtpLogin& login) //noexcept startsWithAsciiNoCase(loginTmp.server, "ftp:" ) || startsWithAsciiNoCase(loginTmp.server, "ftps:" ) || startsWithAsciiNoCase(loginTmp.server, "sftp:" )) - loginTmp.server = afterFirst(loginTmp.server, Zstr(':'), IF_MISSING_RETURN_NONE); + loginTmp.server = afterFirst(loginTmp.server, Zstr(':'), IfNotFoundReturn::none); trim(loginTmp.server, true, true, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); }); return makeSharedRef<FtpFileSystem>(loginTmp); @@ -2229,33 +2252,33 @@ AbstractPath fff::createItemPathFtp(const Zstring& itemPathPhrase) //noexcept pathPhrase = pathPhrase.c_str() + strLength(ftpPrefix); trim(pathPhrase, true, false, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); }); - const Zstring credentials = beforeFirst(pathPhrase, Zstr('@'), IF_MISSING_RETURN_NONE); - const Zstring fullPathOpt = afterFirst(pathPhrase, Zstr('@'), IF_MISSING_RETURN_ALL); + const Zstring credentials = beforeFirst(pathPhrase, Zstr('@'), IfNotFoundReturn::none); + const Zstring fullPathOpt = afterFirst(pathPhrase, Zstr('@'), IfNotFoundReturn::all); FtpLogin login; - login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IF_MISSING_RETURN_ALL)); //support standard FTP syntax, even though ':' - login.password = afterFirst(credentials, Zstr(':'), IF_MISSING_RETURN_NONE); //is not used by concatenateFtpFolderPathPhrase()! + login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IfNotFoundReturn::all)); //support standard FTP syntax, even though ':' + login.password = afterFirst(credentials, Zstr(':'), IfNotFoundReturn::none); //is not used by concatenateFtpFolderPathPhrase()! - const Zstring fullPath = beforeFirst(fullPathOpt, Zstr('|'), IF_MISSING_RETURN_ALL); - const Zstring options = afterFirst(fullPathOpt, Zstr('|'), IF_MISSING_RETURN_NONE); + const Zstring fullPath = beforeFirst(fullPathOpt, Zstr('|'), IfNotFoundReturn::all); + const Zstring options = afterFirst(fullPathOpt, Zstr('|'), IfNotFoundReturn::none); auto it = std::find_if(fullPath.begin(), fullPath.end(), [](Zchar c) { return c == '/' || c == '\\'; }); const Zstring serverPort(fullPath.begin(), it); const AfsPath serverRelPath = sanitizeDeviceRelativePath({ it, fullPath.end() }); - login.server = beforeLast(serverPort, Zstr(':'), IF_MISSING_RETURN_ALL); - const Zstring port = afterLast(serverPort, Zstr(':'), IF_MISSING_RETURN_NONE); + login.server = beforeLast(serverPort, Zstr(':'), IfNotFoundReturn::all); + const Zstring port = afterLast(serverPort, Zstr(':'), IfNotFoundReturn::none); login.port = stringTo<int>(port); //0 if empty if (!options.empty()) { - for (const Zstring& optPhrase : split(options, Zstr("|"), SplitType::SKIP_EMPTY)) + for (const Zstring& optPhrase : split(options, Zstr("|"), SplitOnEmpty::skip)) if (startsWith(optPhrase, Zstr("timeout="))) - login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr("="), IF_MISSING_RETURN_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("="), IF_MISSING_RETURN_NONE)); + login.password = decodePasswordBase64(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none)); else assert(false); } //fix "-Wdangling-else" diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp index d2c45381..9e518625 100644 --- a/FreeFileSync/Source/afs/gdrive.cpp +++ b/FreeFileSync/Source/afs/gdrive.cpp @@ -26,7 +26,6 @@ #include "init_curl_libssh2.h" #include "../base/resolve_path.h" - using namespace zen; using namespace fff; using AFS = AbstractFileSystem; @@ -45,16 +44,19 @@ struct GdriveRawPath std::string parentId; //Google Drive item IDs are *globally* unique! Zstring itemName; }; -bool operator<(const GdriveRawPath& lhs, const GdriveRawPath& rhs) +inline +std::weak_ordering operator<=>(const GdriveRawPath& lhs, const GdriveRawPath& rhs) { - if (const int rv = compareString(lhs.parentId, rhs.parentId); - rv != 0) - return rv < 0; + if (const std::strong_ordering cmp = lhs.parentId <=> rhs.parentId; + cmp != std::strong_ordering::equal) + return cmp; - return compareNativePath(lhs.itemName, rhs.itemName) < 0; + return compareNativePath(lhs.itemName, rhs.itemName) <=> 0; } -Global<PathAccessLocker<GdriveRawPath>> globalGdrivePathAccessLocker(std::make_unique<PathAccessLocker<GdriveRawPath>>()); +constinit2 Global<PathAccessLocker<GdriveRawPath>> globalGdrivePathAccessLocker; +GLOBAL_RUN_ONCE(globalGdrivePathAccessLocker.set(std::make_unique<PathAccessLocker<GdriveRawPath>>())); + template <> std::shared_ptr<PathAccessLocker<GdriveRawPath>> PathAccessLocker<GdriveRawPath>::getGlobalInstance() { return globalGdrivePathAccessLocker.get(); } template <> Zstring PathAccessLocker<GdriveRawPath>::getItemName(const GdriveRawPath& nativePath) { return nativePath.itemName; } @@ -96,10 +98,10 @@ struct HttpSessionId Zstring server; }; -bool operator<(const HttpSessionId& lhs, const HttpSessionId& rhs) +std::weak_ordering operator<=>(const HttpSessionId& lhs, const HttpSessionId& rhs) { //exactly the type of case insensitive comparison we need for server names! - return compareAsciiNoCase(lhs.server, rhs.server) < 0; //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs + return compareAsciiNoCase(lhs.server, rhs.server) <=> 0; //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs } @@ -128,7 +130,7 @@ std::wstring getGdriveDisplayPath(const GdrivePath& gdrivePath) } -std::wstring formatGdriveErrorRaw(const std::string& serverResponse) +std::wstring formatGdriveErrorRaw(std::string serverResponse) { /* e.g.: { "error": { "errors": [{ "domain": "global", "reason": "invalidSharingRequest", @@ -140,6 +142,12 @@ std::wstring formatGdriveErrorRaw(const std::string& serverResponse) "error_description": "Unauthorized" } or merely: { "error": "invalid_token" } */ + trim(serverResponse); + + assert(!serverResponse.empty()); + if (serverResponse.empty()) + return L"<" + _("empty") + L">"; //at least give some indication + try { const JsonValue jresponse = parseJson(serverResponse); //throw JsonParsingError @@ -158,16 +166,13 @@ std::wstring formatGdriveErrorRaw(const std::string& serverResponse) } catch (JsonParsingError&) {} //not JSON? - assert(false); - if (trimCpy(serverResponse).empty()) - return L"<" + _("empty") + L">"; //at least give some indication - return utfTo<std::wstring>(serverResponse); } //---------------------------------------------------------------------------------------------------------------- -Global<UniSessionCounter> httpSessionCount(createUniSessionCounter()); +constinit2 Global<UniSessionCounter> httpSessionCount; +GLOBAL_RUN_ONCE(httpSessionCount.set(createUniSessionCounter())); UniInitializer startupInitHttp(*httpSessionCount.get()); //---------------------------------------------------------------------------------------------------------------- @@ -175,19 +180,13 @@ UniInitializer startupInitHttp(*httpSessionCount.get()); class HttpSessionManager //reuse (healthy) HTTP sessions globally { public: - HttpSessionManager(const Zstring& caCertFilePath) : caCertFilePath_(caCertFilePath), + explicit HttpSessionManager(const Zstring& caCertFilePath) : caCertFilePath_(caCertFilePath), sessionCleaner_([this] { - setCurrentThreadName("Session Cleaner[HTTP]"); - runGlobalSessionCleanUp(); //throw ThreadInterruption + setCurrentThreadName(Zstr("Session Cleaner[HTTP]")); + runGlobalSessionCleanUp(); //throw ThreadStopRequest }) {} - ~HttpSessionManager() - { - sessionCleaner_.interrupt(); - sessionCleaner_.join(); - } - void access(const HttpSessionId& login, const std::function<void(HttpSession& session)>& useHttpSession /*throw X*/) //throw SysError, X { Protected<HttpSessionManager::IdleHttpSessions>& sessionStore = getSessionStore(login); @@ -248,7 +247,7 @@ private: //run a dedicated clean-up thread => it's unclear when the server let's a connection time out, so we do it preemptively //context of worker thread: - void runGlobalSessionCleanUp() //throw ThreadInterruption + void runGlobalSessionCleanUp() //throw ThreadStopRequest { std::chrono::steady_clock::time_point lastCleanupTime; for (;;) @@ -256,7 +255,7 @@ private: const auto now = std::chrono::steady_clock::now(); if (now < lastCleanupTime + HTTP_SESSION_CLEANUP_INTERVAL) - interruptibleSleep(lastCleanupTime + HTTP_SESSION_CLEANUP_INTERVAL - now); //throw ThreadInterruption + interruptibleSleep(lastCleanupTime + HTTP_SESSION_CLEANUP_INTERVAL - now); //throw ThreadStopRequest lastCleanupTime = std::chrono::steady_clock::now(); @@ -293,7 +292,7 @@ private: }; //-------------------------------------------------------------------------------------- -Global<HttpSessionManager> globalHttpSessionManager; //caveat: life time must be subset of static UniInitializer! +constinit2 Global<HttpSessionManager> globalHttpSessionManager; //caveat: life time must be subset of static UniInitializer! //-------------------------------------------------------------------------------------- @@ -575,10 +574,10 @@ for (;;) //::accept() blocks forever if no client connects (e.g. user just close if (contains(reqLine, "\r\n")) { - reqLine = beforeFirst(reqLine, "\r\n", IF_MISSING_RETURN_NONE); + reqLine = beforeFirst(reqLine, "\r\n", IfNotFoundReturn::none); break; } - if (bytesReceived == 0 || reqLine.size() >= 100000 /*bogus line length*/) + if (bytesReceived == 0 || reqLine.size() >= 100'000 /*bogus line length*/) break; } @@ -587,11 +586,11 @@ for (;;) //::accept() blocks forever if no client connects (e.g. user just close std::string error; //parse header; e.g.: GET http://127.0.0.1:62054/?code=4/ZgBRsB9k68sFzc1Pz1q0__Kh17QK1oOmetySrGiSliXt6hZtTLUlYzm70uElNTH9vt1OqUMzJVeFfplMsYsn4uI HTTP/1.1 - const std::vector<std::string> statusItems = split(reqLine, ' ', SplitType::ALLOW_EMPTY); //Method SP Request-URI SP HTTP-Version CRLF + const std::vector<std::string> statusItems = split(reqLine, ' ', SplitOnEmpty::allow); //Method SP Request-URI SP HTTP-Version CRLF if (statusItems.size() == 3 && statusItems[0] == "GET" && startsWith(statusItems[2], "HTTP/")) { - for (const auto& [name, value] : xWwwFormUrlDecode(afterFirst(statusItems[1], "?", IF_MISSING_RETURN_NONE))) + for (const auto& [name, value] : xWwwFormUrlDecode(afterFirst(statusItems[1], "?", IfNotFoundReturn::none))) if (name == "code") code = value; else if (name == "error") @@ -800,22 +799,14 @@ FileOwner owner = FileOwner::none; //------------------------ std::string targetId; //for GdriveItemType::shortcut: https://developers.google.com/drive/api/v3/shortcuts std::vector<std::string> parentIds; + +bool operator==(const GdriveItemDetails&) const = default; }; -bool operator==(const GdriveItemDetails& lhs, const GdriveItemDetails& rhs) -{ -return lhs.itemName == rhs.itemName && - lhs.fileSize == rhs.fileSize && - lhs.modTime == rhs.modTime && - lhs.type == rhs.type && - lhs.owner == rhs.owner && - lhs.targetId == rhs.targetId && - lhs.parentIds == rhs.parentIds; -} GdriveItemDetails extractItemDetails(JsonValue jvalue) //throw SysError { -assert(jvalue.type == JsonValue::Type::object); + assert(jvalue.type == JsonValue::Type::object); /**/ std::optional<std::string> itemName = getPrimitiveFromJsonObject(jvalue, "name"); const std::optional<std::string> mimeType = getPrimitiveFromJsonObject(jvalue, "mimeType"); @@ -836,7 +827,7 @@ assert(jvalue.type == JsonValue::Type::object); const uint64_t fileSize = size ? stringTo<uint64_t>(*size) : 0; //not available for folders and shortcuts //RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z" - const TimeComp tc = parseTime("%Y-%m-%dT%H:%M:%S", beforeLast(*modifiedTime, '.', IF_MISSING_RETURN_ALL)); + const TimeComp tc = parseTime("%Y-%m-%dT%H:%M:%S", beforeLast(*modifiedTime, '.', IfNotFoundReturn::all)); if (tc == TimeComp() || !endsWith(*modifiedTime, 'Z')) //'Z' means "UTC" => it seems Google doesn't use the time-zone offset postfix throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L')'); @@ -1195,11 +1186,12 @@ std::string /*folderId*/ gdriveCreateFolderPlain(const Zstring& folderName, cons { "supportsAllDrives", "true" }, { "fields", "id" }, }); - const std::string& postBuf = std::string("{\n") + - "\"mimeType\": \"" + gdriveFolderMimeType + "\",\n" - "\"name\": \"" + utfTo<std::string>(folderName) + "\",\n" - "\"parents\": [\"" + parentId + "\"]\n" //[!] no trailing comma! - "}"; + JsonValue postParams(JsonValue::Type::object); + postParams.objectVal.emplace("mimeType", gdriveFolderMimeType); + postParams.objectVal.emplace("name", utfTo<std::string>(folderName)); + postParams.objectVal.emplace("parents", std::vector<JsonValue> { JsonValue(parentId) }); + const std::string& postBuf = serializeJson(postParams, "" /*lineBreak*/, "" /*indent*/); + std::string response; gdriveHttpsRequest("/drive/v3/files?" + queryParams, { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, @@ -1227,12 +1219,16 @@ std::string /*shortcutId*/ gdriveCreateShortcutPlain(const Zstring& shortcutName { "supportsAllDrives", "true" }, { "fields", "id" }, }); - const std::string& postBuf = std::string("{\n") + - "\"mimeType\": \"" + gdriveShortcutMimeType + "\",\n" - "\"name\": \"" + utfTo<std::string>(shortcutName) + "\",\n" - "\"shortcutDetails\": { \"targetId\": \"" + targetId + "\" },\n" - "\"parents\": [\"" + parentId + "\"]\n" //[!] no trailing comma! - "}"; + JsonValue shortcutDetails(JsonValue::Type::object); + shortcutDetails.objectVal.emplace("targetId", targetId); + + JsonValue postParams(JsonValue::Type::object); + postParams.objectVal.emplace("mimeType", gdriveShortcutMimeType); + postParams.objectVal.emplace("name", utfTo<std::string>(shortcutName)); + postParams.objectVal.emplace("parents", std::vector<JsonValue> { JsonValue(parentId) }); + postParams.objectVal.emplace("shortcutDetails", std::move(shortcutDetails)); + const std::string& postBuf = serializeJson(postParams, "" /*lineBreak*/, "" /*indent*/); + std::string response; gdriveHttpsRequest("/drive/v3/files?" + queryParams, { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, @@ -1267,11 +1263,12 @@ std::string /*fileId*/ gdriveCopyFile(const std::string& fileId, const std::stri if (modTimeRfc.empty()) throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(newModTime) + L')'); - const std::string& postBuf = std::string("{\n") + - "\"name\": \"" + utfTo<std::string>(newName) + "\",\n" + - "\"parents\": [\"" + parentIdTo + "\"],\n" + - "\"modifiedTime\": \"" + modTimeRfc + "\"\n" + //[!] no trailing comma! - "}"; + JsonValue postParams(JsonValue::Type::object); + postParams.objectVal.emplace("name", utfTo<std::string>(newName)); + postParams.objectVal.emplace("parents", std::vector<JsonValue> { JsonValue(parentIdTo) }); + postParams.objectVal.emplace("modifiedTime", modTimeRfc); + const std::string& postBuf = serializeJson(postParams, "" /*lineBreak*/, "" /*indent*/); + std::string response; gdriveHttpsRequest("/drive/v3/files/" + fileId + "/copy?" + queryParams, //throw SysError { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, @@ -1315,10 +1312,11 @@ void gdriveMoveAndRenameItem(const std::string& itemId, const std::string& paren if (modTimeRfc.empty()) throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(newModTime) + L')'); - const std::string& postBuf = std::string("{\n") + - "\"name\": \"" + utfTo<std::string>(newName) + "\",\n" + - "\"modifiedTime\": \"" + modTimeRfc + "\"\n" + //[!] no trailing comma! - "}"; + JsonValue postParams(JsonValue::Type::object); + postParams.objectVal.emplace("name", utfTo<std::string>(newName)); + postParams.objectVal.emplace("modifiedTime", modTimeRfc); + const std::string& postBuf = serializeJson(postParams, "" /*lineBreak*/, "" /*indent*/); + std::string response; gdriveHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, //throw SysError { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, @@ -1373,34 +1371,75 @@ void setModTime(const std::string& itemId, time_t modTime, const std::string& ac #endif -void gdriveDownloadFile(const std::string& fileId, const std::function<void(const void* buffer, size_t bytesToWrite)>& writeBlock /*throw X*/, //throw SysError, X - const std::string& accessToken) +DEFINE_NEW_SYS_ERROR(SysErrorAbusiveFile) +void gdriveDownloadFileImpl(const std::string& fileId, const std::function<void(const void* buffer, size_t bytesToWrite)>& writeBlock /*throw X*/, //throw SysError, SysErrorAbusiveFile, X + bool acknowledgeAbuse, const std::string& accessToken) { //https://developers.google.com/drive/api/v3/manage-downloads //doesn't work for Google-specific file types (.gdoc, .gsheet, .gslides) // => interesting: Google Backup & Sync still "downloads" them but in some URL-file format: // {"url": "https://docs.google.com/open?id=FILE_ID", "doc_id": "FILE_ID", "email": "ACCOUNT_EMAIL"} - warn_static("acknowledgeAbuse: => fix https://freefilesync.org/forum/viewtopic.php?t=7520") - - const std::string& queryParams = xWwwFormUrlEncode( + std::string queryParams = xWwwFormUrlEncode( { { "supportsAllDrives", "true" }, -// { "acknowledgeAbuse", "true" }, { "alt", "media" }, }); - std::string response; + if (acknowledgeAbuse) //apply on demand only! https://freefilesync.org/forum/viewtopic.php?t=7520") + queryParams += '&' + xWwwFormUrlEncode({ { "acknowledgeAbuse", "true" } }); + + std::string responseHead; //save front part of the response in case we get an error + bool headFlushed = false; + const HttpSession::Result httpResult = gdriveHttpsRequest("/drive/v3/files/" + fileId + '?' + queryParams, //throw SysError, X { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, [&](const void* buffer, size_t bytesToWrite) { - writeBlock(buffer, bytesToWrite); //throw X - if (response.size() < 10000) //always save front part of the response in case we get an error - response.append(static_cast<const char*>(buffer), bytesToWrite); + if (responseHead.size() < 10000) //don't access writeBlock() in case of error! (=> support acknowledgeAbuse retry handling) + responseHead.append(static_cast<const char*>(buffer), bytesToWrite); + else + { + if (!headFlushed) + { + headFlushed = true; + writeBlock(responseHead.c_str(), responseHead.size()); //throw X + } + + writeBlock(buffer, bytesToWrite); //throw X + } }, nullptr /*readRequest*/); if (httpResult.statusCode / 100 != 2) - throw SysError(formatGdriveErrorRaw(response)); + { + /* https://freefilesync.org/forum/viewtopic.php?t=7463 => HTTP status code 403 + body: + { "error": { "errors": [{ "domain": "global", + "reason": "cannotDownloadAbusiveFile", + "message": "This file has been identified as malware or spam and cannot be downloaded." }], + "code": 403, + "message": "This file has been identified as malware or spam and cannot be downloaded." }} + */ + if (!headFlushed && httpResult.statusCode == 403 && contains(responseHead, "\"cannotDownloadAbusiveFile\"")) + throw SysErrorAbusiveFile(formatGdriveErrorRaw(responseHead)); + + throw SysError(formatGdriveErrorRaw(responseHead)); + } + + if (!headFlushed) + writeBlock(responseHead.c_str(), responseHead.size()); //throw X +} + + +void gdriveDownloadFile(const std::string& fileId, const std::function<void(const void* buffer, size_t bytesToWrite)>& writeBlock /*throw X*/, //throw SysError, X + const std::string& accessToken) +{ + try + { + gdriveDownloadFileImpl(fileId, writeBlock /*throw X*/, false /*acknowledgeAbuse*/, accessToken); //throw SysError, SysErrorAbusiveFile, X + } + catch (SysErrorAbusiveFile&) + { + gdriveDownloadFileImpl(fileId, writeBlock /*throw X*/, true /*acknowledgeAbuse*/, accessToken); //throw SysError, (SysErrorAbusiveFile), X + } } @@ -1415,18 +1454,18 @@ std::string /*itemId*/ gdriveUploadSmallFile(const Zstring& fileName, const std: //https://developers.google.com/drive/api/v3/folder#inserting_a_file_in_a_folder //https://developers.google.com/drive/api/v3/manage-uploads#http_1 - std::string metaDataBuf = "{\n"; + JsonValue postParams(JsonValue::Type::object); + postParams.objectVal.emplace("name", utfTo<std::string>(fileName)); + 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 if (modTimeRfc.empty()) throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(*modTime) + L')'); - metaDataBuf += "\"modifiedTime\": \"" + modTimeRfc + "\",\n"; + postParams.objectVal.emplace("modifiedTime", modTimeRfc); } - metaDataBuf += "\"name\": \"" + utfTo<std::string>(fileName) + "\",\n"; - metaDataBuf += "\"parents\": [\"" + parentId + "\"]\n"; //[!] no trailing comma! - metaDataBuf += "}"; + const std::string& metaDataBuf = serializeJson(postParams, "" /*lineBreak*/, "" /*indent*/); //allowed chars for border: DIGIT ALPHA ' ( ) + _ , - . / : = ? const std::string boundaryString = stringEncodeBase64(generateGUID() + generateGUID()); @@ -1520,18 +1559,24 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri //step 1: initiate resumable upload session std::string uploadUrlRelative; { - std::string postBuf = "{\n"; + const std::string& queryParams = xWwwFormUrlEncode( + { + { "supportsAllDrives", "true" }, + { "uploadType", "resumable" }, + }); + JsonValue postParams(JsonValue::Type::object); + postParams.objectVal.emplace("name", utfTo<std::string>(fileName)); + 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 if (modTimeRfc.empty()) throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(*modTime) + L')'); - postBuf += "\"modifiedTime\": \"" + modTimeRfc + "\",\n"; + postParams.objectVal.emplace("modifiedTime", modTimeRfc); } - postBuf += "\"name\": \"" + utfTo<std::string>(fileName) + "\",\n"; - postBuf += "\"parents\": [\"" + parentId + "\"]\n"; //[!] no trailing comma! - postBuf += "}"; + const std::string& postBuf = serializeJson(postParams, "" /*lineBreak*/, "" /*indent*/); + //--------------------------------------------------- std::string uploadUrl; @@ -1542,7 +1587,7 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri if (startsWithAsciiNoCase(std::string_view(buffer, len), "Location:")) { uploadUrl.assign(buffer, len); //not null-terminated! - uploadUrl = afterFirst(uploadUrl, ':', IF_MISSING_RETURN_NONE); + uploadUrl = afterFirst(uploadUrl, ':', IfNotFoundReturn::none); trim(uploadUrl); } return len; @@ -1554,11 +1599,6 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri return (*callbackData)(buffer, size * nitems); //free this poor little C-API from its shackles and redirect to a proper lambda }; - const std::string& queryParams = xWwwFormUrlEncode( - { - { "supportsAllDrives", "true" }, - { "uploadType", "resumable" }, - }); std::string response; const HttpSession::Result httpResult = gdriveHttpsRequest("/upload/drive/v3/files?" + queryParams, //throw SysError { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" }, @@ -1571,7 +1611,7 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri if (!startsWith(uploadUrl, "https://www.googleapis.com/")) throw SysError(L"Invalid upload URL: " + utfTo<std::wstring>(uploadUrl)); //user should never see this - uploadUrlRelative = afterFirst(uploadUrl, "googleapis.com", IF_MISSING_RETURN_NONE); + uploadUrlRelative = afterFirst(uploadUrl, "googleapis.com", IfNotFoundReturn::none); } //--------------------------------------------------- //step 2: upload file content @@ -1834,7 +1874,7 @@ class GdrivePersistentSessions; return itFound->first; }(); - const std::vector<Zstring> relPath = split(afsPath.value, FILE_NAME_SEPARATOR, SplitType::SKIP_EMPTY); + const std::vector<Zstring> relPath = split(afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip); if (relPath.empty()) return { driveId, GdriveItemType::folder, AfsPath(), {} }; else @@ -2281,17 +2321,7 @@ public: try { const Zstring dbFilePath = getDbFilePath(holder.session->accessBuf.ref().getUserEmail()); - - //generate (hopefully) unique file name to avoid clashing with unrelated tmp file (concurrent FFS shutdown!) - const Zstring shortGuid = printNumber<Zstring>(Zstr("%04x"), static_cast<unsigned int>(getCrc16(generateGUID()))); - const Zstring dbFilePathTmp = dbFilePath + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING; - - ZEN_ON_SCOPE_FAIL(try { removeFilePlain(dbFilePathTmp); } - catch (FileError&) {}); - - saveSession(dbFilePathTmp, *holder.session); //throw FileError - - moveAndRenameItem(dbFilePathTmp, dbFilePath, true /*replaceExisting*/); //throw FileError, (ErrorMoveUnsupported), (ErrorTargetExisting) + saveSession(dbFilePath, *holder.session); //throw FileError } catch (FileError&) { if (!firstError) firstError = std::current_exception(); } }); @@ -2377,7 +2407,7 @@ public: //also include available, but not-yet-loaded sessions traverseFolder(configDirPath_, - [&](const FileInfo& fi) { if (endsWith(fi.itemName, Zstr(".db"))) emails.push_back(utfTo<std::string>(beforeLast(fi.itemName, Zstr('.'), IF_MISSING_RETURN_NONE))); }, + [&](const FileInfo& fi) { if (endsWith(fi.itemName, Zstr(".db"))) emails.push_back(utfTo<std::string>(beforeLast(fi.itemName, Zstr('.'), IfNotFoundReturn::none))); }, [&](const FolderInfo& fi) {}, [&](const SymlinkInfo& si) {}, [&](const std::wstring& errorMsg) @@ -2470,7 +2500,7 @@ private: } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(dbFilePath)), e.toString()); } - saveBinContainer(dbFilePath, streamOut.ref(), nullptr /*notifyUnbufferedIO*/); //throw FileError + setFileContent(dbFilePath, streamOut.ref(), nullptr /*notifyUnbufferedIO*/); //throw FileError } static std::optional<UserSession> loadSession(const Zstring& dbFilePath) //throw FileError @@ -2478,7 +2508,7 @@ private: std::string byteStream; try { - byteStream = loadBinContainer<std::string>(dbFilePath, nullptr /*notifyUnbufferedIO*/); //throw FileError + byteStream = getFileContent(dbFilePath, nullptr /*notifyUnbufferedIO*/); //throw FileError } catch (FileError&) { @@ -2562,7 +2592,7 @@ private: const Zstring configDirPath_; }; //========================================================================================== -Global<GdrivePersistentSessions> globalGdriveSessions; +constinit2 Global<GdrivePersistentSessions> globalGdriveSessions; //========================================================================================== GdrivePersistentSessions::AsyncAccessInfo accessGlobalFileState(const std::string& accountEmail, const std::function<void(GdriveFileState& fileState)>& useFileState /*throw X*/) //throw SysError, X @@ -2768,7 +2798,7 @@ struct InputStreamGdrive : public AbstractFileSystem::InputStream { worker_ = InterruptibleThread([asyncStreamOut = this->asyncStreamIn_, gdrivePath] { - setCurrentThreadName(("Istream[Gdrive] " + utfTo<std::string>(getGdriveDisplayPath(gdrivePath))). c_str()); + setCurrentThreadName(Zstr("Istream[Gdrive] ") + utfTo<Zstring>(getGdriveDisplayPath(gdrivePath))); try { std::string accessToken; @@ -2786,23 +2816,22 @@ struct InputStreamGdrive : public AbstractFileSystem::InputStream { auto writeBlock = [&](const void* buffer, size_t bytesToWrite) { - return asyncStreamOut->write(buffer, bytesToWrite); //throw ThreadInterruption + return asyncStreamOut->write(buffer, bytesToWrite); //throw ThreadStopRequest }; - gdriveDownloadFile(fileId, writeBlock, accessToken); //throw SysError, ThreadInterruption + gdriveDownloadFile(fileId, writeBlock, accessToken); //throw SysError, ThreadStopRequest } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getGdriveDisplayPath(gdrivePath))), e.toString()); } asyncStreamOut->closeStream(); } - catch (FileError&) { asyncStreamOut->setWriteError(std::current_exception()); } //let ThreadInterruption pass through! + catch (FileError&) { asyncStreamOut->setWriteError(std::current_exception()); } //let ThreadStopRequest pass through! }); } ~InputStreamGdrive() { - asyncStreamIn_->setReadError(std::make_exception_ptr(ThreadInterruption())); - worker_.join(); + asyncStreamIn_->setReadError(std::make_exception_ptr(ThreadStopRequest())); } size_t read(void* buffer, size_t bytesToRead) override //throw FileError, (ErrorFileLocked), X; return "bytesToRead" bytes unless end of stream! @@ -2885,19 +2914,19 @@ struct OutputStreamGdrive : public AbstractFileSystem::OutputStreamImpl pal = std::move(pal)]() mutable { assert(pal); //bind life time to worker thread! - setCurrentThreadName(("Ostream[Gdrive] " + utfTo<std::string>(getGdriveDisplayPath(gdrivePath))). c_str()); + setCurrentThreadName(Zstr("Ostream[Gdrive] ") + utfTo<Zstring>(getGdriveDisplayPath(gdrivePath))); try { auto readBlock = [&](void* buffer, size_t bytesToRead) { //returns "bytesToRead" bytes unless end of stream! => maps nicely into Posix read() semantics expected by gdriveUploadFile() - return asyncStreamIn->read(buffer, bytesToRead); //throw ThreadInterruption + return asyncStreamIn->read(buffer, bytesToRead); //throw ThreadStopRequest }; //for whatever reason, gdriveUploadFile() is slightly faster than gdriveUploadSmallFile()! despite its two roundtrips! even when file sizes are 0! //=> 1. issue likely on Google's side => 2. persists even after having fixed "Expect: 100-continue" const std::string fileIdNew = //streamSize && *streamSize < 5 * 1024 * 1024 ? - //gdriveUploadSmallFile(fileName, parentId, *streamSize, modTime, readBlock, aai.accessToken) : //throw SysError, ThreadInterruption - gdriveUploadFile (fileName, parentId, modTime, readBlock, aai.accessToken); //throw SysError, ThreadInterruption + //gdriveUploadSmallFile(fileName, parentId, *streamSize, modTime, readBlock, aai.accessToken) : //throw SysError, ThreadStopRequest + gdriveUploadFile (fileName, parentId, modTime, readBlock, aai.accessToken); //throw SysError, ThreadStopRequest assert(asyncStreamIn->getTotalBytesRead() == asyncStreamIn->getTotalBytesWritten()); //already existing: creates duplicate @@ -2922,19 +2951,17 @@ struct OutputStreamGdrive : public AbstractFileSystem::OutputStreamImpl catch (const SysError& e) { FileError fe(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getGdriveDisplayPath(gdrivePath))), e.toString()); - asyncStreamIn->setReadError(std::make_exception_ptr(std::move(fe))); + const std::exception_ptr exptr = std::make_exception_ptr(std::move(fe)); + asyncStreamIn->setReadError(exptr); //set both! + pFileId.set_exception(exptr); // } - //let ThreadInterruption pass through! + //let ThreadStopRequest pass through! }); } ~OutputStreamGdrive() { - if (worker_.joinable()) - { - asyncStreamOut_->setWriteError(std::make_exception_ptr(ThreadInterruption())); - worker_.join(); - } + asyncStreamOut_->setWriteError(std::make_exception_ptr(ThreadStopRequest())); } void write(const void* buffer, size_t bytesToWrite) override //throw FileError, X @@ -2947,15 +2974,16 @@ struct OutputStreamGdrive : public AbstractFileSystem::OutputStreamImpl { asyncStreamOut_->closeStream(); - while (!worker_.tryJoinFor(std::chrono::milliseconds(50))) + while (futFileId_.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout) reportBytesProcessed(); //throw X reportBytesProcessed(); //[!] once more, now that *all* bytes were written asyncStreamOut_->checkReadErrors(); //throw FileError //-------------------------------------------------------------------- + AFS::FinalizeResult result; - assert(isReady(futFileId_)); //*must* be available since file creation completed successfully at this point - result.fileId = futFileId_.get(); + assert(isReady(futFileId_)); + result.fileId = futFileId_.get(); //throw FileError //result.errorModTime -> already (successfully) set during file creation return result; } @@ -2972,7 +3000,7 @@ private: int64_t totalBytesReported_ = 0; std::shared_ptr<AsyncStreamBuffer> asyncStreamOut_ = std::make_shared<AsyncStreamBuffer>(GDRIVE_STREAM_BUFFER_SIZE); InterruptibleThread worker_; - std::future<AFS::FileId> futFileId_; //"play it safe", however with our current access pattern, also could have used an unprotected AFS::FileId + std::future<AFS::FileId> futFileId_; }; //========================================================================================== @@ -2980,7 +3008,7 @@ private: class GdriveFileSystem : public AbstractFileSystem { public: - GdriveFileSystem(const GdriveLogin& gdriveLogin) : gdriveLogin_(gdriveLogin) {} + explicit GdriveFileSystem(const GdriveLogin& gdriveLogin) : gdriveLogin_(gdriveLogin) {} const GdriveLogin& getGdriveLogin() const { return gdriveLogin_; } @@ -3641,11 +3669,11 @@ AbstractPath fff::createItemPathGdrive(const Zstring& itemPathPhrase) //noexcept const AfsPath& sanPath = sanitizeDeviceRelativePath(path); //Win/macOS compatibility: let's ignore slash/backslash differences - const Zstring& accountEmailAndDrive = beforeFirst(sanPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); - const AfsPath afsPath (afterFirst(sanPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); + const Zstring& accountEmailAndDrive = beforeFirst(sanPath.value, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + const AfsPath afsPath (afterFirst(sanPath.value, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); - const Zstring& accountEmail = beforeFirst(accountEmailAndDrive, Zstr(':'), IF_MISSING_RETURN_ALL); - const Zstring& sharedDrive = afterFirst (accountEmailAndDrive, Zstr(':'), IF_MISSING_RETURN_NONE); + const Zstring& accountEmail = beforeFirst(accountEmailAndDrive, Zstr(':'), IfNotFoundReturn::all); + const Zstring& sharedDrive = afterFirst (accountEmailAndDrive, Zstr(':'), IfNotFoundReturn::none); return AbstractPath(makeSharedRef<GdriveFileSystem>(GdriveLogin{utfTo<std::string>(accountEmail), sharedDrive}), afsPath); } diff --git a/FreeFileSync/Source/afs/init_curl_libssh2.cpp b/FreeFileSync/Source/afs/init_curl_libssh2.cpp index d1645ee1..11f5e502 100644 --- a/FreeFileSync/Source/afs/init_curl_libssh2.cpp +++ b/FreeFileSync/Source/afs/init_curl_libssh2.cpp @@ -23,7 +23,7 @@ int uniInitLevel = 0; //support interleaving initialization calls! (e.g. use for void libsshCurlUnifiedInit() { - assert(runningMainThread()); //all OpenSSL/libssh2/libcurl require init on main thread! + assert(runningOnMainThread()); //all OpenSSL/libssh2/libcurl require init on main thread! assert(uniInitLevel >= 0); if (++uniInitLevel != 1) //non-atomic => also require call from main thread return; @@ -50,7 +50,7 @@ void libsshCurlUnifiedInit() void libsshCurlUnifiedTearDown() { - assert(runningMainThread()); //symmetry with libsshCurlUnifiedInit + assert(runningOnMainThread()); //symmetry with libsshCurlUnifiedInit assert(uniInitLevel >= 1); if (--uniInitLevel != 0) return; @@ -167,10 +167,8 @@ UniInitializer::~UniInitializer() { //wait until all (S)FTP sessions running on detached threads have ended! otherwise they'll crash during ::WSACleanup()! sessionCount_.pimpl->onBeforeTearDown(); - /* - alternatively we could use a Global<UniInitializer> and have each session own a shared_ptr<UniInitializer>: - drawback 1: SFTP clean-up may happen on worker thread => probably not supported!!! - drawback 2: cleanup will not happen when the C++ runtime on Windows kills all worker threads during shutdown - */ + /* alternatively we could use a Global<UniInitializer> and have each session own a shared_ptr<UniInitializer>: + drawback 1: SFTP clean-up may happen on worker thread => probably not supported!!! + drawback 2: cleanup will not happen when the C++ runtime on Windows kills all worker threads during shutdown */ libsshCurlUnifiedTearDown(); } diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp index 4ca18710..7c2877ae 100644 --- a/FreeFileSync/Source/afs/native.cpp +++ b/FreeFileSync/Source/afs/native.cpp @@ -19,7 +19,6 @@ #include "../base/icon_loader.h" - #include <cstddef> //offsetof #include <sys/stat.h> #include <dirent.h> #include <fcntl.h> //fallocate, fcntl @@ -126,7 +125,7 @@ std::vector<FsItem> getDirContentFlat(const Zstring& dirPath) //throw FileError Linux all <input> Windows (NTFS, FAT) all <input> - some file systems return precomposed others decomposed UTF8: https://developer.apple.com/library/mac/#qa/qa1173/_index.html + some file systems return precomposed others decomposed UTF8: https://developer.apple.com/library/archive/qa/qa1173/_index.html - OS X edit controls and text fields may return precomposed UTF as directly received by keyboard or decomposed UTF that was copy & pasted! - Posix APIs require decomposed form: https://freefilesync.org/forum/viewtopic.php?t=2480 @@ -322,11 +321,11 @@ struct OutputStreamNative : public AbstractFileSystem::OutputStreamImpl std::optional<uint64_t> streamSize, std::optional<time_t> modTime, const IOCallback& notifyUnbufferedIO /*throw X*/) : - fo_(FileOutput::ACC_CREATE_NEW, filePath, notifyUnbufferedIO), //throw FileError, ErrorTargetExisting + fo_(filePath, notifyUnbufferedIO), //throw FileError, ErrorTargetExisting modTime_(modTime) { - if (streamSize) //pre-allocate file space, because we can - fo_.preAllocateSpaceBestEffort(*streamSize); //throw FileError + if (streamSize) //preallocate disk space + reduce fragmentation + fo_.reserveSpace(*streamSize); //throw FileError } void write(const void* buffer, size_t bytesToWrite) override { fo_.write(buffer, bytesToWrite); } //throw FileError, X @@ -345,7 +344,7 @@ struct OutputStreamNative : public AbstractFileSystem::OutputStreamImpl try { if (modTime_) - zen::setFileTime(fo_.getFilePath(), *modTime_, ProcSymlink::FOLLOW); //throw FileError + setFileTime(fo_.getFilePath(), *modTime_, ProcSymlink::FOLLOW); //throw FileError /* is setting modtime after closing the file handle a pessimization? Native: no, needed for functional correctness, see file_access.cpp */ } diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp index 7921d89b..6d23d330 100644 --- a/FreeFileSync/Source/afs/sftp.cpp +++ b/FreeFileSync/Source/afs/sftp.cpp @@ -121,48 +121,45 @@ struct SshSessionId bool allowZlib = false; //timeoutSec, traverserChannelsPerConnection => irrelevant for session equality }; - - -bool operator<(const SshSessionId& lhs, const SshSessionId& rhs) +std::weak_ordering operator<=>(const SshSessionId& lhs, const SshSessionId& rhs) { //exactly the type of case insensitive comparison we need for server names! - if (const int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs - rv != 0) - return rv < 0; + if (const int cmp = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs + cmp != 0) + return cmp <=> 0; if (lhs.port != rhs.port) - return lhs.port < rhs.port; + return lhs.port <=> rhs.port; - if (const int rv = compareString(lhs.username, rhs.username); //case sensitive! - rv != 0) - return rv < 0; + if (const std::strong_ordering cmp = lhs.username <=> rhs.username; //case sensitive! + cmp != std::strong_ordering::equal) + return cmp; if (lhs.allowZlib != rhs.allowZlib) - return lhs.allowZlib < rhs.allowZlib; + return lhs.allowZlib <=> rhs.allowZlib; if (lhs.authType != rhs.authType) - return lhs.authType < rhs.authType; + return lhs.authType <=> rhs.authType; switch (lhs.authType) { case SftpAuthType::password: - return compareString(lhs.password, rhs.password) < 0; //case sensitive! + return lhs.password <=> rhs.password; //case sensitive! case SftpAuthType::keyFile: - if (const int rv = compareString(lhs.password, rhs.password); //case sensitive! - rv != 0) - return rv < 0; + if (const std::strong_ordering cmp = lhs.password <=> rhs.password; //case sensitive! + cmp != std::strong_ordering::equal) + return cmp; - return compareString(lhs.privateKeyFilePath, rhs.privateKeyFilePath) < 0; //case sensitive! + return lhs.privateKeyFilePath <=> rhs.privateKeyFilePath; //case sensitive! case SftpAuthType::agent: - return false; + return std::strong_ordering::equal; } assert(false); - return false; + return std::strong_ordering::equal; } - std::string getLibssh2Path(const AfsPath& afsPath) { return utfTo<std::string>(getServerRelPath(afsPath)); @@ -192,7 +189,8 @@ private: }; -Global<UniSessionCounter> globalSftpSessionCount(createUniSessionCounter()); +constinit2 Global<UniSessionCounter> globalSftpSessionCount; +GLOBAL_RUN_ONCE(globalSftpSessionCount.set(createUniSessionCounter())); class SshSession //throw SysError @@ -248,7 +246,7 @@ public: bool supportAuthPassword = false; bool supportAuthKeyfile = false; bool supportAuthInteractive = false; - for (const std::string& str : split<std::string>(authList, ',', SplitType::SKIP_EMPTY)) + for (const std::string& str : split<std::string>(authList, ',', SplitOnEmpty::skip)) { const std::string authMethod = trimCpy(str); if (authMethod == "password") @@ -325,7 +323,7 @@ public: std::string pkStream; try { - pkStream = loadBinContainer<std::string>(sessionId_.privateKeyFilePath, nullptr /*notifyUnbufferedIO*/); //throw FileError + pkStream = getFileContent(sessionId_.privateKeyFilePath, nullptr /*notifyUnbufferedIO*/); //throw FileError trim(pkStream); } catch (const FileError& e) { throw SysError(replaceCpy(e.toString(), L"\n\n", L'\n')); } //errors should be further enriched by context info => SysError @@ -722,16 +720,10 @@ class SftpSessionManager //reuse (healthy) SFTP sessions globally public: SftpSessionManager() : sessionCleaner_([this] { - setCurrentThreadName("Session Cleaner[SFTP]"); - runGlobalSessionCleanUp(); /*throw ThreadInterruption*/ + setCurrentThreadName(Zstr("Session Cleaner[SFTP]")); + runGlobalSessionCleanUp(); /*throw ThreadStopRequest*/ }) {} - ~SftpSessionManager() - { - sessionCleaner_.interrupt(); - sessionCleaner_.join(); - } - struct ReUseOnDelete { void operator()(SshSession* s) const; @@ -754,7 +746,7 @@ public: void executeBlocking(const char* functionName, const std::function<int(const SshSession::Details& sd)>& sftpCommand /*noexcept!*/) //throw SysError, FatalSshError { - assert(threadId_ == getThreadId()); + assert(threadId_ == std::this_thread::get_id()); assert(session_->getSftpChannelCount() > 0); const auto sftpCommandStartTime = std::chrono::steady_clock::now(); @@ -767,7 +759,7 @@ public: private: std::unique_ptr<SshSession, ReUseOnDelete> session_; //bound! - const uint64_t threadId_ = getThreadId(); + const std::thread::id threadId_ = std::this_thread::get_id(); const int timeoutSec_; }; @@ -839,7 +831,7 @@ public: { Protected<IdleSshSessions>& sessionStore = getSessionStore(login); - const uint64_t threadId = getThreadId(); + const std::thread::id threadId = std::this_thread::get_id(); std::shared_ptr<SshSessionShared> sharedSession; //no need to protect against concurrency: same thread! sessionStore.access([&](IdleSshSessions& sessions) @@ -924,7 +916,7 @@ private: //run a dedicated clean-up thread => it's unclear when the server let's a connection time out, so we do it preemptively //context of worker thread: - void runGlobalSessionCleanUp() //throw ThreadInterruption + void runGlobalSessionCleanUp() //throw ThreadStopRequest { std::chrono::steady_clock::time_point lastCleanupTime; for (;;) @@ -932,7 +924,7 @@ private: const auto now = std::chrono::steady_clock::now(); if (now < lastCleanupTime + SFTP_SESSION_CLEANUP_INTERVAL) - interruptibleSleep(lastCleanupTime + SFTP_SESSION_CLEANUP_INTERVAL - now); //throw ThreadInterruption + interruptibleSleep(lastCleanupTime + SFTP_SESSION_CLEANUP_INTERVAL - now); //throw ThreadStopRequest lastCleanupTime = std::chrono::steady_clock::now(); @@ -964,8 +956,8 @@ private: struct IdleSshSessions { - std::vector<std::unique_ptr<SshSession>> idleSshSessions; //extract *temporarily* from this list during use - std::map<uint64_t, 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::map<std::thread::id, std::weak_ptr<SshSessionShared>> sshSessionsWithThreadAffinity; //Win32 thread IDs may be REUSED! still, shouldn't be a problem... }; using GlobalSshSessions = std::map<SshSessionId, Protected<IdleSshSessions>>; @@ -977,7 +969,7 @@ private: //-------------------------------------------------------------------------------------- UniInitializer globalStartupInitSftp(*globalSftpSessionCount.get()); -Global<SftpSessionManager> globalSftpSessionManager; //caveat: life time must be subset of static UniInitializer! +constinit2 Global<SftpSessionManager> globalSftpSessionManager; //caveat: life time must be subset of static UniInitializer! //-------------------------------------------------------------------------------------- @@ -1369,7 +1361,7 @@ struct OutputStreamSftp : public AbstractFileSystem::OutputStreamImpl ~OutputStreamSftp() { - if (fileHandle_) //basic exception guarantee only! in general we expect a call to finalize()! + if (fileHandle_) try { close(); //throw FileError @@ -1934,7 +1926,7 @@ AfsDevice fff::condenseToSftpDevice(const SftpLogin& login) //noexcept startsWithAsciiNoCase(loginTmp.server, "ftp:" ) || startsWithAsciiNoCase(loginTmp.server, "ftps:" ) || startsWithAsciiNoCase(loginTmp.server, "sftp:" )) - loginTmp.server = afterFirst(loginTmp.server, Zstr(':'), IF_MISSING_RETURN_NONE); + loginTmp.server = afterFirst(loginTmp.server, Zstr(':'), IfNotFoundReturn::none); trim(loginTmp.server, true, true, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); }); return makeSharedRef<SftpFileSystem>(loginTmp); @@ -2004,42 +1996,42 @@ AbstractPath fff::createItemPathSftp(const Zstring& itemPathPhrase) //noexcept pathPhrase = pathPhrase.c_str() + strLength(sftpPrefix); trim(pathPhrase, true, false, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); }); - const Zstring credentials = beforeFirst(pathPhrase, Zstr('@'), IF_MISSING_RETURN_NONE); - const Zstring fullPathOpt = afterFirst(pathPhrase, Zstr('@'), IF_MISSING_RETURN_ALL); + const Zstring credentials = beforeFirst(pathPhrase, Zstr('@'), IfNotFoundReturn::none); + const Zstring fullPathOpt = afterFirst(pathPhrase, Zstr('@'), IfNotFoundReturn::all); SftpLogin login; - login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IF_MISSING_RETURN_ALL)); //support standard FTP syntax, even though ':' - login.password = afterFirst(credentials, Zstr(':'), IF_MISSING_RETURN_NONE); //is not used by our concatenateSftpFolderPathPhrase()! + login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IfNotFoundReturn::all)); //support standard FTP syntax, even though ':' + login.password = afterFirst(credentials, Zstr(':'), IfNotFoundReturn::none); //is not used by our concatenateSftpFolderPathPhrase()! - const Zstring fullPath = beforeFirst(fullPathOpt, Zstr('|'), IF_MISSING_RETURN_ALL); - const Zstring options = afterFirst(fullPathOpt, Zstr('|'), IF_MISSING_RETURN_NONE); + const Zstring fullPath = beforeFirst(fullPathOpt, Zstr('|'), IfNotFoundReturn::all); + const Zstring options = afterFirst(fullPathOpt, Zstr('|'), IfNotFoundReturn::none); auto it = std::find_if(fullPath.begin(), fullPath.end(), [](Zchar c) { return c == '/' || c == '\\'; }); const Zstring serverPort(fullPath.begin(), it); const AfsPath serverRelPath = sanitizeDeviceRelativePath({ it, fullPath.end() }); - login.server = beforeLast(serverPort, Zstr(':'), IF_MISSING_RETURN_ALL); - const Zstring port = afterLast(serverPort, Zstr(':'), IF_MISSING_RETURN_NONE); + login.server = beforeLast(serverPort, Zstr(':'), IfNotFoundReturn::all); + const Zstring port = afterLast(serverPort, Zstr(':'), IfNotFoundReturn::none); login.port = stringTo<int>(port); //0 if empty assert(login.allowZlib == false); if (!options.empty()) { - for (const Zstring& optPhrase : split(options, Zstr("|"), SplitType::SKIP_EMPTY)) + for (const Zstring& optPhrase : split(options, Zstr("|"), SplitOnEmpty::skip)) if (startsWith(optPhrase, Zstr("timeout="))) - login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr("="), IF_MISSING_RETURN_NONE)); + login.timeoutSec = stringTo<int>(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none)); else if (startsWith(optPhrase, Zstr("chan="))) - login.traverserChannelsPerConnection = stringTo<int>(afterFirst(optPhrase, Zstr("="), IF_MISSING_RETURN_NONE)); + login.traverserChannelsPerConnection = stringTo<int>(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none)); else if (startsWith(optPhrase, Zstr("keyfile="))) { login.authType = SftpAuthType::keyFile; - login.privateKeyFilePath = afterFirst(optPhrase, Zstr("="), IF_MISSING_RETURN_NONE); + login.privateKeyFilePath = 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("="), IF_MISSING_RETURN_NONE)); + login.password = decodePasswordBase64(afterFirst(optPhrase, Zstr("="), IfNotFoundReturn::none)); else if (optPhrase == Zstr("zlib")) login.allowZlib = true; else diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index f0bdaa6d..89cc655b 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -50,7 +50,7 @@ std::vector<Zstring> getCommandlineArgs(const wxApp& app) return args; } -const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); +wxDEFINE_EVENT(EVENT_ENTER_EVENT_LOOP, wxCommandEvent); } //################################################################################################################## @@ -91,9 +91,7 @@ bool Application::OnInit() (getResourceDirPf() + fileName).c_str(), //const gchar* path, &error); //GError** error if (error) - throw SysError(formatSystemError("gtk_css_provider_load_from_path", - replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)), - utfTo<std::wstring>(error->message))); + throw SysError(formatGlibError("gtk_css_provider_load_from_path", error)); ::gtk_style_context_add_provider_for_screen(::gdk_screen_get_default(), //GdkScreen* screen, GTK_STYLE_PROVIDER(provider), //GtkStyleProvider* provider, @@ -119,7 +117,7 @@ bool Application::OnInit() //Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise: wxToolTip::Enable(true); //yawn, a wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it - wxToolTip::SetAutoPop(10000); //https://docs.microsoft.com/en-us/windows/win32/uxguide/ctrl-tooltips-and-infotips + wxToolTip::SetAutoPop(10'000); //https://docs.microsoft.com/en-us/windows/win32/uxguide/ctrl-tooltips-and-infotips SetAppName(L"FreeFileSync"); //if not set, the default is the executable's name! @@ -133,11 +131,11 @@ bool Application::OnInit() initAfs({ getResourceDirPf(), getConfigDirPathPf() }); //bonus: using FTP Gdrive implicitly inits OpenSSL (used in runSanityChecks() on Linux) alredy during globals init - Connect(wxEVT_QUERY_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); //can veto - Connect(wxEVT_END_SESSION, wxEventHandler(Application::onQueryEndSession), nullptr, this); //can *not* veto + Bind(wxEVT_QUERY_END_SESSION, [this](wxCloseEvent& event) { onQueryEndSession(event); }); //can veto + Bind(wxEVT_END_SESSION, [this](wxCloseEvent& event) { onQueryEndSession(event); }); //can *not* veto //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()! - Connect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); + Bind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this); AddPendingEvent(wxCommandEvent(EVENT_ENTER_EVENT_LOOP)); return true; //true: continue processing; false: exit immediately. } @@ -157,7 +155,8 @@ wxLayoutDirection Application::GetLayoutDirection() const { return getLayoutDire void Application::onEnterEventLoop(wxEvent& event) { - Disconnect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this); + [[maybe_unused]] bool ubOk = Unbind(EVENT_ENTER_EVENT_LOOP, &Application::onEnterEventLoop, this); + assert(ubOk); launch(getCommandlineArgs(*this)); //determine FFS mode of operation } @@ -176,7 +175,7 @@ void Application::OnUnhandledException() //handles both wxApp::OnInit() + wxApp: { try { - throw; //just re-throw and avoid display of additional messagebox + throw; //just re-throw } catch (const std::bad_alloc& e) //the only kind of exception we don't want crash dumps for { diff --git a/FreeFileSync/Source/base/algorithm.cpp b/FreeFileSync/Source/base/algorithm.cpp index 72f7e0d8..0a70a2d0 100644 --- a/FreeFileSync/Source/base/algorithm.cpp +++ b/FreeFileSync/Source/base/algorithm.cpp @@ -1188,8 +1188,8 @@ std::optional<PathDependency> fff::getPathDependency(const AbstractPath& basePat { if (basePathL.afsDevice == basePathR.afsDevice) { - const std::vector<Zstring> relPathL = split(basePathL.afsPath.value, FILE_NAME_SEPARATOR, SplitType::SKIP_EMPTY); - const std::vector<Zstring> relPathR = split(basePathR.afsPath.value, FILE_NAME_SEPARATOR, SplitType::SKIP_EMPTY); + const std::vector<Zstring> relPathL = split(basePathL.afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip); + const std::vector<Zstring> relPathR = split(basePathR.afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip); const bool leftParent = relPathL.size() <= relPathR.size(); @@ -1637,27 +1637,6 @@ void fff::deleteFromGridAndHD(const std::vector<FileSystemObject*>& rowsToDelete //############################################################################################################ -bool fff::operator<(const FileDescriptor& lhs, const FileDescriptor& rhs) -{ - if (lhs.attr.modTime != rhs.attr.modTime) - return lhs.attr.modTime < rhs.attr.modTime; - - if (lhs.attr.fileSize != rhs.attr.fileSize) - return lhs.attr.fileSize < rhs.attr.fileSize; - - if (lhs.attr.isFollowedSymlink != rhs.attr.isFollowedSymlink) - return lhs.attr.isFollowedSymlink < rhs.attr.isFollowedSymlink; - - if (lhs.attr.fileId != rhs.attr.fileId) - return lhs.attr.fileId < rhs.attr.fileId; - - //if (!lhs.attr.fileId.empty()) - // return false; //when (non-empty) file IDs match we don't have to check the path => pre-mature optimization? - //else - return lhs.path < rhs.path; -} - - TempFileBuffer::~TempFileBuffer() { if (!tempFolderPath_.empty()) diff --git a/FreeFileSync/Source/base/algorithm.h b/FreeFileSync/Source/base/algorithm.h index 938ac955..7b2f8a84 100644 --- a/FreeFileSync/Source/base/algorithm.h +++ b/FreeFileSync/Source/base/algorithm.h @@ -79,8 +79,9 @@ struct FileDescriptor { AbstractPath path; FileAttributes attr; + + std::weak_ordering operator<=>(const FileDescriptor&) const = default; }; -bool operator<(const FileDescriptor& lhs, const FileDescriptor& rhs); //get native Win32 paths or create temporary copy for SFTP/MTP, etc. class TempFileBuffer diff --git a/FreeFileSync/Source/base/comparison.cpp b/FreeFileSync/Source/base/comparison.cpp index 784cc12a..431f6719 100644 --- a/FreeFileSync/Source/base/comparison.cpp +++ b/FreeFileSync/Source/base/comparison.cpp @@ -465,9 +465,9 @@ bool filesHaveSameContent(const AbstractPath& filePath1, const AbstractPath& fil namespace { -void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingContentOfFiles, AsyncCallback& acb, std::mutex& singleThread) //throw ThreadInterruption +void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingContentOfFiles, AsyncCallback& acb, std::mutex& singleThread) //throw ThreadStopRequest { - acb.updateStatus(replaceCpy(txtComparingContentOfFiles, L"%x", fmtPath(file.getRelativePathAny()))); //throw ThreadInterruption + acb.updateStatus(replaceCpy(txtComparingContentOfFiles, L"%x", fmtPath(file.getRelativePathAny()))); //throw ThreadStopRequest bool haveSameContent = false; const std::wstring errMsg = tryReportingError([&] @@ -478,13 +478,13 @@ void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingCon auto notifyUnbufferedIO = [&statReporter](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); - interruptionPoint(); //throw ThreadInterruption + interruptionPoint(); //throw ThreadStopRequest }; haveSameContent = parallel::filesHaveSameContent(file.getAbstractPath< LEFT_SIDE>(), - file.getAbstractPath<RIGHT_SIDE>(), notifyUnbufferedIO, singleThread); //throw FileError, ThreadInterruption + file.getAbstractPath<RIGHT_SIDE>(), notifyUnbufferedIO, singleThread); //throw FileError, ThreadStopRequest statReporter.reportDelta(1, 0); - }, acb); //throw ThreadInterruption + }, acb); //throw ThreadStopRequest if (!errMsg.empty()) file.setCategoryConflict(utfTo<Zstringc>(errMsg)); @@ -596,7 +596,7 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co std::function<void()> scheduleMoreTasks; //manage life time: enclose ThreadGroup! const std::wstring txtComparingContentOfFiles = _("Comparing content of files %x"); // - ThreadGroup<std::function<void()>> tg(std::numeric_limits<size_t>::max(), "Binary Comparison"); + ThreadGroup<std::function<void()>> tg(std::numeric_limits<size_t>::max(), Zstr("Binary Comparison")); scheduleMoreTasks = [&] { @@ -624,7 +624,7 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co /**/ --posR.current; scheduleMoreTasks()); - categorizeFileByContent(file, txtComparingContentOfFiles, acb, singleThread); //throw ThreadInterruption + categorizeFileByContent(file, txtComparingContentOfFiles, acb, singleThread); //throw ThreadStopRequest }); bwl.filesToCompareBytewise.pop_front(); diff --git a/FreeFileSync/Source/base/comparison.h b/FreeFileSync/Source/base/comparison.h index 5a33f5b4..4594c428 100644 --- a/FreeFileSync/Source/base/comparison.h +++ b/FreeFileSync/Source/base/comparison.h @@ -7,7 +7,6 @@ #ifndef COMPARISON_H_8032178534545426 #define COMPARISON_H_8032178534545426 -//#include "config.h" #include "file_hierarchy.h" #include "process_callback.h" #include "norm_filter.h" diff --git a/FreeFileSync/Source/base/db_file.cpp b/FreeFileSync/Source/base/db_file.cpp index 7fcb0287..de9741b0 100644 --- a/FreeFileSync/Source/base/db_file.cpp +++ b/FreeFileSync/Source/base/db_file.cpp @@ -31,9 +31,10 @@ struct SessionData { bool isLeadStream = false; std::string rawStream; + + bool operator==(const SessionData&) const = default; }; -inline -bool operator==(const SessionData& lhs, const SessionData& rhs) { return lhs.isLeadStream == rhs.isLeadStream && lhs.rawStream == rhs.rawStream; } + using UniqueId = std::string; using DbStreams = std::unordered_map<UniqueId, SessionData>; //list of streams by session GUID @@ -705,13 +706,13 @@ private: struct StreamStatusNotifier { - StreamStatusNotifier(const std::wstring& msgPrefix, AsyncCallback& acb /*throw ThreadInterruption*/) : + StreamStatusNotifier(const std::wstring& msgPrefix, AsyncCallback& acb /*throw ThreadStopRequest*/) : msgPrefix_(msgPrefix), acb_(acb) {} - void operator()(int64_t bytesDelta) //throw ThreadInterruption + void operator()(int64_t bytesDelta) //throw ThreadStopRequest { bytesTotal_ += bytesDelta; - acb_.updateStatus(msgPrefix_ + L" (" + formatFilesizeShort(bytesTotal_) + L')'); //throw ThreadInterruption + acb_.updateStatus(msgPrefix_ + L" (" + formatFilesizeShort(bytesTotal_) + L')'); //throw ThreadStopRequest } private: @@ -775,15 +776,15 @@ std::unordered_map<const BaseFolderPair*, SharedRef<const InSyncFolder>> fff::lo std::vector<std::pair<AbstractPath, ParallelWorkItem>> parallelWorkload; for (const AbstractPath& dbPath : dbFilePaths) - parallelWorkload.emplace_back(dbPath, [&dbStreamsByPathShared](ParallelContext& ctx) //throw ThreadInterruption + parallelWorkload.emplace_back(dbPath, [&dbStreamsByPathShared](ParallelContext& ctx) //throw ThreadStopRequest { StreamStatusNotifier notifyLoad(replaceCpy(_("Loading file %x..."), L"%x", fmtPath(AFS::getDisplayPath(ctx.itemPath))), ctx.acb); - tryReportingError([&] //throw ThreadInterruption + tryReportingError([&] //throw ThreadStopRequest { try { - DbStreams dbStreams = ::loadStreams(ctx.itemPath, notifyLoad); //throw FileError, FileErrorDatabaseNotExisting, ThreadInterruption + DbStreams dbStreams = ::loadStreams(ctx.itemPath, notifyLoad); //throw FileError, FileErrorDatabaseNotExisting, ThreadStopRequest dbStreamsByPathShared.access([&](auto& dbStreamsByPath2) { dbStreamsByPath2.emplace(ctx.itemPath, std::move(dbStreams)); }); } @@ -792,7 +793,7 @@ std::unordered_map<const BaseFolderPair*, SharedRef<const InSyncFolder>> fff::lo }); massParallelExecute(parallelWorkload, - "Load sync.ffs_db", callback /*throw X*/); //throw X + Zstr("Load sync.ffs_db"), callback /*throw X*/); //throw X } //---------------------------------------------------------------- @@ -856,20 +857,20 @@ void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transa std::tuple(dbPathL, &streamsL, &loadSuccessL), std::tuple(dbPathR, &streamsR, &loadSuccessR) }) - parallelWorkload.emplace_back(dbPath, [&streamsOut = *streamsOut, &loadSuccess = *loadSuccess](ParallelContext& ctx) //throw ThreadInterruption + parallelWorkload.emplace_back(dbPath, [&streamsOut = *streamsOut, &loadSuccess = *loadSuccess](ParallelContext& ctx) //throw ThreadStopRequest { StreamStatusNotifier notifyLoad(replaceCpy(_("Loading file %x..."), L"%x", fmtPath(AFS::getDisplayPath(ctx.itemPath))), ctx.acb); - const std::wstring errMsg = tryReportingError([&] //throw ThreadInterruption + const std::wstring errMsg = tryReportingError([&] //throw ThreadStopRequest { - try { streamsOut = ::loadStreams(ctx.itemPath, notifyLoad); } //throw FileError, FileErrorDatabaseNotExisting, ThreadInterruption + try { streamsOut = ::loadStreams(ctx.itemPath, notifyLoad); } //throw FileError, FileErrorDatabaseNotExisting, ThreadStopRequest catch (FileErrorDatabaseNotExisting&) {} }, ctx.acb); loadSuccess = errMsg.empty(); }); massParallelExecute(parallelWorkload, - "Load sync.ffs_db", callback /*throw X*/); //throw X + Zstr("Load sync.ffs_db"), callback /*throw X*/); //throw X if (!loadSuccessL || !loadSuccessR) return; /* don't continue when one of the two files failed to load (e.g. network drop): @@ -944,9 +945,9 @@ void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transa std::pair(dbPathL, &streamsL), std::pair(dbPathR, &streamsR) }) - parallelWorkload.emplace_back(dbPath, [&streams = *streams, transactionalCopy](ParallelContext& ctx) //throw ThreadInterruption + parallelWorkload.emplace_back(dbPath, [&streams = *streams, transactionalCopy](ParallelContext& ctx) //throw ThreadStopRequest { - tryReportingError([&] //throw ThreadInterruption + tryReportingError([&] //throw ThreadStopRequest { StreamStatusNotifier notifySave(replaceCpy(_("Saving file %x..."), L"%x", fmtPath(AFS::getDisplayPath(ctx.itemPath))), ctx.acb); @@ -956,7 +957,7 @@ void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transa const Zstring shortGuid = printNumber<Zstring>(Zstr("%04x"), static_cast<unsigned int>(getCrc16(generateGUID()))); const AbstractPath dbPathTmp = AFS::appendRelPath(*AFS::getParentPath(ctx.itemPath), AFS::getItemName(ctx.itemPath) + Zstr('.') + shortGuid + AFS::TEMP_FILE_ENDING); - saveStreams(streams, dbPathTmp, notifySave); //throw FileError, ThreadInterruption + saveStreams(streams, dbPathTmp, notifySave); //throw FileError, ThreadStopRequest ZEN_ON_SCOPE_FAIL(try { AFS::removeFilePlain(dbPathTmp); } catch (FileError&) {}); @@ -968,13 +969,13 @@ void fff::saveLastSynchronousState(const BaseFolderPair& baseFolder, bool transa else //some MTP devices don't even allow renaming files: https://freefilesync.org/forum/viewtopic.php?t=6531 { AFS::removeFileIfExists(ctx.itemPath); //throw FileError - saveStreams(streams, ctx.itemPath, notifySave); //throw FileError, ThreadInterruption + saveStreams(streams, ctx.itemPath, notifySave); //throw FileError, ThreadStopRequest } }, ctx.acb); }); massParallelExecute(parallelWorkload, - "Save sync.ffs_db", callback /*throw X*/); //throw X + Zstr("Save sync.ffs_db"), callback /*throw X*/); //throw X } //---------------------------------------------------------------- } diff --git a/FreeFileSync/Source/base/dir_exist_async.h b/FreeFileSync/Source/base/dir_exist_async.h index 93a4aa96..b5be2adc 100644 --- a/FreeFileSync/Source/base/dir_exist_async.h +++ b/FreeFileSync/Source/base/dir_exist_async.h @@ -48,7 +48,7 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath std::vector<ThreadGroup<std::packaged_task<bool()>>> perDeviceThreads; for (const auto& [afsDevice, deviceFolderPaths] : perDevicePaths) { - perDeviceThreads.emplace_back(1, "DirExist: " + utfTo<std::string>(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath())))); + perDeviceThreads.emplace_back(1, Zstr("DirExist: ") + utfTo<Zstring>(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath())))); auto& threadGroup = perDeviceThreads.back(); threadGroup.detach(); //don't wait on threads hanging longer than "folderAccessTimeout" @@ -98,7 +98,7 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath const auto timeoutTime = startTime + std::chrono::seconds(deviceTimeOutSec); while (std::chrono::steady_clock::now() < timeoutTime && - future.wait_for(UI_UPDATE_INTERVAL / 2) != std::future_status::ready) + future.wait_for(UI_UPDATE_INTERVAL / 2) == std::future_status::timeout) procCallback.requestUiUpdate(); //throw X if (!isReady(future)) diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp index 4a53c2de..9e247b10 100644 --- a/FreeFileSync/Source/base/dir_lock.cpp +++ b/FreeFileSync/Source/base/dir_lock.cpp @@ -13,8 +13,9 @@ #include <zen/guid.h> #include <zen/file_access.h> #include <zen/file_io.h> -#include <zen/system.h> +#include <zen/sys_info.h> + #include <iostream> //std::cout #include <fcntl.h> //open() #include <unistd.h> //close() #include <signal.h> //kill() @@ -50,12 +51,12 @@ Zstring fff::impl::getLockFilePathForAbandonedLock(const Zstring& lockFilePath) //recursive abandoned locks!? (almost) impossible, except for file system bugs: https://freefilesync.org/forum/viewtopic.php?t=6568 if (startsWith(fileName, Zstr("Delete."))) //e.g. Delete.1.sync.ffs_lock { - const Zstring tmp = afterFirst(fileName, Zstr('.'), IF_MISSING_RETURN_NONE); + const Zstring tmp = afterFirst(fileName, Zstr('.'), IfNotFoundReturn::none); - const Zstring levelStr = beforeFirst(tmp, Zstr('.'), IF_MISSING_RETURN_NONE); + const Zstring levelStr = beforeFirst(tmp, Zstr('.'), IfNotFoundReturn::none); if (!levelStr.empty() && std::all_of(levelStr.begin(), levelStr.end(), [](Zchar c) { return zen::isDigit(c); })) { - fileName = afterFirst(tmp, Zstr('.'), IF_MISSING_RETURN_NONE); + fileName = afterFirst(tmp, Zstr('.'), IfNotFoundReturn::none); level = stringTo<int>(levelStr) + 1; if (level >= ABANDONED_LOCK_LEVEL_MAX) @@ -77,14 +78,14 @@ public: { } - void operator()() const //throw ThreadInterruption + void operator()() const //throw ThreadStopRequest { const std::optional<Zstring> parentDirPath = getParentFolderPath(lockFilePath_); - setCurrentThreadName(("DirLock: " + (parentDirPath ? utfTo<std::string>(*parentDirPath) : "")).c_str()); + setCurrentThreadName(Zstr("DirLock: ") + (parentDirPath ? *parentDirPath : Zstr(""))); for (;;) { - interruptibleSleep(EMIT_LIFE_SIGN_INTERVAL); //throw ThreadInterruption + interruptibleSleep(EMIT_LIFE_SIGN_INTERVAL); //throw ThreadStopRequest emitLifeSign(); //noexcept } } @@ -93,12 +94,38 @@ private: //try to append one byte... void emitLifeSign() const //noexcept { - const int fileHandle = ::open(lockFilePath_.c_str(), O_WRONLY | O_APPEND | O_CLOEXEC); - if (fileHandle == -1) - return; - ZEN_ON_SCOPE_EXIT(::close(fileHandle)); - - [[maybe_unused]] const ssize_t bytesWritten = ::write(fileHandle, " ", 1); + try + { + const int fdLockFile = ::open(lockFilePath_.c_str(), O_WRONLY | O_APPEND | O_CLOEXEC); + if (fdLockFile == -1) + THROW_LAST_SYS_ERROR("open"); + ZEN_ON_SCOPE_EXIT(::close(fdLockFile)); + +#if 0//alternative using lseek => no apparent benefit https://freefilesync.org/forum/viewtopic.php?t=7553#p25505 + const int fdLockFile = ::open(lockFilePath_.c_str(), O_WRONLY | O_CLOEXEC); + if (fdLockFile == -1) + THROW_LAST_SYS_ERROR("open"); + ZEN_ON_SCOPE_EXIT(::close(fdLockFile)); + + if (const off_t offset = ::lseek(fdLockFile, 0, SEEK_END); + offset == -1) + THROW_LAST_SYS_ERROR("lseek"); +#endif + if (const ssize_t bytesWritten = ::write(fdLockFile, " ", 1); //writes *up to* count bytes + bytesWritten <= 0) + { + if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers + errno = ENOSPC; + THROW_LAST_SYS_ERROR("write"); + } + else if (bytesWritten > 1) //better safe than sorry + throw SysError(formatSystemError("write", L"", L"Buffer overflow.")); + } + catch (const SysError& e) + { + const std::wstring logMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(lockFilePath_)) + L' ' + e.toString(); + std::cerr << utfTo<std::string>(logMsg) << '\n'; + } } const Zstring lockFilePath_; //thread-local! @@ -227,7 +254,7 @@ LockInformation unserialize(const std::string& byteStream) //throw SysError LockInformation retrieveLockInfo(const Zstring& lockFilePath) //throw FileError { - const std::string byteStream = loadBinContainer<std::string>(lockFilePath, nullptr /*notifyUnbufferedIO*/); //throw FileError + const std::string byteStream = getFileContent(lockFilePath, nullptr /*notifyUnbufferedIO*/); //throw FileError try { return unserialize(byteStream); //throw SysError @@ -405,8 +432,6 @@ bool tryLock(const Zstring& lockFilePath) //throw FileError THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(lockFilePath)), "open"); } - ZEN_ON_SCOPE_FAIL(try { removeFilePlain(lockFilePath); } - catch (FileError&) {}); FileOutput fileOut(hFile, lockFilePath, nullptr /*notifyUnbufferedIO*/); //pass handle ownership //write housekeeping info: user, process info, lock GUID @@ -437,7 +462,7 @@ public: ~SharedDirLock() { - lifeSignthread_.interrupt(); //thread lifetime is subset of this instances's life + lifeSignthread_.requestStop(); //thread lifetime is subset of this instances's life lifeSignthread_.join(); ::releaseLock(lockFilePath_); //noexcept @@ -464,7 +489,7 @@ public: //create or retrieve a SharedDirLock std::shared_ptr<SharedDirLock> retrieve(const Zstring& lockFilePath, const DirLockCallback& notifyStatus, std::chrono::milliseconds cbInterval) //throw FileError { - assert(runningMainThread()); //function is not thread-safe! + assert(runningOnMainThread()); //function is not thread-safe! tidyUp(); diff --git a/FreeFileSync/Source/base/file_hierarchy.cpp b/FreeFileSync/Source/base/file_hierarchy.cpp index 651d0651..88b50354 100644 --- a/FreeFileSync/Source/base/file_hierarchy.cpp +++ b/FreeFileSync/Source/base/file_hierarchy.cpp @@ -493,11 +493,11 @@ std::wstring fff::getSyncOpDescription(const FileSystemObject& fsObj) //attention: ::SetWindowText() doesn't handle tab characters correctly in combination with certain file names, so don't use them return getSyncOpDescription(op) + L'\n' + - (beforeLast(relPathFrom, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) == - beforeLast(relPathTo, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) ? + (beforeLast(relPathFrom, FILE_NAME_SEPARATOR, IfNotFoundReturn::none) == + beforeLast(relPathTo, FILE_NAME_SEPARATOR, IfNotFoundReturn::none) ? //detected pure "rename" - fmtPath(afterLast(relPathFrom, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) + L' ' + arrowRight + L'\n' + //show short name only - fmtPath(afterLast(relPathTo, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) : + fmtPath(afterLast(relPathFrom, FILE_NAME_SEPARATOR, IfNotFoundReturn::all)) + L' ' + arrowRight + L'\n' + //show short name only + fmtPath(afterLast(relPathTo, FILE_NAME_SEPARATOR, IfNotFoundReturn::all)) : //"move" or "move + rename" fmtPath(relPathFrom) + L' ' + arrowRight + L'\n' + fmtPath(relPathTo)) /*+ footer -> redundant */; diff --git a/FreeFileSync/Source/base/file_hierarchy.h b/FreeFileSync/Source/base/file_hierarchy.h index 2cd9bd78..14e1cbb6 100644 --- a/FreeFileSync/Source/base/file_hierarchy.h +++ b/FreeFileSync/Source/base/file_hierarchy.h @@ -42,6 +42,8 @@ struct FileAttributes uint64_t fileSize = 0; AFS::FileId fileId; //optional! bool isFollowedSymlink = false; + + std::strong_ordering operator<=>(const FileAttributes&) const = default; }; @@ -346,8 +348,7 @@ public: inline friend DerefIter operator++(DerefIter& it, int) { return it++; } inline friend DerefIter operator--(DerefIter& it, int) { return it--; } inline friend ptrdiff_t operator-(const DerefIter& lhs, const DerefIter& rhs) { return lhs.it_ - rhs.it_; } - inline friend bool operator==(const DerefIter& lhs, const DerefIter& rhs) { return lhs.it_ == rhs.it_; } - inline friend bool operator!=(const DerefIter& lhs, const DerefIter& rhs) { return !(lhs == rhs); } + bool operator==(const DerefIter&) const = default; T& operator* () const { return **it_; } T* operator->() const { return &** it_; } private: @@ -387,26 +388,22 @@ public: static const T* retrieve(ObjectIdConst id) //returns nullptr if object is not valid anymore { - return static_cast<const T*>(zen::contains(activeObjects(), id) ? id : nullptr); + return static_cast<const T*>(zen::contains(activeObjects_, id) ? id : nullptr); } static T* retrieve(ObjectId id) { return const_cast<T*>(retrieve(static_cast<ObjectIdConst>(id))); } protected: - ObjectMgr () { activeObjects().insert(this); } - ~ObjectMgr() { activeObjects().erase (this); } + ObjectMgr () { activeObjects_.insert(this); } + ~ObjectMgr() { activeObjects_.erase (this); } private: ObjectMgr (const ObjectMgr& rhs) = delete; ObjectMgr& operator=(const ObjectMgr& rhs) = delete; //it's not well-defined what copying an objects means regarding object-identity in this context - static std::unordered_set<const ObjectMgr*>& activeObjects() - { - //our global ObjectMgr is not thread-safe (and currently does not need to be!) - //assert(runningMainThread()); -> still, may be accessed by synchronization worker threads, one thread at a time - static std::unordered_set<const ObjectMgr*> inst; - return inst; //external linkage (even in header file!) - } + //our global ObjectMgr is not thread-safe (and currently does not need to be!) + //assert(runningOnMainThread()); -> still, may be accessed by synchronization worker threads, one thread at a time + static inline std::unordered_set<const ObjectMgr*> activeObjects_; //external linkage! }; //------------------------------------------------------------------ diff --git a/FreeFileSync/Source/base/icon_loader.cpp b/FreeFileSync/Source/base/icon_loader.cpp index 41b5aa0b..0c9dfd94 100644 --- a/FreeFileSync/Source/base/icon_loader.cpp +++ b/FreeFileSync/Source/base/icon_loader.cpp @@ -98,12 +98,11 @@ ImageHolder copyToImageHolder(const GdkPixbuf& pixBuf, int maxSize) //throw SysE ImageHolder imageHolderFromGicon(GIcon& gicon, int maxSize) //throw SysError { - assert(runningMainThread()); //GTK is NOT thread safe!!! + assert(runningOnMainThread()); //GTK is NOT thread safe!!! assert(!G_IS_FILE_ICON(&gicon) && !G_IS_LOADABLE_ICON(&gicon)); //see comment in image_holder.h => icon loading must not block main thread GtkIconTheme* const defaultTheme = ::gtk_icon_theme_get_default(); //not owned! - if (!defaultTheme) - throw SysError(formatSystemError("gtk_icon_theme_get_default", L"", L"Unknown error.")); + ASSERT_SYSERROR(defaultTheme); //no more error details GtkIconInfo* const iconInfo = ::gtk_icon_theme_lookup_by_gicon(defaultTheme, &gicon, maxSize, GTK_ICON_LOOKUP_USE_BUILTIN); if (!iconInfo) @@ -120,9 +119,7 @@ ImageHolder imageHolderFromGicon(GIcon& gicon, int maxSize) //throw SysError GdkPixbuf* const pixBuf = ::gtk_icon_info_load_icon(iconInfo, &error); if (!pixBuf) - throw SysError(formatSystemError("gtk_icon_info_load_icon", - error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"", - error ? utfTo<std::wstring>(error->message) : L"Icon not available..")); + throw SysError(formatGlibError("gtk_icon_info_load_icon", error)); ZEN_ON_SCOPE_EXIT(::g_object_unref(pixBuf)); //we may have to shrink (e.g. GTK3, openSUSE): "an icon theme may have icons that differ slightly from their nominal sizes" @@ -139,12 +136,12 @@ FileIconHolder fff::getIconByTemplatePath(const Zstring& templatePath, int maxSi 0, //gsize data_size, nullptr); //gboolean* result_uncertain if (!contentType) - throw SysError(formatSystemError("g_content_type_guess", L"", L"Unknown error.")); + throw SysError(formatSystemError("g_content_type_guess", L"", L"Unknown content type.")); ZEN_ON_SCOPE_EXIT(::g_free(contentType)); GIcon* const fileIcon = ::g_content_type_get_icon(contentType); if (!fileIcon) - throw SysError(formatSystemError("g_content_type_get_icon", L"", L"Unknown error.")); + throw SysError(formatSystemError("g_content_type_get_icon", L"", L"Icon not available.")); return FileIconHolder(fileIcon /*pass ownership*/, maxSize); @@ -156,7 +153,7 @@ FileIconHolder fff::genericFileIcon(int maxSize) //throw SysError //we're called by getDisplayIcon()! -> avoid endless recursion! GIcon* const fileIcon = ::g_content_type_get_icon("text/plain"); if (!fileIcon) - throw SysError(formatSystemError("g_content_type_get_icon", L"", L"Unknown error.")); + throw SysError(formatSystemError("g_content_type_get_icon", L"", L"Icon not available.")); return FileIconHolder(fileIcon /*pass ownership*/, maxSize); @@ -167,7 +164,7 @@ FileIconHolder fff::genericDirIcon(int maxSize) //throw SysError { GIcon* const dirIcon = ::g_content_type_get_icon("inode/directory"); //should contain fallback to GTK_STOCK_DIRECTORY ("gtk-directory") if (!dirIcon) - throw SysError(formatSystemError("g_content_type_get_icon", L"", L"Unknown error.")); + throw SysError(formatSystemError("g_content_type_get_icon", L"", L"Icon not available.")); return FileIconHolder(dirIcon /*pass ownership*/, maxSize); @@ -184,9 +181,7 @@ FileIconHolder fff::getFileIcon(const Zstring& filePath, int maxSize) //throw Sy GFileInfo* const fileInfo = ::g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_ICON, G_FILE_QUERY_INFO_NONE, nullptr /*cancellable*/, &error); if (!fileInfo) - throw SysError(formatSystemError("g_file_query_info", - error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"", - error ? utfTo<std::wstring>(error->message) : L"Unknown error.")); + throw SysError(formatGlibError("g_file_query_info", error)); ZEN_ON_SCOPE_EXIT(::g_object_unref(fileInfo)); GIcon* const gicon = ::g_file_info_get_icon(fileInfo); //not owned! @@ -220,9 +215,7 @@ ImageHolder fff::getThumbnailImage(const Zstring& filePath, int maxSize) //throw GdkPixbuf* const pixBuf = ::gdk_pixbuf_new_from_file(filePath.c_str(), &error); if (!pixBuf) - throw SysError(formatSystemError("gdk_pixbuf_new_from_file", - error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"", - error ? utfTo<std::wstring>(error->message) : L"Unknown error.")); + throw SysError(formatGlibError("gdk_pixbuf_new_from_file", error)); ZEN_ON_SCOPE_EXIT(::g_object_unref(pixBuf)); return copyToImageHolder(*pixBuf, maxSize); //throw SysError @@ -232,7 +225,7 @@ ImageHolder fff::getThumbnailImage(const Zstring& filePath, int maxSize) //throw wxImage fff::extractWxImage(ImageHolder&& ih) { - assert(runningMainThread()); + assert(runningOnMainThread()); if (!ih.getRgb()) return wxNullImage; @@ -246,7 +239,7 @@ wxImage fff::extractWxImage(ImageHolder&& ih) wxImage fff::extractWxImage(zen::FileIconHolder&& fih) { - assert(runningMainThread()); + assert(runningOnMainThread()); wxImage img; if (GIcon* gicon = fih.gicon.get()) diff --git a/FreeFileSync/Source/base/parallel_scan.cpp b/FreeFileSync/Source/base/parallel_scan.cpp index 6c2cf046..d78ef486 100644 --- a/FreeFileSync/Source/base/parallel_scan.cpp +++ b/FreeFileSync/Source/base/parallel_scan.cpp @@ -47,16 +47,16 @@ public: AsyncCallback(size_t threadsToFinish, std::chrono::milliseconds cbInterval) : threadsToFinish_(threadsToFinish), cbInterval_(cbInterval) {} //blocking call: context of worker thread - AFS::TraverserCallback::HandleError reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption + AFS::TraverserCallback::HandleError reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadStopRequest { - assert(!runningMainThread()); + assert(!runningOnMainThread()); std::unique_lock dummy(lockRequest_); - interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadInterruption + interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadStopRequest errorRequest_ = std::make_pair(msg, retryNumber); conditionNewRequest.notify_all(); - interruptibleWait(conditionHaveResponse_, dummy, [this] { return static_cast<bool>(errorResponse_); }); //throw ThreadInterruption + interruptibleWait(conditionHaveResponse_, dummy, [this] { return static_cast<bool>(errorResponse_); }); //throw ThreadStopRequest AFS::TraverserCallback::HandleError rv = *errorResponse_; @@ -72,7 +72,7 @@ public: //context of main thread void waitUntilDone(std::chrono::milliseconds duration, const TravErrorCb& onError, const TravStatusCb& onStatusUpdate) //throw X { - assert(runningMainThread()); + assert(runningOnMainThread()); for (;;) { const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + duration; @@ -119,7 +119,7 @@ public: void reportCurrentFile(const std::wstring& filePath) //context of worker thread { - assert(!runningMainThread()); + assert(!runningOnMainThread()); std::lock_guard dummy(lockCurrentStatus_); currentFile_ = filePath; } @@ -157,7 +157,7 @@ public: private: std::wstring getStatusLine() //context of main thread, call repreatedly { - assert(runningMainThread()); + assert(runningOnMainThread()); size_t parallelOpsTotal = 0; std::wstring filePath; @@ -224,14 +224,14 @@ public: level_(level) {} //MUST NOT use cfg_ during construction! see BaseDirCallback() virtual void onFile (const AFS::FileInfo& fi) override; // - virtual std::shared_ptr<TraverserCallback> onFolder (const AFS::FolderInfo& fi) override; //throw ThreadInterruption + virtual std::shared_ptr<TraverserCallback> onFolder (const AFS::FolderInfo& fi) override; //throw ThreadStopRequest virtual HandleLink onSymlink(const AFS::SymlinkInfo& li) override; // - HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { return reportError(msg, retryNumber, Zstring()); } //throw ThreadInterruption + HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { return reportError(msg, retryNumber, Zstring()); } //throw ThreadStopRequest HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName) override { return reportError(msg, retryNumber, itemName); } // private: - HandleError reportError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName /*optional*/); //throw ThreadInterruption + HandleError reportError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName /*optional*/); //throw ThreadStopRequest TraverserConfig& cfg_; const Zstring parentRelPathPf_; @@ -267,9 +267,9 @@ private: }; -void DirCallback::onFile(const AFS::FileInfo& fi) //throw ThreadInterruption +void DirCallback::onFile(const AFS::FileInfo& fi) //throw ThreadStopRequest { - interruptionPoint(); //throw ThreadInterruption + interruptionPoint(); //throw ThreadStopRequest const Zstring& relPath = parentRelPathPf_ + fi.itemName; @@ -300,9 +300,9 @@ void DirCallback::onFile(const AFS::FileInfo& fi) //throw ThreadInterruption } -std::shared_ptr<AFS::TraverserCallback> DirCallback::onFolder(const AFS::FolderInfo& fi) //throw ThreadInterruption +std::shared_ptr<AFS::TraverserCallback> DirCallback::onFolder(const AFS::FolderInfo& fi) //throw ThreadStopRequest { - interruptionPoint(); //throw ThreadInterruption + interruptionPoint(); //throw ThreadStopRequest const Zstring& relPath = parentRelPathPf_ + fi.itemName; @@ -327,7 +327,7 @@ std::shared_ptr<AFS::TraverserCallback> DirCallback::onFolder(const AFS::FolderI //check after FolderContainer::addSubFolder() 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.", retryNumber, fi.itemName)) //throw ThreadInterruption + L"\n\n" L"Endless recursion.", retryNumber, fi.itemName)) //throw ThreadStopRequest { case AbstractFileSystem::TraverserCallback::ON_ERROR_RETRY: break; @@ -339,9 +339,9 @@ std::shared_ptr<AFS::TraverserCallback> DirCallback::onFolder(const AFS::FolderI } -DirCallback::HandleLink DirCallback::onSymlink(const AFS::SymlinkInfo& si) //throw ThreadInterruption +DirCallback::HandleLink DirCallback::onSymlink(const AFS::SymlinkInfo& si) //throw ThreadStopRequest { - interruptionPoint(); //throw ThreadInterruption + interruptionPoint(); //throw ThreadStopRequest const Zstring& relPath = parentRelPathPf_ + si.itemName; @@ -380,13 +380,13 @@ DirCallback::HandleLink DirCallback::onSymlink(const AFS::SymlinkInfo& si) //thr } -DirCallback::HandleError DirCallback::reportError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName /*optional*/) //throw ThreadInterruption +DirCallback::HandleError DirCallback::reportError(const std::wstring& msg, size_t retryNumber, const Zstring& itemName /*optional*/) //throw ThreadStopRequest { - switch (cfg_.acb.reportError(msg, retryNumber)) //throw ThreadInterruption + switch (cfg_.acb.reportError(msg, retryNumber)) //throw ThreadStopRequest { case ON_ERROR_CONTINUE: if (itemName.empty()) - cfg_.failedDirReads.emplace(beforeLast(parentRelPathPf_, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE), utfTo<Zstringc>(msg)); + cfg_.failedDirReads.emplace(beforeLast(parentRelPathPf_, FILE_NAME_SEPARATOR, IfNotFoundReturn::none), utfTo<Zstringc>(msg)); else cfg_.failedItemReads.emplace(parentRelPathPf_ + itemName, utfTo<Zstringc>(msg)); return ON_ERROR_CONTINUE; @@ -420,14 +420,14 @@ void fff::parallelDeviceTraversal(const std::set<DirectoryKey>& foldersToRead, AsyncCallback acb(perDeviceFolders.size() /*threadsToFinish*/, cbInterval); //manage life time: enclose InterruptibleThread's!!! std::vector<InterruptibleThread> worker; - ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.join (); ); - ZEN_ON_SCOPE_FAIL( for (InterruptibleThread& wt : worker) wt.interrupt(); ); //interrupt all first, then join + ZEN_ON_SCOPE_SUCCESS( for (InterruptibleThread& wt : worker) wt.join(); ); //no stop needed in success case => preempt ~InterruptibleThread() + ZEN_ON_SCOPE_FAIL( for (InterruptibleThread& wt : worker) wt.requestStop(); ); //stop *all* at the same time before join! //init worker threads for (const auto& [afsDevice, dirKeys] : perDeviceFolders) { const int threadIdx = static_cast<int>(worker.size()); - std::string threadName = "Comp Device[" + numberTo<std::string>(threadIdx + 1) + '/' + numberTo<std::string>(perDeviceFolders.size()) + ']'; + Zstring threadName = Zstr("Comp Device[") + numberTo<Zstring>(threadIdx + 1) + Zstr('/') + numberTo<Zstring>(perDeviceFolders.size()) + Zstr(']'); const size_t parallelOps = 1; std::map<DirectoryKey, DirectoryValue*> workload; @@ -437,7 +437,7 @@ void fff::parallelDeviceTraversal(const std::set<DirectoryKey>& foldersToRead, worker.emplace_back([afsDevice /*clang bug*/= afsDevice, workload, threadIdx, &acb, parallelOps, threadName = std::move(threadName)]() mutable { - setCurrentThreadName(threadName.c_str()); + setCurrentThreadName(threadName); acb.notifyWorkBegin(threadIdx, parallelOps); ZEN_ON_SCOPE_EXIT(acb.notifyWorkEnd(threadIdx)); @@ -451,7 +451,7 @@ void fff::parallelDeviceTraversal(const std::set<DirectoryKey>& foldersToRead, assert(folderKey.folderPath.afsDevice == afsDevice); travWorkload.emplace_back(folderKey.folderPath.afsPath, std::make_shared<BaseDirCallback>(folderKey, *folderVal, acb, threadIdx, lastReportTime)); } - AFS::traverseFolderRecursive(afsDevice, travWorkload, parallelOps); //throw ThreadInterruption + AFS::traverseFolderRecursive(afsDevice, travWorkload, parallelOps); //throw ThreadStopRequest }); } diff --git a/FreeFileSync/Source/base/parallel_scan.h b/FreeFileSync/Source/base/parallel_scan.h index 5610e5d3..9f44f89d 100644 --- a/FreeFileSync/Source/base/parallel_scan.h +++ b/FreeFileSync/Source/base/parallel_scan.h @@ -24,21 +24,22 @@ struct DirectoryKey SymLinkHandling handleSymlinks = SymLinkHandling::exclude; }; - inline -bool operator<(const DirectoryKey& lhs, const DirectoryKey& rhs) +std::weak_ordering operator<=>(const DirectoryKey& lhs, const DirectoryKey& rhs) { - if (lhs.handleSymlinks != rhs.handleSymlinks) - return lhs.handleSymlinks < rhs.handleSymlinks; + if (const std::strong_ordering cmp = lhs.handleSymlinks <=> rhs.handleSymlinks; + cmp != std::strong_ordering::equal) + return cmp; - const int cmp = AbstractFileSystem::comparePath(lhs.folderPath, rhs.folderPath); - if (cmp != 0) - return cmp < 0; + if (const std::weak_ordering cmp = lhs.folderPath <=> rhs.folderPath; + cmp != std::weak_ordering::equivalent) + return cmp; - return lhs.filter.ref() < rhs.filter.ref(); + return lhs.filter.ref() <=> rhs.filter.ref(); } + struct DirectoryValue { FolderContainer folderCont; diff --git a/FreeFileSync/Source/base/path_filter.cpp b/FreeFileSync/Source/base/path_filter.cpp index d7e555a5..f50f5def 100644 --- a/FreeFileSync/Source/base/path_filter.cpp +++ b/FreeFileSync/Source/base/path_filter.cpp @@ -10,18 +10,21 @@ #include <vector> #include <typeinfo> #include <iterator> +#include <typeindex> using namespace zen; using namespace fff; -bool fff::operator<(const PathFilter& lhs, const PathFilter& rhs) +std::strong_ordering PathFilter::operator<=>(const PathFilter& rhs) const { - if (typeid(lhs) != typeid(rhs)) - return typeid(lhs).before(typeid(rhs)); //in worst case, order is guaranteed to be stable only during each program run + //caveat: typeid returns static type for pointers, dynamic type for references!!! + if (const std::strong_ordering cmp = std::type_index(typeid(*this)) <=> std::type_index(typeid(rhs)); + cmp != std::strong_ordering::equal) + return cmp; //lhs, rhs have same type: - return lhs.cmpLessSameType(rhs); + return compareSameType(rhs) <=> 0; } @@ -63,7 +66,7 @@ void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& masksFile if (endsWith(phrase, FILE_NAME_SEPARATOR) || //only relevant for folder filtering endsWith(phrase, sepAsterisk)) // abc\* { - const Zstring dirPhrase = beforeLast(phrase, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + const Zstring dirPhrase = beforeLast(phrase, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); if (!dirPhrase.empty()) masksFolder.push_back(dirPhrase); } @@ -72,12 +75,12 @@ void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& masksFile }; if (startsWith(filterFmt, FILE_NAME_SEPARATOR)) // \abc - processTail(afterFirst(filterFmt, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); + processTail(afterFirst(filterFmt, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); else { processTail(filterFmt); if (startsWith(filterFmt, asteriskSep)) // *\abc - processTail(afterFirst(filterFmt, asteriskSep, IF_MISSING_RETURN_NONE)); + processTail(afterFirst(filterFmt, asteriskSep, IfNotFoundReturn::none)); } } @@ -224,8 +227,8 @@ std::vector<Zstring> fff::splitByDelimiter(const Zstring& filterPhrase) //delimiters may be FILTER_ITEM_SEPARATOR or '\n' std::vector<Zstring> output; - for (const Zstring& str : split(filterPhrase, FILTER_ITEM_SEPARATOR, SplitType::SKIP_EMPTY)) //split by less common delimiter first (create few, large strings) - for (Zstring entry : split(str, Zstr('\n'), SplitType::SKIP_EMPTY)) + for (const Zstring& str : split(filterPhrase, FILTER_ITEM_SEPARATOR, SplitOnEmpty::skip)) //split by less common delimiter first (create few, large strings) + for (Zstring entry : split(str, Zstr('\n'), SplitOnEmpty::skip)) { trim(entry); if (!entry.empty()) @@ -339,24 +342,25 @@ bool NameFilter::isNull() const } - -bool NameFilter::cmpLessSameType(const PathFilter& other) const +int NameFilter::compareSameType(const PathFilter& other) const { assert(typeid(*this) == typeid(other)); //always given in this context! - const NameFilter& otherNameFilt = static_cast<const NameFilter&>(other); + const NameFilter& otherName = static_cast<const NameFilter&>(other); - if (includeMasksFileFolder != otherNameFilt.includeMasksFileFolder) - return includeMasksFileFolder < otherNameFilt.includeMasksFileFolder; + if (const std::strong_ordering cmp = includeMasksFileFolder <=> otherName.includeMasksFileFolder; + cmp != std::strong_ordering::equal) + return cmp < 0 ? -1 : 1; - if (includeMasksFolder != otherNameFilt.includeMasksFolder) - return includeMasksFolder < otherNameFilt.includeMasksFolder; + if (const std::strong_ordering cmp = includeMasksFolder <=> otherName.includeMasksFolder; + cmp != std::strong_ordering::equal) + return cmp < 0 ? -1 : 1; - if (excludeMasksFileFolder != otherNameFilt.excludeMasksFileFolder) - return excludeMasksFileFolder < otherNameFilt.excludeMasksFileFolder; + if (const std::strong_ordering cmp = excludeMasksFileFolder <=> otherName.excludeMasksFileFolder; + cmp != std::strong_ordering::equal) + return cmp < 0 ? -1 : 1; - if (excludeMasksFolder != otherNameFilt.excludeMasksFolder) - return excludeMasksFolder < otherNameFilt.excludeMasksFolder; + const std::strong_ordering cmp = excludeMasksFolder <=> otherName.excludeMasksFolder; + return cmp == std::strong_ordering::equal ? 0 : (cmp < 0 ? -1 : 1); - return false; //all equal } diff --git a/FreeFileSync/Source/base/path_filter.h b/FreeFileSync/Source/base/path_filter.h index af7d6dc2..73ac97ab 100644 --- a/FreeFileSync/Source/base/path_filter.h +++ b/FreeFileSync/Source/base/path_filter.h @@ -22,15 +22,14 @@ namespace fff /|\ ____________|_____________ | | | - NullFilter NameFilter CombinedFilter - */ + NullFilter NameFilter CombinedFilter */ + class PathFilter; using FilterRef = zen::SharedRef<const PathFilter>; const Zchar FILTER_ITEM_SEPARATOR = Zstr('|'); - -class PathFilter //interface for filtering +class PathFilter { public: virtual ~PathFilter() {} @@ -44,16 +43,12 @@ public: virtual FilterRef copyFilterAddingExclusion(const Zstring& excludePhrase) const = 0; -private: - friend bool operator<(const PathFilter& lhs, const PathFilter& rhs); + std::strong_ordering operator<=>(const PathFilter& other) const; - virtual bool cmpLessSameType(const PathFilter& other) const = 0; //typeid(*this) == typeid(other) in this context! +private: + virtual int compareSameType(const PathFilter& other) const = 0; //assumes typeid(*this) == typeid(other)! }; -bool operator<(const PathFilter& lhs, const PathFilter& rhs); //GCC: friend-declaration is not a "proper" declaration -inline bool operator==(const PathFilter& lhs, const PathFilter& rhs) { return !(lhs < rhs) && !(rhs < lhs); } -inline bool operator!=(const PathFilter& lhs, const PathFilter& rhs) { return !(lhs == rhs); } - //small helper method: merge two hard filters (thereby remove Null-filters) FilterRef combineFilters(const FilterRef& first, const FilterRef& second); @@ -68,7 +63,7 @@ public: FilterRef copyFilterAddingExclusion(const Zstring& excludePhrase) const override; private: - bool cmpLessSameType(const PathFilter& other) const override; + int compareSameType(const PathFilter& other) const override { assert(typeid(*this) == typeid(other)); return 0; } }; @@ -87,7 +82,8 @@ public: FilterRef copyFilterAddingExclusion(const Zstring& excludePhrase) const override; private: - bool cmpLessSameType(const PathFilter& other) const override; + friend class CombinedFilter; + int compareSameType(const PathFilter& other) const override; std::vector<Zstring> includeMasksFileFolder; // std::vector<Zstring> includeMasksFolder; //upper-case + Unicode-normalized by construction @@ -107,7 +103,7 @@ public: FilterRef copyFilterAddingExclusion(const Zstring& excludePhrase) const override; private: - bool cmpLessSameType(const PathFilter& other) const override; + int compareSameType(const PathFilter& other) const override; const NameFilter first_; const NameFilter second_; @@ -128,14 +124,6 @@ bool NullFilter::passDirFilter(const Zstring& relDirPath, bool* childItemMightMa inline -bool NullFilter::cmpLessSameType(const PathFilter& other) const -{ - assert(typeid(*this) == typeid(other)); //always given in this context! - return false; -} - - -inline FilterRef NullFilter::copyFilterAddingExclusion(const Zstring& excludePhrase) const { auto filter = zen::makeSharedRef<NameFilter>(Zstr("*"), excludePhrase); @@ -194,16 +182,17 @@ FilterRef CombinedFilter::copyFilterAddingExclusion(const Zstring& excludePhrase inline -bool CombinedFilter::cmpLessSameType(const PathFilter& other) const +int CombinedFilter::compareSameType(const PathFilter& other) const { assert(typeid(*this) == typeid(other)); //always given in this context! - const CombinedFilter& otherCombFilt = static_cast<const CombinedFilter&>(other); + const CombinedFilter& otherComb = static_cast<const CombinedFilter&>(other); - if (first_ != otherCombFilt.first_) - return first_ < otherCombFilt.first_; + if (const int cmp = first_.compareSameType(otherComb.first_); + cmp != 0) + return cmp; - return second_ < otherCombFilt.second_; + return second_.compareSameType(otherComb.second_); } diff --git a/FreeFileSync/Source/base/resolve_path.cpp b/FreeFileSync/Source/base/resolve_path.cpp index a720bc44..b1c56898 100644 --- a/FreeFileSync/Source/base/resolve_path.cpp +++ b/FreeFileSync/Source/base/resolve_path.cpp @@ -24,7 +24,7 @@ namespace { std::optional<Zstring> getEnvironmentVar(const Zstring& name) { - assert(runningMainThread()); //getenv() is not thread-safe! + assert(runningOnMainThread()); //getenv() is not thread-safe! const char* buffer = ::getenv(name.c_str()); //no extended error reporting if (!buffer) @@ -46,7 +46,7 @@ std::optional<Zstring> getEnvironmentVar(const Zstring& name) Zstring resolveRelativePath(const Zstring& relativePath) { - assert(runningMainThread()); //GetFullPathName() is documented to NOT be thread-safe! + assert(runningOnMainThread()); //GetFullPathName() is documented to NOT be thread-safe! /* 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, */ @@ -69,7 +69,7 @@ Zstring resolveRelativePath(const Zstring& relativePath) if (const std::optional<Zstring> homeDir = getEnvironmentVar("HOME")) { if (startsWith(pathTmp, "~/")) - pathTmp = appendSeparator(*homeDir) + afterFirst(pathTmp, '/', IF_MISSING_RETURN_NONE); + pathTmp = appendSeparator(*homeDir) + afterFirst(pathTmp, '/', IfNotFoundReturn::none); else //pathTmp == "~" pathTmp = *homeDir; } @@ -77,7 +77,7 @@ Zstring resolveRelativePath(const Zstring& relativePath) } else { - //we cannot use ::realpath() since it resolves *existing* relative paths only! + //we cannot use ::realpath() which only resolves *existing* relative paths! if (char* dirPath = ::getcwd(nullptr, 0)) { ZEN_ON_SCOPE_EXIT(::free(dirPath)); @@ -157,12 +157,12 @@ Zstring fff::expandMacros(const Zstring& text) { if (contains(text, MACRO_SEP)) { - Zstring prefix = beforeFirst(text, MACRO_SEP, IF_MISSING_RETURN_NONE); - Zstring rest = afterFirst (text, MACRO_SEP, IF_MISSING_RETURN_NONE); + Zstring prefix = beforeFirst(text, MACRO_SEP, IfNotFoundReturn::none); + Zstring rest = afterFirst (text, MACRO_SEP, IfNotFoundReturn::none); if (contains(rest, MACRO_SEP)) { - Zstring potentialMacro = beforeFirst(rest, MACRO_SEP, IF_MISSING_RETURN_NONE); - Zstring postfix = afterFirst (rest, MACRO_SEP, IF_MISSING_RETURN_NONE); //text == prefix + MACRO_SEP + potentialMacro + MACRO_SEP + postfix + Zstring potentialMacro = beforeFirst(rest, MACRO_SEP, IfNotFoundReturn::none); + Zstring postfix = afterFirst (rest, MACRO_SEP, IfNotFoundReturn::none); //text == prefix + MACRO_SEP + potentialMacro + MACRO_SEP + postfix if (std::optional<Zstring> value = tryResolveMacro(potentialMacro)) return prefix + *value + expandMacros(postfix); @@ -194,9 +194,9 @@ Zstring expandVolumeName(Zstring pathPhrase) // [volname]:\folder [volnam Zstring relPath = Zstring(pathPhrase.c_str() + posEnd + 1); if (startsWith(relPath, FILE_NAME_SEPARATOR)) - relPath = afterFirst(relPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + relPath = afterFirst(relPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); else if (startsWith(relPath, Zstr(":\\"))) //Win-only - relPath = afterFirst(relPath, Zstr('\\'), IF_MISSING_RETURN_NONE); + relPath = afterFirst(relPath, Zstr('\\'), IfNotFoundReturn::none); return "/.../[" + volName + "]/" + relPath; } } diff --git a/FreeFileSync/Source/base/status_handler_impl.h b/FreeFileSync/Source/base/status_handler_impl.h index 642b2cb0..d81bc121 100644 --- a/FreeFileSync/Source/base/status_handler_impl.h +++ b/FreeFileSync/Source/base/status_handler_impl.h @@ -32,28 +32,28 @@ public: } //context of worker thread - void updateStatus(const std::wstring& msg) //throw ThreadInterruption + void updateStatus(const std::wstring& msg) //throw ThreadStopRequest { - assert(!zen::runningMainThread()); + assert(!zen::runningOnMainThread()); { std::lock_guard dummy(lockCurrentStatus_); if (ThreadStatus* ts = getThreadStatus()) //call while holding "lockCurrentStatus_" lock!! ts->statusMsg = msg; else assert(false); } - zen::interruptionPoint(); //throw ThreadInterruption + zen::interruptionPoint(); //throw ThreadStopRequest } //blocking call: context of worker thread //=> indirect support for "pause": reportInfo() is called under singleThread lock, // so all other worker threads will wait when coming out of parallel I/O (trying to lock singleThread) - void reportInfo(const std::wstring& msg) //throw ThreadInterruption + void reportInfo(const std::wstring& msg) //throw ThreadStopRequest { - updateStatus(msg); //throw ThreadInterruption + updateStatus(msg); //throw ThreadStopRequest - assert(!zen::runningMainThread()); + assert(!zen::runningOnMainThread()); std::unique_lock dummy(lockRequest_); - zen::interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !reportInfoRequest_; }); //throw ThreadInterruption + zen::interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !reportInfoRequest_; }); //throw ThreadStopRequest reportInfoRequest_ = /*std::move(taskPrefix) + */ msg; @@ -62,16 +62,16 @@ public: } //blocking call: context of worker thread - PhaseCallback::Response reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption + PhaseCallback::Response reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadStopRequest { - assert(!zen::runningMainThread()); + assert(!zen::runningOnMainThread()); std::unique_lock dummy(lockRequest_); - zen::interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadInterruption + zen::interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadStopRequest errorRequest_ = ErrorInfo({ /*std::move(taskPrefix) + */ msg, retryNumber }); conditionNewRequest.notify_all(); - zen::interruptibleWait(conditionHaveResponse_, dummy, [this] { return static_cast<bool>(errorResponse_); }); //throw ThreadInterruption + zen::interruptibleWait(conditionHaveResponse_, dummy, [this] { return static_cast<bool>(errorResponse_); }); //throw ThreadStopRequest PhaseCallback::Response rv = *errorResponse_; @@ -86,7 +86,7 @@ public: //context of main thread void waitUntilDone(std::chrono::milliseconds duration, PhaseCallback& cb) //throw X { - assert(zen::runningMainThread()); + assert(zen::runningOnMainThread()); for (;;) { const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + duration; @@ -125,8 +125,8 @@ public: void notifyTaskBegin(size_t prio) //noexcept { - assert(!zen::runningMainThread()); - const uint64_t threadId = zen::getThreadId(); + assert(!zen::runningOnMainThread()); + const std::thread::id threadId = std::this_thread::get_id(); std::lock_guard dummy(lockCurrentStatus_); assert(!getThreadStatus()); @@ -151,8 +151,8 @@ public: void notifyTaskEnd() //noexcept { - assert(!zen::runningMainThread()); - const uint64_t threadId = zen::getThreadId(); + assert(!zen::runningOnMainThread()); + const std::thread::id threadId = std::this_thread::get_id(); std::lock_guard dummy(lockCurrentStatus_); for (std::vector<ThreadStatus>& sbp : statusByPriority_) @@ -181,15 +181,15 @@ private: struct ThreadStatus { - uint64_t threadId = 0; + std::thread::id threadId; //size_t taskIdx = 0; //nice human-readable task id for GUI std::wstring statusMsg; }; ThreadStatus* getThreadStatus() //call while holding "lockCurrentStatus_" lock!! { - assert(!zen::runningMainThread()); - const uint64_t threadId = zen::getThreadId(); + assert(!zen::runningOnMainThread()); + const std::thread::id threadId = std::this_thread::get_id(); for (std::vector<ThreadStatus>& sbp : statusByPriority_) for (ThreadStatus& ts : sbp) //thread count is (hopefully) small enough so that linear search won't hurt perf @@ -214,7 +214,7 @@ private: //context of main thread void reportStats(PhaseCallback& cb) { - assert(zen::runningMainThread()); + assert(zen::runningOnMainThread()); const std::pair<int, int64_t> deltaProcessed(itemsDeltaProcessed_, bytesDeltaProcessed_); if (deltaProcessed.first != 0 || deltaProcessed.second != 0) @@ -233,7 +233,7 @@ private: //context of main thread, call repreatedly std::wstring getCurrentStatus() { - assert(zen::runningMainThread()); + assert(zen::runningOnMainThread()); size_t parallelOpsTotal = 0; std::wstring statusMsg; @@ -309,7 +309,7 @@ public: cb_.updateDataTotal(itemsReported_ - itemsExpected_, bytesReported_ - bytesExpected_); //noexcept! } - void updateStatus(const std::wstring& msg) { cb_.updateStatus(msg); } //throw ThreadInterruption + void updateStatus(const std::wstring& msg) { cb_.updateStatus(msg); } //throw ThreadStopRequest void reportDelta(int itemsDelta, int64_t bytesDelta) //noexcept! { @@ -371,13 +371,13 @@ struct ParallelContext const AbstractPath& itemPath; AsyncCallback& acb; }; -using ParallelWorkItem = std::function<void(ParallelContext& ctx)> /*throw ThreadInterruption*/; +using ParallelWorkItem = std::function<void(ParallelContext& ctx)> /*throw ThreadStopRequest*/; namespace { void massParallelExecute(const std::vector<std::pair<AbstractPath, ParallelWorkItem>>& workload, - const std::string& threadGroupName, + const Zstring& threadGroupName, PhaseCallback& callback /*throw X*/) //throw X { using namespace zen; @@ -402,7 +402,7 @@ void massParallelExecute(const std::vector<std::pair<AbstractPath, ParallelWorkI auto& threadGroup = deviceThreadGroups.emplace(afsDevice, ThreadGroup<std::function<void()>>( 1, - threadGroupName + ' ' + utfTo<std::string>(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath()))))).first->second; + threadGroupName + Zstr(' ') + utfTo<Zstring>(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath()))))).first->second; for (const std::pair<AbstractPath, ParallelWorkItem>* item : wl) threadGroup.run([&acb, statusPrio, &itemPath = item->first, &task = item->second] @@ -411,7 +411,7 @@ void massParallelExecute(const std::vector<std::pair<AbstractPath, ParallelWorkI ZEN_ON_SCOPE_EXIT(acb.notifyTaskEnd()); ParallelContext pctx{ itemPath, acb }; - task(pctx); //throw ThreadInterruption + task(pctx); //throw ThreadStopRequest }); threadGroup.notifyWhenDone([&acb, &activeDeviceCount] /*noexcept! runs on worker thread!*/ diff --git a/FreeFileSync/Source/base/structures.h b/FreeFileSync/Source/base/structures.h index c1b43071..4d8d8f44 100644 --- a/FreeFileSync/Source/base/structures.h +++ b/FreeFileSync/Source/base/structures.h @@ -113,20 +113,12 @@ struct DirectionSet SyncDirection rightNewer = SyncDirection::left; // SyncDirection different = SyncDirection::none; //CompareVariant::content, CompareVariant::size only! SyncDirection conflict = SyncDirection::none; + + bool operator==(const DirectionSet&) const = default; }; DirectionSet getTwoWayUpdateSet(); -inline -bool operator==(const DirectionSet& lhs, const DirectionSet& rhs) -{ - return lhs.exLeftSideOnly == rhs.exLeftSideOnly && - lhs.exRightSideOnly == rhs.exRightSideOnly && - lhs.leftNewer == rhs.leftNewer && - lhs.rightNewer == rhs.rightNewer && - lhs.different == rhs.different && - lhs.conflict == rhs.conflict; -} enum class SyncVariant { @@ -160,7 +152,6 @@ bool operator==(const SyncDirectionConfig& lhs, const SyncDirectionConfig& rhs) lhs.detectMovedFiles == rhs.detectMovedFiles; //useful to remember this setting even if the current sync variant does not need it //adapt effectivelyEqual() on changes, too! } -inline bool operator!=(const SyncDirectionConfig& lhs, const SyncDirectionConfig& rhs) { return !(lhs == rhs); } inline bool effectivelyEqual(const SyncDirectionConfig& lhs, const SyncDirectionConfig& rhs) @@ -176,16 +167,9 @@ struct CompConfig CompareVariant compareVar = CompareVariant::timeSize; SymLinkHandling handleSymlinks = SymLinkHandling::exclude; std::vector<unsigned int> ignoreTimeShiftMinutes; //treat modification times with these offsets as equal -}; -inline -bool operator==(const CompConfig& lhs, const CompConfig& rhs) -{ - return lhs.compareVar == rhs.compareVar && - lhs.handleSymlinks == rhs.handleSymlinks && - lhs.ignoreTimeShiftMinutes == rhs.ignoreTimeShiftMinutes; -} -inline bool operator!=(const CompConfig& lhs, const CompConfig& rhs) { return !(lhs == rhs); } + bool operator==(const CompConfig&) const = default; +}; inline bool effectivelyEqual(const CompConfig& lhs, const CompConfig& rhs) { return lhs == rhs; } //no change in behavior @@ -239,7 +223,6 @@ bool operator==(const SyncConfig& lhs, const SyncConfig& rhs) )); //adapt effectivelyEqual() on changes, too! } -inline bool operator!=(const SyncConfig& lhs, const SyncConfig& rhs) { return !(lhs == rhs); } inline @@ -321,21 +304,10 @@ struct FilterConfig size_t sizeMax = 0; UnitSize unitSizeMax = UnitSize::none; + + bool operator==(const FilterConfig&) const = default; }; -inline -bool operator==(const FilterConfig& lhs, const FilterConfig& rhs) -{ - return lhs.includeFilter == rhs.includeFilter && - lhs.excludeFilter == rhs.excludeFilter && - lhs.timeSpan == rhs.timeSpan && - lhs.unitTimeSpan == rhs.unitTimeSpan && - lhs.sizeMin == rhs.sizeMin && - lhs.unitSizeMin == rhs.unitSizeMin && - lhs.sizeMax == rhs.sizeMax && - lhs.unitSizeMax == rhs.unitSizeMax; -} -inline bool operator!=(const FilterConfig& lhs, const FilterConfig& rhs) { return !(lhs == rhs); } void resolveUnits(size_t timeSpan, UnitTime unitTimeSpan, size_t sizeMin, UnitSize unitSizeMin, @@ -366,19 +338,10 @@ struct LocalPairConfig //enhanced folder pairs with (optional) alternate configu std::optional<CompConfig> localCmpCfg; std::optional<SyncConfig> localSyncCfg; FilterConfig localFilter; -}; -inline -bool operator==(const LocalPairConfig& lhs, const LocalPairConfig& rhs) -{ - return lhs.folderPathPhraseLeft == rhs.folderPathPhraseLeft && - lhs.folderPathPhraseRight == rhs.folderPathPhraseRight && - lhs.localCmpCfg == rhs.localCmpCfg && - lhs.localSyncCfg == rhs.localSyncCfg && - lhs.localFilter == rhs.localFilter; -} -inline bool operator!=(const LocalPairConfig& lhs, const LocalPairConfig& rhs) { return !(lhs == rhs); } + bool operator==(const LocalPairConfig& rhs) const = default; +}; enum class ResultsNotification @@ -419,6 +382,8 @@ struct MainConfiguration std::string emailNotifyAddress; //optional ResultsNotification emailNotifyCondition = ResultsNotification::always; + + bool operator==(const MainConfiguration&) const = default; }; @@ -427,25 +392,6 @@ void setDeviceParallelOps( std::map<AfsDevice, size_t>& deviceParallelOps size_t getDeviceParallelOps(const std::map<AfsDevice, size_t>& deviceParallelOps, const Zstring& folderPathPhrase); void setDeviceParallelOps( std::map<AfsDevice, size_t>& deviceParallelOps, const Zstring& folderPathPhrase, size_t parallelOps); -inline -bool operator==(const MainConfiguration& lhs, const MainConfiguration& rhs) -{ - return lhs.cmpCfg == rhs.cmpCfg && - lhs.syncCfg == rhs.syncCfg && - lhs.globalFilter == rhs.globalFilter && - lhs.firstPair == rhs.firstPair && - lhs.additionalPairs == rhs.additionalPairs && - lhs.deviceParallelOps == rhs.deviceParallelOps && - lhs.ignoreErrors == rhs.ignoreErrors && - lhs.automaticRetryCount == rhs.automaticRetryCount && - lhs.automaticRetryDelay == rhs.automaticRetryDelay && - lhs.postSyncCommand == rhs.postSyncCommand && - lhs.postSyncCondition == rhs.postSyncCondition && - lhs.altLogFolderPathPhrase == rhs.altLogFolderPathPhrase && - lhs.emailNotifyAddress == rhs.emailNotifyAddress && - lhs.emailNotifyCondition == rhs.emailNotifyCondition; -} - std::optional<CompareVariant> getCompVariant(const MainConfiguration& mainCfg); std::optional<SyncVariant> getSyncVariant(const MainConfiguration& mainCfg); @@ -465,23 +411,9 @@ struct WarningDialogs bool warnInputFieldEmpty = true; bool warnDirectoryLockFailed = true; bool warnVersioningFolderPartOfSync = true; + + bool operator==(const WarningDialogs&) const = default; }; -inline bool operator==(const WarningDialogs& lhs, const WarningDialogs& rhs) -{ - return lhs.warnFolderNotExisting == rhs.warnFolderNotExisting && - lhs.warnFoldersDifferInCase == rhs.warnFoldersDifferInCase && - lhs.warnDependentFolderPair == rhs.warnDependentFolderPair && - lhs.warnDependentBaseFolders == rhs.warnDependentBaseFolders && - lhs.warnSignificantDifference == rhs.warnSignificantDifference && - lhs.warnNotEnoughDiskSpace == rhs.warnNotEnoughDiskSpace && - lhs.warnUnresolvedConflicts == rhs.warnUnresolvedConflicts && - lhs.warnModificationTimeError == rhs.warnModificationTimeError && - lhs.warnRecyclerMissing == rhs.warnRecyclerMissing && - lhs.warnInputFieldEmpty == rhs.warnInputFieldEmpty && - lhs.warnDirectoryLockFailed == rhs.warnDirectoryLockFailed && - lhs.warnVersioningFolderPartOfSync == rhs.warnVersioningFolderPartOfSync; -} -inline bool operator!=(const WarningDialogs& lhs, const WarningDialogs& rhs) { return !(lhs == rhs); } } #endif //STRUCTURES_H_8210478915019450901745 diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp index 08a517c5..66d81210 100644 --- a/FreeFileSync/Source/base/synchronization.cpp +++ b/FreeFileSync/Source/base/synchronization.cpp @@ -453,12 +453,12 @@ bool significantDifferenceDetected(const SyncStatistics& folderPairStat) //--------------------- data verification ------------------------- void flushFileBuffers(const Zstring& nativeFilePath) //throw FileError { - const int fileHandle = ::open(nativeFilePath.c_str(), O_WRONLY | O_APPEND | O_CLOEXEC); - if (fileHandle == -1) + const int fdFile = ::open(nativeFilePath.c_str(), O_WRONLY | O_APPEND | O_CLOEXEC); + if (fdFile == -1) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(nativeFilePath)), "open"); - ZEN_ON_SCOPE_EXIT(::close(fileHandle)); + ZEN_ON_SCOPE_EXIT(::close(fdFile)); - if (::fsync(fileHandle) != 0) + if (::fsync(fdFile) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(nativeFilePath)), "fsync"); } @@ -598,7 +598,7 @@ public: void tryCleanup(PhaseCallback& cb /*throw X*/); //throw X void removeDirWithCallback (const AbstractPath& dirPath, const Zstring& relativePath, AsyncItemStatReporter& statReporter, std::mutex& singleThread); // - void removeFileWithCallback(const FileDescriptor& fileDescr, const Zstring& relativePath, AsyncItemStatReporter& statReporter, std::mutex& singleThread); //throw FileError, ThreadInterruption + void removeFileWithCallback(const FileDescriptor& fileDescr, const Zstring& relativePath, AsyncItemStatReporter& statReporter, std::mutex& singleThread); //throw FileError, ThreadStopRequest void removeLinkWithCallback(const AbstractPath& linkPath, const Zstring& relativePath, AsyncItemStatReporter& statReporter, std::mutex& singleThread); // const std::wstring& getTxtRemovingFile () const { return txtRemovingFile_; } // @@ -698,7 +698,7 @@ txtRemovingFolder_([&] void DeletionHandler::tryCleanup(PhaseCallback& cb /*throw X*/) //throw X { - assert(runningMainThread()); + assert(runningOnMainThread()); switch (deletionPolicy_) { case DeletionPolicy::recycler: @@ -723,7 +723,7 @@ void DeletionHandler::tryCleanup(PhaseCallback& cb /*throw X*/) //throw X } -void DeletionHandler::removeDirWithCallback(const AbstractPath& folderPath,//throw FileError, ThreadInterruption +void DeletionHandler::removeDirWithCallback(const AbstractPath& folderPath,//throw FileError, ThreadStopRequest const Zstring& relativePath, AsyncItemStatReporter& statReporter, std::mutex& singleThread) { @@ -734,9 +734,9 @@ void DeletionHandler::removeDirWithCallback(const AbstractPath& folderPath,//thr //callbacks run *outside* singleThread_ lock! => fine auto notifyDeletion = [&statReporter](const std::wstring& statusText, const std::wstring& displayPath) { - statReporter.updateStatus(replaceCpy(statusText, L"%x", fmtPath(displayPath))); //throw ThreadInterruption + statReporter.updateStatus(replaceCpy(statusText, L"%x", fmtPath(displayPath))); //throw ThreadStopRequest statReporter.reportDelta(1, 0); //it would be more correct to report *after* work was done! - //OTOH: ThreadInterruption must not happen just after last deletion was successful: allow for transactional file model update! + //OTOH: ThreadStopRequest must not happen just after last deletion was successful: allow for transactional file model update! }; static_assert(std::is_const_v<decltype(txtRemovingFile_)>, "callbacks better be thread-safe!"); auto onBeforeFileDeletion = [&](const std::wstring& displayPath) { notifyDeletion(txtRemovingFile_, displayPath); }; @@ -756,22 +756,22 @@ void DeletionHandler::removeDirWithCallback(const AbstractPath& folderPath,//thr //callbacks run *outside* singleThread_ lock! => fine auto notifyMove = [&statReporter](const std::wstring& statusText, const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { - statReporter.updateStatus(replaceCpy(replaceCpy(statusText, L"%x", L'\n' + fmtPath(displayPathFrom)), L"%y", L'\n' + fmtPath(displayPathTo))); //throw ThreadInterruption + statReporter.updateStatus(replaceCpy(replaceCpy(statusText, L"%x", L'\n' + fmtPath(displayPathFrom)), L"%y", L'\n' + fmtPath(displayPathTo))); //throw ThreadStopRequest statReporter.reportDelta(1, 0); //it would be more correct to report *after* work was done! }; static_assert(std::is_const_v<decltype(txtMovingFileXtoY_)>, "callbacks better be thread-safe!"); auto onBeforeFileMove = [&](const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { notifyMove(txtMovingFileXtoY_, displayPathFrom, displayPathTo); }; auto onBeforeFolderMove = [&](const std::wstring& displayPathFrom, const std::wstring& displayPathTo) { notifyMove(txtMovingFolderXtoY_, displayPathFrom, displayPathTo); }; - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); interruptionPoint(); }; //throw ThreadInterruption + auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); interruptionPoint(); }; //throw ThreadStopRequest - parallel::revisionFolder(getOrCreateVersioner(), folderPath, relativePath, onBeforeFileMove, onBeforeFolderMove, notifyUnbufferedIO, singleThread); //throw FileError, ThreadInterruption + parallel::revisionFolder(getOrCreateVersioner(), folderPath, relativePath, onBeforeFileMove, onBeforeFolderMove, notifyUnbufferedIO, singleThread); //throw FileError, ThreadStopRequest } break; } } -void DeletionHandler::removeFileWithCallback(const FileDescriptor& fileDescr, //throw FileError, ThreadInterruption +void DeletionHandler::removeFileWithCallback(const FileDescriptor& fileDescr, //throw FileError, ThreadStopRequest const Zstring& relativePath, AsyncItemStatReporter& statReporter, std::mutex& singleThread) { @@ -790,9 +790,9 @@ void DeletionHandler::removeFileWithCallback(const FileDescriptor& fileDescr, // case DeletionPolicy::versioning: { //callback runs *outside* singleThread_ lock! => fine - auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); interruptionPoint(); }; //throw ThreadInterruption + auto notifyUnbufferedIO = [&](int64_t bytesDelta) { statReporter.reportDelta(0, bytesDelta); interruptionPoint(); }; //throw ThreadStopRequest - parallel::revisionFile(getOrCreateVersioner(), fileDescr, relativePath, notifyUnbufferedIO, singleThread); //throw FileError, ThreadInterruption + parallel::revisionFile(getOrCreateVersioner(), fileDescr, relativePath, notifyUnbufferedIO, singleThread); //throw FileError, ThreadStopRequest } break; } @@ -803,7 +803,7 @@ void DeletionHandler::removeFileWithCallback(const FileDescriptor& fileDescr, // } -void DeletionHandler::removeLinkWithCallback(const AbstractPath& linkPath, //throw FileError, throw ThreadInterruption +void DeletionHandler::removeLinkWithCallback(const AbstractPath& linkPath, //throw FileError, throw ThreadStopRequest const Zstring& relativePath, AsyncItemStatReporter& statReporter, std::mutex& singleThread) { @@ -833,13 +833,13 @@ class Workload public: Workload(size_t threadCount, AsyncCallback& acb) : acb_(acb), workload_(threadCount) { assert(threadCount > 0); } - using WorkItem = std::function<void() /*throw ThreadInterruption*/>; + using WorkItem = std::function<void() /*throw ThreadStopRequest*/>; using WorkItems = RingBuffer<WorkItem>; //FIFO! //blocking call: context of worker thread - WorkItem getNext(size_t threadIdx) //throw ThreadInterruption + WorkItem getNext(size_t threadIdx) //throw ThreadStopRequest { - interruptionPoint(); //throw ThreadInterruption + interruptionPoint(); //throw ThreadStopRequest std::unique_lock dummy(lockWork_); for (;;) @@ -881,7 +881,7 @@ public: auto haveNewWork = [&] { return !pendingWorkload_.empty() || std::any_of(workload_.begin(), workload_.end(), [](const WorkItems& wi) { return !wi.empty(); }); }; - interruptibleWait(conditionNewWork_, dummy, [&] { return haveNewWork(); }); //throw ThreadInterruption + interruptibleWait(conditionNewWork_, dummy, [&] { return haveNewWork(); }); //throw ThreadStopRequest //it's sufficient to notify condition in addWorkItems() only (as long as we use std::condition_variable::notify_all()) } } @@ -977,26 +977,26 @@ private: RingBuffer<Workload::WorkItems> getFolderLevelWorkItems(PassNo pass, ContainerObject& parentFolder, Workload& workload); static bool containsMoveTarget(const FolderPair& parent); - void executeFileMove(FilePair& file); //throw ThreadInterruption - template <SelectedSide side> void executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo); //throw ThreadInterruption + void executeFileMove(FilePair& file); //throw ThreadStopRequest + template <SelectedSide side> void executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo); //throw ThreadStopRequest void synchronizeFile(FilePair& file); // - template <SelectedSide side> void synchronizeFileInt(FilePair& file, SyncOperation syncOp); //throw FileError, ErrorMoveUnsupported, ThreadInterruption + template <SelectedSide side> void synchronizeFileInt(FilePair& file, SyncOperation syncOp); //throw FileError, ErrorMoveUnsupported, ThreadStopRequest void synchronizeLink(SymlinkPair& link); // - template <SelectedSide sideTrg> void synchronizeLinkInt(SymlinkPair& link, SyncOperation syncOp); //throw FileError, ThreadInterruption + template <SelectedSide sideTrg> void synchronizeLinkInt(SymlinkPair& link, SyncOperation syncOp); //throw FileError, ThreadStopRequest void synchronizeFolder(FolderPair& folder); // - template <SelectedSide sideTrg> void synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp); //throw FileError, ThreadInterruption + template <SelectedSide sideTrg> void synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp); //throw FileError, ThreadStopRequest void reportInfo(const std::wstring& rawText, const std::wstring& displayPath) { acb_.reportInfo(replaceCpy(rawText, L"%x", fmtPath(displayPath))); } - void reportInfo(const std::wstring& rawText, const std::wstring& displayPath1, const std::wstring& displayPath2) //throw ThreadInterruption + void reportInfo(const std::wstring& rawText, const std::wstring& displayPath1, const std::wstring& displayPath2) //throw ThreadStopRequest { - acb_.reportInfo(replaceCpy(replaceCpy(rawText, L"%x", L'\n' + fmtPath(displayPath1)), L"%y", L'\n' + fmtPath(displayPath2))); //throw ThreadInterruption + acb_.reportInfo(replaceCpy(replaceCpy(rawText, L"%x", L'\n' + fmtPath(displayPath1)), L"%y", L'\n' + fmtPath(displayPath2))); //throw ThreadStopRequest } //already existing after onDeleteTargetFile(): undefined behavior! (e.g. fail/overwrite/auto-rename) - AFS::FileCopyResult copyFileWithCallback(const FileDescriptor& sourceDescr, //throw FileError, ThreadInterruption, X + AFS::FileCopyResult copyFileWithCallback(const FileDescriptor& sourceDescr, //throw FileError, ThreadStopRequest, X const AbstractPath& targetPath, const std::function<void()>& onDeleteTargetFile /*throw X*/, //optional! AsyncItemStatReporter& statReporter); @@ -1059,22 +1059,21 @@ void FolderPairSyncer::runPass(PassNo pass, SyncCtx& syncCtx, BaseFolderPair& ba workload.addWorkItems(fps.getFolderLevelWorkItems(pass, baseFolder, workload)); //initial workload: set *before* threads get access! std::vector<InterruptibleThread> worker; - ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.join (); ); //interrupt all first, then join - ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.interrupt(); ); // + ZEN_ON_SCOPE_EXIT( for (InterruptibleThread& wt : worker) wt.requestStop(); ); //stop *all* at the same time before join! size_t threadIdx = 0; - std::string threadName = "Sync Worker"; + Zstring threadName = Zstr("Sync Worker"); worker.emplace_back([threadIdx, &singleThread, &acb, &workload, threadName = std::move(threadName)] { - setCurrentThreadName(threadName.c_str()); + setCurrentThreadName(threadName); - while (/*blocking call:*/ std::function<void()> workItem = workload.getNext(threadIdx)) //throw ThreadInterruption + while (/*blocking call:*/ std::function<void()> workItem = workload.getNext(threadIdx)) //throw ThreadStopRequest { acb.notifyTaskBegin(0 /*prio*/); //same prio, while processing only one folder pair at a time ZEN_ON_SCOPE_EXIT(acb.notifyTaskEnd()); std::lock_guard dummy(singleThread); //protect ALL accesses to "fps" and workItem execution! - workItem(); //throw ThreadInterruption + workItem(); //throw ThreadStopRequest } }); acb.waitUntilDone(UI_UPDATE_INTERVAL / 2 /*every ~50 ms*/, cb); //throw X @@ -1100,7 +1099,7 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo { for (FilePair& file : hierObj.refSubFiles()) if (needZeroPass(file)) - workItems.push_back([this, &file] { executeFileMove(file); /*throw ThreadInterruption*/ }); + workItems.push_back([this, &file] { executeFileMove(file); /*throw ThreadStopRequest*/ }); //create folders as required by file move targets: for (FolderPair& folder : hierObj.refSubFolders()) @@ -1109,7 +1108,7 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo !haveNameClash(folder.getItemNameAny(), folder.parent().refSubLinks())) // => move: fall back to delete + copy workItems.push_back([this, &folder, &workload, pass] { - tryReportingError([&] { synchronizeFolder(folder); }, acb_); //throw ThreadInterruption + tryReportingError([&] { synchronizeFolder(folder); }, acb_); //throw ThreadStopRequest //error? => still process move targets (for delete + copy fall back!) workload.addWorkItems(getFolderLevelWorkItems(pass, folder, workload)); }); @@ -1123,7 +1122,7 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo if (pass == getPass(folder)) workItems.push_back([this, &folder, &workload, pass] { - tryReportingError([&] { synchronizeFolder(folder); }, acb_); //throw ThreadInterruption + tryReportingError([&] { synchronizeFolder(folder); }, acb_); //throw ThreadStopRequest workload.addWorkItems(getFolderLevelWorkItems(pass, folder, workload)); }); @@ -1135,7 +1134,7 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo if (pass == getPass(file)) workItems.push_back([this, &file] { - tryReportingError([&] { synchronizeFile(file); }, acb_); //throw ThreadInterruption + tryReportingError([&] { synchronizeFile(file); }, acb_); //throw ThreadStopRequest }); //synchronize symbolic links: @@ -1143,7 +1142,7 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo if (pass == getPass(symlink)) workItems.push_back([this, &symlink] { - tryReportingError([&] { synchronizeLink(symlink); }, acb_); //throw ThreadInterruption + tryReportingError([&] { synchronizeLink(symlink); }, acb_); //throw ThreadStopRequest }); } @@ -1184,7 +1183,7 @@ RingBuffer<Workload::WorkItems> FolderPairSyncer::getFolderLevelWorkItems(PassNo a -> b/a */ template <SelectedSide side> -void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo) //throw ThreadInterruption +void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo) //throw ThreadStopRequest { const bool fallBackCopyDelete = [&] { @@ -1198,7 +1197,7 @@ void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo) reportInfo(_("Cannot move file %x to %y.") + L"\n\n" + replaceCpy(_("Parent folder %x is not existing."), L"%x", fmtPath(AFS::getDisplayPath(parentMissing->getAbstractPath<side>()))), AFS::getDisplayPath(fileFrom.getAbstractPath<side>()), - AFS::getDisplayPath(fileTo .getAbstractPath<side>())); //throw ThreadInterruption + AFS::getDisplayPath(fileTo .getAbstractPath<side>())); //throw ThreadStopRequest return true; } @@ -1208,16 +1207,16 @@ void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo) { reportInfo(_("Cannot move file %x to %y.") + L"\n\n" + replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(fileTo.getItemNameAny())), AFS::getDisplayPath(fileFrom.getAbstractPath<side>()), - AFS::getDisplayPath(fileTo .getAbstractPath<side>())); //throw ThreadInterruption + AFS::getDisplayPath(fileTo .getAbstractPath<side>())); //throw ThreadStopRequest return true; } bool moveSupported = true; - const std::wstring errMsg = tryReportingError([&] //throw ThreadInterruption + const std::wstring errMsg = tryReportingError([&] //throw ThreadStopRequest { try { - synchronizeFile(fileTo); //throw FileError, ErrorMoveUnsupported, ThreadInterruption + synchronizeFile(fileTo); //throw FileError, ErrorMoveUnsupported, ThreadStopRequest } catch (const ErrorMoveUnsupported& e) { @@ -1248,7 +1247,7 @@ void FolderPairSyncer::executeFileMoveImpl(FilePair& fileFrom, FilePair& fileTo) } -void FolderPairSyncer::executeFileMove(FilePair& file) //throw ThreadInterruption +void FolderPairSyncer::executeFileMove(FilePair& file) //throw ThreadStopRequest { const SyncOperation syncOp = file.getSyncOperation(); switch (syncOp) @@ -1260,9 +1259,9 @@ void FolderPairSyncer::executeFileMove(FilePair& file) //throw ThreadInterruptio assert(fileFrom->getMoveRef() == file.getId()); if (syncOp == SO_MOVE_LEFT_TO) - executeFileMoveImpl<LEFT_SIDE>(*fileFrom, file); //throw ThreadInterruption + executeFileMoveImpl<LEFT_SIDE>(*fileFrom, file); //throw ThreadStopRequest else - executeFileMoveImpl<RIGHT_SIDE>(*fileFrom, file); //throw ThreadInterruption + executeFileMoveImpl<RIGHT_SIDE>(*fileFrom, file); //throw ThreadStopRequest } else assert(false); break; @@ -1472,7 +1471,7 @@ FolderPairSyncer::PassNo FolderPairSyncer::getPass(const FolderPair& folder) //--------------------------------------------------------------------------------------------------------------- inline -void FolderPairSyncer::synchronizeFile(FilePair& file) //throw FileError, ErrorMoveUnsupported, ThreadInterruption +void FolderPairSyncer::synchronizeFile(FilePair& file) //throw FileError, ErrorMoveUnsupported, ThreadStopRequest { const SyncOperation syncOp = file.getSyncOperation(); @@ -1487,7 +1486,7 @@ void FolderPairSyncer::synchronizeFile(FilePair& file) //throw FileError, ErrorM template <SelectedSide sideTrg> -void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) //throw FileError, ErrorMoveUnsupported, ThreadInterruption +void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) //throw FileError, ErrorMoveUnsupported, ThreadStopRequest { constexpr SelectedSide sideSrc = OtherSide<sideTrg>::value; DeletionHandler& delHandlerTrg = SelectParam<sideTrg>::ref(delHandlerLeft_, delHandlerRight_); @@ -1502,7 +1501,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) return; //if parent directory creation failed, there's no reason to show more errors! const AbstractPath targetPath = file.getAbstractPath<sideTrg>(); - reportInfo(txtCreatingFile_, AFS::getDisplayPath(targetPath)); //throw ThreadInterruption + reportInfo(txtCreatingFile_, AFS::getDisplayPath(targetPath)); //throw ThreadStopRequest AsyncItemStatReporter statReporter(1, file.getFileSize<sideSrc>(), acb_); try @@ -1511,7 +1510,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) targetPath, nullptr, //onDeleteTargetFile: nothing to delete //if existing: undefined behavior! (e.g. fail/overwrite/auto-rename) - statReporter); //throw FileError, ThreadInterruption + statReporter); //throw FileError, ThreadStopRequest if (result.errorModTime) errorsModTime_.push_back(*result.errorModTime); //show all warnings later as a single message @@ -1539,7 +1538,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) statReporter.reportDelta(1, 0); //even if the source item does not exist anymore, significant I/O work was done => report file.removeObject<sideSrc>(); //source deleted meanwhile...nothing was done (logical point of view!) - reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(file.getAbstractPath<sideSrc>())); //throw ThreadInterruption + reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(file.getAbstractPath<sideSrc>())); //throw ThreadStopRequest } else throw; @@ -1549,7 +1548,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) case SO_DELETE_LEFT: case SO_DELETE_RIGHT: - reportInfo(delHandlerTrg.getTxtRemovingFile(), AFS::getDisplayPath(file.getAbstractPath<sideTrg>())); //throw ThreadInterruption + reportInfo(delHandlerTrg.getTxtRemovingFile(), AFS::getDisplayPath(file.getAbstractPath<sideTrg>())); //throw ThreadStopRequest { AsyncItemStatReporter statReporter(1, 0, acb_); @@ -1572,7 +1571,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) const AbstractPath pathFrom = fileFrom->getAbstractPath<sideTrg>(); const AbstractPath pathTo = fileTo ->getAbstractPath<sideTrg>(); - reportInfo(txtMovingFileXtoY_, AFS::getDisplayPath(pathFrom), AFS::getDisplayPath(pathTo)); //throw ThreadInterruption + reportInfo(txtMovingFileXtoY_, AFS::getDisplayPath(pathFrom), AFS::getDisplayPath(pathTo)); //throw ThreadStopRequest AsyncItemStatReporter statReporter(1, 0, acb_); @@ -1607,7 +1606,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) if (file.isFollowedSymlink<sideTrg>()) //follow link when updating file rather than delete it and replace with regular file!!! targetPathResolvedOld = targetPathResolvedNew = parallel::getSymlinkResolvedPath(file.getAbstractPath<sideTrg>(), singleThread_); //throw FileError - reportInfo(txtUpdatingFile_, AFS::getDisplayPath(targetPathResolvedOld)); //throw ThreadInterruption + reportInfo(txtUpdatingFile_, AFS::getDisplayPath(targetPathResolvedOld)); //throw ThreadStopRequest AsyncItemStatReporter statReporter(1, file.getFileSize<sideSrc>(), acb_); @@ -1631,14 +1630,14 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) //file.removeObject<sideTrg>(); -> doesn't make sense for isFollowedSymlink(); "file, sideTrg" evaluated below! //if fail-safe file copy is active, then the next operation will be a simple "rename" - //=> don't risk updateStatus() throwing ThreadInterruption() leaving the target deleted rather than updated! + //=> don't risk updateStatus() throwing ThreadStopRequest() leaving the target deleted rather than updated! //=> if failSafeFileCopy_ : don't run callbacks that could throw }; const AFS::FileCopyResult result = copyFileWithCallback({ file.getAbstractPath<sideSrc>(), file.getAttributes<sideSrc>() }, targetPathResolvedNew, onDeleteTargetFile, - statReporter); //throw FileError, ThreadInterruption, X + statReporter); //throw FileError, ThreadStopRequest, X if (result.errorModTime) errorsModTime_.push_back(*result.errorModTime); //show all warnings later as a single message @@ -1658,7 +1657,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: //harmonize with file_hierarchy.cpp::getSyncOpDescription!! - reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(file.getAbstractPath<sideTrg>())); //throw ThreadInterruption + reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(file.getAbstractPath<sideTrg>())); //throw ThreadStopRequest { AsyncItemStatReporter statReporter(1, 0, acb_); @@ -1703,7 +1702,7 @@ void FolderPairSyncer::synchronizeFileInt(FilePair& file, SyncOperation syncOp) inline -void FolderPairSyncer::synchronizeLink(SymlinkPair& link) //throw FileError, ThreadInterruption +void FolderPairSyncer::synchronizeLink(SymlinkPair& link) //throw FileError, ThreadStopRequest { const SyncOperation syncOp = link.getSyncOperation(); @@ -1718,7 +1717,7 @@ void FolderPairSyncer::synchronizeLink(SymlinkPair& link) //throw FileError, Thr template <SelectedSide sideTrg> -void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation syncOp) //throw FileError, ThreadInterruption +void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation syncOp) //throw FileError, ThreadStopRequest { constexpr SelectedSide sideSrc = OtherSide<sideTrg>::value; DeletionHandler& delHandlerTrg = SelectParam<sideTrg>::ref(delHandlerLeft_, delHandlerRight_); @@ -1733,7 +1732,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy return; //if parent directory creation failed, there's no reason to show more errors! const AbstractPath targetPath = symlink.getAbstractPath<sideTrg>(); - reportInfo(txtCreatingLink_, AFS::getDisplayPath(targetPath)); //throw ThreadInterruption + reportInfo(txtCreatingLink_, AFS::getDisplayPath(targetPath)); //throw ThreadStopRequest AsyncItemStatReporter statReporter(1, 0, acb_); try @@ -1762,7 +1761,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy statReporter.reportDelta(1, 0); symlink.removeObject<sideSrc>(); //source deleted meanwhile...nothing was done (logical point of view!) - reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(symlink.getAbstractPath<sideSrc>())); //throw ThreadInterruption + reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(symlink.getAbstractPath<sideSrc>())); //throw ThreadStopRequest } else throw; @@ -1772,7 +1771,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy case SO_DELETE_LEFT: case SO_DELETE_RIGHT: - reportInfo(delHandlerTrg.getTxtRemovingSymLink(), AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //throw ThreadInterruption + reportInfo(delHandlerTrg.getTxtRemovingSymLink(), AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //throw ThreadStopRequest { AsyncItemStatReporter statReporter(1, 0, acb_); @@ -1784,7 +1783,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy case SO_OVERWRITE_LEFT: case SO_OVERWRITE_RIGHT: - reportInfo(txtUpdatingLink_, AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //throw ThreadInterruption + reportInfo(txtUpdatingLink_, AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //throw ThreadStopRequest { AsyncItemStatReporter statReporter(1, 0, acb_); @@ -1794,7 +1793,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy //symlink.removeObject<sideTrg>(); -> "symlink, sideTrg" evaluated below! - //=> don't risk updateStatus() throwing ThreadInterruption() leaving the target deleted rather than updated: + //=> don't risk updateStatus() throwing ThreadStopRequest() leaving the target deleted rather than updated: //updateStatus(txtUpdatingLink_, AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //restore status text parallel::copySymlink(symlink.getAbstractPath<sideSrc>(), @@ -1812,7 +1811,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: - reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //throw ThreadInterruption + reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(symlink.getAbstractPath<sideTrg>())); //throw ThreadStopRequest { AsyncItemStatReporter statReporter(1, 0, acb_); @@ -1852,7 +1851,7 @@ void FolderPairSyncer::synchronizeLinkInt(SymlinkPair& symlink, SyncOperation sy inline -void FolderPairSyncer::synchronizeFolder(FolderPair& folder) //throw FileError, ThreadInterruption +void FolderPairSyncer::synchronizeFolder(FolderPair& folder) //throw FileError, ThreadStopRequest { const SyncOperation syncOp = folder.getSyncOperation(); @@ -1867,7 +1866,7 @@ void FolderPairSyncer::synchronizeFolder(FolderPair& folder) //throw FileError, template <SelectedSide sideTrg> -void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp) //throw FileError, ThreadInterruption +void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation syncOp) //throw FileError, ThreadStopRequest { constexpr SelectedSide sideSrc = OtherSide<sideTrg>::value; DeletionHandler& delHandlerTrg = SelectParam<sideTrg>::ref(delHandlerLeft_, delHandlerRight_); @@ -1882,7 +1881,7 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy return; //if parent directory creation failed, there's no reason to show more errors! const AbstractPath targetPath = folder.getAbstractPath<sideTrg>(); - reportInfo(txtCreatingFolder_, AFS::getDisplayPath(targetPath)); //throw ThreadInterruption + reportInfo(txtCreatingFolder_, AFS::getDisplayPath(targetPath)); //throw ThreadStopRequest //shallow-"copying" a folder might not fail if source is missing, so we need to check this first: if (parallel::itemStillExists(folder.getAbstractPath<sideSrc>(), singleThread_)) //throw FileError @@ -1923,14 +1922,14 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy acb_.updateDataProcessed(1, 0); //even if the source item does not exist anymore, significant I/O work was done => report acb_.updateDataTotal(getCUD(statsAfter) - getCUD(statsBefore) + 1, statsAfter.getBytesToProcess() - statsBefore.getBytesToProcess()); //noexcept - reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(folder.getAbstractPath<sideSrc>())); //throw ThreadInterruption + reportInfo(txtSourceItemNotFound_, AFS::getDisplayPath(folder.getAbstractPath<sideSrc>())); //throw ThreadStopRequest } } break; case SO_DELETE_LEFT: case SO_DELETE_RIGHT: - reportInfo(delHandlerTrg.getTxtRemovingFolder(), AFS::getDisplayPath(folder.getAbstractPath<sideTrg>())); //throw ThreadInterruption + reportInfo(delHandlerTrg.getTxtRemovingFolder(), AFS::getDisplayPath(folder.getAbstractPath<sideTrg>())); //throw ThreadStopRequest { const SyncStatistics subStats(folder); //counts sub-objects only! AsyncItemStatReporter statReporter(1 + getCUD(subStats), subStats.getBytesToProcess(), acb_); @@ -1950,7 +1949,7 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy case SO_OVERWRITE_RIGHT: // case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: - reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(folder.getAbstractPath<sideTrg>())); //throw ThreadInterruption + reportInfo(txtUpdatingAttributes_, AFS::getDisplayPath(folder.getAbstractPath<sideTrg>())); //throw ThreadStopRequest { AsyncItemStatReporter statReporter(1, 0, acb_); @@ -1987,7 +1986,7 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy //########################################################################################### //returns current attributes of source file -AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor& sourceDescr, //throw FileError, ThreadInterruption, X +AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor& sourceDescr, //throw FileError, ThreadStopRequest, X const AbstractPath& targetPath, const std::function<void()>& onDeleteTargetFile /*throw X*/, AsyncItemStatReporter& statReporter) @@ -1998,7 +1997,7 @@ AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor& auto copyOperation = [this, &sourceAttr, &targetPath, &onDeleteTargetFile, &statReporter](const AbstractPath& sourcePathTmp) { //already existing + no onDeleteTargetFile: undefined behavior! (e.g. fail/overwrite/auto-rename) - const AFS::FileCopyResult result = parallel::copyFileTransactional(sourcePathTmp, sourceAttr, //throw FileError, ErrorFileLocked, ThreadInterruption, X + const AFS::FileCopyResult result = parallel::copyFileTransactional(sourcePathTmp, sourceAttr, //throw FileError, ErrorFileLocked, ThreadStopRequest, X targetPath, copyFilePermissions_, failSafeFileCopy_, [&] @@ -2012,7 +2011,7 @@ AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor& [&](int64_t bytesDelta) //callback runs *outside* singleThread_ lock! => fine { statReporter.reportDelta(0, bytesDelta); - interruptionPoint(); //throw ThreadInterruption + interruptionPoint(); //throw ThreadStopRequest }, singleThread_); @@ -2022,19 +2021,19 @@ AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor& ZEN_ON_SCOPE_FAIL(try { parallel::removeFilePlain(targetPath, singleThread_); } catch (FileError&) {}); //delete target if verification fails - reportInfo(txtVerifyingFile_, AFS::getDisplayPath(targetPath)); //throw ThreadInterruption + reportInfo(txtVerifyingFile_, AFS::getDisplayPath(targetPath)); //throw ThreadStopRequest //callback runs *outside* singleThread_ lock! => fine - auto verifyCallback = [&](int64_t bytesDelta) { interruptionPoint(); }; //throw ThreadInterruption + auto verifyCallback = [&](int64_t bytesDelta) { interruptionPoint(); }; //throw ThreadStopRequest - parallel::verifyFiles(sourcePathTmp, targetPath, verifyCallback, singleThread_); //throw FileError, ThreadInterruption + parallel::verifyFiles(sourcePathTmp, targetPath, verifyCallback, singleThread_); //throw FileError, ThreadStopRequest } //#################### /Verification ############################# return result; }; - return copyOperation(sourcePath); //throw FileError, (ErrorFileLocked), ThreadInterruption + return copyOperation(sourcePath); //throw FileError, (ErrorFileLocked), ThreadStopRequest } //########################################################################################### diff --git a/FreeFileSync/Source/base/synchronization.h b/FreeFileSync/Source/base/synchronization.h index c9991a70..6fce469b 100644 --- a/FreeFileSync/Source/base/synchronization.h +++ b/FreeFileSync/Source/base/synchronization.h @@ -8,7 +8,6 @@ #define SYNCHRONIZATION_H_8913470815943295 #include <chrono> -//#include "config.h" #include "structures.h" #include "file_hierarchy.h" #include "process_callback.h" diff --git a/FreeFileSync/Source/base/versioning.cpp b/FreeFileSync/Source/base/versioning.cpp index 6aa241af..7ea21d9b 100644 --- a/FreeFileSync/Source/base/versioning.cpp +++ b/FreeFileSync/Source/base/versioning.cpp @@ -87,8 +87,8 @@ AbstractPath FileVersioner::generateVersionedPath(const Zstring& relativePath) c break; case VersioningStyle::timestampFile: //assemble time-stamped version name versionedRelPath = relativePath + Zstr(' ') + timeStamp_ + getDotExtension(relativePath); - assert(impl::parseVersionedFileName(afterLast(versionedRelPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)) == - std::pair(syncStartTime_, afterLast(relativePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL))); + assert(impl::parseVersionedFileName(afterLast(versionedRelPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all)) == + std::pair(syncStartTime_, afterLast(relativePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all))); (void)syncStartTime_; //clang: -Wunused-private-field break; } @@ -385,22 +385,20 @@ void getFolderItemCount(std::map<AbstractPath, size_t>& folderItemCount, const F } -bool fff::operator<(const VersioningLimitFolder& lhs, const VersioningLimitFolder& rhs) +std::weak_ordering fff::operator<=>(const VersioningLimitFolder& lhs, const VersioningLimitFolder& rhs) { - const int cmp = AFS::comparePath(lhs.versioningFolderPath, rhs.versioningFolderPath); - if (cmp != 0) - return cmp < 0; + if (const std::weak_ordering cmp = lhs.versioningFolderPath <=> rhs.versioningFolderPath; + cmp != std::weak_ordering::equivalent) + return cmp; if (lhs.versionMaxAgeDays != rhs.versionMaxAgeDays) - return lhs.versionMaxAgeDays < rhs.versionMaxAgeDays; + return lhs.versionMaxAgeDays <=> rhs.versionMaxAgeDays; if (lhs.versionMaxAgeDays > 0) - { if (lhs.versionCountMin != rhs.versionCountMin) - return lhs.versionCountMin < rhs.versionCountMin; - } + return lhs.versionCountMin <=> rhs.versionCountMin; - return lhs.versionCountMax < rhs.versionCountMax; + return lhs.versionCountMax <=> rhs.versionCountMax; } @@ -499,7 +497,7 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& folderLimi //similarly, failed folder traversal should not make folders look empty: for (const auto& [relPath, errorMsg] : folderVal.failedFolderReads) ++folderItemCount[AFS::appendRelPath(versioningFolderPath, relPath)]; - for (const auto& [relPath, errorMsg] : folderVal.failedItemReads ) ++folderItemCount[AFS::appendRelPath(versioningFolderPath, beforeLast(relPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE))]; + for (const auto& [relPath, errorMsg] : folderVal.failedItemReads ) ++folderItemCount[AFS::appendRelPath(versioningFolderPath, beforeLast(relPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none))]; } //--------- calculate excess file versions --------- @@ -553,11 +551,11 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& folderLimi const std::wstring txtDeletingFolder = _("Deleting folder %x"); std::function<void(const AbstractPath& folderPath, AsyncCallback& acb)> deleteEmptyFolderTask; - deleteEmptyFolderTask = [&txtDeletingFolder, &folderItemCountShared, &deleteEmptyFolderTask](const AbstractPath& folderPath, AsyncCallback& acb) //throw ThreadInterruption + deleteEmptyFolderTask = [&txtDeletingFolder, &folderItemCountShared, &deleteEmptyFolderTask](const AbstractPath& folderPath, AsyncCallback& acb) //throw ThreadStopRequest { - const std::wstring errMsg = tryReportingError([&] //throw ThreadInterruption + const std::wstring errMsg = tryReportingError([&] //throw ThreadStopRequest { - acb.updateStatus(replaceCpy(txtDeletingFolder, L"%x", fmtPath(AFS::getDisplayPath(folderPath)))); //throw ThreadInterruption + acb.updateStatus(replaceCpy(txtDeletingFolder, L"%x", fmtPath(AFS::getDisplayPath(folderPath)))); //throw ThreadStopRequest AFS::removeEmptyFolderIfExists(folderPath); //throw FileError }, acb); @@ -567,7 +565,7 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& folderLimi bool deleteParent = false; folderItemCountShared.access([&](auto& folderItemCount2) { deleteParent = --folderItemCount2[*parentPath] == 0; }); if (deleteParent) //we're done here anyway => no need to schedule parent deletion in a separate task! - deleteEmptyFolderTask(*parentPath, acb); //throw ThreadInterruption + deleteEmptyFolderTask(*parentPath, acb); //throw ThreadStopRequest } }; @@ -577,15 +575,15 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& folderLimi if (itemCount == 0) parallelWorkload.emplace_back(folderPath, [&deleteEmptyFolderTask](ParallelContext& ctx) { - deleteEmptyFolderTask(ctx.itemPath, ctx.acb); //throw ThreadInterruption + deleteEmptyFolderTask(ctx.itemPath, ctx.acb); //throw ThreadStopRequest }); for (const auto& [itemPath, isSymlink] : itemsToDelete) - parallelWorkload.emplace_back(itemPath, [isSymlink /*clang bug*/= isSymlink, &txtRemoving, &folderItemCountShared, &deleteEmptyFolderTask](ParallelContext& ctx) //throw ThreadInterruption + parallelWorkload.emplace_back(itemPath, [isSymlink /*clang bug*/= isSymlink, &txtRemoving, &folderItemCountShared, &deleteEmptyFolderTask](ParallelContext& ctx) //throw ThreadStopRequest { - const std::wstring errMsg = tryReportingError([&] //throw ThreadInterruption + const std::wstring errMsg = tryReportingError([&] //throw ThreadStopRequest { - ctx.acb.reportInfo(txtRemoving + AFS::getDisplayPath(ctx.itemPath)); //throw ThreadInterruption + ctx.acb.reportInfo(txtRemoving + AFS::getDisplayPath(ctx.itemPath)); //throw ThreadStopRequest if (isSymlink) AFS::removeSymlinkIfExists(ctx.itemPath); //throw FileError else @@ -598,10 +596,10 @@ void fff::applyVersioningLimit(const std::set<VersioningLimitFolder>& folderLimi bool deleteParent = false; folderItemCountShared.access([&](auto& folderItemCount2) { deleteParent = --folderItemCount2[*parentPath] == 0; }); if (deleteParent) - deleteEmptyFolderTask(*parentPath, ctx.acb); //throw ThreadInterruption + deleteEmptyFolderTask(*parentPath, ctx.acb); //throw ThreadStopRequest } }); massParallelExecute(parallelWorkload, - "Versioning Limit", callback /*throw X*/); //throw X + Zstr("Versioning Limit"), callback /*throw X*/); //throw X } diff --git a/FreeFileSync/Source/base/versioning.h b/FreeFileSync/Source/base/versioning.h index d5edb345..68120c1e 100644 --- a/FreeFileSync/Source/base/versioning.h +++ b/FreeFileSync/Source/base/versioning.h @@ -97,7 +97,7 @@ struct VersioningLimitFolder int versionCountMin = 0; //only used if versionMaxAgeDays > 0 => < versionCountMax (if versionCountMax > 0) int versionCountMax = 0; //<= 0 := no limit }; -bool operator<(const VersioningLimitFolder& lhs, const VersioningLimitFolder& rhs); + std::weak_ordering operator<=>(const VersioningLimitFolder& lhs, const VersioningLimitFolder& rhs); void applyVersioningLimit(const std::set<VersioningLimitFolder>& folderLimits, diff --git a/FreeFileSync/Source/base_tools.cpp b/FreeFileSync/Source/base_tools.cpp index 01ebc563..cdd7056a 100644 --- a/FreeFileSync/Source/base_tools.cpp +++ b/FreeFileSync/Source/base_tools.cpp @@ -19,11 +19,11 @@ std::vector<unsigned int> fff::fromTimeShiftPhrase(const std::wstring& timeShift replace(tmp, L'-', L""); //there is no negative shift => treat as positive! std::set<unsigned int> minutes; - for (const std::wstring& part : split(tmp, L',', SplitType::SKIP_EMPTY)) + for (const std::wstring& part : split(tmp, L',', SplitOnEmpty::skip)) { if (contains(part, L':')) - minutes.insert(stringTo<unsigned int>(beforeFirst(part, L':', IF_MISSING_RETURN_NONE)) * 60 + - stringTo<unsigned int>(afterFirst (part, L':', IF_MISSING_RETURN_NONE))); + minutes.insert(stringTo<unsigned int>(beforeFirst(part, L':', IfNotFoundReturn::none)) * 60 + + stringTo<unsigned int>(afterFirst (part, L':', IfNotFoundReturn::none))); else minutes.insert(stringTo<unsigned int>(part) * 60); } diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp index f74c178e..b81005b2 100644 --- a/FreeFileSync/Source/config.cpp +++ b/FreeFileSync/Source/config.cpp @@ -111,7 +111,7 @@ std::vector<Zstring> splitFilterByLines(Zstring filterPhrase) if (filterPhrase.empty()) return {}; - return split(filterPhrase, Zstr('\n'), SplitType::ALLOW_EMPTY); + return split(filterPhrase, Zstr('\n'), SplitOnEmpty::allow); } Zstring mergeFilterLines(const std::vector<Zstring>& filterLines) @@ -995,14 +995,14 @@ Zstring substituteFfsResourcePath(const Zstring& filePath) { const Zstring resPathPf = getResourceDirPf(); if (startsWith(trimCpy(filePath, true, false), resPathPf)) - return Zstring(Zstr("%ffs_resource%")) + FILE_NAME_SEPARATOR + afterFirst(filePath, resPathPf, IF_MISSING_RETURN_NONE); + return Zstring(Zstr("%ffs_resource%")) + FILE_NAME_SEPARATOR + afterFirst(filePath, resPathPf, IfNotFoundReturn::none); return filePath; } Zstring resolveFfsResourceMacro(const Zstring& filePhrase) { if (startsWith(trimCpy(filePhrase, true, false), Zstring(Zstr("%ffs_resource%")) + FILE_NAME_SEPARATOR)) - return getResourceDirPf() + afterFirst(filePhrase, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + return getResourceDirPf() + afterFirst(filePhrase, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); return filePhrase; } } @@ -1224,9 +1224,9 @@ void readConfig(const XmlIn& in, LocalPairConfig& lpc, std::map<AfsDevice, size_ if (startsWithAsciiNoCase(folderPathPhrase, "sftp:") || startsWithAsciiNoCase(folderPathPhrase, "ftp:")) { - for (const Zstring& optPhrase : split(folderPathPhrase, Zstr("|"), SplitType::SKIP_EMPTY)) + for (const Zstring& optPhrase : split(folderPathPhrase, Zstr("|"), SplitOnEmpty::skip)) if (startsWith(optPhrase, Zstr("con="))) - parallelOps = stringTo<int>(afterFirst(optPhrase, Zstr("con="), IF_MISSING_RETURN_NONE)); + parallelOps = stringTo<int>(afterFirst(optPhrase, Zstr("con="), IfNotFoundReturn::none)); } }; getParallelOps(lpc.folderPathPhraseLeft, parallelOpsL); @@ -1734,13 +1734,15 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) inFileGrid["ColumnsLeft"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatLeftGrid); inFileGrid["ColumnsLeft"](cfg.gui.mainDlg.columnAttribLeft); - inFileGrid["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); - inFileGrid["ColumnsRight"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatRightGrid); inFileGrid["ColumnsRight"](cfg.gui.mainDlg.columnAttribRight); + inFileGrid["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); inFileGrid["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); + //inFileGrid["FolderHistoryLeft" ].attribute("DefaultPath", cfg.gui.mainDlg.defaultFolderPathLeft); + //inFileGrid["FolderHistoryRight"].attribute("DefaultPath", cfg.gui.mainDlg.defaultFolderPathRight); + //TODO: remove parameter migration after some time! 2018-01-08 if (formatVer < 6) { @@ -1778,7 +1780,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) //TODO: remove after migration! 2019-11-30 auto splitEditMerge = [](wxString& perspective, wchar_t delim, const std::function<void(wxString& item)>& editItem) { - std::vector<wxString> v = split(perspective, delim, SplitType::ALLOW_EMPTY); + std::vector<wxString> v = split(perspective, delim, SplitOnEmpty::allow); assert(!v.empty()); perspective.clear(); @@ -1813,11 +1815,11 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) splitEditMerge(paneCfg, L';', [&](wxString& paneAttr) { if (startsWith(paneAttr, L"dir=")) - tpDir = stringTo<int>(afterFirst(paneAttr, L'=', IF_MISSING_RETURN_NONE)); + tpDir = stringTo<int>(afterFirst(paneAttr, L'=', IfNotFoundReturn::none)); else if (startsWith(paneAttr, L"layer=")) - tpLayer = stringTo<int>(afterFirst(paneAttr, L'=', IF_MISSING_RETURN_NONE)); + tpLayer = stringTo<int>(afterFirst(paneAttr, L'=', IfNotFoundReturn::none)); else if (startsWith(paneAttr, L"row=")) - tpRow = stringTo<int>(afterFirst(paneAttr, L'=', IF_MISSING_RETURN_NONE)); + tpRow = stringTo<int>(afterFirst(paneAttr, L'=', IfNotFoundReturn::none)); }); }); @@ -2302,13 +2304,15 @@ void writeConfig(const XmlGlobalSettings& cfg, XmlOut& out) outFileGrid["ColumnsLeft"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatLeftGrid); outFileGrid["ColumnsLeft"](cfg.gui.mainDlg.columnAttribLeft); - outFileGrid["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); - outFileGrid["ColumnsRight"].attribute("PathFormat", cfg.gui.mainDlg.itemPathFormatRightGrid); outFileGrid["ColumnsRight"](cfg.gui.mainDlg.columnAttribRight); + outFileGrid["FolderHistoryLeft" ](cfg.gui.mainDlg.folderHistoryLeft); outFileGrid["FolderHistoryRight"](cfg.gui.mainDlg.folderHistoryRight); + //outFileGrid["FolderHistoryLeft" ].attribute("DefaultPath", cfg.gui.mainDlg.defaultFolderPathLeft); + //outFileGrid["FolderHistoryRight"].attribute("DefaultPath", cfg.gui.mainDlg.defaultFolderPathRight); + //########################################################### XmlOut outCopyTo = outWnd["ManualCopyTo"]; outCopyTo.attribute("KeepRelativePaths", cfg.gui.mainDlg.copyToCfg.keepRelPaths); @@ -2382,7 +2386,7 @@ void fff::writeConfig(const XmlGlobalSettings& cfg, const Zstring& filePath) std::wstring fff::extractJobName(const Zstring& cfgFilePath) { - const Zstring fileName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); - const Zstring jobName = beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_ALL); + const Zstring fileName = afterLast(cfgFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + const Zstring jobName = beforeLast(fileName, Zstr('.'), IfNotFoundReturn::all); return utfTo<std::wstring>(jobName); } diff --git a/FreeFileSync/Source/config.h b/FreeFileSync/Source/config.h index 76cd40ee..b85b7756 100644 --- a/FreeFileSync/Source/config.h +++ b/FreeFileSync/Source/config.h @@ -53,16 +53,9 @@ struct XmlGuiConfig { MainConfiguration mainCfg; GridViewType gridViewType = GridViewType::action; -}; - -inline -bool operator==(const XmlGuiConfig& lhs, const XmlGuiConfig& rhs) -{ - return lhs.mainCfg == rhs.mainCfg && - lhs.gridViewType == rhs.gridViewType; -} -inline bool operator!=(const XmlGuiConfig& lhs, const XmlGuiConfig& rhs) { return !(lhs == rhs); } + bool operator==(const XmlGuiConfig&) const = default; +}; struct BatchExclusiveConfig @@ -86,14 +79,9 @@ struct ConfirmationDialogs bool popupOnConfigChange = true; bool confirmSyncStart = true; bool confirmCommandMassInvoke = true; + + bool operator==(const ConfirmationDialogs&) const = default; }; -inline bool operator==(const ConfirmationDialogs& lhs, const ConfirmationDialogs& rhs) -{ - return lhs.popupOnConfigChange == rhs.popupOnConfigChange && - lhs.confirmSyncStart == rhs.confirmSyncStart && - lhs.confirmCommandMassInvoke == rhs.confirmCommandMassInvoke; -} -inline bool operator!=(const ConfirmationDialogs& lhs, const ConfirmationDialogs& rhs) { return !(lhs == rhs); } enum class FileIconSize @@ -192,6 +180,11 @@ struct XmlGlobalSettings std::vector<Zstring> folderHistoryLeft; std::vector<Zstring> folderHistoryRight; + + //warn_static("finish") + //Zstring defaultFolderPathLeft; + //Zstring defaultFolderPathRight; + bool showIcons = true; FileIconSize iconSize = FileIconSize::small; int sashOffset = 0; @@ -199,8 +192,8 @@ struct XmlGlobalSettings ItemPathFormat itemPathFormatLeftGrid = defaultItemPathFormatLeftGrid; ItemPathFormat itemPathFormatRightGrid = defaultItemPathFormatRightGrid; - std::vector<ColAttributesRim> columnAttribLeft = getFileGridDefaultColAttribsLeft(); - std::vector<ColAttributesRim> columnAttribRight = getFileGridDefaultColAttribsRight(); + std::vector<ColAttributesRim> columnAttribLeft = getFileGridDefaultColAttribsLeft(); + std::vector<ColAttributesRim> columnAttribRight = getFileGridDefaultColAttribsRight(); ViewFilterDefault viewFilterDefault; wxString guiPerspectiveLast; //used by wxAuiManager diff --git a/FreeFileSync/Source/fatal_error.h b/FreeFileSync/Source/fatal_error.h index 476a8075..eb025472 100644 --- a/FreeFileSync/Source/fatal_error.h +++ b/FreeFileSync/Source/fatal_error.h @@ -36,9 +36,9 @@ void logFatalError(const std::string& msg) //noexcept const std::string logEntry = '[' + utfTo<std::string>(formatTime(formatDateTimeTag)) + "] " + msg; try { - saveBinContainer(getConfigDirPathPf() + Zstr("LastError.log"), logEntry, nullptr /*notifyUnbufferedIO*/); //throw FileError + setFileContent(getConfigDirPathPf() + Zstr("LastError.log"), logEntry, nullptr /*notifyUnbufferedIO*/); //throw FileError } - catch (FileError&) {} + catch (FileError&) { assert(false); } } } diff --git a/FreeFileSync/Source/ffs_paths.cpp b/FreeFileSync/Source/ffs_paths.cpp index 843e702e..d62299b2 100644 --- a/FreeFileSync/Source/ffs_paths.cpp +++ b/FreeFileSync/Source/ffs_paths.cpp @@ -8,6 +8,7 @@ #include <zen/file_access.h> #include <zen/thread.h> #include <zen/symlink_target.h> +#include <zen/sys_info.h> #include <wx/stdpaths.h> #include <wx/app.h> @@ -23,7 +24,7 @@ Zstring getProcessParentFolderPath() //note: compiler generates magic-statics code => fine, we don't expect accesses during shutdown => don't need FunStatGlobal<> static const Zstring exeFolderParentPath = [] { - Zstring exeFolderPath = beforeLast(utfTo<Zstring>(wxStandardPaths::Get().GetExecutablePath()), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + Zstring exeFolderPath = beforeLast(utfTo<Zstring>(wxStandardPaths::Get().GetExecutablePath()), FILE_NAME_SEPARATOR, IfNotFoundReturn::none); try { //get rid of relative path fragments, e.g.: C:\Data\Projects\FreeFileSync\Source\..\Build\Bin @@ -31,7 +32,7 @@ Zstring getProcessParentFolderPath() } catch (FileError&) { assert(false); } - return beforeLast(exeFolderPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + return beforeLast(exeFolderPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); }(); return exeFolderParentPath; } @@ -50,7 +51,7 @@ std::once_flag onceFlagGetFfsVolumeId; VolumeId fff::getFfsVolumeId() //throw FileError { static VolumeId volumeId; //POD => no "magic static" code gen - std::call_once(onceFlagGetFfsVolumeId, [] { volumeId = getVolumeId(getProcessParentFolderPath()); }); //throw FileError + std::call_once(onceFlagGetFfsVolumeId, [] { volumeId = getVolumeId(getProcessPath()); }); //throw FileError return volumeId; } diff --git a/FreeFileSync/Source/icon_buffer.cpp b/FreeFileSync/Source/icon_buffer.cpp index a93b173d..d63eaf29 100644 --- a/FreeFileSync/Source/icon_buffer.cpp +++ b/FreeFileSync/Source/icon_buffer.cpp @@ -71,7 +71,7 @@ public: //context of main thread void set(const std::vector<AbstractPath>& newLoad) { - assert(runningMainThread()); + assert(runningOnMainThread()); { std::lock_guard dummy(lockFiles_); @@ -85,7 +85,7 @@ public: void add(const AbstractPath& filePath) //context of main thread { - assert(runningMainThread()); + assert(runningOnMainThread()); { std::lock_guard dummy(lockFiles_); workLoad_.emplace_back(filePath); //set as next item to retrieve @@ -94,12 +94,12 @@ public: } //context of worker thread, blocking: - AbstractPath extractNext() //throw ThreadInterruption + AbstractPath extractNext() //throw ThreadStopRequest { - assert(!runningMainThread()); + assert(!runningOnMainThread()); std::unique_lock dummy(lockFiles_); - interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty(); }); //throw ThreadInterruption + interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty(); }); //throw ThreadStopRequest AbstractPath filePath = workLoad_. back(); //yes, no strong exception guarantee (std::bad_alloc) /**/ workLoad_.pop_back(); // @@ -128,7 +128,7 @@ public: //- check wxImage::IsOk() + implement fallback if needed std::optional<wxImage> retrieve(const AbstractPath& filePath) { - assert(runningMainThread()); + assert(runningOnMainThread()); std::lock_guard dummy(lockIconList_); auto it = iconList.find(filePath); @@ -179,7 +179,7 @@ public: //call at an appropriate time, e.g. after Workload::set() void limitSize() { - assert(runningMainThread()); + assert(runningOnMainThread()); std::lock_guard dummy(lockIconList_); while (iconList.size() > BUFFER_SIZE_MAX) @@ -297,12 +297,12 @@ IconBuffer::IconBuffer(IconSize sz) : pimpl_(std::make_unique<Impl>()), iconSize { pimpl_->worker = InterruptibleThread([&workload = pimpl_->workload, &buffer = pimpl_->buffer, sz] { - setCurrentThreadName("Icon Buffer"); + setCurrentThreadName(Zstr("Icon Buffer")); for (;;) { //start work: blocks until next icon to load is retrieved: - const AbstractPath itemPath = workload.extractNext(); //throw ThreadInterruption + const AbstractPath itemPath = workload.extractNext(); //throw ThreadStopRequest if (!buffer.hasIcon(itemPath)) //perf: workload may contain duplicate entries? buffer.insert(itemPath, getDisplayIcon(itemPath, sz)); @@ -314,8 +314,8 @@ IconBuffer::IconBuffer(IconSize sz) : pimpl_(std::make_unique<Impl>()), iconSize IconBuffer::~IconBuffer() { setWorkload({}); //make sure interruption point is always reached! needed??? - pimpl_->worker.interrupt(); - pimpl_->worker.join(); + pimpl_->worker.requestStop(); //end thread life time *before* + pimpl_->worker.join(); //IconBuffer::Impl member clean up! } @@ -374,7 +374,7 @@ wxImage IconBuffer::getIconByExtension(const Zstring& filePath) { const Zstring& ext = getFileExtension(filePath); - assert(runningMainThread()); + assert(runningOnMainThread()); auto it = pimpl_->extensionIcons.find(ext); if (it == pimpl_->extensionIcons.end()) diff --git a/FreeFileSync/Source/localization.cpp b/FreeFileSync/Source/localization.cpp index f3c751f8..8108312c 100644 --- a/FreeFileSync/Source/localization.cpp +++ b/FreeFileSync/Source/localization.cpp @@ -6,6 +6,7 @@ #include "localization.h" #include <unordered_map> +#include <clocale> //setlocale #include <map> #include <list> #include <iterator> @@ -107,7 +108,7 @@ std::vector<TranslationInfo> loadTranslations() try //to load from ZIP first: { - const std::string rawStream = loadBinContainer<std::string>(zipPath, nullptr /*notifyUnbufferedIO*/); //throw FileError + const std::string rawStream = getFileContent(zipPath, nullptr /*notifyUnbufferedIO*/); //throw FileError wxMemoryInputStream memStream(rawStream.c_str(), rawStream.size()); //does not take ownership wxZipInputStream zipStream(memStream, wxConvUTF8); @@ -120,12 +121,12 @@ std::vector<TranslationInfo> loadTranslations() } catch (FileError&) //fall back to folder { - traverseFolder(beforeLast(zipPath, Zstr(".zip"), IF_MISSING_RETURN_NONE), [&](const FileInfo& fi) + traverseFolder(beforeLast(zipPath, Zstr(".zip"), IfNotFoundReturn::none), [&](const FileInfo& fi) { if (endsWith(fi.fullPath, Zstr(".lng"))) try { - std::string stream = loadBinContainer<std::string>(fi.fullPath, nullptr /*notifyUnbufferedIO*/); //throw FileError + std::string stream = getFileContent(fi.fullPath, nullptr /*notifyUnbufferedIO*/); //throw FileError streams.emplace_back(fi.itemName, std::move(stream)); } catch (FileError&) { assert(false); } @@ -400,8 +401,8 @@ public: private: static wxString extractIsoLangCode(wxString langCode) { - langCode = beforeLast(langCode, L".", IF_MISSING_RETURN_ALL); - return beforeLast(langCode, L"_", IF_MISSING_RETURN_ALL); + langCode = beforeLast(langCode, L".", IfNotFoundReturn::all); + return beforeLast(langCode, L"_", IfNotFoundReturn::all); } const wxString canonicalName_; @@ -439,6 +440,12 @@ public: //locale_.reset(); //avoid global locale lifetime overlap! wxWidgets cannot handle this and will crash! locale_ = std::make_unique<wxLocale>(sysLng_, wxLOCALE_DONT_LOAD_DEFAULT /*we're not using wxwin.mo*/); assert(locale_->IsOk()); + + //*needed* for Linux: wxWidgets overwrites the default locale "C" with a locale matching sysLng_, e.g. "en_US.UTF-8" + //which may be *different* from actual user-preferred locale as set up in "Region & Language/Formats"! + [[maybe_unused]]const char* newLocale = std::setlocale(LC_ALL, "" /*== user-preferred locale*/); + assert(newLocale); + //const char* currentLocale = std::setlocale(LC_ALL, nullptr); } } diff --git a/FreeFileSync/Source/log_file.cpp b/FreeFileSync/Source/log_file.cpp index 067f48b1..fb9c5b92 100644 --- a/FreeFileSync/Source/log_file.cpp +++ b/FreeFileSync/Source/log_file.cpp @@ -7,7 +7,7 @@ #include "log_file.h" #include <zen/file_io.h> #include <zen/http.h> -#include <zen/system.h> +#include <zen/sys_info.h> #include <wx/datetime.h> #include "ffs_paths.h" #include "afs/concrete.h" @@ -164,10 +164,10 @@ std::string formatMessageHtml(const LogEntry& entry) } return R"( <tr> - <td valign="top">)" + htmlTxt(formatTime(formatTimeTag, getLocalTime(entry.time))) + R"(</td> - <td valign="top"><img src="https://freefilesync.org/images/log/)" + typeImage + R"(" height="16" alt=")" + typeLabel + R"(:"></td> - <td>)" + htmlTxt(makeStringView(entry.message.begin(), entry.message.end())) + R"(</td> - </tr> + <td valign="top">)" + htmlTxt(formatTime(formatTimeTag, getLocalTime(entry.time))) + R"(</td> + <td valign="top"><img src="https://freefilesync.org/images/log/)" + typeImage + R"(" height="16" alt=")" + typeLabel + R"(:"></td> + <td>)" + htmlTxt(makeStringView(entry.message.begin(), entry.message.end())) + R"(</td> + </tr> )"; } @@ -199,20 +199,20 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, std::string output = R"(<!DOCTYPE html> <html lang="en"> <head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>)" + htmlTxt(generateLogTitle(s)) + R"(</title> - <style> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>)" + htmlTxt(generateLogTitle(s)) + R"(</title> + <style> )" + /*caveat: non-inline CSS is often ignored by email clients!*/ R"( - .summary-table td:nth-child(1) { padding-right: 10px; } - .summary-table td:nth-child(2) { padding-right: 5px; } - .summary-table img { display: block; } + .summary-table td:nth-child(1) { padding-right: 10px; } + .summary-table td:nth-child(2) { padding-right: 5px; } + .summary-table img { display: block; } .log-items img { display: block; } - .log-items td { padding-bottom: 0.1em; } - .log-items td:nth-child(1) { padding-right: 10px; white-space: nowrap; } - .log-items td:nth-child(2) { padding-right: 10px; } - </style> + .log-items td { padding-bottom: 0.1em; } + .log-items td:nth-child(1) { padding-right: 10px; white-space: nowrap; } + .log-items td:nth-child(2) { padding-right: 10px; } + </style> </head> <body style="font-family: -apple-system, 'Segoe UI', arial, Tahoma, Helvetica, sans-serif;"> )"; @@ -234,60 +234,60 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, case SyncResult::aborted: resultsStatusImage = "result-error.png"; break; } output += R"( - <div style="margin:10px 0; display:inline-block; border-radius:7px; background:#f8f8f8; box-shadow:1px 1px 4px #888; overflow:hidden;"> - <div style="background-color:white; border-bottom:1px solid #AAA; font-size:larger; padding:10px;"> - <img src="https://freefilesync.org/images/log/)" + resultsStatusImage + R"(" width="32" height="32" alt="" style="vertical-align:middle;"> - <span style="font-weight:600; vertical-align:middle;">)" + htmlTxt(getSyncResultLabel(s.syncResult)) + R"(</span> - </div> - <table role="presentation" class="summary-table" style="border-spacing:0; margin-left:10px; padding:5px 10px;">)"; + <div style="margin:10px 0; display:inline-block; border-radius:7px; background:#f8f8f8; box-shadow:1px 1px 4px #888; overflow:hidden;"> + <div style="background-color:white; border-bottom:1px solid #AAA; font-size:larger; padding:10px;"> + <img src="https://freefilesync.org/images/log/)" + resultsStatusImage + R"(" width="32" height="32" alt="" style="vertical-align:middle;"> + <span style="font-weight:600; vertical-align:middle;">)" + htmlTxt(getSyncResultLabel(s.syncResult)) + R"(</span> + </div> + <table role="presentation" class="summary-table" style="border-spacing:0; margin-left:10px; padding:5px 10px;">)"; const ErrorLog::Stats logCount = log.getStats(); if (logCount.error > 0) output += R"( - <tr> - <td>)" + htmlTxt(_("Errors:")) + R"(</td> - <td><img src="https://freefilesync.org/images/log/msg-error.png" width="24" height="24" alt=""></td> - <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(logCount.error)) + R"(</span></td> - </tr>)"; + <tr> + <td>)" + htmlTxt(_("Errors:")) + R"(</td> + <td><img src="https://freefilesync.org/images/log/msg-error.png" width="24" height="24" alt=""></td> + <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(logCount.error)) + R"(</span></td> + </tr>)"; if (logCount.warning > 0) output += R"( - <tr> - <td>)" + htmlTxt(_("Warnings:")) + R"(</td> - <td><img src="https://freefilesync.org/images/log/msg-warning.png" width="24" height="24" alt=""></td> - <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(logCount.warning)) + R"(</span></td> - </tr>)"; + <tr> + <td>)" + htmlTxt(_("Warnings:")) + R"(</td> + <td><img src="https://freefilesync.org/images/log/msg-warning.png" width="24" height="24" alt=""></td> + <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(logCount.warning)) + R"(</span></td> + </tr>)"; output += R"( - <tr> - <td>)" + htmlTxt(_("Items processed:")) + R"(</td> - <td><img src="https://freefilesync.org/images/log/file.png" width="24" height="24" alt=""></td> - <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(s.statsProcessed.items)) + "</span> (" + + <tr> + <td>)" + htmlTxt(_("Items processed:")) + R"(</td> + <td><img src="https://freefilesync.org/images/log/file.png" width="24" height="24" alt=""></td> + <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(s.statsProcessed.items)) + "</span> (" + htmlTxt(formatFilesizeShort(s.statsProcessed.bytes)) + R"()</td> - </tr>)"; + </tr>)"; if ((s.statsTotal.items < 0 && s.statsTotal.bytes < 0) || //no total items/bytes: e.g. for pure folder comparison s.statsProcessed == s.statsTotal) //...if everything was processed successfully ; else output += R"( - <tr> - <td>)" + htmlTxt(_("Items remaining:")) + R"(</td> - <td></td> - <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(s.statsTotal.items - s.statsProcessed.items)) + "</span> (" + + <tr> + <td>)" + htmlTxt(_("Items remaining:")) + R"(</td> + <td></td> + <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(s.statsTotal.items - s.statsProcessed.items)) + "</span> (" + htmlTxt(formatFilesizeShort(s.statsTotal.bytes - s.statsProcessed.bytes)) + R"()</td> - </tr>)"; + </tr>)"; const int64_t totalTimeSec = std::chrono::duration_cast<std::chrono::seconds>(s.totalTime).count(); output += R"( - <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> - </tr> - </table> - </div> + <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> + </tr> + </table> + </div> )"; //------------ warnings/errors preview ---------------- @@ -295,9 +295,9 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, if (logFailTotal > 0) { output += R"( - <div style="font-weight:600; font-size: large;">)" + htmlTxt(_("Errors and warnings:")) + R"(</div> - <div style="border-bottom: 1px solid #AAA; margin: 5px 0;"></div> - <table class="log-items" style="line-height:1em; border-spacing:0;"> + <div style="font-weight:600; font-size: large;">)" + htmlTxt(_("Errors and warnings:")) + R"(</div> + <div style="border-bottom: 1px solid #AAA; margin: 5px 0;"></div> + <table class="log-items" style="line-height:1em; border-spacing:0;"> )"; int previewCount = 0; if (logFailsPreviewMax > 0) @@ -320,7 +320,7 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log, } output += R"( - <table class="log-items" style="line-height:1em; border-spacing:0;"> + <table class="log-items" style="line-height:1em; border-spacing:0;"> )"; return output; } @@ -340,18 +340,18 @@ std::string generateLogFooterHtml(const std::wstring& logFilePath, int logItemsT return output += R"( <br> - <div style="border-bottom:1px solid #AAA; margin:5px 0;"></div> - <div style="font-size:small;"> - <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*/ + + <div style="border-bottom:1px solid #AAA; margin:5px 0;"></div> + <div style="font-size:small;"> + <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*/ + " [" + htmlTxt(getUserName()) /*throw FileError*/ + ']' + (!cm.model .empty() ? " – " + htmlTxt(cm.model ) : "") + (!cm.vendor.empty() ? " – " + htmlTxt(cm.vendor) : "") + R"(</span> - </div> - <div style="font-size:small;"> - <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> + </div> + <div style="font-size:small;"> + <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> </body> </html> )"; diff --git a/FreeFileSync/Source/parse_lng.h b/FreeFileSync/Source/parse_lng.h index a2734da0..7b8e09ec 100644 --- a/FreeFileSync/Source/parse_lng.h +++ b/FreeFileSync/Source/parse_lng.h @@ -539,8 +539,10 @@ private: if (!translation.empty()) { //check for invalid number of plural forms - if (pluralInfo.getCount() != static_cast<int>(translation.size())) - throw ParsingError({ replaceCpy(replaceCpy<std::wstring>(L"Invalid number of plural forms; actual: %x, expected: %y", L"%x", numberTo<std::wstring>(translation.size())), L"%y", numberTo<std::wstring>(pluralInfo.getCount())), scn_.posRow(), scn_.posCol() }); + if (pluralInfo.getCount() != translation.size()) + throw ParsingError({ replaceCpy(replaceCpy<std::wstring>(L"Invalid number of plural forms; actual: %x, expected: %y", + L"%x", numberTo<std::wstring>(translation.size())), + L"%y", numberTo<std::wstring>(pluralInfo.getCount())), scn_.posRow(), scn_.posCol() }); //check for duplicate plural form translations (catch copy & paste errors for single-number form translations) for (auto it = translation.begin(); it != translation.end(); ++it) @@ -548,10 +550,11 @@ private: { auto it2 = std::find(it + 1, translation.end(), *it); if (it2 != translation.end()) - throw ParsingError({ replaceCpy<std::wstring>(L"Duplicate plural form translation at index position %x", L"%x", numberTo<std::wstring>(it2 - translation.begin())), scn_.posRow(), scn_.posCol() }); + throw ParsingError({ replaceCpy<std::wstring>(L"Duplicate plural form translation at index position %x", + L"%x", numberTo<std::wstring>(it2 - translation.begin())), scn_.posRow(), scn_.posCol() }); } - for (int pos = 0; pos < static_cast<int>(translation.size()); ++pos) + for (size_t pos = 0; pos < translation.size(); ++pos) if (pluralInfo.isSingleNumberForm(pos)) { //translation needs to use decimal number if english source does so (e.g. frequently changing text like statistics) @@ -559,8 +562,8 @@ private: contains(original.first, "1")) { const int firstNumber = pluralInfo.getFirstNumber(pos); - if (!(contains(translation[pos], "%x") || - contains(translation[pos], numberTo<std::string>(firstNumber)))) + if (!contains(translation[pos], "%x") && + !contains(translation[pos], numberTo<std::string>(firstNumber))) throw ParsingError({ replaceCpy<std::wstring>(replaceCpy<std::wstring>(L"Plural form translation at index position %y needs to use the decimal number %z or the %x placeholder", L"%y", numberTo<std::wstring>(pos)), L"%z", numberTo<std::wstring>(firstNumber)), scn_.posRow(), scn_.posCol() }); } diff --git a/FreeFileSync/Source/parse_plural.h b/FreeFileSync/Source/parse_plural.h index 242dd4a6..bbf635f1 100644 --- a/FreeFileSync/Source/parse_plural.h +++ b/FreeFileSync/Source/parse_plural.h @@ -31,7 +31,7 @@ class PluralForm { public: PluralForm(const std::string& stream); //throw ParsingError - int getForm(int64_t n) const { n_ = std::abs(n) ; return static_cast<int>(expr_->eval()); } + size_t getForm(int64_t n) const { n_ = std::abs(n) ; return static_cast<size_t>(expr_->eval()); } private: std::shared_ptr<Expr<int64_t>> expr_; @@ -47,9 +47,9 @@ class PluralFormInfo public: PluralFormInfo(const std::string& definition, int pluralCount); //throw InvalidPluralForm - int getCount() const { return static_cast<int>(forms_.size()); } - bool isSingleNumberForm(int index) const { return 0 <= index && index < static_cast<int>(forms_.size()) ? forms_[index].count == 1 : false; } - int getFirstNumber (int index) const { return 0 <= index && index < static_cast<int>(forms_.size()) ? forms_[index].firstNumber : -1; } + size_t getCount() const { return forms_.size(); } + bool isSingleNumberForm(size_t index) const { return index < forms_.size() ? forms_[index].count == 1 : false; } + int getFirstNumber (size_t index) const { return index < forms_.size() ? forms_[index].firstNumber : -1; } private: struct FormInfo @@ -452,17 +452,15 @@ PluralFormInfo::PluralFormInfo(const std::string& definition, int pluralCount) / //perf: 80ns per iteration max (for arabic) //=> 1000 iterations should be fast enough and still detect all "single number forms" for (int j = 0; j < 1000; ++j) - { - const int form = pf.getForm(j); - if (0 <= form && form < static_cast<int>(forms_.size())) + if (const size_t formNo = pf.getForm(j); + formNo < forms_.size()) { - if (forms_[form].count == 0) - forms_[form].firstNumber = j; - ++forms_[form].count; + if (forms_[formNo].count == 0) + forms_[formNo].firstNumber = j; + ++forms_[formNo].count; } else throw InvalidPluralForm(); - } } catch (const plural::ParsingError&) { diff --git a/FreeFileSync/Source/status_handler.h b/FreeFileSync/Source/status_handler.h index 7c22f2cd..6bd31b60 100644 --- a/FreeFileSync/Source/status_handler.h +++ b/FreeFileSync/Source/status_handler.h @@ -8,11 +8,6 @@ #define STATUS_HANDLER_H_81704805908341534 #include <vector> -//#include <chrono> -//#include <thread> -//#include <string> -//#include <zen/i18n.h> -//#include <zen/basic_math.h> #include "base/process_callback.h" #include "return_codes.h" @@ -50,8 +45,9 @@ struct ProgressStats { int items = 0; int64_t bytes = 0; + + std::strong_ordering operator<=>(const ProgressStats&) const = default; }; -inline bool operator==(const ProgressStats& lhs, const ProgressStats& rhs) { return lhs.items == rhs.items && lhs.bytes == rhs.bytes; } //common statistics "everybody" needs diff --git a/FreeFileSync/Source/ui/abstract_folder_picker.cpp b/FreeFileSync/Source/ui/abstract_folder_picker.cpp index 9108e5f4..0fc1a056 100644 --- a/FreeFileSync/Source/ui/abstract_folder_picker.cpp +++ b/FreeFileSync/Source/ui/abstract_folder_picker.cpp @@ -53,13 +53,13 @@ public: AbstractFolderPickerDlg(wxWindow* parent, AbstractPath& folderPath); private: - void OnOkay (wxCommandEvent& event) override; - void OnCancel(wxCommandEvent& event) override { EndModal(ReturnAfsPicker::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnAfsPicker::BUTTON_CANCEL); } + void onOkay (wxCommandEvent& event) override; + void onCancel(wxCommandEvent& event) override { EndModal(ReturnAfsPicker::BUTTON_CANCEL); } + void onClose (wxCloseEvent& event) override { EndModal(ReturnAfsPicker::BUTTON_CANCEL); } - void OnKeyPressed(wxKeyEvent& event); - void OnExpandNode(wxTreeEvent& event) override; - void OnItemTooltip(wxTreeEvent& event); + void onLocalKeyEvent(wxKeyEvent& event); + void onExpandNode(wxTreeEvent& event) override; + void onItemTooltip(wxTreeEvent& event); void populateNodeThen(const wxTreeItemId& itemId, const std::function<void()>& evalOnGui /*optional*/, bool popupErrors); @@ -117,7 +117,7 @@ AbstractFolderPickerDlg::AbstractFolderPickerDlg(wxWindow* parent, AbstractPath& //1. test server connection: const AFS::ItemType type = AFS::getItemType(folderPath); //throw FileError //2. navigate + select path - navigateToExistingPath(rootId, split(folderPath.afsPath.value, FILE_NAME_SEPARATOR, SplitType::SKIP_EMPTY), type); + navigateToExistingPath(rootId, split(folderPath.afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip), type); } catch (const FileError& e) //not existing or access error { @@ -131,14 +131,14 @@ AbstractFolderPickerDlg::AbstractFolderPickerDlg(wxWindow* parent, AbstractPath& //=> works like a charm for GTK2 with window resizing problems and title bar corruption; e.g. Debian!!! Center(); //needs to be re-applied after a dialog size change! - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler (AbstractFolderPickerDlg::OnKeyPressed), nullptr, this); //dialog-specific local key events - Connect(wxEVT_TREE_ITEM_GETTOOLTIP, wxTreeEventHandler(AbstractFolderPickerDlg::OnItemTooltip), nullptr, this); + Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //dialog-specific local key events + Bind(wxEVT_TREE_ITEM_GETTOOLTIP, [this](wxTreeEvent& event) { onItemTooltip (event); }); m_treeCtrlFileSystem->SetFocus(); } -void AbstractFolderPickerDlg::OnKeyPressed(wxKeyEvent& event) +void AbstractFolderPickerDlg::onLocalKeyEvent(wxKeyEvent& event) { switch (event.GetKeyCode()) { @@ -147,7 +147,7 @@ void AbstractFolderPickerDlg::OnKeyPressed(wxKeyEvent& event) case WXK_NUMPAD_ENTER: { wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED); - OnOkay(dummy); + onOkay(dummy); return; } } @@ -280,7 +280,7 @@ void AbstractFolderPickerDlg::findAndNavigateToExistingPath(const AbstractPath& if (type) { m_staticTextStatus->SetLabel(L""); - navigateToExistingPath(m_treeCtrlFileSystem->GetRootItem(), split(folderPath.afsPath.value, FILE_NAME_SEPARATOR, SplitType::SKIP_EMPTY), *type); + navigateToExistingPath(m_treeCtrlFileSystem->GetRootItem(), split(folderPath.afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip), *type); } else //split into multiple small async tasks rather than a single large one! findAndNavigateToExistingPath(*AFS::getParentPath(folderPath)); @@ -348,7 +348,7 @@ void AbstractFolderPickerDlg::navigateToExistingPath(const wxTreeItemId& itemId, } -void AbstractFolderPickerDlg::OnExpandNode(wxTreeEvent& event) +void AbstractFolderPickerDlg::onExpandNode(wxTreeEvent& event) { const wxTreeItemId itemId = event.GetItem(); @@ -358,7 +358,7 @@ void AbstractFolderPickerDlg::OnExpandNode(wxTreeEvent& event) } -void AbstractFolderPickerDlg::OnItemTooltip(wxTreeEvent& event) +void AbstractFolderPickerDlg::onItemTooltip(wxTreeEvent& event) { wxString tooltip; if (auto itemData = dynamic_cast<AfsTreeItemData*>(m_treeCtrlFileSystem->GetItemData(event.GetItem()))) @@ -367,7 +367,7 @@ void AbstractFolderPickerDlg::OnItemTooltip(wxTreeEvent& event) } -void AbstractFolderPickerDlg::OnOkay(wxCommandEvent& event) +void AbstractFolderPickerDlg::onOkay(wxCommandEvent& event) { const wxTreeItemId itemId = m_treeCtrlFileSystem->GetFocusedItem(); diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp index 9b604bd4..a6e0a900 100644 --- a/FreeFileSync/Source/ui/batch_config.cpp +++ b/FreeFileSync/Source/ui/batch_config.cpp @@ -36,18 +36,18 @@ public: BatchDialog(wxWindow* parent, BatchDialogConfig& dlgCfg); private: - void OnClose (wxCloseEvent& event) override { EndModal(ReturnBatchConfig::BUTTON_CANCEL); } - void OnCancel (wxCommandEvent& event) override { EndModal(ReturnBatchConfig::BUTTON_CANCEL); } - void OnSaveBatchJob(wxCommandEvent& event) override; + void onClose (wxCloseEvent& event) override { EndModal(ReturnBatchConfig::BUTTON_CANCEL); } + void onCancel (wxCommandEvent& event) override { EndModal(ReturnBatchConfig::BUTTON_CANCEL); } + void onSaveBatchJob(wxCommandEvent& event) override; - void OnToggleIgnoreErrors(wxCommandEvent& event) override { updateGui(); } - void OnToggleRunMinimized(wxCommandEvent& event) override + void onToggleIgnoreErrors(wxCommandEvent& event) override { updateGui(); } + void onToggleRunMinimized(wxCommandEvent& event) override { m_checkBoxAutoClose->SetValue(m_checkBoxRunMinimized->GetValue()); //usually user wants to change both updateGui(); } - void OnHelpScheduleBatch(wxHyperlinkEvent& event) override { displayHelpEntry(L"schedule-a-batch-job", this); } + void onHelpScheduleBatch(wxHyperlinkEvent& event) override { displayHelpEntry(L"schedule-a-batch-job", this); } void onLocalKeyEvent(wxKeyEvent& event); @@ -82,8 +82,7 @@ BatchDialog::BatchDialog(wxWindow* parent, BatchDialogConfig& dlgCfg) : setConfig(dlgCfg); - //enable dialog-specific key events - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(BatchDialog::onLocalKeyEvent), nullptr, this); + 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!!! @@ -154,7 +153,7 @@ void BatchDialog::onLocalKeyEvent(wxKeyEvent& event) } -void BatchDialog::OnSaveBatchJob(wxCommandEvent& event) +void BatchDialog::onSaveBatchJob(wxCommandEvent& event) { //BatchDialogConfig dlgCfg = getConfig(); diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp index e3385a37..30df75e1 100644 --- a/FreeFileSync/Source/ui/cfg_grid.cpp +++ b/FreeFileSync/Source/ui/cfg_grid.cpp @@ -91,12 +91,12 @@ void ConfigView::addCfgFilesImpl(const std::vector<Zstring>& filePaths) if (equalNativePath(filePath, lastRunConfigPath_)) return std::make_tuple(utfTo<Zstring>(L'[' + _("Last session") + L']'), Details::CFG_TYPE_GUI, true); - const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); + const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); if (endsWithAsciiNoCase(fileName, ".ffs_gui")) - return std::make_tuple(beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_NONE), Details::CFG_TYPE_GUI, false); + return std::make_tuple(beforeLast(fileName, Zstr('.'), IfNotFoundReturn::none), Details::CFG_TYPE_GUI, false); else if (endsWithAsciiNoCase(fileName, ".ffs_batch")) - return std::make_tuple(beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_NONE), Details::CFG_TYPE_BATCH, false); + return std::make_tuple(beforeLast(fileName, Zstr('.'), IfNotFoundReturn::none), Details::CFG_TYPE_BATCH, false); else return std::make_tuple(fileName, Details::CFG_TYPE_NONE, false); }(); @@ -269,8 +269,8 @@ class GridDataCfg : private wxEvtHandler, public GridData public: GridDataCfg(Grid& grid) : grid_(grid) { - grid.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler(GridDataCfg::onMouseLeft), nullptr, this); - grid.Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(GridDataCfg::onMouseLeftDouble), nullptr, this); + grid.Bind(EVENT_GRID_MOUSE_LEFT_DOWN, [this](GridClickEvent& event) { onMouseLeft (event); }); + grid.Bind(EVENT_GRID_MOUSE_LEFT_DOUBLE, [this](GridClickEvent& event) { onMouseLeftDouble(event); }); } ConfigView& getDataView() { return cfgView_; } @@ -344,7 +344,7 @@ private: enum class HoverAreaLog { - LINK, + link, }; void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override @@ -446,7 +446,7 @@ private: }(); drawBitmapRtlNoMirror(dc, enabled ? statusIcon : statusIcon.ConvertToDisabled(), rectTmp, wxALIGN_CENTER); } - if (static_cast<HoverAreaLog>(rowHover) == HoverAreaLog::LINK) + if (static_cast<HoverAreaLog>(rowHover) == HoverAreaLog::link) drawBitmapRtlNoMirror(dc, loadImage("link_16"), rectTmp, wxALIGN_CENTER); break; } @@ -471,7 +471,7 @@ private: return 0; } - HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override + HoverArea getRowMouseHover(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)) @@ -484,10 +484,10 @@ private: if (!item->isLastRunCfg && !AFS::isNullPath(item->cfgItem.logFilePath) && AFS::getNativeItemPath(item->cfgItem.logFilePath)) - return static_cast<HoverArea>(HoverAreaLog::LINK); + return static_cast<HoverArea>(HoverAreaLog::link); break; } - return HoverArea::NONE; + return HoverArea::none; } void renderColumnLabel(wxDC& dc, const wxRect& rect, ColumnType colType, bool enabled, bool highlighted) override @@ -586,7 +586,7 @@ private: if (const ConfigView::Details* item = cfgView_.getItem(event.row_)) switch (static_cast<HoverAreaLog>(event.hoverArea_)) { - case HoverAreaLog::LINK: + case HoverAreaLog::link: try { if (std::optional<Zstring> nativePath = AFS::getNativeItemPath(item->cfgItem.logFilePath)) @@ -605,7 +605,7 @@ private: { switch (static_cast<HoverAreaLog>(event.hoverArea_)) { - case HoverAreaLog::LINK: + case HoverAreaLog::link: return; //swallow event here before MainDialog considers it as a request to start comparison } event.Skip(); @@ -643,7 +643,7 @@ void cfggrid::addAndSelect(Grid& grid, const std::vector<Zstring>& filePaths, bo getDataView(grid).addCfgFiles(filePaths); grid.Refresh(); //[!] let Grid know about changed row count *before* fiddling with selection!!! - grid.clearSelection(GridEventPolicy::DENY); + grid.clearSelection(GridEventPolicy::deny); const std::set<Zstring, LessNativePath> pathsSorted(filePaths.begin(), filePaths.end()); std::optional<size_t> selectionTopRow; @@ -654,7 +654,7 @@ void cfggrid::addAndSelect(Grid& grid, const std::vector<Zstring>& filePaths, bo if (!selectionTopRow) selectionTopRow = i; - grid.selectRow(i, GridEventPolicy::DENY); + grid.selectRow(i, GridEventPolicy::deny); } if (scrollToSelection && selectionTopRow) diff --git a/FreeFileSync/Source/ui/cfg_grid.h b/FreeFileSync/Source/ui/cfg_grid.h index 5be3ce71..19a7b427 100644 --- a/FreeFileSync/Source/ui/cfg_grid.h +++ b/FreeFileSync/Source/ui/cfg_grid.h @@ -59,7 +59,7 @@ std::vector<ColAttributesCfg> getCfgGridDefaultColAttribs() using namespace zen; return { - { ColumnTypeCfg::name, fastFromDIP(0 - 75 - 42), 1, true }, + { ColumnTypeCfg::name, -fastFromDIP(75) - fastFromDIP(42), 1, true }, { ColumnTypeCfg::lastSync, fastFromDIP(75), 0, true }, { ColumnTypeCfg::lastLog, fastFromDIP(42), 0, true }, //leave some room for the sort direction indicator }; diff --git a/FreeFileSync/Source/ui/command_box.cpp b/FreeFileSync/Source/ui/command_box.cpp index 16cf4d1c..4c1b177f 100644 --- a/FreeFileSync/Source/ui/command_box.cpp +++ b/FreeFileSync/Source/ui/command_box.cpp @@ -21,8 +21,7 @@ namespace inline wxString getSeparationLine() { return std::wstring(50, EM_DASH); } //no space between dashes! - -const wxEventType EVENT_VALIDATE_USER_SELECTION = wxNewEventType(); +wxDEFINE_EVENT(EVENT_VALIDATE_USER_SELECTION, wxCommandEvent); } @@ -42,12 +41,12 @@ CommandBox::CommandBox(wxWindow* parent, /*#*/ SetMinSize({fastFromDIP(150), -1}); //# workaround yet another wxWidgets bug: default minimum size is much too large for a wxComboBox //#################################### - Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (CommandBox::OnKeyEvent ), nullptr, this); - Connect(wxEVT_LEFT_DOWN, wxEventHandler (CommandBox::OnUpdateList), nullptr, this); - Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(CommandBox::OnSelection ), nullptr, this); - Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (CommandBox::OnMouseWheel), nullptr, this); + Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyEvent (event); }); + 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 - Connect(EVENT_VALIDATE_USER_SELECTION, wxCommandEventHandler(CommandBox::OnValidateSelection), nullptr, this); + Bind(EVENT_VALIDATE_USER_SELECTION, [this](wxCommandEvent& event) { onValidateSelection(event); }); } @@ -127,7 +126,7 @@ void CommandBox::setValueAndUpdateList(const wxString& value) } -void CommandBox::OnSelection(wxCommandEvent& event) +void CommandBox::onSelection(wxCommandEvent& event) { wxCommandEvent dummy(EVENT_VALIDATE_USER_SELECTION); //we cannot replace built-in commands at this position in call stack, so defer to a later time! if (auto handler = GetEventHandler()) @@ -137,7 +136,7 @@ void CommandBox::OnSelection(wxCommandEvent& event) } -void CommandBox::OnValidateSelection(wxCommandEvent& event) +void CommandBox::onValidateSelection(wxCommandEvent& event) { const wxString value = GetValue(); @@ -150,14 +149,14 @@ void CommandBox::OnValidateSelection(wxCommandEvent& event) } -void CommandBox::OnUpdateList(wxEvent& event) +void CommandBox::onUpdateList(wxEvent& event) { setValue(getValue()); event.Skip(); } -void CommandBox::OnKeyEvent(wxKeyEvent& event) +void CommandBox::onKeyEvent(wxKeyEvent& event) { const int keyCode = event.GetKeyCode(); diff --git a/FreeFileSync/Source/ui/command_box.h b/FreeFileSync/Source/ui/command_box.h index 99715f84..71e46335 100644 --- a/FreeFileSync/Source/ui/command_box.h +++ b/FreeFileSync/Source/ui/command_box.h @@ -30,7 +30,7 @@ public: const wxString choices[] = nullptr, long style = 0, const wxValidator& validator = wxDefaultValidator, - const wxString& name = wxComboBoxNameStr); + const wxString& name = wxASCII_STR(wxComboBoxNameStr)); void setHistory(const std::vector<Zstring>& history, size_t historyMax) { history_ = history; historyMax_ = historyMax; } std::vector<Zstring> getHistory() const { return history_; } @@ -42,11 +42,10 @@ public: //required for setting value correctly + Linux to ensure the dropdown is shown as being populated private: - void OnKeyEvent(wxKeyEvent& event); - void OnMouseWheel(wxMouseEvent& event) {} //swallow! this gives confusing UI feedback anyway - void OnSelection(wxCommandEvent& event); - void OnValidateSelection(wxCommandEvent& event); - void OnUpdateList(wxEvent& event); + void onKeyEvent(wxKeyEvent& event); + void onSelection(wxCommandEvent& event); + void onValidateSelection(wxCommandEvent& event); + 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 85451e3d..4ee72b97 100644 --- a/FreeFileSync/Source/ui/file_grid.cpp +++ b/FreeFileSync/Source/ui/file_grid.cpp @@ -24,8 +24,11 @@ using namespace zen; using namespace fff; -const wxEventType fff::EVENT_GRID_CHECK_ROWS = wxNewEventType(); -const wxEventType fff::EVENT_GRID_SYNC_DIRECTION = wxNewEventType(); +namespace fff +{ +wxDEFINE_EVENT(EVENT_GRID_CHECK_ROWS, CheckRowsEvent); +wxDEFINE_EVENT(EVENT_GRID_SYNC_DIRECTION, SyncDirectionEvent); +} namespace @@ -75,48 +78,42 @@ std::pair<ptrdiff_t, ptrdiff_t> getVisibleRows(const Grid& grid) //returns range } -void fillBackgroundDefaultColorAlternating(wxDC& dc, const wxRect& rect, bool evenRowNumber) +//accessibility, support high-contrast schemes => work with user-defined background color! +wxColor getAlternateBackgroundColor() { - //alternate background color to improve readability (while lacking cell borders) - if (!evenRowNumber) - { - //accessibility, support high-contrast schemes => work with user-defined background color! - const auto backCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + const auto backCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - auto incChannel = [](unsigned char c, int diff) { return static_cast<unsigned char>(std::max(0, std::min(255, c + diff))); }; + auto incChannel = [](unsigned char c, int diff) { return static_cast<unsigned char>(std::max(0, std::min(255, c + diff))); }; - auto getAdjustedColor = [&](int diff) - { - return wxColor(incChannel(backCol.Red (), diff), - incChannel(backCol.Green(), diff), - incChannel(backCol.Blue (), diff)); - }; + auto getAdjustedColor = [&](int diff) + { + return wxColor(incChannel(backCol.Red (), diff), + incChannel(backCol.Green(), diff), + incChannel(backCol.Blue (), diff)); + }; - auto colorDist = [](const wxColor& lhs, const wxColor& rhs) //just some metric - { - return numeric::power<2>(static_cast<int>(lhs.Red ()) - static_cast<int>(rhs.Red ())) + - numeric::power<2>(static_cast<int>(lhs.Green()) - static_cast<int>(rhs.Green())) + - numeric::power<2>(static_cast<int>(lhs.Blue ()) - static_cast<int>(rhs.Blue ())); - }; + auto colorDist = [](const wxColor& lhs, const wxColor& rhs) //just some metric + { + return numeric::power<2>(static_cast<int>(lhs.Red ()) - static_cast<int>(rhs.Red ())) + + numeric::power<2>(static_cast<int>(lhs.Green()) - static_cast<int>(rhs.Green())) + + numeric::power<2>(static_cast<int>(lhs.Blue ()) - static_cast<int>(rhs.Blue ())); + }; - const int signLevel = colorDist(backCol, *wxBLACK) < colorDist(backCol, *wxWHITE) ? 1 : -1; //brighten or darken + const int signLevel = colorDist(backCol, *wxBLACK) < colorDist(backCol, *wxWHITE) ? 1 : -1; //brighten or darken - const wxColor colOutter = getAdjustedColor(signLevel * 14); //just some very faint gradient to avoid visual distraction - const wxColor colInner = getAdjustedColor(signLevel * 11); // + //just some very faint gradient to avoid visual distraction + const wxColor altCol = getAdjustedColor(signLevel * 10); + return altCol; +} - //clearArea(dc, rect, backColAlt); - //add some nice background gradient - wxRect rectUpper = rect; - rectUpper.height /= 2; - wxRect rectLower = rect; - rectLower.y += rectUpper.height; - rectLower.height -= rectUpper.height; - dc.GradientFillLinear(rectUpper, colOutter, colInner, wxSOUTH); - dc.GradientFillLinear(rectLower, colOutter, colInner, wxNORTH); - } +//improve readability (while lacking cell borders) +wxColor getDefaultBackgroundColorAlternating(bool wantStandardColor) +{ + if (wantStandardColor) + return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); else - clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + return getAlternateBackgroundColor(); } @@ -237,9 +234,8 @@ public: fsObj2 = dynamic_cast<const FolderPair*>(&parent); if (!fsObj2) - break; + return false; } - return false; } private: @@ -251,11 +247,12 @@ private: struct SharedComponents //...between left, center, and right grids { - FileView gridDataView; + SharedRef<FileView> gridDataView = makeSharedRef<FileView>(); std::unique_ptr<IconManager> iconMgr; NavigationMarker navMarker; std::unique_ptr<GridEventManager> evtMgr; GridViewType gridViewType = GridViewType::action; + std::unordered_map<std::wstring, wxSize> compExtentsBuf_; //buffer expensive wxDC::GetTextExtent() calls! }; //######################################################################################################## @@ -266,10 +263,17 @@ public: GridDataBase(Grid& grid, const SharedRef<SharedComponents>& sharedComp) : grid_(grid), sharedComp_(sharedComp) {} + void setData(FolderComparison& folderCmp) + { + sharedComp_.ref().gridDataView = makeSharedRef<FileView>(); //clear old data view first! avoid memory peaks! + sharedComp_.ref().gridDataView = makeSharedRef<FileView>(folderCmp); + sharedComp_.ref().compExtentsBuf_.clear(); //doesn't become stale! but still: re-calculate and save some memory... + } + GridEventManager* getEventManager() { return sharedComp_.ref().evtMgr.get(); } - /**/ FileView& getDataView() { return sharedComp_.ref().gridDataView; } - const FileView& getDataView() const { return sharedComp_.ref().gridDataView; } + /**/ FileView& getDataView() { return sharedComp_.ref().gridDataView.ref(); } + const FileView& getDataView() const { return sharedComp_.ref().gridDataView.ref(); } void setIconManager(std::unique_ptr<IconManager> iconMgr) { sharedComp_.ref().iconMgr = std::move(iconMgr); } @@ -291,6 +295,20 @@ public: const FileSystemObject* getFsObject(size_t row) const { return getDataView().getFsObject(row); } + const wxSize& getTextExtentBuffered(wxDC& dc, const std::wstring& text) + { + auto& compExtentsBuf = sharedComp_.ref().compExtentsBuf_; + //- only used for parent path names and file names on view => should not grow "too big" + //- cleaned up during GridDataBase::setData() + + auto it = compExtentsBuf.find(text); + if (it == compExtentsBuf.end()) + it = compExtentsBuf.emplace(text, dc.GetTextExtent(text)).first; + return it->second; + } + + int getGroupItemNamesWidth(wxDC& dc, const FileView::PathDrawInfo& pdi); + private: size_t getRowCount() const override { return getDataView().rowsOnView(); } @@ -381,11 +399,74 @@ private: return pos % 2 == 0 ? pos / 2 : total - 1 - pos / 2; } -protected: +private: + enum class DisplayType + { + inactive, + normal, + folder, + symlink, + }; + DisplayType getObjectDisplayType(const FileSystemObject* fsObj) const + { + if (!fsObj || !fsObj->isActive()) + return DisplayType::inactive; + + DisplayType output = DisplayType::normal; + + visitFSObject(*fsObj, [&](const FolderPair& folder) { output = DisplayType::folder; }, + [](const FilePair& file) {}, + [&](const SymlinkPair& symlink) { output = DisplayType::symlink; }); + + return output; + } + + + std::wstring getValue(size_t row, ColumnType colType) const override + { + std::wstring value; + if (const FileSystemObject* fsObj = getFsObject(row)) + if (!fsObj->isEmpty<side>()) + 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; + + case ColumnTypeRim::size: + visitFSObject(*fsObj, [&](const FolderPair& folder) { value = L"<" + _("Folder") + L">"; }, + [&](const FilePair& file) { value = formatNumber(file.getFileSize<side>()); }, + //[&](const FilePair& file) { value = utfTo<std::wstring>(formatAsHexString(file.getFileId<side>())); }, // -> test file id + [&](const SymlinkPair& symlink) { value = L"<" + _("Symlink") + L">"; }); + break; + + case ColumnTypeRim::date: + visitFSObject(*fsObj, [](const FolderPair& folder) {}, + [&](const FilePair& file) { value = formatUtcToLocalTime(file .getLastWriteTime<side>()); }, + [&](const SymlinkPair& symlink) { value = formatUtcToLocalTime(symlink.getLastWriteTime<side>()); }); + break; + + case ColumnTypeRim::extension: + visitFSObject(*fsObj, [](const FolderPair& folder) {}, + [&](const FilePair& file) { value = utfTo<std::wstring>(getFileExtension(file .getItemName<side>())); }, + [&](const SymlinkPair& symlink) { value = utfTo<std::wstring>(getFileExtension(symlink.getItemName<side>())); }); + break; + } + return value; + } + void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override { const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); - bool drawBottomLine = pdi.isLastGroupItem; if (enabled && !selected) { @@ -408,18 +489,14 @@ protected: } } - if (dispTp == DisplayType::normal) - { - //alternate background color to improve readability (without using cell borders) - fillBackgroundDefaultColorAlternating(dc, rect, row % 2 == 0); - return wxNullColour; - } - + if (dispTp == DisplayType::normal) //improve readability (without using cell borders) + return getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0); +#if 0 //draw horizontal border if required if (const DisplayType dispTpNext = getObjectDisplayType(getFsObject(row + 1)); dispTp == dispTpNext) drawBottomLine = true; - +#endif switch (dispTp) { //*INDENT-OFF* @@ -436,78 +513,220 @@ protected: clearArea(dc, rect, backCol); } else - GridData::renderRowBackgound(dc, rect, row, enabled, enabled && selected); + GridData::renderRowBackgound(dc, rect, row, enabled, selected); //---------------------------------------------------------------------------------- - if (drawBottomLine) + wxDCPenChanger dummy(dc, wxPen(row == pdi.groupEndRow - 1 /*last group item*/ ? + getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0), fastFromDIP(1))); + dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); + } + + + int getGroupItemNamesWidth(wxDC& dc, const FileView::PathDrawInfo& pdi) + { + //FileView::updateView() called? => invalidates group item render buffer + if (pdi.viewUpdateId != viewUpdateIdLast_) + { + viewUpdateIdLast_ = pdi.viewUpdateId; + groupItemNamesWidthBuf_.clear(); + } + + auto& widthBuf = groupItemNamesWidthBuf_; + if (pdi.groupIdx >= widthBuf.size()) + widthBuf.resize(pdi.groupIdx + 1); + + int& itemNamesWidth = widthBuf[pdi.groupIdx]; + if (itemNamesWidth == 0) { - wxDCPenChanger dummy(dc, wxPen(getColorGridLine(), fastFromDIP(1))); - dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); + itemNamesWidth = getTextExtentBuffered(dc, ELLIPSIS).x; + + std::vector<int> itemWidths; + for (size_t row2 = pdi.groupBeginRow; row2 < pdi.groupEndRow; ++row2) + if (const FileSystemObject* fsObj = getDataView().getFsObject(row2)) + if (!fsObj->isEmpty<side>() && !dynamic_cast<const FolderPair*>(fsObj)) + itemWidths.push_back(getTextExtentBuffered(dc, utfTo<std::wstring>(fsObj->getItemName<side>())).x); + + if (!itemWidths.empty()) + { + //ignore (small number of) excess item lengths: + auto itPercentile = itemWidths.begin() + itemWidths.size() * 8 / 10; //80th percentile + std::nth_element(itemWidths.begin(), itPercentile, itemWidths.end()); //complexity: O(n) + itemNamesWidth = std::max(itemNamesWidth, *itPercentile); + } + assert(itemNamesWidth > 0); } + return itemNamesWidth; } -private: - enum class DisplayType + + struct GroupRenderLayout { - inactive, - normal, - folder, - symlink, + std::wstring itemName; + std::wstring groupName; + std::wstring groupParentFolder; + int iconSize; + size_t groupBeginRow; + bool stackedGroupRender; + int widthGroupParent; + int widthGroupName; }; - DisplayType getObjectDisplayType(const FileSystemObject* fsObj) const + GroupRenderLayout getGroupRenderLayout(wxDC& dc, size_t row, const FileView::PathDrawInfo& pdi, int maxWidth) { - if (!fsObj || !fsObj->isActive()) - return DisplayType::inactive; + assert(pdi.fsObj && pdi.folderGroupObj); - DisplayType output = DisplayType::normal; + IconManager* const iconMgr = getIconManager(); + const int iconSize = iconMgr ? iconMgr->refIconBuffer().getSize() : 0; - visitFSObject(*fsObj, [&](const FolderPair& folder) { output = DisplayType::folder; }, - [](const FilePair& file) {}, - [&](const SymlinkPair& symlink) { output = DisplayType::symlink; }); + //-------------------------------------------------------------------- + const int ellipsisWidth = getTextExtentBuffered(dc, ELLIPSIS).x; + const int groupItemNamesWidth = getGroupItemNamesWidth(dc, pdi); + //-------------------------------------------------------------------- - return output; - } + //exception for readability: top row is always group start! + const size_t groupBeginRow = std::max(pdi.groupBeginRow, refGrid().getTopRow()); - std::wstring getValue(size_t row, ColumnType colType) const override - { - std::wstring value; - if (const FileSystemObject* fsObj = getFsObject(row)) - if (!fsObj->isEmpty<side>()) - switch (static_cast<ColumnTypeRim>(colType)) + const bool multiItemGroup = pdi.groupEndRow - groupBeginRow > 1; + + std::wstring itemName; + if (!pdi.fsObj->isEmpty<side>() && !dynamic_cast<const FolderPair*>(pdi.fsObj)) + itemName = utfTo<std::wstring>(pdi.fsObj->getItemName<side>()); + + std::wstring groupName; + std::wstring groupParentFolder; + switch (itemPathFormat_) + { + case ItemPathFormat::name: + break; + + case ItemPathFormat::relative: + if (auto groupFolder = dynamic_cast<const FolderPair*>(pdi.folderGroupObj)) { - 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; + groupName = utfTo<std::wstring>(groupFolder->template getItemName<side>()); + groupParentFolder = utfTo<std::wstring>(groupFolder->parent().template getRelativePath<side>()); + } + break; - case ColumnTypeRim::size: - visitFSObject(*fsObj, [&](const FolderPair& folder) { value = L"<" + _("Folder") + L">"; }, - [&](const FilePair& file) { value = formatNumber(file.getFileSize<side>()); }, - //[&](const FilePair& file) { value = utfTo<std::wstring>(formatAsHexString(file.getFileId<side>())); }, // -> test file id - [&](const SymlinkPair& symlink) { value = L"<" + _("Symlink") + L">"; }); - break; + case ItemPathFormat::full: + if (auto groupFolder = dynamic_cast<const FolderPair*>(pdi.folderGroupObj)) + { + groupName = utfTo<std::wstring>(groupFolder->template getItemName<side>()); + groupParentFolder = AFS::getDisplayPath(groupFolder->parent().template getAbstractPath<side>()); + } + else //=> BaseFolderPair + groupParentFolder = AFS::getDisplayPath(pdi.fsObj->base().getAbstractPath<side>()); + break; + } + //add slashes for better readability + assert(!contains(groupParentFolder, L'/') || !contains(groupParentFolder, L'\\')); + const wchar_t groupParentSep = contains(groupParentFolder, L'/') ? L'/' : (contains(groupParentFolder, L'\\') ? L'\\' : FILE_NAME_SEPARATOR); + + if (!iconMgr && !groupParentFolder.empty() && + !endsWith(groupParentFolder, L'/' ) && //e.g. ftp://server/ + !endsWith(groupParentFolder, L'\\')) /*e.g C:\ */ + groupParentFolder += groupParentSep; + if (!iconMgr && !groupName.empty()) + groupName += FILE_NAME_SEPARATOR; + + //path components should follow the app layout direction and are NOT a single piece of text! + //caveat: add Bidi support only during rendering and not in getValue() or AFS::getDisplayPath(): e.g. support "open file in Explorer" + assert(!contains(groupParentFolder, slashBidi_) && !contains(groupParentFolder, bslashBidi_)); + replace(groupParentFolder, L'/', slashBidi_); + replace(groupParentFolder, L'\\', bslashBidi_); + + + /* group details: single row + _______ __________________________ _______________________________________ ____________________________ + | gap | | (group parent | (gap)) | | ((icon | gap) | group name | (gap)) | | (icon | gap) | item name | + ------- -------------------------- --------------------------------------- ---------------------------- + + group details: stacked + _______ _________________________________________________________ ____________________________ + | gap | | <right-aligned> ((icon | gap) | group name | (gap)) | | (icon | gap) | item name | <- group name on first row + ------- --------------------------------------------------------- ---------------------------- + | gap | | (group parent/... | gap) | | (icon | gap) | item name | <- group parent on second + ------- --------------------------------------------------------- ---------------------------- */ + bool stackedGroupRender = false; + int widthGroupParent = groupParentFolder.empty() ? 0 : (getTextExtentBuffered(dc, groupParentFolder).x + (iconMgr ? gridGap_ : 0)); + int widthGroupName = groupName .empty() ? 0 : ((iconMgr ? iconSize + gridGap_ : 0) + getTextExtentBuffered(dc, groupName).x + (iconMgr ? gridGap_ : 0)); + int widthGroupItems = (iconMgr ? iconSize + gridGap_ : 0) + groupItemNamesWidth; + + //not enough space? => collapse + if (int excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth; + excessWidth > 0) + { + if (multiItemGroup && !groupParentFolder.empty() && !groupName.empty()) + { + //1. render group components on two rows + stackedGroupRender = true; - case ColumnTypeRim::date: - visitFSObject(*fsObj, [](const FolderPair& folder) {}, - [&](const FilePair& file) { value = formatUtcToLocalTime(file .getLastWriteTime<side>()); }, - [&](const SymlinkPair& symlink) { value = formatUtcToLocalTime(symlink.getLastWriteTime<side>()); }); - break; + if (!endsWith(groupParentFolder, L'/' ) && + !endsWith(groupParentFolder, L'\\')) + groupParentFolder += groupParentSep; + groupParentFolder += ELLIPSIS; - case ColumnTypeRim::extension: - visitFSObject(*fsObj, [](const FolderPair& folder) {}, - [&](const FilePair& file) { value = utfTo<std::wstring>(getFileExtension(file .getItemName<side>())); }, - [&](const SymlinkPair& symlink) { value = utfTo<std::wstring>(getFileExtension(symlink.getItemName<side>())); }); - break; + widthGroupParent = getTextExtentBuffered(dc, groupParentFolder).x + gridGap_; + + int widthGroupStack = std::max(widthGroupParent, widthGroupName); + excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth; + + if (excessWidth > 0) + { + //2. shrink group stack (group parent only) + if (widthGroupParent > widthGroupName) + { + widthGroupStack = widthGroupParent = std::max(widthGroupParent - excessWidth, widthGroupName); + excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth; + } + if (excessWidth > 0) + { + //3. shrink item rendering + widthGroupItems = std::max(widthGroupItems - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth); + excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth; + + if (excessWidth > 0) + { + //4. shrink group stack + widthGroupStack = std::max(widthGroupStack - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth + (iconMgr ? gridGap_ : 0)); + + widthGroupParent = std::min(widthGroupParent, widthGroupStack); + widthGroupName = std::min(widthGroupName, widthGroupStack); + } + } } - return value; + } + else //group details on single row + { + //1. shrink group parent + if (!groupParentFolder.empty()) + { + widthGroupParent = std::max(widthGroupParent - excessWidth, ellipsisWidth + (iconMgr ? gridGap_ : 0)); + excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth; + } + if (excessWidth > 0) + { + //2. shrink item rendering + widthGroupItems = std::max(widthGroupItems - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth); + excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth; + + if (excessWidth > 0) + //3. shrink group name + if (!groupName.empty()) + widthGroupName = std::max(widthGroupName - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth + (iconMgr ? gridGap_ : 0)); + } + } + } + + return + { + itemName, + groupName, + groupParentFolder, + iconSize, + groupBeginRow, + stackedGroupRender, + widthGroupParent, + widthGroupName, + }; } void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override @@ -516,12 +735,15 @@ private: //don't forget: harmonize with getBestSize()!!! //----------------------------------------------- + wxDCTextColourChanger textColor(dc); + if (enabled && selected) //accessibility: always set *both* foreground AND background colors! + textColor.Set(*wxBLACK); + if (const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); pdi.fsObj) { const DisplayType dispTp = getObjectDisplayType(pdi.fsObj); - wxDCTextColourChanger textColor(dc); //accessibility: always set both foreground AND background colors! if (enabled && !selected) //=> coordinate with renderRowBackgound() { @@ -537,147 +759,70 @@ private: { case ColumnTypeRim::path: { - const size_t topRow = refGrid().getTopRow(); - //exception for readability: top row is always group start! - const size_t groupStartRow = pdi.groupStartRow >= topRow ? pdi.groupStartRow : topRow; - - const FileView::PathDrawInfo pdiGroupStart = row == groupStartRow ? pdi : getDataView().getDrawInfo(groupStartRow); - - //caveat: pdiGroupStart.fsObj may be nullptr! - const bool singleItemGroup = pdiGroupStart.isLastGroupItem; - const bool groupStartIsFolder = dynamic_cast<const FolderPair*>(pdiGroupStart.fsObj); - - const auto groupObj = [&]() -> const FileSystemObject* - { - if (singleItemGroup) - return pdi.fsObj; - - if (groupStartIsFolder) - return pdiGroupStart.fsObj; - - assert(!pdiGroupStart.fsObj || &pdiGroupStart.fsObj->parent() == &pdi.fsObj->parent()); - return dynamic_cast<const FolderPair*>(&pdi.fsObj->parent()); - }(); + const auto& [itemName, + groupName, + groupParentFolder, + iconSize, + groupBeginRow, + stackedGroupRender, + widthGroupParent, + widthGroupName] = getGroupRenderLayout(dc, row, pdi, rectTmp.width); IconManager* const iconMgr = getIconManager(); - const int childIndent = iconMgr ? iconMgr->refIconBuffer().getSize() : IconBuffer::getSize(IconBuffer::SIZE_SMALL); - const int iconSize = iconMgr ? iconMgr->refIconBuffer().getSize() : 0; - auto drawIcon = [&](const wxImage& icon, wxRect rectIcon) + auto drawIcon = [&, iconSize /*clang bug*/= iconSize](wxImage icon, wxRect rectIcon) { - rectIcon.width = iconSize; //support small thumbnail centering + if (!pdi.fsObj->isActive()) + icon = icon.ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3); //treat all channels equally! - if (pdi.fsObj->isActive()) - drawBitmapRtlNoMirror(dc, icon, rectIcon, wxALIGN_CENTER); - else - drawBitmapRtlNoMirror(dc, icon.ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3), //treat all channels equally! - rectIcon, wxALIGN_CENTER); + rectIcon.width = iconSize; //center smaller-than-default icons + drawBitmapRtlNoMirror(dc, icon, rectIcon, wxALIGN_CENTER); }; + //------------------------------------------------------------------------- + rectTmp.x += gridGap_; + rectTmp.width -= gridGap_; - std::wstring itemName; - if (!pdi.fsObj->isEmpty<side>()) - itemName = utfTo<std::wstring>(pdi.fsObj->getItemName<side>()); - - std::wstring groupName; - std::wstring groupParentFolder; - switch (itemPathFormat_) - { - case ItemPathFormat::name: - break; + wxRect rectGroup, rectGroupParent, rectGroupName; + rectGroup = rectGroupParent = rectGroupName = rectTmp; - case ItemPathFormat::relative: - if (groupObj) - { - groupName = utfTo<std::wstring>(groupObj->template getItemName<side>()); - groupParentFolder = utfTo<std::wstring>(groupObj->parent().template getRelativePath<side>()); - } - break; + rectGroupParent.width = widthGroupParent; + rectGroupName .width = widthGroupName; - case ItemPathFormat::full: - if (groupObj) - { - groupName = utfTo<std::wstring>(groupObj->template getItemName<side>()); - groupParentFolder = AFS::getDisplayPath(groupObj->parent().template getAbstractPath<side>()); - } - else //=> BaseFolderPair - groupParentFolder = AFS::getDisplayPath(pdi.fsObj->base().getAbstractPath<side>()); - break; - } - if (!iconMgr) //add slashes for better readability + if (stackedGroupRender) { - if (!endsWith(groupParentFolder, L'/' ) && - !endsWith(groupParentFolder, L'\\') && - !groupParentFolder.empty()) groupParentFolder += FILE_NAME_SEPARATOR; - if (!groupName .empty()) groupName += FILE_NAME_SEPARATOR; + rectGroup.width = std::max(widthGroupParent, widthGroupName); + rectGroupName.x += rectGroup.width - widthGroupName; //right-align } - //path components should follow the app layout direction and are NOT a single piece of text! - //caveat: add Bidi support only during rendering and not in getValue() or AFS::getDisplayPath(): e.g. support "open file in Explorer" - assert(!contains(groupParentFolder, slashBidi_) && !contains(groupParentFolder, bslashBidi_)); - replace(groupParentFolder, L'/', slashBidi_); - replace(groupParentFolder, L'\\', bslashBidi_); - - const wxSize groupNameExt = getTextExtentBuffered(dc, groupName); - const wxSize groupParentExt = getTextExtentBuffered(dc, groupParentFolder); - - /* Partitioning: single-item group - _____________________________ ____________________________ - | gap | (parent path | gap) | | (icon | gap) | item name | - ----------------------------- ---------------------------- - - multi-item group (with folder-head): - ______________________________ _____________________________ - | gap | (group parent | gap) | | (icon | gap) | group name | - ---------------------------------------------------------------------------- - | <indent> -> | (childIndent) | | (icon | gap) | item name | - ---------------------------------------------- ---------------------------- - - multi-item group (files only) - _____________________________________________________________ ____________________________ - | gap | (group parent | gap | (childIndent)) | | (icon | gap) | item name | - ------------------------------------------------------------- ---------------------------- - | gap | <right-aligned> ((icon | gap) | group name | gap) | | (icon | gap) | item name | <- group name only on second row - ------------------------------------------------------------- ---------------------------- */ - int parentsRenderWidth = 0; - if (singleItemGroup || groupStartIsFolder) + else //group details on single row { - parentsRenderWidth = gridGap_ + (groupParentFolder.empty() ? 0 : groupParentExt.GetWidth() + gridGap_); - //indent child items slightly after parent folder icon - if (row != groupStartRow && !groupName.empty() /*for ItemPathFormat::name*/) - parentsRenderWidth += childIndent; + rectGroup.width = widthGroupParent + widthGroupName; + rectGroupName.x += widthGroupParent; } - else - parentsRenderWidth = std::max(gridGap_ + (groupParentFolder.empty() ? 0 : groupParentExt.GetWidth() + gridGap_ + (groupName.empty() ? 0 : childIndent)), - gridGap_ + (groupName.empty() ? 0 : (iconSize > 0 ? iconSize + gridGap_ : 0) + groupNameExt.GetWidth() + gridGap_)); + rectTmp.x += rectGroup.width; + rectTmp.width -= rectGroup.width; - //reserve space for leaf component rendering (at the expense of parent path) - //=> don't reserve more: e.g. showing file name conflicts with horizontal position indicating hierarchy! - const int leafReservedWidth = itemName.empty() ? 0 : childIndent; - - wxRect rectParents = rectTmp; - rectParents.width = std::min(parentsRenderWidth, rectTmp.width - leafReservedWidth); - - wxRect rectLeaf = rectTmp; - rectLeaf.x += std::max(0, rectParents.width); - rectLeaf.width -= std::max(0, rectParents.width); + wxRect rectGroupItems = rectTmp; //------------------------------------------------------------------------- - { //clear background below parent path => harmonize with renderRowBackgound() - wxDCTextColourChanger textColorParents(dc); + wxDCTextColourChanger textColorGroup(dc); if (enabled && !selected && //!pdi.fsObj->isEmpty<side>() && - rectParents.width > gridGap_ && - dispTp != DisplayType::inactive) + (!groupParentFolder.empty() || !groupName.empty()) && + pdi.fsObj->isActive()) { - clearArea(dc, rectParents, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + rectGroup.x -= gridGap_; //include lead gap + rectGroup.width += gridGap_; // + + clearArea(dc, rectGroup, getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0)); //clearArea() is surprisingly expensive => call just once! - textColorParents.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + textColorGroup.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //accessibility: always set *both* foreground AND background colors! - if (pdi.isLastGroupItem) //restore the group separation line we just cleared + if (row == pdi.groupEndRow - 1 /*last group item*/) //restore the group separation line we just cleared { wxDCPenChanger dummy(dc, wxPen(getColorGridLine(), fastFromDIP(1))); - dc.DrawLine(rectParents.GetBottomLeft(), rectParents.GetBottomRight() + wxPoint(1, 0)); + dc.DrawLine(rectGroup.GetBottomLeft(), rectGroup.GetBottomRight() + wxPoint(1, 0)); } } @@ -692,32 +837,33 @@ private: dc.GradientFillLinear(rectNav, getColorSelectionGradientFrom(), backCol, wxEAST); } - rectParents.x += gridGap_; - rectParents.width -= gridGap_; - - if (row == groupStartRow) - drawCellText(dc, rectParents, groupParentFolder, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &groupParentExt); - - if (!singleItemGroup && !groupStartIsFolder && !groupName.empty() && - ((!groupParentFolder.empty() && row == groupStartRow + 1) || - (groupParentFolder.empty() && row == groupStartRow))) //exception: show groupName in first row if free + if (!groupName.empty() && row == groupBeginRow) { + wxDCTextColourChanger textColorGroupName(dc); + if (static_cast<HoverAreaGroup>(rowHover) == HoverAreaGroup::groupName) + { + dc.GradientFillLinear(rectGroupName, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST); + textColorGroupName.Set(*wxBLACK); + } - wxRect rectGroupName = rectParents; - rectGroupName.width = std::min(rectParents.width, (iconSize > 0 ? iconSize + gridGap_ : 0) + groupNameExt.GetWidth() + gridGap_); - rectGroupName.x += rectParents.width - rectGroupName.width; - - if (iconMgr) //draw file icon + if (iconMgr) { drawIcon(iconMgr->getGenericDirIcon(), rectGroupName); rectGroupName.x += iconSize + gridGap_; rectGroupName.width -= iconSize + gridGap_; } - drawCellText(dc, rectGroupName, groupName, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &groupNameExt); + drawCellText(dc, rectGroupName, groupName, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupName)); + } + + if (!groupParentFolder.empty() && + ((stackedGroupRender && row == groupBeginRow + 1) || + (!stackedGroupRender && row == groupBeginRow))) + { + drawCellText(dc, rectGroupParent, groupParentFolder, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupParentFolder)); } } - if (!itemName.empty() && rectLeaf.width > 0) + if (!itemName.empty()) { if (iconMgr) //draw file icon { @@ -754,16 +900,16 @@ private: if (fileIcon.IsOk()) { - drawIcon(fileIcon, rectLeaf); + drawIcon(fileIcon, rectGroupItems); if (ii.drawAsLink) - drawIcon(iconMgr->getLinkOverlayIcon(), rectLeaf); + drawIcon(iconMgr->getLinkOverlayIcon(), rectGroupItems); } - rectLeaf.x += iconSize + gridGap_; - rectLeaf.width -= iconSize + gridGap_; + rectGroupItems.x += iconSize + gridGap_; + rectGroupItems.width -= iconSize + gridGap_; } - drawCellText(dc, rectLeaf, itemName, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, itemName)); + drawCellText(dc, rectGroupItems, itemName, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, itemName)); } } break; @@ -792,97 +938,64 @@ private: } } - int getBestSize(wxDC& dc, size_t row, ColumnType colType) override + + HoverArea getRowMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override { if (static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::path) - { - int bestSize = 0; - //*almost* a copy and paste from renderCell(): - if (const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); pdi.fsObj) { - const size_t topRow = refGrid().getTopRow(); - const size_t groupStartRow = pdi.groupStartRow >= topRow ? pdi.groupStartRow : topRow; - - const FileView::PathDrawInfo pdiGroupStart = row == groupStartRow ? pdi : getDataView().getDrawInfo(groupStartRow); - - const bool singleItemGroup = pdiGroupStart.isLastGroupItem; - const bool groupStartIsFolder = dynamic_cast<const FolderPair*>(pdiGroupStart.fsObj); - - const auto groupObj = [&]() -> const FileSystemObject* + const auto& [itemName, + groupName, + groupParentFolder, + iconSize, + groupBeginRow, + stackedGroupRender, + widthGroupParent, + widthGroupName] = getGroupRenderLayout(dc, row, pdi, cellWidth); + + if (!groupName.empty() && row == groupBeginRow) { - if (singleItemGroup) - return pdi.fsObj; - - if (groupStartIsFolder) - return pdiGroupStart.fsObj; - - assert(!pdiGroupStart.fsObj || &pdiGroupStart.fsObj->parent() == &pdi.fsObj->parent()); - return dynamic_cast<const FolderPair*>(&pdi.fsObj->parent()); - }(); - - IconManager* const iconMgr = getIconManager(); - const int childIndent = iconMgr ? iconMgr->refIconBuffer().getSize() : IconBuffer::getSize(IconBuffer::SIZE_SMALL); - const int iconSize = iconMgr ? iconMgr->refIconBuffer().getSize() : 0; - - //getBestSize() => don't care if FileSystemObject::isEmpty() - const std::wstring itemName = utfTo<std::wstring>(pdi.fsObj->getItemName<side>()); + const int groupNameCellBeginX = gridGap_ + + (stackedGroupRender ? std::max(widthGroupParent, widthGroupName) - widthGroupName : //right-align + widthGroupParent); //group details on single row - std::wstring groupName; - std::wstring groupParentFolder; - switch (itemPathFormat_) - { - case ItemPathFormat::name: - break; + if (groupNameCellBeginX <= cellRelativePosX && cellRelativePosX < groupNameCellBeginX + widthGroupName) + return static_cast<HoverArea>(HoverAreaGroup::groupName); + } + } + return HoverArea::none; + } - case ItemPathFormat::relative: - if (groupObj) - { - groupName = utfTo<std::wstring>(groupObj->template getItemName<side>()); - groupParentFolder = utfTo<std::wstring>(groupObj->parent().template getRelativePath<side>()); - } - break; - case ItemPathFormat::full: - if (groupObj) - { - groupName = utfTo<std::wstring>(groupObj->template getItemName<side>()); - groupParentFolder = AFS::getDisplayPath(groupObj->parent().template getAbstractPath<side>()); - } - else //=> BaseFolderPair - groupParentFolder = AFS::getDisplayPath(pdi.fsObj->base().getAbstractPath<side>()); - break; - } - if (!iconMgr) //add slashes for better readability - { - if (!endsWith(groupParentFolder, L'/' ) && - !endsWith(groupParentFolder, L'\\') && - !groupParentFolder.empty()) groupParentFolder += FILE_NAME_SEPARATOR; - if (!groupName .empty()) groupName += FILE_NAME_SEPARATOR; - } + int getBestSize(wxDC& dc, size_t row, ColumnType colType) override + { + if (static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::path) + { + int bestSize = 0; - const wxSize groupNameExt = getTextExtentBuffered(dc, groupName); - const wxSize groupParentExt = getTextExtentBuffered(dc, groupParentFolder); + if (const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); + pdi.fsObj) + { + /* _______ __________________________ _______________________________________ ____________________________ + | gap | | (group parent | (gap)) | | ((icon | gap) | group name | (gap)) | | (icon | gap) | item name | + ------- -------------------------- --------------------------------------- ---------------------------- */ - int parentsRenderWidth = 0; - if (singleItemGroup || groupStartIsFolder) - { - parentsRenderWidth = gridGap_ + (groupParentFolder.empty() ? 0 : groupParentExt.GetWidth() + gridGap_); - //indent child items slightly after parent folder icon - if (row != groupStartRow && !groupName.empty() /*for ItemPathFormat::name*/) - parentsRenderWidth += childIndent; - } - else - parentsRenderWidth = std::max(gridGap_ + (groupParentFolder.empty() ? 0 : groupParentExt.GetWidth() + gridGap_ + (groupName.empty() ? 0 : childIndent)), - gridGap_ + (groupName.empty() ? 0 : (iconSize > 0 ? iconSize + gridGap_ : 0) + groupNameExt.GetWidth() + gridGap_)); + const int insanelyHugeWidth = 1000'000'000; //(hopefully) still small enough to avoid integer overflows - bestSize += parentsRenderWidth; + const auto& [itemName, + groupName, + groupParentFolder, + iconSize, + groupBeginRow, + stackedGroupRender, + widthGroupParent, + widthGroupName] = getGroupRenderLayout(dc, row, pdi, insanelyHugeWidth); + assert(!stackedGroupRender); - if (iconMgr) - bestSize += iconSize + gridGap_; + const int widthGroupItem = itemName.empty() ? 0 : ((iconSize > 0 ? iconSize + gridGap_ : 0) + getTextExtentBuffered(dc, itemName).x); - bestSize += getTextExtentBuffered(dc, itemName).GetWidth() + gridGap_ /*[!]*/; + bestSize += gridGap_ + widthGroupParent + widthGroupName + widthGroupItem + gridGap_ /*[!]*/; } return bestSize; } @@ -893,6 +1006,7 @@ private: } } + std::wstring getColumnLabel(ColumnType colType) const override { switch (static_cast<ColumnTypeRim>(colType)) @@ -916,7 +1030,7 @@ private: case ColumnTypeRim::extension: return _("Extension"); } - //assert(false); may be ColumnType::NONE + //assert(false); may be ColumnType::none return std::wstring(); } @@ -930,7 +1044,7 @@ private: drawColumnLabelText(dc, rectRemain, getColumnLabel(colType), enabled); //draw sort marker - if (auto sortInfo = getDataView().getSortInfo()) + if (auto sortInfo = getDataView().getSortConfig()) if (const ColumnTypeRim* sortType = std::get_if<ColumnTypeRim>(&sortInfo->sortCol)) if (*sortType == static_cast<ColumnTypeRim>(colType) && sortInfo->onLeft == (side == LEFT_SIDE)) { @@ -939,6 +1053,7 @@ private: } } + std::wstring getToolTip(size_t row, ColumnType colType) const override { std::wstring toolTip; @@ -973,6 +1088,7 @@ private: return toolTip; } + enum class IconType { none, @@ -1016,19 +1132,6 @@ private: return out; } - const wxSize& getTextExtentBuffered(wxDC& dc, const std::wstring& text) - { - auto& compExtentsBuf = getDataView().refCompExtentsBuf(); - //- shared between GridDataLeft/GridDataRight - //- only used for parent path names and file names on view => should not grow "too big" - //- cleaned up during FileView::setData() - - auto it = compExtentsBuf.find(text); - if (it == compExtentsBuf.end()) - it = compExtentsBuf.emplace(text, dc.GetTextExtent(text)).first; - return it->second; - } - const int gridGap_ = fastFromDIP(FILE_GRID_GAP_SIZE_DIP); ItemPathFormat itemPathFormat_ = ItemPathFormat::full; @@ -1038,6 +1141,9 @@ private: const std::wstring slashBidi_ = (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ? RTL_MARK : LTR_MARK) + std::wstring() + L"/"; const std::wstring bslashBidi_ = (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ? RTL_MARK : LTR_MARK) + std::wstring() + L"\\"; //no need for LTR/RTL marks on both sides: text follows main direction if slash is between two strong characters with different directions + + std::vector<int> groupItemNamesWidthBuf_; //buffer! groupItemNamesWidths essentially only depends on (groupIdx, side) + uint64_t viewUpdateIdLast_ = 0; // }; @@ -1064,13 +1170,13 @@ public: void onSelectBegin() { selectionInProgress_ = true; - refGrid().clearSelection(GridEventPolicy::DENY); //don't emit event, prevent recursion! + refGrid().clearSelection(GridEventPolicy::deny); //don't emit event, prevent recursion! toolTip_.hide(); //handle custom tooltip } void onSelectEnd(size_t rowFirst, size_t rowLast, HoverArea rowHover, ptrdiff_t clickInitRow) { - refGrid().clearSelection(GridEventPolicy::DENY); //don't emit event, prevent recursion! + refGrid().clearSelection(GridEventPolicy::deny); //don't emit event, prevent recursion! //issue custom event if (selectionInProgress_) //don't process selections initiated by right-click @@ -1078,7 +1184,7 @@ public: if (wxEvtHandler* evtHandler = refGrid().GetEventHandler()) switch (static_cast<HoverAreaCenter>(rowHover)) { - case HoverAreaCenter::CHECK_BOX: + case HoverAreaCenter::checkbox: if (const FileSystemObject* fsObj = getFsObject(clickInitRow)) { const bool setIncluded = !fsObj->isActive(); @@ -1086,19 +1192,19 @@ public: evtHandler->ProcessEvent(evt); } break; - case HoverAreaCenter::DIR_LEFT: + case HoverAreaCenter::dirLeft: { SyncDirectionEvent evt(rowFirst, rowLast, SyncDirection::left); evtHandler->ProcessEvent(evt); } break; - case HoverAreaCenter::DIR_NONE: + case HoverAreaCenter::dirNone: { SyncDirectionEvent evt(rowFirst, rowLast, SyncDirection::none); evtHandler->ProcessEvent(evt); } break; - case HoverAreaCenter::DIR_RIGHT: + case HoverAreaCenter::dirRight: { SyncDirectionEvent evt(rowFirst, rowLast, SyncDirection::right); evtHandler->ProcessEvent(evt); @@ -1119,9 +1225,9 @@ public: { const wxPoint& topLeftAbs = refGrid().CalcUnscrolledPosition(clientPos); const size_t row = refGrid().getRowAtPos(topLeftAbs.y); //return -1 for invalid position, rowCount if one past the end - const Grid::ColumnPosInfo cpi = refGrid().getColumnAtPos(topLeftAbs.x); //returns ColumnType::NONE if no column at x position! + const Grid::ColumnPosInfo cpi = refGrid().getColumnAtPos(topLeftAbs.x); //returns ColumnType::none if no column at x position! - if (row < refGrid().getRowCount() && cpi.colType != ColumnType::NONE && + if (row < refGrid().getRowCount() && cpi.colType != ColumnType::none && refGrid().getMainWin().GetClientRect().Contains(clientPos)) //cursor might have moved outside visible client area showToolTip(row, static_cast<ColumnTypeCenter>(cpi.colType), refGrid().getMainWin().ClientToScreen(clientPos)); else @@ -1152,12 +1258,14 @@ private: void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override { + const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); + if (enabled && !selected) { - if (const FileSystemObject* fsObj = getFsObject(row)) + if (pdi.fsObj) { - if (fsObj->isActive()) - fillBackgroundDefaultColorAlternating(dc, rect, row % 2 == 0); + if (pdi.fsObj->isActive()) + clearArea(dc, rect, getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0)); else clearArea(dc, rect, getColorInactiveBack(false /*faint*/)); } @@ -1165,44 +1273,62 @@ private: clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); } else - GridData::renderRowBackgound(dc, rect, row, enabled, enabled && selected); + GridData::renderRowBackgound(dc, rect, row, enabled, selected); + + //---------------------------------------------------------------------------------- + wxDCPenChanger dummy(dc, wxPen(row == pdi.groupEndRow - 1 /*last group item*/ ? + getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0), fastFromDIP(1))); + dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); } enum class HoverAreaCenter //each cell can be divided into four blocks concerning mouse selections { - CHECK_BOX, - DIR_LEFT, - DIR_NONE, - DIR_RIGHT + checkbox, + dirLeft, + dirNone, + dirRight }; void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override { - auto drawHighlightBackground = [&](const FileSystemObject& fsObj, const wxColor& col) - { - if (enabled && !selected && fsObj.isActive()) //coordinate with renderRowBackgound()! - clearArea(dc, rect, col); - }; + wxDCTextColourChanger textColor(dc); + if (enabled && selected) //accessibility: always set *both* foreground AND background colors! + textColor.Set(*wxBLACK); - switch (static_cast<ColumnTypeCenter>(colType)) + if (const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); + pdi.fsObj) { - case ColumnTypeCenter::checkbox: - if (const FileSystemObject* fsObj = getFsObject(row)) + auto drawHighlightBackground = [&](const wxColor& col) + { + if (enabled && !selected && pdi.fsObj->isActive()) //coordinate with renderRowBackgound()! + { + clearArea(dc, rect, col); + + if (row == pdi.groupEndRow - 1 /*last group item*/) //restore the group separation line we just cleared + { + wxDCPenChanger dummy(dc, wxPen(getColorGridLine(), fastFromDIP(1))); + dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); + } + } + }; + + switch (static_cast<ColumnTypeCenter>(colType)) + { + case ColumnTypeCenter::checkbox: { - const bool drawMouseHover = static_cast<HoverAreaCenter>(rowHover) == HoverAreaCenter::CHECK_BOX; + const bool drawMouseHover = static_cast<HoverAreaCenter>(rowHover) == HoverAreaCenter::checkbox; - if (fsObj->isActive()) + if (pdi.fsObj->isActive()) drawBitmapRtlNoMirror(dc, loadImage(drawMouseHover ? "checkbox_true_hover" : "checkbox_true"), rect, wxALIGN_CENTER); else //default drawBitmapRtlNoMirror(dc, loadImage(drawMouseHover ? "checkbox_false_hover" : "checkbox_false"), rect, wxALIGN_CENTER); } break; - case ColumnTypeCenter::category: - if (const FileSystemObject* fsObj = getFsObject(row)) + case ColumnTypeCenter::category: { if (getViewType() == GridViewType::category) - drawHighlightBackground(*fsObj, getBackGroundColorCmpCategory(fsObj->getCategory(), false /*faint*/)); + drawHighlightBackground(getBackGroundColorCmpCategory(pdi.fsObj->getCategory(), false /*faint*/)); wxRect rectTmp = rect; { @@ -1217,70 +1343,70 @@ private: } if (getViewType() == GridViewType::category) - drawBitmapRtlMirror(dc, getCmpResultImage(fsObj->getCategory()), rectTmp, wxALIGN_CENTER, renderBufCmp_); - else if (fsObj->getCategory() != FILE_EQUAL) //don't show = in both middle columns - drawBitmapRtlMirror(dc, greyScale(getCmpResultImage(fsObj->getCategory())), rectTmp, wxALIGN_CENTER, renderBufCmp_); + drawBitmapRtlMirror(dc, getCmpResultImage(pdi.fsObj->getCategory()), rectTmp, wxALIGN_CENTER, renderBufCmp_); + else if (pdi.fsObj->getCategory() != FILE_EQUAL) //don't show = in both middle columns + drawBitmapRtlMirror(dc, greyScale(getCmpResultImage(pdi.fsObj->getCategory())), rectTmp, wxALIGN_CENTER, renderBufCmp_); } break; - case ColumnTypeCenter::action: - if (const FileSystemObject* fsObj = getFsObject(row)) + case ColumnTypeCenter::action: { if (getViewType() == GridViewType::action) - drawHighlightBackground(*fsObj, getBackGroundColorSyncAction(fsObj->getSyncOperation(), false /*faint*/)); + drawHighlightBackground(getBackGroundColorSyncAction(pdi.fsObj->getSyncOperation(), false /*faint*/)); //synchronization preview - const auto rowHoverCenter = rowHover == HoverArea::NONE ? HoverAreaCenter::CHECK_BOX : static_cast<HoverAreaCenter>(rowHover); + const auto rowHoverCenter = rowHover == HoverArea::none ? HoverAreaCenter::checkbox : static_cast<HoverAreaCenter>(rowHover); switch (rowHoverCenter) { - case HoverAreaCenter::DIR_LEFT: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::left)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, renderBufSync_); + case HoverAreaCenter::dirLeft: + drawBitmapRtlMirror(dc, getSyncOpImage(pdi.fsObj->testSyncOperation(SyncDirection::left)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, renderBufSync_); break; - case HoverAreaCenter::DIR_NONE: - drawBitmapRtlNoMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::none)), rect, wxALIGN_CENTER); + case HoverAreaCenter::dirNone: + drawBitmapRtlNoMirror(dc, getSyncOpImage(pdi.fsObj->testSyncOperation(SyncDirection::none)), rect, wxALIGN_CENTER); break; - case HoverAreaCenter::DIR_RIGHT: - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SyncDirection::right)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, renderBufSync_); + case HoverAreaCenter::dirRight: + drawBitmapRtlMirror(dc, getSyncOpImage(pdi.fsObj->testSyncOperation(SyncDirection::right)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, renderBufSync_); break; - case HoverAreaCenter::CHECK_BOX: + case HoverAreaCenter::checkbox: if (getViewType() == GridViewType::action) - drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->getSyncOperation()), rect, wxALIGN_CENTER, renderBufSync_); - else if (fsObj->getSyncOperation() != SO_EQUAL) //don't show = in both middle columns - drawBitmapRtlMirror(dc, greyScale(getSyncOpImage(fsObj->getSyncOperation())), rect, wxALIGN_CENTER, renderBufSync_); + drawBitmapRtlMirror(dc, getSyncOpImage(pdi.fsObj->getSyncOperation()), rect, wxALIGN_CENTER, renderBufSync_); + else if (pdi.fsObj->getSyncOperation() != SO_EQUAL) //don't show = in both middle columns + drawBitmapRtlMirror(dc, greyScale(getSyncOpImage(pdi.fsObj->getSyncOperation())), rect, wxALIGN_CENTER, renderBufSync_); break; } } break; + } } } - HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override + HoverArea getRowMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override { if (const FileSystemObject* const fsObj = getFsObject(row)) switch (static_cast<ColumnTypeCenter>(colType)) { case ColumnTypeCenter::checkbox: case ColumnTypeCenter::category: - return static_cast<HoverArea>(HoverAreaCenter::CHECK_BOX); + return static_cast<HoverArea>(HoverAreaCenter::checkbox); case ColumnTypeCenter::action: if (fsObj->getSyncOperation() == SO_EQUAL) //in sync-preview equal files shall be treated like a checkbox - return static_cast<HoverArea>(HoverAreaCenter::CHECK_BOX); + return static_cast<HoverArea>(HoverAreaCenter::checkbox); /* cell: ------------------------ | left | middle | right| ------------------------ */ if (0 <= cellRelativePosX) { if (cellRelativePosX < cellWidth / 3) - return static_cast<HoverArea>(HoverAreaCenter::DIR_LEFT); + return static_cast<HoverArea>(HoverAreaCenter::dirLeft); else if (cellRelativePosX < 2 * cellWidth / 3) - return static_cast<HoverArea>(HoverAreaCenter::DIR_NONE); + return static_cast<HoverArea>(HoverAreaCenter::dirNone); else if (cellRelativePosX < cellWidth) - return static_cast<HoverArea>(HoverAreaCenter::DIR_RIGHT); + return static_cast<HoverArea>(HoverAreaCenter::dirRight); } break; } - return HoverArea::NONE; + return HoverArea::none; } std::wstring getColumnLabel(ColumnType colType) const override @@ -1324,7 +1450,7 @@ private: drawBitmapRtlNoMirror(dc, enabled ? colIcon : colIcon.ConvertToDisabled(), rectInner, wxALIGN_CENTER); //draw sort marker - if (auto sortInfo = getDataView().getSortInfo()) + if (auto sortInfo = getDataView().getSortConfig()) if (const ColumnTypeCenter* sortType = std::get_if<ColumnTypeCenter>(&sortInfo->sortCol)) if (*sortType == colTypeCenter) { @@ -1365,7 +1491,8 @@ case FILE_CONFLICT: return "cat_conflict"; } assert(false); return ""; - }(); + } + (); const auto& img = mirrorIfRtl(loadImage(imageName)); toolTip_.show(getCategoryDescription(*fsObj), posScreen, &img); } @@ -1393,8 +1520,8 @@ case FILE_CONFLICT: return "cat_conflict"; case SO_COPY_METADATA_TO_RIGHT: return "so_move_right"; case SO_DO_NOTHING: return "so_none"; case SO_EQUAL: return "cat_equal"; - case SO_UNRESOLVED_CONFLICT: return "cat_conflict"; - //*INDENT-ON* +case SO_UNRESOLVED_CONFLICT: return "cat_conflict"; +//*INDENT-ON* }; assert(false); return ""; @@ -1419,7 +1546,8 @@ else //######################################################################################################## -const wxEventType EVENT_ALIGN_SCROLLBARS = wxNewEventType(); +wxDEFINE_EVENT(EVENT_ALIGN_SCROLLBARS, wxCommandEvent); + class GridEventManager : private wxEvtHandler { @@ -1431,61 +1559,61 @@ public: gridL_(gridL), gridC_(gridC), gridR_(gridR), provCenter_(provCenter) { -gridL_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnL), nullptr, this); -gridR_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnR), nullptr, this); +gridL_.Bind(EVENT_GRID_COL_RESIZE, [this](GridColumnResizeEvent& event) { onResizeColumnL(event); }); +gridR_.Bind(EVENT_GRID_COL_RESIZE, [this](GridColumnResizeEvent& event) { onResizeColumnR(event); }); -gridL_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GridEventManager::onKeyDownL), nullptr, this); -gridC_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GridEventManager::onKeyDownC), nullptr, this); -gridR_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GridEventManager::onKeyDownR), nullptr, this); +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_); }); -gridC_.getMainWin().Connect(wxEVT_MOTION, wxMouseEventHandler(GridEventManager::onCenterMouseMovement), nullptr, this); -gridC_.getMainWin().Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(GridEventManager::onCenterMouseLeave ), nullptr, this); +gridC_.getMainWin().Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { onCenterMouseMovement(event); }); +gridC_.getMainWin().Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { onCenterMouseLeave (event); }); -gridC_.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler (GridEventManager::onCenterSelectBegin), nullptr, this); -gridC_.Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(GridEventManager::onCenterSelectEnd ), nullptr, this); +gridC_.Bind(EVENT_GRID_MOUSE_LEFT_DOWN, [this](GridClickEvent& event) { onCenterSelectBegin(event); }); +gridC_.Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onCenterSelectEnd (event); }); //clear selection of other grid when selecting on -gridL_.Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(GridEventManager::onGridSelectionL), nullptr, this); -gridR_.Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(GridEventManager::onGridSelectionR), nullptr, this); +gridL_.Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onGridSelectionL(event); }); +gridR_.Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onGridSelectionR(event); }); //parallel grid scrolling: do NOT use DoPrepareDC() to align grids! GDI resource leak! Use regular paint event instead: -gridL_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridL), nullptr, this); -gridC_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridC), nullptr, this); -gridR_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridR), nullptr, this); +gridL_.getMainWin().Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { onPaintGrid(gridL_); event.Skip(); }); +gridC_.getMainWin().Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { onPaintGrid(gridC_); event.Skip(); }); +gridR_.getMainWin().Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { onPaintGrid(gridR_); event.Skip(); }); -auto connectGridAccess = [&](Grid& grid, wxObjectEventFunction func) +auto connectGridAccess = [&](Grid& grid, std::function<void(wxEvent& event)> handler) { - grid.Connect(wxEVT_SCROLLWIN_TOP, func, nullptr, this); - grid.Connect(wxEVT_SCROLLWIN_BOTTOM, func, nullptr, this); - grid.Connect(wxEVT_SCROLLWIN_LINEUP, func, nullptr, this); - grid.Connect(wxEVT_SCROLLWIN_LINEDOWN, func, nullptr, this); - grid.Connect(wxEVT_SCROLLWIN_PAGEUP, func, nullptr, this); - grid.Connect(wxEVT_SCROLLWIN_PAGEDOWN, func, nullptr, this); - grid.Connect(wxEVT_SCROLLWIN_THUMBTRACK, func, nullptr, this); + grid.Bind(wxEVT_SCROLLWIN_TOP, handler); + grid.Bind(wxEVT_SCROLLWIN_BOTTOM, handler); + grid.Bind(wxEVT_SCROLLWIN_LINEUP, handler); + grid.Bind(wxEVT_SCROLLWIN_LINEDOWN, handler); + grid.Bind(wxEVT_SCROLLWIN_PAGEUP, handler); + grid.Bind(wxEVT_SCROLLWIN_PAGEDOWN, handler); + grid.Bind(wxEVT_SCROLLWIN_THUMBTRACK, handler); //wxEVT_KILL_FOCUS -> there's no need to reset "scrollMaster" //wxEVT_SET_FOCUS -> not good enough: //e.g.: left grid has input, right grid is "scrollMaster" due to dragging scroll thumb via mouse. //=> 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().Connect(wxEVT_CHAR, func, nullptr, this); - grid.getMainWin().Connect(wxEVT_KEY_UP, func, nullptr, this); - grid.getMainWin().Connect(wxEVT_KEY_DOWN, func, nullptr, this); - - grid.getMainWin().Connect(wxEVT_LEFT_DOWN, func, nullptr, this); - grid.getMainWin().Connect(wxEVT_LEFT_DCLICK, func, nullptr, this); - grid.getMainWin().Connect(wxEVT_RIGHT_DOWN, func, nullptr, this); - //grid.getMainWin().Connect(wxEVT_MOUSEWHEEL, func, nullptr, this); -> should be covered by wxEVT_SCROLLWIN_* + grid.getMainWin().Bind(wxEVT_CHAR, handler); + grid.getMainWin().Bind(wxEVT_KEY_UP, handler); + grid.getMainWin().Bind(wxEVT_KEY_DOWN, handler); + + grid.getMainWin().Bind(wxEVT_LEFT_DOWN, handler); + grid.getMainWin().Bind(wxEVT_LEFT_DCLICK, handler); + grid.getMainWin().Bind(wxEVT_RIGHT_DOWN, handler); + grid.getMainWin().Bind(wxEVT_MOUSEWHEEL, handler); }; -connectGridAccess(gridL_, wxEventHandler(GridEventManager::onGridAccessL)); // -connectGridAccess(gridC_, wxEventHandler(GridEventManager::onGridAccessC)); //connect *after* onKeyDown() in order to receive callback *before*!!! -connectGridAccess(gridR_, wxEventHandler(GridEventManager::onGridAccessR)); // +connectGridAccess(gridL_, [this](wxEvent& event) { onGridAccessL(event); }); // +connectGridAccess(gridC_, [this](wxEvent& event) { onGridAccessC(event); }); //connect *after* onKeyDown() in order to receive callback *before*!!! +connectGridAccess(gridR_, [this](wxEvent& event) { onGridAccessR(event); }); // -Connect(EVENT_ALIGN_SCROLLBARS, wxEventHandler(GridEventManager::onAlignScrollBars), NULL, this); +Bind(EVENT_ALIGN_SCROLLBARS, [this](wxCommandEvent& event) { onAlignScrollBars(event); }); } ~GridEventManager() { -//assert(!scrollbarUpdatePending_); => false-positives: e.g. start ffs, right-click on grid, close by clicking X +//assert(!scrollbarUpdatePending_); => false-positives: e.g. start ffs, right-click on grid, close dialog by clicking X } void setScrollMaster(const Grid& grid) { scrollMaster_ = &grid; } @@ -1504,7 +1632,7 @@ if (event.positive_) if (event.mouseClick_) provCenter_.onSelectEnd(event.rowFirst_, event.rowLast_, event.mouseClick_->hoverArea_, event.mouseClick_->row_); else - provCenter_.onSelectEnd(event.rowFirst_, event.rowLast_, HoverArea::NONE, -1); + provCenter_.onSelectEnd(event.rowFirst_, event.rowLast_, HoverArea::none, -1); } event.Skip(); } @@ -1527,13 +1655,9 @@ event.Skip(); void onGridSelection(const Grid& grid, Grid& other) { if (!wxGetKeyState(WXK_CONTROL)) //clear other grid unless user is holding CTRL - other.clearSelection(GridEventPolicy::DENY); //don't emit event, prevent recursion! + other.clearSelection(GridEventPolicy::deny); //don't emit event, prevent recursion! } - void onKeyDownL(wxKeyEvent& event) { onKeyDown(event, gridL_); } - void onKeyDownC(wxKeyEvent& event) { onKeyDown(event, gridC_); } - void onKeyDownR(wxKeyEvent& event) { onKeyDown(event, gridR_); } - void onKeyDown(wxKeyEvent& event, const Grid& grid) { int keyCode = event.GetKeyCode(); @@ -1557,7 +1681,7 @@ else { case WXK_LEFT: case WXK_NUMPAD_LEFT: - gridL_.setGridCursor(row, GridEventPolicy::ALLOW); + gridL_.setGridCursor(row, GridEventPolicy::allow); gridL_.SetFocus(); //since key event is likely originating from right grid, we need to set scrollMaster manually! scrollMaster_ = &gridL_; //onKeyDown is called *after* onGridAccessL()! @@ -1565,7 +1689,7 @@ else case WXK_RIGHT: case WXK_NUMPAD_RIGHT: - gridR_.setGridCursor(row, GridEventPolicy::ALLOW); + gridR_.setGridCursor(row, GridEventPolicy::allow); gridR_.SetFocus(); scrollMaster_ = &gridR_; return; //swallow event @@ -1602,10 +1726,6 @@ trg.setColumnConfig(cfgTrg); void onGridAccessC(wxEvent& event) { scrollMaster_ = &gridC_; event.Skip(); } void onGridAccessR(wxEvent& event) { scrollMaster_ = &gridR_; event.Skip(); } - void onPaintGridL(wxEvent& event) { onPaintGrid(gridL_); event.Skip(); } - void onPaintGridC(wxEvent& event) { onPaintGrid(gridC_); event.Skip(); } - void onPaintGridR(wxEvent& event) { onPaintGrid(gridR_); event.Skip(); } - void onPaintGrid(const Grid& grid) { //align scroll positions of all three grids *synchronously* during paint event! (wxGTK has visible delay when this is done asynchronously, no delay on Windows) @@ -1715,20 +1835,29 @@ void filegrid::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) //gridLeft .showScrollBars(Grid::SB_SHOW_AUTOMATIC, Grid::SB_SHOW_NEVER); -> redundant: configuration happens in GridEventManager::onAlignScrollBars() //gridCenter.showScrollBars(Grid::SB_SHOW_NEVER, Grid::SB_SHOW_NEVER); - const int widthCheckbox = loadImage("checkbox_true").GetWidth() + fastFromDIP(3); + const int widthCheckbox = loadImage("checkbox_true").GetWidth() + fastFromDIP(3); const int widthCategory = 2 * loadImage("sort_ascending").GetWidth() + loadImage("cat_left_only_sicon").GetWidth() + loadImage("notch").GetWidth(); const int widthAction = 3 * loadImage("so_create_left_sicon").GetWidth(); gridCenter.SetSize(widthCategory + widthCheckbox + widthAction, -1); gridCenter.setColumnConfig( { -{ static_cast<ColumnType>(ColumnTypeCenter::checkbox ), widthCheckbox, 0, true }, +{ static_cast<ColumnType>(ColumnTypeCenter::checkbox), widthCheckbox, 0, true }, { static_cast<ColumnType>(ColumnTypeCenter::category), widthCategory, 0, true }, -{ static_cast<ColumnType>(ColumnTypeCenter::action ), widthAction, 0, true }, +{ static_cast<ColumnType>(ColumnTypeCenter::action), widthAction, 0, true }, }); } +void filegrid::setData(Grid& grid, FolderComparison& folderCmp) +{ + if (auto* prov = dynamic_cast<GridDataBase*>(grid.getDataProvider())) +return prov->setData(folderCmp); + + throw std::runtime_error("filegrid was not initialized! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); +} + + FileView& filegrid::getDataView(Grid& grid) { if (auto* prov = dynamic_cast<GridDataBase*>(grid.getDataProvider())) @@ -1745,7 +1874,7 @@ class IconUpdater : private wxEvtHandler //update file icons periodically: use S public: IconUpdater(GridDataLeft& provLeft, GridDataRight& provRight, IconBuffer& iconBuffer) : provLeft_(provLeft), provRight_(provRight), iconBuffer_(iconBuffer) { -timer_.Connect(wxEVT_TIMER, wxEventHandler(IconUpdater::loadIconsAsynchronously), nullptr, this); +timer_.Bind(wxEVT_TIMER, [this](wxTimerEvent& event) { loadIconsAsynchronously(event); }); } void start() { if (!timer_.IsRunning()) timer_.Start(100); } //timer interval in [ms] diff --git a/FreeFileSync/Source/ui/file_grid.h b/FreeFileSync/Source/ui/file_grid.h index 2de0dee3..a18d8daa 100644 --- a/FreeFileSync/Source/ui/file_grid.h +++ b/FreeFileSync/Source/ui/file_grid.h @@ -21,6 +21,8 @@ namespace filegrid void init(zen::Grid& gridLeft, zen::Grid& gridCenter, zen::Grid& gridRight); FileView& getDataView(zen::Grid& grid); +void setData(zen::Grid& grid, FolderComparison& folderCmp); //takes (shared) ownership + void setViewType(zen::Grid& gridCenter, GridViewType vt); void setupIcons(zen::Grid& gridLeft, zen::Grid& gridCenter, zen::Grid& gridRight, bool show, IconBuffer::IconSize sz); @@ -41,17 +43,23 @@ wxImage getSyncOpImage(SyncOperation syncOp); wxImage getCmpResultImage(CompareFileResult cmpResult); +//grid hover area for file group rendering +enum class HoverAreaGroup +{ + groupName +}; + //---------- custom events for middle grid ---------- +struct CheckRowsEvent; +struct SyncDirectionEvent; +wxDECLARE_EVENT(EVENT_GRID_CHECK_ROWS, CheckRowsEvent); +wxDECLARE_EVENT(EVENT_GRID_SYNC_DIRECTION, SyncDirectionEvent); -//(UN-)CHECKING ROWS FROM SYNCHRONIZATION -extern const wxEventType EVENT_GRID_CHECK_ROWS; -//SELECTING SYNC DIRECTION -extern const wxEventType EVENT_GRID_SYNC_DIRECTION; -struct CheckRowsEvent : public wxCommandEvent +struct CheckRowsEvent : public wxEvent { - CheckRowsEvent(size_t rowFirst, size_t rowLast, bool setIncluded) : wxCommandEvent(EVENT_GRID_CHECK_ROWS), rowFirst_(rowFirst), rowLast_(rowLast), setActive_(setIncluded) { assert(rowFirst <= rowLast); } - wxEvent* Clone() const override { return new CheckRowsEvent(*this); } + CheckRowsEvent(size_t rowFirst, size_t rowLast, bool setIncluded) : wxEvent(0 /*winid*/, EVENT_GRID_CHECK_ROWS), rowFirst_(rowFirst), rowLast_(rowLast), setActive_(setIncluded) { assert(rowFirst <= rowLast); } + CheckRowsEvent* Clone() const override { return new CheckRowsEvent(*this); } const size_t rowFirst_; //selected range: [rowFirst_, rowLast_) const size_t rowLast_; //range is empty when clearing selection @@ -59,24 +67,15 @@ struct CheckRowsEvent : public wxCommandEvent }; -struct SyncDirectionEvent : public wxCommandEvent +struct SyncDirectionEvent : public wxEvent { - SyncDirectionEvent(size_t rowFirst, size_t rowLast, SyncDirection direction) : wxCommandEvent(EVENT_GRID_SYNC_DIRECTION), rowFirst_(rowFirst), rowLast_(rowLast), direction_(direction) { assert(rowFirst <= rowLast); } - wxEvent* Clone() const override { return new SyncDirectionEvent(*this); } + SyncDirectionEvent(size_t rowFirst, size_t rowLast, SyncDirection direction) : wxEvent(0 /*winid*/, EVENT_GRID_SYNC_DIRECTION), rowFirst_(rowFirst), rowLast_(rowLast), direction_(direction) { assert(rowFirst <= rowLast); } + SyncDirectionEvent* Clone() const override { return new SyncDirectionEvent(*this); } const size_t rowFirst_; //see CheckRowsEvent const size_t rowLast_; // const SyncDirection direction_; }; - -using CheckRowsEventFunction = void (wxEvtHandler::*)(CheckRowsEvent&); -using SyncDirectionEventFunction = void (wxEvtHandler::*)(SyncDirectionEvent&); - -#define CheckRowsEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(CheckRowsEventFunction, &func) - -#define SyncDirectionEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(SyncDirectionEventFunction, &func) } #endif //CUSTOM_GRID_H_8405817408327894 diff --git a/FreeFileSync/Source/ui/file_grid_attr.h b/FreeFileSync/Source/ui/file_grid_attr.h index 3b7c60c4..324619c1 100644 --- a/FreeFileSync/Source/ui/file_grid_attr.h +++ b/FreeFileSync/Source/ui/file_grid_attr.h @@ -42,7 +42,7 @@ std::vector<ColAttributesRim> getFileGridDefaultColAttribsLeft() using namespace zen; return //harmonize with main_dlg.cpp::onGridLabelContextRim() => expects stretched path and non-stretched other columns! { - { ColumnTypeRim::path, fastFromDIP(-100), 1, true }, + { ColumnTypeRim::path, -fastFromDIP(100), 1, true }, { ColumnTypeRim::extension, fastFromDIP( 60), 0, false }, { ColumnTypeRim::date, fastFromDIP( 140), 0, false }, { ColumnTypeRim::size, fastFromDIP( 100), 0, true }, diff --git a/FreeFileSync/Source/ui/file_view.cpp b/FreeFileSync/Source/ui/file_view.cpp index 960870a1..ad25d37e 100644 --- a/FreeFileSync/Source/ui/file_view.cpp +++ b/FreeFileSync/Source/ui/file_view.cpp @@ -15,54 +15,72 @@ using namespace fff; namespace { -template <class ViewStats> -void addNumbers(const FileSystemObject& fsObj, ViewStats& stats) +void serializeHierarchy(ContainerObject& hierObj, std::vector<FileSystemObject::ObjectId>& output) { - visitFSObject(fsObj, [&](const FolderPair& folder) + for (FilePair& file : hierObj.refSubFiles()) + output.push_back(file.getId()); + + for (SymlinkPair& symlink : hierObj.refSubLinks()) + output.push_back(symlink.getId()); + + for (FolderPair& folder : hierObj.refSubFolders()) { - if (!folder.isEmpty<LEFT_SIDE>()) - ++stats.fileStatsLeft.folderCount; + output.push_back(folder.getId()); + serializeHierarchy(folder, output); //add recursion here to list sub-objects directly below parent! + } - if (!folder.isEmpty<RIGHT_SIDE>()) - ++stats.fileStatsRight.folderCount; - }, +#if 0 + /* Spend additional CPU cycles to sort the standard file list? - [&](const FilePair& file) + Test case: 690.000 item pairs, Windows 7 x64 (C:\ vs I:\) + ---------------------- + CmpNaturalSort: 850 ms + CmpLocalPath: 233 ms + CmpAsciiNoCase: 189 ms + No sorting: 30 ms */ + + template <class ItemPair> + static std::vector<ItemPair*> getItemsSorted(std::list<ItemPair>& itemList) { - if (!file.isEmpty<LEFT_SIDE>()) - { - stats.fileStatsLeft.bytes += file.getFileSize<LEFT_SIDE>(); - ++stats.fileStatsLeft.fileCount; - } - if (!file.isEmpty<RIGHT_SIDE>()) - { - stats.fileStatsRight.bytes += file.getFileSize<RIGHT_SIDE>(); - ++stats.fileStatsRight.fileCount; - } - }, + std::vector<ItemPair*> output; + for (ItemPair& item : itemList) + output.push_back(&item); - [&](const SymlinkPair& symlink) + std::sort(output.begin(), output.end(), [](const ItemPair* lhs, const ItemPair* rhs) { return LessNaturalSort()(lhs->getItemNameAny(), rhs->getItemNameAny()); }); + return output; + } +#endif +} +} + + +FileView::FileView(FolderComparison& folderCmp) +{ + std::for_each(begin(folderCmp), end(folderCmp), [&](BaseFolderPair& baseObj) { - if (!symlink.isEmpty<LEFT_SIDE>()) - ++stats.fileStatsLeft.fileCount; + serializeHierarchy(baseObj, sortedRef_); - if (!symlink.isEmpty<RIGHT_SIDE>()) - ++stats.fileStatsRight.fileCount; + folderPairs_.emplace_back(&baseObj, + baseObj.getAbstractPath< LEFT_SIDE>(), + baseObj.getAbstractPath<RIGHT_SIDE>()); }); } -} template <class Predicate> void FileView::updateView(Predicate pred) { viewRef_ .clear(); + groupDetails_ .clear(); rowPositions_ .clear(); rowPositionsFirstChild_.clear(); + static uint64_t globalViewUpdateId; + viewUpdateId_ = ++globalViewUpdateId; + assert(runningOnMainThread()); + std::vector<const ContainerObject*> parentsBuf; //from bottom to top of hierarchy const ContainerObject* groupStartObj = nullptr; - size_t groupStartRow = 0; for (const FileSystemObject::ObjectId& objId : sortedRef_) if (const FileSystemObject* const fsObj = FileSystemObject::retrieve(objId)) @@ -94,16 +112,18 @@ void FileView::updateView(Predicate pred) //------ save info to aggregate rows by parent folders ------ if (const auto folder = dynamic_cast<const FolderPair*>(fsObj)) { - groupStartRow = row; groupStartObj = folder; + groupDetails_.push_back({row}); } else if (&fsObj->parent() != groupStartObj) { - groupStartRow = row; groupStartObj = &fsObj->parent(); + groupDetails_.push_back({row}); } + assert(!groupDetails_.empty()); + const size_t groupIdx = groupDetails_.size() - 1; //----------------------------------------------------------- - viewRef_.push_back({ objId, groupStartRow }); + viewRef_.push_back({ objId, groupIdx }); } } @@ -122,6 +142,46 @@ ptrdiff_t FileView::findRowFirstChild(const ContainerObject* hierObj) const } +namespace +{ +template <class ViewStats> +void addNumbers(const FileSystemObject& fsObj, ViewStats& stats) +{ + visitFSObject(fsObj, [&](const FolderPair& folder) + { + if (!folder.isEmpty<LEFT_SIDE>()) + ++stats.fileStatsLeft.folderCount; + + if (!folder.isEmpty<RIGHT_SIDE>()) + ++stats.fileStatsRight.folderCount; + }, + + [&](const FilePair& file) + { + if (!file.isEmpty<LEFT_SIDE>()) + { + stats.fileStatsLeft.bytes += file.getFileSize<LEFT_SIDE>(); + ++stats.fileStatsLeft.fileCount; + } + if (!file.isEmpty<RIGHT_SIDE>()) + { + stats.fileStatsRight.bytes += file.getFileSize<RIGHT_SIDE>(); + ++stats.fileStatsRight.fileCount; + } + }, + + [&](const SymlinkPair& symlink) + { + if (!symlink.isEmpty<LEFT_SIDE>()) + ++stats.fileStatsLeft.fileCount; + + if (!symlink.isEmpty<RIGHT_SIDE>()) + ++stats.fileStatsRight.fileCount; + }); +} +} + + FileView::CategoryViewStats FileView::applyFilterByCategory(bool showExcluded, //maps sortedRef to viewRef bool showLeftOnly, bool showRightOnly, @@ -267,74 +327,40 @@ std::vector<FileSystemObject*> FileView::getAllFileRef(const std::vector<size_t> } -void FileView::removeInvalidRows() -{ - //remove rows that have been deleted meanwhile - std::erase_if(sortedRef_, [&](const FileSystemObject::ObjectId& objId) { return !FileSystemObject::retrieve(objId); }); - - viewRef_ .clear(); - rowPositions_ .clear(); - rowPositionsFirstChild_.clear(); -} - - -void serializeHierarchy(ContainerObject& hierObj, std::vector<FileSystemObject::ObjectId>& output) +FileView::PathDrawInfo FileView::getDrawInfo(size_t row) { - for (FilePair& file : hierObj.refSubFiles()) - output.push_back(file.getId()); - - for (SymlinkPair& symlink : hierObj.refSubLinks()) - output.push_back(symlink.getId()); - - for (FolderPair& folder : hierObj.refSubFolders()) + if (row < viewRef_.size()) { - output.push_back(folder.getId()); - serializeHierarchy(folder, output); //add recursion here to list sub-objects directly below parent! - } + const size_t groupIdx = viewRef_[row].groupIdx; + assert(groupIdx < groupDetails_.size()); -#if 0 - /* Spend additional CPU cycles to sort the standard file list? + const size_t groupBeginRow = groupDetails_[groupIdx].groupBeginRow; - Test case: 690.000 item pairs, Windows 7 x64 (C:\ vs I:\) - ---------------------- - CmpNaturalSort: 850 ms - CmpLocalPath: 233 ms - CmpAsciiNoCase: 189 ms - No sorting: 30 ms */ + const size_t groupEndRow = groupIdx + 1 < groupDetails_.size() ? + groupDetails_[groupIdx + 1].groupBeginRow : + viewRef_.size(); + FileSystemObject* fsObj = FileSystemObject::retrieve(viewRef_[row].objId); - template <class ItemPair> - static std::vector<ItemPair*> getItemsSorted(std::list<ItemPair>& itemList) - { - std::vector<ItemPair*> output; - for (ItemPair& item : itemList) - output.push_back(&item); + ContainerObject* folderGroupObj = dynamic_cast<FolderPair*>(fsObj); + if (fsObj && !folderGroupObj) + folderGroupObj = &fsObj->parent(); - std::sort(output.begin(), output.end(), [](const ItemPair* lhs, const ItemPair* rhs) { return LessNaturalSort()(lhs->getItemNameAny(), rhs->getItemNameAny()); }); - return output; + return { groupBeginRow, groupEndRow, groupIdx, viewUpdateId_, folderGroupObj, fsObj }; } -#endif + assert(false); //unexpected: check rowsOnView()! + return {}; } -void FileView::setData(FolderComparison& folderCmp) +void FileView::removeInvalidRows() { - //clear everything - std::unordered_map<FileSystemObject::ObjectIdConst, size_t>().swap(rowPositions_); - std::unordered_map<const void* /*ContainerObject*/, size_t>().swap(rowPositionsFirstChild_); - std::vector<ViewRow >().swap(viewRef_); //+ free mem - std::vector<FileSystemObject::ObjectId>().swap(sortedRef_); // - folderPairs_.clear(); - currentSort_ = {}; - std::unordered_map<std::wstring, wxSize>().swap(compExtentsBuf_); //ensure buffer size does not get out of hand! - - std::for_each(begin(folderCmp), end(folderCmp), [&](BaseFolderPair& baseObj) - { - serializeHierarchy(baseObj, sortedRef_); + //remove rows that have been deleted meanwhile + std::erase_if(sortedRef_, [&](const FileSystemObject::ObjectId& objId) { return !FileSystemObject::retrieve(objId); }); - folderPairs_.emplace_back(&baseObj, - baseObj.getAbstractPath< LEFT_SIDE>(), - baseObj.getAbstractPath<RIGHT_SIDE>()); - }); + viewRef_ .clear(); + groupDetails_ .clear(); + rowPositions_ .clear(); + rowPositionsFirstChild_.clear(); } @@ -556,7 +582,7 @@ bool lessExtension(const FileSystemObject& lhs, const FileSystemObject& rhs) auto getExtension = [](const FileSystemObject& fsObj) { - return afterLast(fsObj.getItemName<side>(), Zstr('.'), zen::IF_MISSING_RETURN_NONE); + return afterLast(fsObj.getItemName<side>(), Zstr('.'), zen::IfNotFoundReturn::none); }; return zen::makeSortDirection(LessNaturalSort() /*even on Linux*/, std::bool_constant<ascending>())(getExtension(lhs), getExtension(rhs)); @@ -584,11 +610,10 @@ bool lessSyncDirection(const FileSystemObject& lhs, const FileSystemObject& rhs) { return zen::makeSortDirection(std::less<>(), std::bool_constant<ascending>())(lhs.getSyncOperation(), rhs.getSyncOperation()); } -} template <bool ascending, SelectedSide side> -struct FileView::LessFullPath +struct LessFullPath { LessFullPath(std::vector<std::tuple<const void* /*BaseFolderPair*/, AbstractPath, AbstractPath>> folderPairs) { @@ -622,7 +647,7 @@ private: template <bool ascending> -struct FileView::LessRelativeFolder +struct LessRelativeFolder { LessRelativeFolder(const std::vector<std::tuple<const void* /*BaseFolderPair*/, AbstractPath, AbstractPath>>& folderPairs) { @@ -644,7 +669,7 @@ private: template <bool ascending, SelectedSide side> -struct FileView::LessFileName +struct LessFileName { bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const { @@ -661,7 +686,7 @@ struct FileView::LessFileName template <bool ascending, SelectedSide side> -struct FileView::LessFilesize +struct LessFilesize { bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const { @@ -678,7 +703,7 @@ struct FileView::LessFilesize template <bool ascending, SelectedSide side> -struct FileView::LessFiletime +struct LessFiletime { bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const { @@ -695,7 +720,7 @@ struct FileView::LessFiletime template <bool ascending, SelectedSide side> -struct FileView::LessExtension +struct LessExtension { bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const { @@ -712,7 +737,7 @@ struct FileView::LessExtension template <bool ascending> -struct FileView::LessCmpResult +struct LessCmpResult { bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const { @@ -729,7 +754,7 @@ struct FileView::LessCmpResult template <bool ascending> -struct FileView::LessSyncDirection +struct LessSyncDirection { bool operator()(const FileSystemObject::ObjectId& lhs, const FileSystemObject::ObjectId& rhs) const { @@ -743,12 +768,14 @@ struct FileView::LessSyncDirection return lessSyncDirection<ascending>(*fsObjA, *fsObjB); } }; +} //------------------------------------------------------------------------------------------------------- void FileView::sortView(ColumnTypeRim type, ItemPathFormat pathFmt, bool onLeft, bool ascending) { viewRef_ .clear(); + groupDetails_ .clear(); rowPositions_ .clear(); rowPositionsFirstChild_.clear(); currentSort_ = SortInfo({ type, onLeft, ascending }); @@ -804,6 +831,7 @@ void FileView::sortView(ColumnTypeRim type, ItemPathFormat pathFmt, bool onLeft, void FileView::sortView(ColumnTypeCenter type, bool ascending) { viewRef_ .clear(); + groupDetails_ .clear(); rowPositions_ .clear(); rowPositionsFirstChild_.clear(); currentSort_ = SortInfo({ type, false, ascending }); diff --git a/FreeFileSync/Source/ui/file_view.h b/FreeFileSync/Source/ui/file_view.h index 897a104e..955b385f 100644 --- a/FreeFileSync/Source/ui/file_view.h +++ b/FreeFileSync/Source/ui/file_view.h @@ -22,30 +22,34 @@ class FileView //grid view of FolderComparison { public: FileView() {} + explicit FileView(FolderComparison& folderCmp); //takes (shared) ownership size_t rowsOnView() const { return viewRef_ .size(); } //only visible elements size_t rowsTotal () const { return sortedRef_.size(); } //total rows available - //direct data access via row number - const FileSystemObject* getFsObject(size_t row) const; //returns nullptr if object is not found; complexity: constant! - /**/ FileSystemObject* getFsObject(size_t row); // + //returns nullptr if object is not found; complexity: constant! + const FileSystemObject* getFsObject(size_t row) const { return row < viewRef_.size() ? FileSystemObject::retrieve(viewRef_[row].objId) : nullptr; } + /**/ FileSystemObject* getFsObject(size_t row) { return const_cast<FileSystemObject*>(static_cast<const FileView&>(*this).getFsObject(row)); } //see Meyers Effective C++ + + //references to FileSystemObject: no nullptr-check needed! everything is bound + std::vector<FileSystemObject*> getAllFileRef(const std::vector<size_t>& rows); struct PathDrawInfo { - size_t groupStartRow = 0; - bool isLastGroupItem = false; - const FileSystemObject* fsObj; //nullptr if object is not found + size_t groupBeginRow = 0; //half-open range + size_t groupEndRow = 0; // + const size_t groupIdx = 0; + uint64_t viewUpdateId = 0; //detect invalid buffers after updateView() + ContainerObject* folderGroupObj = nullptr; // + FileSystemObject* fsObj = nullptr; //nullptr if object is not found }; - PathDrawInfo getDrawInfo(size_t row) const; //complexity: constant! - - //get references to FileSystemObject: no nullptr-check needed! Everything's bound. - std::vector<FileSystemObject*> getAllFileRef(const std::vector<size_t>& rows); + PathDrawInfo getDrawInfo(size_t row); //complexity: constant! struct FileStats { - int fileCount = 0; + int fileCount = 0; int folderCount = 0; - uint64_t bytes = 0; + uint64_t bytes = 0; }; struct CategoryViewStats @@ -100,7 +104,6 @@ public: bool showEqual, bool showConflict); - void setData(FolderComparison& newData); void removeInvalidRows(); //remove references to rows that have been deleted meanwhile: call after manual deletion and synchronization! //sorting... @@ -113,19 +116,15 @@ public: bool onLeft = false; //if sortCol is ColumnTypeRim bool ascending = false; }; - const SortInfo* getSortInfo() const { return zen::get(currentSort_); } //return nullptr if currently not sorted + const SortInfo* getSortConfig() const { return zen::get(currentSort_); } //return nullptr if currently not sorted - ptrdiff_t findRowDirect(FileSystemObject::ObjectIdConst objId) const; // find an object's row position on view list directly, return < 0 if not found - ptrdiff_t findRowFirstChild(const ContainerObject* hierObj) const; // find first child of FolderPair or BaseFolderPair *on sorted sub view* + ptrdiff_t findRowDirect(FileSystemObject::ObjectIdConst objId) const; //find an object's row position on view list directly, return < 0 if not found + ptrdiff_t findRowFirstChild(const ContainerObject* hierObj) const; //find first child of FolderPair or BaseFolderPair *on sorted sub view* //"hierObj" may be invalid, it is NOT dereferenced, return < 0 if not found //count non-empty pairs to distinguish single/multiple folder pair cases size_t getEffectiveFolderPairCount() const; - //buffer expensive wxDC::GetTextExtent() calls! - //=> shared between GridDataLeft/GridDataRight - std::unordered_map<std::wstring, wxSize>& refCompExtentsBuf() { return compExtentsBuf_; } - private: FileView (const FileView&) = delete; FileView& operator=(const FileView&) = delete; @@ -137,95 +136,31 @@ private: std::unordered_map<const void* /*ContainerObject*/, size_t> rowPositionsFirstChild_; //find first child on sortedRef of a hierarchy object //void* instead of ContainerObject*: these are weak pointers and should *never be dereferenced*! + struct GroupDetail + { + size_t groupBeginRow = 0; + }; + std::vector<GroupDetail> groupDetails_; + struct ViewRow { FileSystemObject::ObjectId objId = nullptr; - size_t groupStartRow = 0; + size_t groupIdx = 0; //...into groupDetails_ }; std::vector<ViewRow> viewRef_; //partial view on sortedRef_ /* /|\ | (applyFilterBy...) */ std::vector<FileSystemObject::ObjectId> sortedRef_; //flat view of weak pointers on folderCmp; may be sorted /* /|\ - | (setData...) + | (constructor) FolderComparison folderCmp */ std::vector<std::tuple<const void* /*BaseFolderPair*/, AbstractPath, AbstractPath>> folderPairs_; - class SerializeHierarchy; - - //sorting classes - template <bool ascending, SelectedSide side> - struct LessFullPath; - - template <bool ascending> - struct LessRelativeFolder; - - template <bool ascending, SelectedSide side> - struct LessFileName; - - template <bool ascending, SelectedSide side> - struct LessFilesize; - - template <bool ascending, SelectedSide side> - struct LessFiletime; - - template <bool ascending, SelectedSide side> - struct LessExtension; - - template <bool ascending> - struct LessCmpResult; - - template <bool ascending> - struct LessSyncDirection; - std::optional<SortInfo> currentSort_; - std::unordered_map<std::wstring, wxSize> compExtentsBuf_; //buffer expensive wxDC::GetTextExtent() calls! + uint64_t viewUpdateId_ = 0; //help clients detect invalid buffers after updateView() }; - - - - - - - -//##################### implementation ######################################### - -inline -const FileSystemObject* FileView::getFsObject(size_t row) const -{ - return row < viewRef_.size() ? - FileSystemObject::retrieve(viewRef_[row].objId) : nullptr; -} - - -inline -FileSystemObject* FileView::getFsObject(size_t row) -{ - //code re-use of const method: see Meyers Effective C++ - return const_cast<FileSystemObject*>(static_cast<const FileView&>(*this).getFsObject(row)); } - -inline -FileView::PathDrawInfo FileView::getDrawInfo(size_t row) const -{ - if (row < viewRef_.size()) - { - const FileSystemObject* fsObj = FileSystemObject::retrieve(viewRef_[row].objId); - - const size_t groupStartRow = viewRef_[row].groupStartRow; - assert(groupStartRow < viewRef_.size() && viewRef_[groupStartRow].groupStartRow == groupStartRow); - - const bool isLastGroupItem = row + 1 >= viewRef_.size() || viewRef_[row + 1].groupStartRow != groupStartRow; - - return { groupStartRow, isLastGroupItem, fsObj }; - } - assert(false); //unexpected: check rowsOnView()! - return {}; -} -} - - #endif //GRID_VIEW_H_9285028345703475842569 diff --git a/FreeFileSync/Source/ui/folder_history_box.cpp b/FreeFileSync/Source/ui/folder_history_box.cpp index 81ea5a6f..0d8a1710 100644 --- a/FreeFileSync/Source/ui/folder_history_box.cpp +++ b/FreeFileSync/Source/ui/folder_history_box.cpp @@ -31,7 +31,7 @@ FolderHistoryBox::FolderHistoryBox(wxWindow* parent, /*##*/ SetMinSize({fastFromDIP(150), -1}); //## workaround yet another wxWidgets bug: default minimum size is much too large for a wxComboBox //##################################### - Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(FolderHistoryBox::OnKeyEvent), nullptr, this); + Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyEvent(event); }); /* we can't attach to wxEVT_COMMAND_TEXT_UPDATED, since setValueAndUpdateList() will implicitly emit wxEVT_COMMAND_TEXT_UPDATED again when calling Clear()! @@ -50,7 +50,7 @@ FolderHistoryBox::FolderHistoryBox(wxWindow* parent, } -void FolderHistoryBox::OnRequireHistoryUpdate(wxEvent& event) +void FolderHistoryBox::onRequireHistoryUpdate(wxEvent& event) { setValueAndUpdateList(GetValue()); event.Skip(); @@ -97,7 +97,7 @@ void FolderHistoryBox::setValueAndUpdateList(const wxString& folderPathPhrase) } -void FolderHistoryBox::OnKeyEvent(wxKeyEvent& event) +void FolderHistoryBox::onKeyEvent(wxKeyEvent& event) { const int keyCode = event.GetKeyCode(); diff --git a/FreeFileSync/Source/ui/folder_history_box.h b/FreeFileSync/Source/ui/folder_history_box.h index f2e0e076..e15d38ed 100644 --- a/FreeFileSync/Source/ui/folder_history_box.h +++ b/FreeFileSync/Source/ui/folder_history_box.h @@ -27,17 +27,17 @@ public: static const wxString separationLine() { return wxString(50, EM_DASH); } - void addItem(const Zstring& folderPathPhrase) + void addItem(Zstring folderPathPhrase) { + zen::trim(folderPathPhrase); + if (folderPathPhrase.empty() || folderPathPhrase == zen::utfTo<Zstring>(separationLine())) return; - const Zstring nameTmp = zen::trimCpy(folderPathPhrase); - //insert new folder or put it to the front if already existing - std::erase_if(folderPathPhrases_, [&](const Zstring& item) { return equalNoCase(item, nameTmp); }); + std::erase_if(folderPathPhrases_, [&](const Zstring& item) { return equalNoCase(item, folderPathPhrase); }); - folderPathPhrases_.insert(folderPathPhrases_.begin(), nameTmp); + folderPathPhrases_.insert(folderPathPhrases_.begin(), folderPathPhrase); truncate(); } @@ -68,7 +68,7 @@ public: const wxString choices[] = nullptr, long style = 0, const wxValidator& validator = wxDefaultValidator, - const wxString& name = wxComboBoxNameStr); + const wxString& name = wxASCII_STR(wxComboBoxNameStr)); void setHistory(std::shared_ptr<HistoryList> sharedHistory) { sharedHistory_ = std::move(sharedHistory); } std::shared_ptr<HistoryList> getHistory() { return sharedHistory_; } @@ -78,11 +78,11 @@ public: setValueAndUpdateList(folderPathPhrase); //required for setting value correctly; Linux: ensure the dropdown is shown as being populated } - // GetValue + //wxString wxComboBox::GetValue() const; private: - void OnKeyEvent(wxKeyEvent& event); - void OnRequireHistoryUpdate(wxEvent& event); + void onKeyEvent(wxKeyEvent& event); + void onRequireHistoryUpdate(wxEvent& event); void setValueAndUpdateList(const wxString& folderPathPhrase); std::shared_ptr<HistoryList> sharedHistory_; diff --git a/FreeFileSync/Source/ui/folder_pair.h b/FreeFileSync/Source/ui/folder_pair.h index aa399671..fac14b8d 100644 --- a/FreeFileSync/Source/ui/folder_pair.h +++ b/FreeFileSync/Source/ui/folder_pair.h @@ -45,9 +45,9 @@ public: basicPanel_(basicPanel) { //register events for removal of alternate configuration - basicPanel_.m_bpButtonLocalCompCfg ->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnLocalCompCfgContext ), nullptr, this); - basicPanel_.m_bpButtonLocalSyncCfg ->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnLocalSyncCfgContext ), nullptr, this); - basicPanel_.m_bpButtonLocalFilter ->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnLocalFilterCfgContext), nullptr, this); + basicPanel_.m_bpButtonLocalCompCfg ->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onLocalCompCfgContext (event); }); + basicPanel_.m_bpButtonLocalSyncCfg ->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onLocalSyncCfgContext (event); }); + basicPanel_.m_bpButtonLocalFilter ->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onLocalFilterCfgContext(event); }); basicPanel_.m_bpButtonRemovePair->SetBitmapLabel(zen::loadImage("item_remove")); } @@ -73,7 +73,7 @@ private: _("Local filter") + L" (" + _("None") + L')'); } - void OnLocalCompCfgContext(wxCommandEvent& event) + void onLocalCompCfgContext(wxEvent& event) { auto removeLocalCompCfg = [&] { @@ -87,7 +87,7 @@ private: menu.popup(basicPanel_); } - void OnLocalSyncCfgContext(wxCommandEvent& event) + void onLocalSyncCfgContext(wxEvent& event) { auto removeLocalSyncCfg = [&] { @@ -101,7 +101,7 @@ private: menu.popup(basicPanel_); } - void OnLocalFilterCfgContext(wxCommandEvent& event) + void onLocalFilterCfgContext(wxEvent& event) { auto removeLocalFilterCfg = [&] { diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp index 198c3de5..c62243d8 100644 --- a/FreeFileSync/Source/ui/folder_selector.cpp +++ b/FreeFileSync/Source/ui/folder_selector.cpp @@ -54,8 +54,11 @@ void setFolderPathPhrase(const Zstring& folderPathPhrase, FolderHistoryBox* comb //############################################################################################################## -const wxEventType fff::EVENT_ON_FOLDER_SELECTED = wxNewEventType(); -const wxEventType fff::EVENT_ON_FOLDER_MANUAL_EDIT = wxNewEventType(); +namespace fff +{ +wxDEFINE_EVENT(EVENT_ON_FOLDER_SELECTED, wxCommandEvent); +wxDEFINE_EVENT(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEvent); +} FolderSelector::FolderSelector(wxWindow* parent, @@ -84,35 +87,37 @@ FolderSelector::FolderSelector(wxWindow* parent, auto setupDragDrop = [&](wxWindow& dropWin) { setupFileDrop(dropWin); - dropWin.Connect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector::onItemPathDropped), nullptr, this); + dropWin.Bind(EVENT_DROP_FILE, &FolderSelector::onItemPathDropped, this); }; setupDragDrop(dropWindow_); - if (dropWindow2_) setupDragDrop(*dropWindow2_); + if (dropWindow2_) + setupDragDrop(*dropWindow2_); selectAltFolderButton_.SetBitmapLabel(loadImage("cloud_small")); //keep dirPicker and dirpath synchronous - folderComboBox_ .Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector::onMouseWheel ), nullptr, this); - folderComboBox_ .Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector::onEditFolderPath ), nullptr, this); - selectFolderButton_ .Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectFolder ), nullptr, this); - selectAltFolderButton_.Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectAltFolder), nullptr, this); - //selectAltFolderButton_.Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderSelector::onSelectAltFolder), nullptr, this); + folderComboBox_ .Bind(wxEVT_MOUSEWHEEL, &FolderSelector::onMouseWheel, this); + folderComboBox_ .Bind(wxEVT_COMMAND_TEXT_UPDATED, &FolderSelector::onEditFolderPath, this); + folderComboBox_ .Bind(wxEVT_COMMAND_COMBOBOX_SELECTED, &FolderSelector::onHistoryPathSelected, this); + selectFolderButton_ .Bind(wxEVT_COMMAND_BUTTON_CLICKED, &FolderSelector::onSelectFolder, this); + selectAltFolderButton_.Bind(wxEVT_COMMAND_BUTTON_CLICKED, &FolderSelector::onSelectAltFolder, this); } FolderSelector::~FolderSelector() { - dropWindow_.Disconnect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector::onItemPathDropped), nullptr, this); - + [[maybe_unused]] bool ubOk1 = dropWindow_.Unbind(EVENT_DROP_FILE, &FolderSelector::onItemPathDropped, this); + [[maybe_unused]] bool ubOk2 = true; if (dropWindow2_) - dropWindow2_->Disconnect(EVENT_DROP_FILE, FileDropEventHandler(FolderSelector::onItemPathDropped), nullptr, this); - - folderComboBox_ .Disconnect(wxEVT_MOUSEWHEEL, wxMouseEventHandler (FolderSelector::onMouseWheel ), nullptr, this); - folderComboBox_ .Disconnect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(FolderSelector::onEditFolderPath ), nullptr, this); - selectFolderButton_ .Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectFolder ), nullptr, this); - selectAltFolderButton_.Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderSelector::onSelectAltFolder), nullptr, this); - //selectAltFolderButton_.Disconnect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderSelector::onSelectAltFolder), nullptr, this); + ubOk2 = dropWindow2_->Unbind(EVENT_DROP_FILE, &FolderSelector::onItemPathDropped, this); + + [[maybe_unused]] bool ubOk3 = folderComboBox_ .Unbind(wxEVT_MOUSEWHEEL, &FolderSelector::onMouseWheel, this); + [[maybe_unused]] bool ubOk4 = folderComboBox_ .Unbind(wxEVT_COMMAND_TEXT_UPDATED, &FolderSelector::onEditFolderPath, this); + [[maybe_unused]] bool ubOk5 = folderComboBox_ .Unbind(wxEVT_COMMAND_COMBOBOX_SELECTED, &FolderSelector::onHistoryPathSelected, this); + [[maybe_unused]] bool ubOk6 = selectFolderButton_ .Unbind(wxEVT_COMMAND_BUTTON_CLICKED, &FolderSelector::onSelectFolder, this); + [[maybe_unused]] bool ubOk7 = selectAltFolderButton_.Unbind(wxEVT_COMMAND_BUTTON_CLICKED, &FolderSelector::onSelectAltFolder, this); + assert(ubOk1 && ubOk2 && ubOk3 && ubOk4 && ubOk5 && ubOk6 && ubOk7); } @@ -136,11 +141,10 @@ void FolderSelector::onMouseWheel(wxMouseEvent& event) void FolderSelector::onItemPathDropped(FileDropEvent& event) { - const auto& itemPaths = event.getPaths(); - if (itemPaths.empty()) + if (event.itemPaths_.empty()) return; - if (!droppedPathsFilter_ || droppedPathsFilter_(itemPaths)) + if (!droppedPathsFilter_ || droppedPathsFilter_(event.itemPaths_)) { auto fmtShellPath = [](Zstring shellItemPath) { @@ -159,10 +163,10 @@ void FolderSelector::onItemPathDropped(FileDropEvent& event) return AFS::getInitPathPhrase(itemPath); }; - setPath(fmtShellPath(itemPaths[0])); + setPath(fmtShellPath(event.itemPaths_[0])); //drop two folder paths at once: - if (siblingSelector_ && itemPaths.size() >= 2) - siblingSelector_->setPath(fmtShellPath(itemPaths[1])); + if (siblingSelector_ && event.itemPaths_.size() >= 2) + siblingSelector_->setPath(fmtShellPath(event.itemPaths_[1])); //notify action invoked by user wxCommandEvent dummy(EVENT_ON_FOLDER_SELECTED); @@ -173,6 +177,16 @@ void FolderSelector::onItemPathDropped(FileDropEvent& event) } +void FolderSelector::onHistoryPathSelected(wxEvent& event) +{ + //setFolderPathPhrase() => already called by onEditFolderPath() (wxEVT_COMMAND_COMBOBOX_SELECTED implies wxEVT_COMMAND_TEXT_UPDATED) + + //notify action invoked by user + wxCommandEvent dummy(EVENT_ON_FOLDER_SELECTED); + ProcessEvent(dummy); +} + + void FolderSelector::onEditFolderPath(wxCommandEvent& event) { setFolderPathPhrase(utfTo<Zstring>(event.GetString()), nullptr, folderComboBox_, staticText_); @@ -185,9 +199,9 @@ void FolderSelector::onEditFolderPath(wxCommandEvent& event) void FolderSelector::onSelectFolder(wxCommandEvent& event) { - //make sure default folder exists: don't let folder picker hang on non-existing network share! Zstring defaultFolderPath; { + //make sure default folder exists: don't let folder picker hang on non-existing network share! auto folderExistsTimed = [](const AbstractPath& folderPath) { auto ft = runAsync([folderPath] @@ -202,6 +216,7 @@ void FolderSelector::onSelectFolder(wxCommandEvent& event) }; const Zstring folderPathPhrase = getPath(); + if (acceptsItemPathPhraseNative(folderPathPhrase)) //noexcept { const AbstractPath folderPath = createItemPathNative(folderPathPhrase); @@ -212,10 +227,9 @@ void FolderSelector::onSelectFolder(wxCommandEvent& event) } Zstring shellItemPath; - wxDirDialog dirPicker(parent_, _("Select a folder"), utfTo<wxString>(defaultFolderPath)); //put modal wxWidgets dialogs on stack: creating on freestore leads to memleak! - - //-> following doesn't seem to do anything at all! still "Show hidden" is available as a context menu option: - //::gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dirPicker.m_widget), true /*show_hidden*/); + wxDirDialog dirPicker(parent_, _("Select a folder"), utfTo<wxString>(defaultFolderPath), wxDD_DEFAULT_STYLE | wxDD_SHOW_HIDDEN); + //GTK2: "Show hidden" is also available as a context menu option in the folder picker! + //It looks like wxDD_SHOW_HIDDEN only sets the default when opening for the first time!? if (dirPicker.ShowModal() != wxID_OK) return; diff --git a/FreeFileSync/Source/ui/folder_selector.h b/FreeFileSync/Source/ui/folder_selector.h index 975ef75a..0b46acb2 100644 --- a/FreeFileSync/Source/ui/folder_selector.h +++ b/FreeFileSync/Source/ui/folder_selector.h @@ -17,18 +17,17 @@ namespace fff { -//handle drag and drop, tooltip, label and manual input, coordinating a wxWindow, wxButton, and wxComboBox/wxTextCtrl -/* -Reasons NOT to use wxDirPickerCtrl, but wxButton instead: +/* handle drag and drop, tooltip, label and manual input, coordinating a wxWindow, wxButton, and wxComboBox/wxTextCtrl + + Reasons NOT to use wxDirPickerCtrl, but wxButton instead: - Crash on GTK 2: https://favapps.wordpress.com/2012/06/11/freefilesync-crash-in-linux-when-syncing-solved/ - still uses outdated ::SHBrowseForFolder() (even on Windows 7) - selection dialog remembers size, but NOT position => if user enlarges window, the next time he opens the dialog it may leap out of visible screen - - hard-codes "Browse" button label -*/ + - hard-codes "Browse" button label */ -extern const wxEventType EVENT_ON_FOLDER_SELECTED; //directory is changed by the user (except manual type-in) -extern const wxEventType EVENT_ON_FOLDER_MANUAL_EDIT; //manual type-in -//example: wnd.Connect(EVENT_ON_FOLDER_SELECTED, wxCommandEventHandler(MyDlg::OnDirSelected), nullptr, this); +wxDECLARE_EVENT(EVENT_ON_FOLDER_SELECTED, wxCommandEvent); //directory is changed by the user (except manual type-in) +wxDECLARE_EVENT(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEvent); //manual type-in +//example: wnd.Bind(EVENT_ON_FOLDER_SELECTED, [this](wxCommandEvent& event) { onDirSelected(event); }); class FolderSelector: public wxEvtHandler { @@ -56,6 +55,7 @@ public: private: void onMouseWheel (wxMouseEvent& event); void onItemPathDropped(zen::FileDropEvent& event); + void onHistoryPathSelected(wxEvent& event); void onEditFolderPath (wxCommandEvent& event); void onSelectFolder (wxCommandEvent& event); void onSelectAltFolder(wxCommandEvent& event); diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index 7a125b81..f7607001 100644 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -141,6 +141,14 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer261->Add( 0, 0, 1, 0, 5 ); + m_bpButtonCmpConfig = new wxBitmapButton( m_panelTopButtons, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); + m_bpButtonCmpConfig->SetToolTip( _("dummy") ); + + bSizer261->Add( m_bpButtonCmpConfig, 0, wxEXPAND, 5 ); + + + bSizer261->Add( 4, 0, 0, 0, 5 ); + m_buttonCancel = new wxButton( m_panelTopButtons, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); m_buttonCancel->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) ); m_buttonCancel->Enable( false ); @@ -162,14 +170,6 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer261->Add( m_bpButtonCmpContext, 0, wxEXPAND, 5 ); - bSizer261->Add( 4, 0, 0, 0, 5 ); - - m_bpButtonCmpConfig = new wxBitmapButton( m_panelTopButtons, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - m_bpButtonCmpConfig->SetToolTip( _("dummy") ); - - bSizer261->Add( m_bpButtonCmpConfig, 0, wxEXPAND, 5 ); - - bSizer261->Add( 0, 0, 1, 0, 5 ); @@ -1114,74 +1114,74 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizerPanelHolder->Fit( this ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MainDialogGenerated::OnClose ) ); - m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigNew ), this, m_menuItemNew->GetId()); - m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigLoad ), this, m_menuItemLoad->GetId()); - m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), this, m_menuItemSave->GetId()); - m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigSaveAs ), this, m_menuItemSaveAs->GetId()); - m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnSaveAsBatchJob ), this, m_menuItemSaveAsBatch->GetId()); - m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuQuit ), this, m_menuItemQuit->GetId()); - m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnShowLog ), this, m_menuItemShowLog->GetId()); - m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnCompare ), this, m_menuItemCompare->GetId()); - m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnCmpSettings ), this, m_menuItemCompSettings->GetId()); - m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigureFilter ), this, m_menuItemFilter->GetId()); - m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnSyncSettings ), this, m_menuItemSyncSettings->GetId()); - m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnStartSync ), this, m_menuItemSynchronize->GetId()); - m_menuTools->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuOptions ), this, m_menuItemOptions->GetId()); - m_menuTools->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuFindItem ), this, m_menuItemFind->GetId()); - m_menuTools->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuExportFileList ), this, m_menuItemExportList->GetId()); - m_menuTools->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuResetLayout ), this, m_menuItem51->GetId()); - m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnShowHelp ), this, m_menuItemHelp->GetId()); - m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuCheckVersion ), this, m_menuItemCheckVersionNow->GetId()); - m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuCheckVersionAutomatically ), this, m_menuItemCheckVersionAuto->GetId()); - m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuAbout ), this, m_menuItemAbout->GetId()); - m_buttonCompare->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnCompare ), NULL, this ); - m_buttonCompare->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnCompSettingsContextMouse ), NULL, this ); - m_bpButtonCmpContext->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnCompSettingsContext ), NULL, this ); - m_bpButtonCmpContext->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnCompSettingsContextMouse ), NULL, this ); - m_bpButtonCmpConfig->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnCmpSettings ), NULL, this ); - m_bpButtonFilter->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigureFilter ), NULL, this ); - m_bpButtonFilter->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnGlobalFilterContextMouse ), NULL, this ); - m_bpButtonFilterContext->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnGlobalFilterContext ), NULL, this ); - m_bpButtonFilterContext->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnGlobalFilterContextMouse ), NULL, this ); - m_bpButtonSyncConfig->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncSettings ), NULL, this ); - m_buttonSync->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnStartSync ), NULL, this ); - m_buttonSync->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnSyncSettingsContextMouse ), NULL, this ); - m_bpButtonSyncContext->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncSettingsContext ), NULL, this ); - m_bpButtonSyncContext->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnSyncSettingsContextMouse ), NULL, this ); - m_bpButtonAddPair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnTopFolderPairAdd ), NULL, this ); - m_bpButtonRemovePair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnTopFolderPairRemove ), NULL, this ); - m_bpButtonSwapSides->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSwapSides ), NULL, this ); - m_bpButtonLocalCompCfg->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnTopLocalCompCfg ), NULL, this ); - m_bpButtonLocalFilter->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnTopLocalFilterCfg ), NULL, this ); - m_bpButtonLocalSyncCfg->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnTopLocalSyncCfg ), NULL, this ); - m_bpButtonHideSearch->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnHideSearchPanel ), NULL, this ); - m_textCtrlSearchTxt->Connect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( MainDialogGenerated::OnSearchGridEnter ), NULL, this ); - m_bpButtonNew->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigNew ), NULL, this ); - m_bpButtonOpen->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigLoad ), NULL, this ); - m_bpButtonSave->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), NULL, this ); - m_bpButtonSaveAs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSaveAs ), NULL, this ); - m_bpButtonSaveAsBatch->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSaveAsBatchJob ), NULL, this ); - m_bpButtonShowLog->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnShowLog ), NULL, this ); - m_bpButtonViewTypeSyncAction->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewType ), NULL, this ); - m_bpButtonViewTypeSyncAction->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewTypeContextMouse ), NULL, this ); - m_bpButtonViewContext->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnViewTypeContext ), NULL, this ); - m_bpButtonViewContext->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewTypeContextMouse ), NULL, this ); - m_bpButtonShowExcluded->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowDeleteLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowUpdateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowCreateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowLeftOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowLeftNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowEqual->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowDoNothing->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowDifferent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowRightOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowCreateRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowUpdateRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowDeleteRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); - m_bpButtonShowConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MainDialogGenerated::onClose ) ); + m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onConfigNew ), this, m_menuItemNew->GetId()); + m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onConfigLoad ), this, m_menuItemLoad->GetId()); + m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onConfigSave ), this, m_menuItemSave->GetId()); + m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onConfigSaveAs ), this, m_menuItemSaveAs->GetId()); + m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onSaveAsBatchJob ), this, m_menuItemSaveAsBatch->GetId()); + m_menuFile->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuQuit ), this, m_menuItemQuit->GetId()); + m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onShowLog ), this, m_menuItemShowLog->GetId()); + m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onCompare ), this, m_menuItemCompare->GetId()); + m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onCmpSettings ), this, m_menuItemCompSettings->GetId()); + m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onConfigureFilter ), this, m_menuItemFilter->GetId()); + m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onSyncSettings ), this, m_menuItemSyncSettings->GetId()); + m_menu4->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onStartSync ), this, m_menuItemSynchronize->GetId()); + m_menuTools->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuOptions ), this, m_menuItemOptions->GetId()); + m_menuTools->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuFindItem ), this, m_menuItemFind->GetId()); + m_menuTools->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuExportFileList ), this, m_menuItemExportList->GetId()); + m_menuTools->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuResetLayout ), this, m_menuItem51->GetId()); + m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onShowHelp ), this, m_menuItemHelp->GetId()); + m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuCheckVersion ), this, m_menuItemCheckVersionNow->GetId()); + m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuCheckVersionAutomatically ), this, m_menuItemCheckVersionAuto->GetId()); + m_menuHelp->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::onMenuAbout ), this, m_menuItemAbout->GetId()); + m_bpButtonCmpConfig->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onCmpSettings ), NULL, this ); + m_buttonCompare->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onCompare ), NULL, this ); + m_buttonCompare->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onCompSettingsContextMouse ), NULL, this ); + m_bpButtonCmpContext->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onCompSettingsContext ), NULL, this ); + m_bpButtonCmpContext->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onCompSettingsContextMouse ), NULL, this ); + m_bpButtonFilter->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onConfigureFilter ), NULL, this ); + m_bpButtonFilter->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onGlobalFilterContextMouse ), NULL, this ); + m_bpButtonFilterContext->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onGlobalFilterContext ), NULL, this ); + m_bpButtonFilterContext->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onGlobalFilterContextMouse ), NULL, this ); + m_bpButtonSyncConfig->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onSyncSettings ), NULL, this ); + m_buttonSync->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onStartSync ), NULL, this ); + m_buttonSync->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onSyncSettingsContextMouse ), NULL, this ); + m_bpButtonSyncContext->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onSyncSettingsContext ), NULL, this ); + m_bpButtonSyncContext->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onSyncSettingsContextMouse ), NULL, this ); + m_bpButtonAddPair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onTopFolderPairAdd ), NULL, this ); + m_bpButtonRemovePair->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onTopFolderPairRemove ), NULL, this ); + m_bpButtonSwapSides->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onSwapSides ), NULL, this ); + m_bpButtonLocalCompCfg->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onTopLocalCompCfg ), NULL, this ); + m_bpButtonLocalFilter->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onTopLocalFilterCfg ), NULL, this ); + m_bpButtonLocalSyncCfg->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onTopLocalSyncCfg ), NULL, this ); + m_bpButtonHideSearch->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onHideSearchPanel ), NULL, this ); + m_textCtrlSearchTxt->Connect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( MainDialogGenerated::onSearchGridEnter ), NULL, this ); + m_bpButtonNew->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onConfigNew ), NULL, this ); + m_bpButtonOpen->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onConfigLoad ), NULL, this ); + m_bpButtonSave->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onConfigSave ), NULL, this ); + m_bpButtonSaveAs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onConfigSaveAs ), NULL, this ); + m_bpButtonSaveAsBatch->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onSaveAsBatchJob ), NULL, this ); + m_bpButtonShowLog->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onShowLog ), NULL, this ); + m_bpButtonViewTypeSyncAction->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewType ), NULL, this ); + m_bpButtonViewTypeSyncAction->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewTypeContextMouse ), NULL, this ); + m_bpButtonViewContext->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onViewTypeContext ), NULL, this ); + m_bpButtonViewContext->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewTypeContextMouse ), NULL, this ); + m_bpButtonShowExcluded->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowDeleteLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowUpdateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowCreateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowLeftOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowLeftNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowEqual->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowDoNothing->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowDifferent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowRightOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowCreateRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowUpdateRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowDeleteRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); } MainDialogGenerated::~MainDialogGenerated() @@ -2458,61 +2458,61 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( ConfigDlgGenerated::OnClose ) ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( ConfigDlgGenerated::onClose ) ); m_listBoxFolderPair->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( ConfigDlgGenerated::onListBoxKeyEvent ), NULL, this ); - m_listBoxFolderPair->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnSelectFolderPair ), NULL, this ); - m_checkBoxUseLocalCmpOptions->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleLocalCompSettings ), NULL, this ); - m_buttonByTimeSize->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnCompByTimeSize ), NULL, this ); - m_buttonByTimeSize->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnCompByTimeSizeDouble ), NULL, this ); - m_buttonByContent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnCompByContent ), NULL, this ); - m_buttonByContent->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnCompByContentDouble ), NULL, this ); - m_buttonBySize->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnCompBySize ), NULL, this ); - m_buttonBySize->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnCompBySizeDouble ), NULL, this ); - m_checkBoxSymlinksInclude->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeCompOption ), NULL, this ); - m_hyperlink24->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpComparisonSettings ), NULL, this ); - m_hyperlink241->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpTimeShift ), NULL, this ); - m_checkBoxIgnoreErrors->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleIgnoreErrors ), NULL, this ); - m_checkBoxAutoRetry->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleAutoRetry ), NULL, this ); - m_hyperlink1711->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpPerformance ), NULL, this ); - m_textCtrlInclude->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); - m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpFilterSettings ), NULL, this ); - m_textCtrlExclude->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); - m_choiceUnitMinSize->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); - m_choiceUnitMaxSize->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); - m_choiceUnitTimespan->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChangeFilterOption ), NULL, this ); - m_buttonClear->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnFilterReset ), NULL, this ); - m_checkBoxUseLocalSyncOptions->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleLocalSyncSettings ), NULL, this ); - m_buttonTwoWay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnSyncTwoWay ), NULL, this ); - m_buttonTwoWay->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnSyncTwoWayDouble ), NULL, this ); - m_buttonMirror->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnSyncMirror ), NULL, this ); - m_buttonMirror->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnSyncMirrorDouble ), NULL, this ); - m_buttonUpdate->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnSyncUpdate ), NULL, this ); - m_buttonUpdate->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnSyncUpdateDouble ), NULL, this ); - m_buttonCustom->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnSyncCustom ), NULL, this ); - m_buttonCustom->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::OnSyncCustomDouble ), NULL, this ); - m_bpButtonLeftOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnExLeftSideOnly ), NULL, this ); - m_bpButtonLeftNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnLeftNewer ), NULL, this ); - m_bpButtonDifferent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnDifferent ), NULL, this ); - m_bpButtonConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnConflict ), NULL, this ); - m_bpButtonRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnRightNewer ), NULL, this ); - m_bpButtonRightOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnExRightSideOnly ), NULL, this ); - m_checkBoxDetectMove->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleDetectMovedFiles ), NULL, this ); - m_hyperlink242->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpDetectMovedFiles ), NULL, this ); - m_buttonRecycler->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnDeletionRecycler ), NULL, this ); - m_buttonPermanent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnDeletionPermanent ), NULL, this ); - m_buttonVersioning->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnDeletionVersioning ), NULL, this ); - m_hyperlink243->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::OnHelpVersioning ), NULL, this ); - m_choiceVersioningStyle->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::OnChanegVersioningStyle ), NULL, this ); - m_checkBoxVersionMaxDays->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleVersioningLimit ), NULL, this ); - m_checkBoxVersionCountMin->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleVersioningLimit ), NULL, this ); - m_checkBoxVersionCountMax->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleVersioningLimit ), NULL, this ); - m_checkBoxSendEmail->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleMiscEmail ), NULL, this ); - m_bpButtonEmailAlways->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnEmailAlways ), NULL, this ); - m_bpButtonEmailErrorWarning->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnEmailErrorWarning ), NULL, this ); - m_bpButtonEmailErrorOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnEmailErrorOnly ), NULL, this ); - m_checkBoxOverrideLogPath->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnToggleMiscOption ), NULL, this ); - m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnOkay ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::OnCancel ), NULL, this ); + m_listBoxFolderPair->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::onSelectFolderPair ), NULL, this ); + m_checkBoxUseLocalCmpOptions->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleLocalCompSettings ), NULL, this ); + m_buttonByTimeSize->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onCompByTimeSize ), NULL, this ); + m_buttonByTimeSize->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::onCompByTimeSizeDouble ), NULL, this ); + m_buttonByContent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onCompByContent ), NULL, this ); + m_buttonByContent->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::onCompByContentDouble ), NULL, this ); + m_buttonBySize->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onCompBySize ), NULL, this ); + m_buttonBySize->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::onCompBySizeDouble ), NULL, this ); + m_checkBoxSymlinksInclude->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onChangeCompOption ), NULL, this ); + m_hyperlink24->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::onHelpComparisonSettings ), NULL, this ); + m_hyperlink241->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::onHelpTimeShift ), NULL, this ); + m_checkBoxIgnoreErrors->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleIgnoreErrors ), NULL, this ); + m_checkBoxAutoRetry->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleAutoRetry ), NULL, this ); + m_hyperlink1711->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::onHelpPerformance ), NULL, this ); + m_textCtrlInclude->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( ConfigDlgGenerated::onChangeFilterOption ), NULL, this ); + m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::onHelpFilterSettings ), NULL, this ); + m_textCtrlExclude->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( ConfigDlgGenerated::onChangeFilterOption ), NULL, this ); + m_choiceUnitMinSize->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::onChangeFilterOption ), NULL, this ); + m_choiceUnitMaxSize->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::onChangeFilterOption ), NULL, this ); + m_choiceUnitTimespan->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::onChangeFilterOption ), NULL, this ); + m_buttonClear->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onFilterReset ), NULL, this ); + m_checkBoxUseLocalSyncOptions->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleLocalSyncSettings ), NULL, this ); + m_buttonTwoWay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onSyncTwoWay ), NULL, this ); + m_buttonTwoWay->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::onSyncTwoWayDouble ), NULL, this ); + m_buttonMirror->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onSyncMirror ), NULL, this ); + m_buttonMirror->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::onSyncMirrorDouble ), NULL, this ); + m_buttonUpdate->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onSyncUpdate ), NULL, this ); + m_buttonUpdate->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::onSyncUpdateDouble ), NULL, this ); + m_buttonCustom->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onSyncCustom ), NULL, this ); + m_buttonCustom->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( ConfigDlgGenerated::onSyncCustomDouble ), NULL, this ); + m_bpButtonLeftOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onExLeftSideOnly ), NULL, this ); + m_bpButtonLeftNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onLeftNewer ), NULL, this ); + m_bpButtonDifferent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onDifferent ), NULL, this ); + m_bpButtonConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onConflict ), NULL, this ); + m_bpButtonRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onRightNewer ), NULL, this ); + m_bpButtonRightOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onExRightSideOnly ), NULL, this ); + m_checkBoxDetectMove->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleDetectMovedFiles ), NULL, this ); + m_hyperlink242->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::onHelpDetectMovedFiles ), NULL, this ); + m_buttonRecycler->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onDeletionRecycler ), NULL, this ); + m_buttonPermanent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onDeletionPermanent ), NULL, this ); + m_buttonVersioning->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onDeletionVersioning ), NULL, this ); + m_hyperlink243->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( ConfigDlgGenerated::onHelpVersioning ), NULL, this ); + m_choiceVersioningStyle->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( ConfigDlgGenerated::onChanegVersioningStyle ), NULL, this ); + m_checkBoxVersionMaxDays->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleVersioningLimit ), NULL, this ); + m_checkBoxVersionCountMin->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleVersioningLimit ), NULL, this ); + m_checkBoxVersionCountMax->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleVersioningLimit ), NULL, this ); + m_checkBoxSendEmail->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleMiscEmail ), NULL, this ); + m_bpButtonEmailAlways->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onEmailAlways ), NULL, this ); + m_bpButtonEmailErrorWarning->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onEmailErrorWarning ), NULL, this ); + m_bpButtonEmailErrorOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onEmailErrorOnly ), NULL, this ); + m_checkBoxOverrideLogPath->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onToggleMiscOption ), NULL, this ); + m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onOkay ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConfigDlgGenerated::onCancel ), NULL, this ); } ConfigDlgGenerated::~ConfigDlgGenerated() @@ -3007,23 +3007,23 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( CloudSetupDlgGenerated::OnClose ) ); - m_toggleBtnGdrive->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnConnectionGdrive ), NULL, this ); - m_toggleBtnSftp->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnConnectionSftp ), NULL, this ); - m_toggleBtnFtp->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnConnectionFtp ), NULL, this ); - m_listBoxGdriveUsers->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( CloudSetupDlgGenerated::OnGdriveUserSelect ), NULL, this ); - m_buttonGdriveAddUser->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnGdriveUserAdd ), NULL, this ); - m_buttonGdriveRemoveUser->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnGdriveUserRemove ), NULL, this ); - m_radioBtnPassword->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( CloudSetupDlgGenerated::OnAuthPassword ), NULL, this ); - m_radioBtnKeyfile->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( CloudSetupDlgGenerated::OnAuthKeyfile ), NULL, this ); - m_radioBtnAgent->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( CloudSetupDlgGenerated::OnAuthAgent ), NULL, this ); - m_buttonSelectKeyfile->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnSelectKeyfile ), NULL, this ); - m_checkBoxShowPassword->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnToggleShowPassword ), NULL, this ); - m_buttonSelectFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnBrowseCloudFolder ), NULL, this ); - m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( CloudSetupDlgGenerated::OnHelpFtpPerformance ), NULL, this ); - m_buttonChannelCountSftp->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnDetectServerChannelLimit ), NULL, this ); - m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnOkay ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::OnCancel ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( CloudSetupDlgGenerated::onClose ) ); + m_toggleBtnGdrive->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onConnectionGdrive ), NULL, this ); + m_toggleBtnSftp->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onConnectionSftp ), NULL, this ); + m_toggleBtnFtp->Connect( wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onConnectionFtp ), NULL, this ); + m_listBoxGdriveUsers->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( CloudSetupDlgGenerated::onGdriveUserSelect ), NULL, this ); + m_buttonGdriveAddUser->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onGdriveUserAdd ), NULL, this ); + m_buttonGdriveRemoveUser->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onGdriveUserRemove ), NULL, this ); + m_radioBtnPassword->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( CloudSetupDlgGenerated::onAuthPassword ), NULL, this ); + m_radioBtnKeyfile->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( CloudSetupDlgGenerated::onAuthKeyfile ), NULL, this ); + m_radioBtnAgent->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( CloudSetupDlgGenerated::onAuthAgent ), NULL, this ); + m_buttonSelectKeyfile->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onSelectKeyfile ), NULL, this ); + m_checkBoxShowPassword->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onToggleShowPassword ), NULL, this ); + m_buttonSelectFolder->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onBrowseCloudFolder ), NULL, this ); + m_hyperlink171->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( CloudSetupDlgGenerated::onHelpFtpPerformance ), NULL, this ); + m_buttonChannelCountSftp->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onDetectServerChannelLimit ), NULL, this ); + m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onOkay ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CloudSetupDlgGenerated::onCancel ), NULL, this ); } CloudSetupDlgGenerated::~CloudSetupDlgGenerated() @@ -3083,10 +3083,10 @@ AbstractFolderPickerGenerated::AbstractFolderPickerGenerated( wxWindow* parent, this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( AbstractFolderPickerGenerated::OnClose ) ); - m_treeCtrlFileSystem->Connect( wxEVT_COMMAND_TREE_ITEM_EXPANDING, wxTreeEventHandler( AbstractFolderPickerGenerated::OnExpandNode ), NULL, this ); - m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AbstractFolderPickerGenerated::OnOkay ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AbstractFolderPickerGenerated::OnCancel ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( AbstractFolderPickerGenerated::onClose ) ); + m_treeCtrlFileSystem->Connect( wxEVT_COMMAND_TREE_ITEM_EXPANDING, wxTreeEventHandler( AbstractFolderPickerGenerated::onExpandNode ), NULL, this ); + m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AbstractFolderPickerGenerated::onOkay ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AbstractFolderPickerGenerated::onCancel ), NULL, this ); } AbstractFolderPickerGenerated::~AbstractFolderPickerGenerated() @@ -3311,9 +3311,9 @@ SyncConfirmationDlgGenerated::SyncConfirmationDlgGenerated( wxWindow* parent, wx this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncConfirmationDlgGenerated::OnClose ) ); - m_buttonStartSync->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::OnStartSync ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::OnCancel ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncConfirmationDlgGenerated::onClose ) ); + m_buttonStartSync->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::onStartSync ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncConfirmationDlgGenerated::onCancel ), NULL, this ); } SyncConfirmationDlgGenerated::~SyncConfirmationDlgGenerated() @@ -3890,9 +3890,9 @@ LogPanelGenerated::LogPanelGenerated( wxWindow* parent, wxWindowID id, const wxP bSizer153->Fit( this ); // Connect Events - m_bpButtonErrors->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( LogPanelGenerated::OnErrors ), NULL, this ); - m_bpButtonWarnings->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( LogPanelGenerated::OnWarnings ), NULL, this ); - m_bpButtonInfo->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( LogPanelGenerated::OnInfo ), NULL, this ); + m_bpButtonErrors->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( LogPanelGenerated::onErrors ), NULL, this ); + m_bpButtonWarnings->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( LogPanelGenerated::onWarnings ), NULL, this ); + m_bpButtonInfo->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( LogPanelGenerated::onInfo ), NULL, this ); } LogPanelGenerated::~LogPanelGenerated() @@ -4061,14 +4061,12 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( BatchDlgGenerated::OnClose ) ); - m_checkBoxRunMinimized->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnToggleRunMinimized ), NULL, this ); - m_checkBoxIgnoreErrors->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnToggleIgnoreErrors ), NULL, this ); - m_radioBtnErrorDialogShow->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorDialogShow ), NULL, this ); - m_radioBtnErrorDialogCancel->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( BatchDlgGenerated::OnErrorDialogCancel ), NULL, this ); - m_hyperlink17->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( BatchDlgGenerated::OnHelpScheduleBatch ), NULL, this ); - m_buttonSaveAs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnSaveBatchJob ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::OnCancel ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( BatchDlgGenerated::onClose ) ); + m_checkBoxRunMinimized->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::onToggleRunMinimized ), NULL, this ); + m_checkBoxIgnoreErrors->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( BatchDlgGenerated::onToggleIgnoreErrors ), NULL, this ); + m_hyperlink17->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( BatchDlgGenerated::onHelpScheduleBatch ), NULL, this ); + m_buttonSaveAs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::onSaveBatchJob ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BatchDlgGenerated::onCancel ), NULL, this ); } BatchDlgGenerated::~BatchDlgGenerated() @@ -4152,10 +4150,10 @@ DeleteDlgGenerated::DeleteDlgGenerated( wxWindow* parent, wxWindowID id, const w this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DeleteDlgGenerated::OnClose ) ); - m_checkBoxUseRecycler->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DeleteDlgGenerated::OnUseRecycler ), NULL, this ); - m_buttonOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DeleteDlgGenerated::OnOK ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DeleteDlgGenerated::OnCancel ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DeleteDlgGenerated::onClose ) ); + m_checkBoxUseRecycler->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DeleteDlgGenerated::onUseRecycler ), NULL, this ); + m_buttonOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DeleteDlgGenerated::onOkay ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DeleteDlgGenerated::onCancel ), NULL, this ); } DeleteDlgGenerated::~DeleteDlgGenerated() @@ -4275,11 +4273,9 @@ CopyToDlgGenerated::CopyToDlgGenerated( wxWindow* parent, wxWindowID id, const w this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( CopyToDlgGenerated::OnClose ) ); - m_checkBoxKeepRelPath->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( CopyToDlgGenerated::OnUseRecycler ), NULL, this ); - m_checkBoxOverwriteIfExists->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( CopyToDlgGenerated::OnUseRecycler ), NULL, this ); - m_buttonOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CopyToDlgGenerated::OnOK ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CopyToDlgGenerated::OnCancel ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( CopyToDlgGenerated::onClose ) ); + m_buttonOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CopyToDlgGenerated::onOkay ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CopyToDlgGenerated::onCancel ), NULL, this ); } CopyToDlgGenerated::~CopyToDlgGenerated() @@ -4821,22 +4817,22 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( OptionsDlgGenerated::OnClose ) ); - m_buttonRestoreDialogs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnRestoreDialogs ), NULL, this ); - m_hyperlinkLogFolder->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( OptionsDlgGenerated::OnShowLogFolder ), NULL, this ); - m_checkBoxLogFilesMaxAge->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnToggleLogfilesLimit ), NULL, this ); - m_textCtrlSoundPathCompareDone->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( OptionsDlgGenerated::OnChangeSoundFilePath ), NULL, this ); - m_buttonSelectSoundCompareDone->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnSelectSoundCompareDone ), NULL, this ); - m_bpButtonPlayCompareDone->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnPlayCompareDone ), NULL, this ); - m_textCtrlSoundPathSyncDone->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( OptionsDlgGenerated::OnChangeSoundFilePath ), NULL, this ); - m_buttonSelectSoundSyncDone->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnSelectSoundSyncDone ), NULL, this ); - m_bpButtonPlaySyncDone->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnPlaySyncDone ), NULL, this ); - m_bpButtonAddRow->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnAddRow ), NULL, this ); - m_bpButtonRemoveRow->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnRemoveRow ), NULL, this ); - m_hyperlink17->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( OptionsDlgGenerated::OnHelpExternalApps ), NULL, this ); - m_buttonDefault->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnDefault ), NULL, this ); - m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnOkay ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::OnCancel ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( OptionsDlgGenerated::onClose ) ); + m_buttonRestoreDialogs->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onRestoreDialogs ), NULL, this ); + m_hyperlinkLogFolder->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( OptionsDlgGenerated::onShowLogFolder ), NULL, this ); + m_checkBoxLogFilesMaxAge->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onToggleLogfilesLimit ), NULL, this ); + m_textCtrlSoundPathCompareDone->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( OptionsDlgGenerated::onChangeSoundFilePath ), NULL, this ); + m_buttonSelectSoundCompareDone->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onSelectSoundCompareDone ), NULL, this ); + m_bpButtonPlayCompareDone->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onPlayCompareDone ), NULL, this ); + m_textCtrlSoundPathSyncDone->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( OptionsDlgGenerated::onChangeSoundFilePath ), NULL, this ); + m_buttonSelectSoundSyncDone->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onSelectSoundSyncDone ), NULL, this ); + m_bpButtonPlaySyncDone->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onPlaySyncDone ), NULL, this ); + m_bpButtonAddRow->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onAddRow ), NULL, this ); + m_bpButtonRemoveRow->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onRemoveRow ), NULL, this ); + m_hyperlink17->Connect( wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler( OptionsDlgGenerated::onHelpExternalApps ), NULL, this ); + m_buttonDefault->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onDefault ), NULL, this ); + m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onOkay ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( OptionsDlgGenerated::onCancel ), NULL, this ); } OptionsDlgGenerated::~OptionsDlgGenerated() @@ -4919,11 +4915,11 @@ SelectTimespanDlgGenerated::SelectTimespanDlgGenerated( wxWindow* parent, wxWind this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SelectTimespanDlgGenerated::OnClose ) ); - m_calendarFrom->Connect( wxEVT_CALENDAR_SEL_CHANGED, wxCalendarEventHandler( SelectTimespanDlgGenerated::OnChangeSelectionFrom ), NULL, this ); - m_calendarTo->Connect( wxEVT_CALENDAR_SEL_CHANGED, wxCalendarEventHandler( SelectTimespanDlgGenerated::OnChangeSelectionTo ), NULL, this ); - m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SelectTimespanDlgGenerated::OnOkay ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SelectTimespanDlgGenerated::OnCancel ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SelectTimespanDlgGenerated::onClose ) ); + m_calendarFrom->Connect( wxEVT_CALENDAR_SEL_CHANGED, wxCalendarEventHandler( SelectTimespanDlgGenerated::onChangeSelectionFrom ), NULL, this ); + m_calendarTo->Connect( wxEVT_CALENDAR_SEL_CHANGED, wxCalendarEventHandler( SelectTimespanDlgGenerated::onChangeSelectionTo ), NULL, this ); + m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SelectTimespanDlgGenerated::onOkay ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SelectTimespanDlgGenerated::onCancel ), NULL, this ); } SelectTimespanDlgGenerated::~SelectTimespanDlgGenerated() @@ -5183,14 +5179,14 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( AboutDlgGenerated::OnClose ) ); - m_buttonDonate->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::OnDonate ), NULL, this ); - m_buttonShowDonationDetails->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::OnShowDonationDetails ), NULL, this ); - m_bpButtonForum->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::OnOpenForum ), NULL, this ); - m_bpButtonHomepage->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::OnOpenHomepage ), NULL, this ); - m_bpButtonEmail->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::OnSendEmail ), NULL, this ); - m_bpButtonGpl->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::OnShowGpl ), NULL, this ); - m_buttonClose->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::OnOK ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( AboutDlgGenerated::onClose ) ); + m_buttonDonate->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onDonate ), NULL, this ); + m_buttonShowDonationDetails->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onShowDonationDetails ), NULL, this ); + m_bpButtonForum->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onOpenForum ), NULL, this ); + m_bpButtonHomepage->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onOpenHomepage ), NULL, this ); + m_bpButtonEmail->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onSendEmail ), NULL, this ); + m_bpButtonGpl->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onShowGpl ), NULL, this ); + m_buttonClose->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( AboutDlgGenerated::onOkay ), NULL, this ); } AboutDlgGenerated::~AboutDlgGenerated() @@ -5253,7 +5249,7 @@ DownloadProgressDlgGenerated::DownloadProgressDlgGenerated( wxWindow* parent, wx bSizer24->Fit( this ); // Connect Events - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DownloadProgressDlgGenerated::OnCancel ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DownloadProgressDlgGenerated::onCancel ), NULL, this ); } DownloadProgressDlgGenerated::~DownloadProgressDlgGenerated() @@ -5431,12 +5427,12 @@ ActivationDlgGenerated::ActivationDlgGenerated( wxWindow* parent, wxWindowID id, this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( ActivationDlgGenerated::OnClose ) ); - m_buttonActivateOnline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::OnActivateOnline ), NULL, this ); - m_buttonCopyUrl->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::OnCopyUrl ), NULL, this ); - m_textCtrlOfflineActivationKey->Connect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( ActivationDlgGenerated::OnOfflineActivationEnter ), NULL, this ); - m_buttonActivateOffline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::OnActivateOffline ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::OnCancel ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( ActivationDlgGenerated::onClose ) ); + m_buttonActivateOnline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::onActivateOnline ), NULL, this ); + m_buttonCopyUrl->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::onCopyUrl ), NULL, this ); + m_textCtrlOfflineActivationKey->Connect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( ActivationDlgGenerated::onOfflineActivationEnter ), NULL, this ); + m_buttonActivateOffline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::onActivateOffline ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActivationDlgGenerated::onCancel ), NULL, this ); } ActivationDlgGenerated::~ActivationDlgGenerated() @@ -5502,9 +5498,9 @@ CfgHighlightDlgGenerated::CfgHighlightDlgGenerated( wxWindow* parent, wxWindowID this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( CfgHighlightDlgGenerated::OnClose ) ); - m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CfgHighlightDlgGenerated::OnOkay ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CfgHighlightDlgGenerated::OnCancel ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( CfgHighlightDlgGenerated::onClose ) ); + m_buttonOkay->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CfgHighlightDlgGenerated::onOkay ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( CfgHighlightDlgGenerated::onCancel ), NULL, this ); } CfgHighlightDlgGenerated::~CfgHighlightDlgGenerated() @@ -5614,11 +5610,11 @@ WarnAccessRightsMissingDlgGenerated::WarnAccessRightsMissingDlgGenerated( wxWind this->Centre( wxBOTH ); // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( WarnAccessRightsMissingDlgGenerated::OnClose ) ); - m_buttonLocateBundle->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::OnShowAppBundle ), NULL, this ); - m_buttonOpenSecurity->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::OnOpenSecuritySettings ), NULL, this ); - m_checkBoxDontShowAgain->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::OnCheckBoxClick ), NULL, this ); - m_buttonClose->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::OnOK ), NULL, this ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( WarnAccessRightsMissingDlgGenerated::onClose ) ); + m_buttonLocateBundle->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::onShowAppBundle ), NULL, this ); + m_buttonOpenSecurity->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::onOpenSecuritySettings ), NULL, this ); + m_checkBoxDontShowAgain->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::onCheckBoxClick ), NULL, this ); + m_buttonClose->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( WarnAccessRightsMissingDlgGenerated::onOkay ), NULL, this ); } WarnAccessRightsMissingDlgGenerated::~WarnAccessRightsMissingDlgGenerated() diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index 93a3fa24..399cb0e0 100644 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -26,8 +26,8 @@ #include <wx/font.h> #include <wx/colour.h> #include <wx/settings.h> -#include <wx/button.h> #include <wx/bmpbuttn.h> +#include <wx/button.h> #include <wx/sizer.h> #include <wx/panel.h> #include <wx/stattext.h> @@ -97,10 +97,10 @@ protected: wxBoxSizer* bSizerPanelHolder; wxPanel* m_panelTopButtons; wxBoxSizer* bSizerTopButtons; + wxBitmapButton* m_bpButtonCmpConfig; wxButton* m_buttonCancel; zen::BitmapTextButton* m_buttonCompare; wxBitmapButton* m_bpButtonCmpContext; - wxBitmapButton* m_bpButtonCmpConfig; wxBitmapButton* m_bpButtonFilter; wxBitmapButton* m_bpButtonFilterContext; wxBitmapButton* m_bpButtonSyncConfig; @@ -208,45 +208,45 @@ protected: wxStaticText* m_staticTextDeleteRight; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnConfigNew( wxCommandEvent& event ) { event.Skip(); } - virtual void OnConfigLoad( wxCommandEvent& event ) { event.Skip(); } - virtual void OnConfigSave( wxCommandEvent& event ) { event.Skip(); } - virtual void OnConfigSaveAs( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSaveAsBatchJob( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuQuit( wxCommandEvent& event ) { event.Skip(); } - virtual void OnShowLog( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCompare( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCmpSettings( wxCommandEvent& event ) { event.Skip(); } - virtual void OnConfigureFilter( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSyncSettings( wxCommandEvent& event ) { event.Skip(); } - virtual void OnStartSync( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuOptions( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuFindItem( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuExportFileList( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuResetLayout( wxCommandEvent& event ) { event.Skip(); } - virtual void OnShowHelp( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuCheckVersion( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuCheckVersionAutomatically( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuAbout( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCompSettingsContextMouse( wxMouseEvent& event ) { event.Skip(); } - virtual void OnCompSettingsContext( wxCommandEvent& event ) { event.Skip(); } - virtual void OnGlobalFilterContextMouse( wxMouseEvent& event ) { event.Skip(); } - virtual void OnGlobalFilterContext( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSyncSettingsContextMouse( wxMouseEvent& event ) { event.Skip(); } - virtual void OnSyncSettingsContext( wxCommandEvent& event ) { event.Skip(); } - virtual void OnTopFolderPairAdd( wxCommandEvent& event ) { event.Skip(); } - virtual void OnTopFolderPairRemove( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSwapSides( wxCommandEvent& event ) { event.Skip(); } - virtual void OnTopLocalCompCfg( wxCommandEvent& event ) { event.Skip(); } - virtual void OnTopLocalFilterCfg( wxCommandEvent& event ) { event.Skip(); } - virtual void OnTopLocalSyncCfg( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHideSearchPanel( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSearchGridEnter( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleViewType( wxCommandEvent& event ) { event.Skip(); } - virtual void OnViewTypeContextMouse( wxMouseEvent& event ) { event.Skip(); } - virtual void OnViewTypeContext( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleViewButton( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onConfigNew( wxCommandEvent& event ) { event.Skip(); } + virtual void onConfigLoad( wxCommandEvent& event ) { event.Skip(); } + virtual void onConfigSave( wxCommandEvent& event ) { event.Skip(); } + virtual void onConfigSaveAs( wxCommandEvent& event ) { event.Skip(); } + virtual void onSaveAsBatchJob( wxCommandEvent& event ) { event.Skip(); } + virtual void onMenuQuit( wxCommandEvent& event ) { event.Skip(); } + virtual void onShowLog( wxCommandEvent& event ) { event.Skip(); } + virtual void onCompare( wxCommandEvent& event ) { event.Skip(); } + virtual void onCmpSettings( wxCommandEvent& event ) { event.Skip(); } + virtual void onConfigureFilter( wxCommandEvent& event ) { event.Skip(); } + virtual void onSyncSettings( wxCommandEvent& event ) { event.Skip(); } + virtual void onStartSync( wxCommandEvent& event ) { event.Skip(); } + virtual void onMenuOptions( wxCommandEvent& event ) { event.Skip(); } + virtual void onMenuFindItem( wxCommandEvent& event ) { event.Skip(); } + virtual void onMenuExportFileList( wxCommandEvent& event ) { event.Skip(); } + virtual void onMenuResetLayout( wxCommandEvent& event ) { event.Skip(); } + virtual void onShowHelp( wxCommandEvent& event ) { event.Skip(); } + virtual void onMenuCheckVersion( wxCommandEvent& event ) { event.Skip(); } + virtual void onMenuCheckVersionAutomatically( wxCommandEvent& event ) { event.Skip(); } + virtual void onMenuAbout( wxCommandEvent& event ) { event.Skip(); } + virtual void onCompSettingsContextMouse( wxMouseEvent& event ) { event.Skip(); } + virtual void onCompSettingsContext( wxCommandEvent& event ) { event.Skip(); } + virtual void onGlobalFilterContextMouse( wxMouseEvent& event ) { event.Skip(); } + virtual void onGlobalFilterContext( wxCommandEvent& event ) { event.Skip(); } + virtual void onSyncSettingsContextMouse( wxMouseEvent& event ) { event.Skip(); } + virtual void onSyncSettingsContext( wxCommandEvent& event ) { event.Skip(); } + virtual void onTopFolderPairAdd( wxCommandEvent& event ) { event.Skip(); } + virtual void onTopFolderPairRemove( wxCommandEvent& event ) { event.Skip(); } + virtual void onSwapSides( wxCommandEvent& event ) { event.Skip(); } + virtual void onTopLocalCompCfg( wxCommandEvent& event ) { event.Skip(); } + virtual void onTopLocalFilterCfg( wxCommandEvent& event ) { event.Skip(); } + virtual void onTopLocalSyncCfg( wxCommandEvent& event ) { event.Skip(); } + virtual void onHideSearchPanel( wxCommandEvent& event ) { event.Skip(); } + virtual void onSearchGridEnter( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleViewType( wxCommandEvent& event ) { event.Skip(); } + virtual void onViewTypeContextMouse( wxMouseEvent& event ) { event.Skip(); } + virtual void onViewTypeContext( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleViewButton( wxCommandEvent& event ) { event.Skip(); } public: @@ -486,55 +486,55 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } virtual void onListBoxKeyEvent( wxKeyEvent& event ) { event.Skip(); } - virtual void OnSelectFolderPair( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleLocalCompSettings( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCompByTimeSize( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCompByTimeSizeDouble( wxMouseEvent& event ) { event.Skip(); } - virtual void OnCompByContent( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCompByContentDouble( wxMouseEvent& event ) { event.Skip(); } - virtual void OnCompBySize( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCompBySizeDouble( wxMouseEvent& event ) { event.Skip(); } - virtual void OnChangeCompOption( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpComparisonSettings( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnHelpTimeShift( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnToggleIgnoreErrors( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleAutoRetry( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpPerformance( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnChangeFilterOption( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpFilterSettings( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnFilterReset( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleLocalSyncSettings( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSyncTwoWay( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSyncTwoWayDouble( wxMouseEvent& event ) { event.Skip(); } - virtual void OnSyncMirror( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSyncMirrorDouble( wxMouseEvent& event ) { event.Skip(); } - virtual void OnSyncUpdate( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSyncUpdateDouble( wxMouseEvent& event ) { event.Skip(); } - virtual void OnSyncCustom( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSyncCustomDouble( wxMouseEvent& event ) { event.Skip(); } - virtual void OnExLeftSideOnly( wxCommandEvent& event ) { event.Skip(); } - virtual void OnLeftNewer( wxCommandEvent& event ) { event.Skip(); } - virtual void OnDifferent( wxCommandEvent& event ) { event.Skip(); } - virtual void OnConflict( wxCommandEvent& event ) { event.Skip(); } - virtual void OnRightNewer( wxCommandEvent& event ) { event.Skip(); } - virtual void OnExRightSideOnly( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleDetectMovedFiles( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpDetectMovedFiles( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnDeletionRecycler( wxCommandEvent& event ) { event.Skip(); } - virtual void OnDeletionPermanent( wxCommandEvent& event ) { event.Skip(); } - virtual void OnDeletionVersioning( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpVersioning( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnChanegVersioningStyle( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleVersioningLimit( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleMiscEmail( wxCommandEvent& event ) { event.Skip(); } - virtual void OnEmailAlways( wxCommandEvent& event ) { event.Skip(); } - virtual void OnEmailErrorWarning( wxCommandEvent& event ) { event.Skip(); } - virtual void OnEmailErrorOnly( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleMiscOption( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onSelectFolderPair( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleLocalCompSettings( wxCommandEvent& event ) { event.Skip(); } + virtual void onCompByTimeSize( wxCommandEvent& event ) { event.Skip(); } + virtual void onCompByTimeSizeDouble( wxMouseEvent& event ) { event.Skip(); } + virtual void onCompByContent( wxCommandEvent& event ) { event.Skip(); } + virtual void onCompByContentDouble( wxMouseEvent& event ) { event.Skip(); } + virtual void onCompBySize( wxCommandEvent& event ) { event.Skip(); } + virtual void onCompBySizeDouble( wxMouseEvent& event ) { event.Skip(); } + virtual void onChangeCompOption( wxCommandEvent& event ) { event.Skip(); } + virtual void onHelpComparisonSettings( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onHelpTimeShift( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onToggleIgnoreErrors( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleAutoRetry( wxCommandEvent& event ) { event.Skip(); } + virtual void onHelpPerformance( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onChangeFilterOption( wxCommandEvent& event ) { event.Skip(); } + virtual void onHelpFilterSettings( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onFilterReset( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleLocalSyncSettings( wxCommandEvent& event ) { event.Skip(); } + virtual void onSyncTwoWay( wxCommandEvent& event ) { event.Skip(); } + virtual void onSyncTwoWayDouble( wxMouseEvent& event ) { event.Skip(); } + virtual void onSyncMirror( wxCommandEvent& event ) { event.Skip(); } + virtual void onSyncMirrorDouble( wxMouseEvent& event ) { event.Skip(); } + virtual void onSyncUpdate( wxCommandEvent& event ) { event.Skip(); } + virtual void onSyncUpdateDouble( wxMouseEvent& event ) { event.Skip(); } + virtual void onSyncCustom( wxCommandEvent& event ) { event.Skip(); } + virtual void onSyncCustomDouble( wxMouseEvent& event ) { event.Skip(); } + virtual void onExLeftSideOnly( wxCommandEvent& event ) { event.Skip(); } + virtual void onLeftNewer( wxCommandEvent& event ) { event.Skip(); } + virtual void onDifferent( wxCommandEvent& event ) { event.Skip(); } + virtual void onConflict( wxCommandEvent& event ) { event.Skip(); } + virtual void onRightNewer( wxCommandEvent& event ) { event.Skip(); } + virtual void onExRightSideOnly( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleDetectMovedFiles( wxCommandEvent& event ) { event.Skip(); } + virtual void onHelpDetectMovedFiles( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onDeletionRecycler( wxCommandEvent& event ) { event.Skip(); } + virtual void onDeletionPermanent( wxCommandEvent& event ) { event.Skip(); } + virtual void onDeletionVersioning( wxCommandEvent& event ) { event.Skip(); } + virtual void onHelpVersioning( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onChanegVersioningStyle( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleVersioningLimit( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleMiscEmail( wxCommandEvent& event ) { event.Skip(); } + virtual void onEmailAlways( wxCommandEvent& event ) { event.Skip(); } + virtual void onEmailErrorWarning( wxCommandEvent& event ) { event.Skip(); } + virtual void onEmailErrorOnly( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleMiscOption( wxCommandEvent& event ) { event.Skip(); } + virtual void onOkay( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -638,23 +638,23 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnConnectionGdrive( wxCommandEvent& event ) { event.Skip(); } - virtual void OnConnectionSftp( wxCommandEvent& event ) { event.Skip(); } - virtual void OnConnectionFtp( wxCommandEvent& event ) { event.Skip(); } - virtual void OnGdriveUserSelect( wxCommandEvent& event ) { event.Skip(); } - virtual void OnGdriveUserAdd( wxCommandEvent& event ) { event.Skip(); } - virtual void OnGdriveUserRemove( wxCommandEvent& event ) { event.Skip(); } - virtual void OnAuthPassword( wxCommandEvent& event ) { event.Skip(); } - virtual void OnAuthKeyfile( wxCommandEvent& event ) { event.Skip(); } - virtual void OnAuthAgent( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSelectKeyfile( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleShowPassword( wxCommandEvent& event ) { event.Skip(); } - virtual void OnBrowseCloudFolder( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpFtpPerformance( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnDetectServerChannelLimit( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onConnectionGdrive( wxCommandEvent& event ) { event.Skip(); } + virtual void onConnectionSftp( wxCommandEvent& event ) { event.Skip(); } + virtual void onConnectionFtp( wxCommandEvent& event ) { event.Skip(); } + virtual void onGdriveUserSelect( wxCommandEvent& event ) { event.Skip(); } + virtual void onGdriveUserAdd( wxCommandEvent& event ) { event.Skip(); } + virtual void onGdriveUserRemove( wxCommandEvent& event ) { event.Skip(); } + virtual void onAuthPassword( wxCommandEvent& event ) { event.Skip(); } + virtual void onAuthKeyfile( wxCommandEvent& event ) { event.Skip(); } + virtual void onAuthAgent( wxCommandEvent& event ) { event.Skip(); } + virtual void onSelectKeyfile( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleShowPassword( wxCommandEvent& event ) { event.Skip(); } + virtual void onBrowseCloudFolder( wxCommandEvent& event ) { event.Skip(); } + virtual void onHelpFtpPerformance( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onDetectServerChannelLimit( wxCommandEvent& event ) { event.Skip(); } + virtual void onOkay( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -681,10 +681,10 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnExpandNode( wxTreeEvent& event ) { event.Skip(); } - virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onExpandNode( wxTreeEvent& event ) { event.Skip(); } + virtual void onOkay( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -734,9 +734,9 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnStartSync( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onStartSync( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -864,9 +864,9 @@ protected: wxStaticLine* m_staticline13; // Virtual event handlers, overide them in your derived class - virtual void OnErrors( wxCommandEvent& event ) { event.Skip(); } - virtual void OnWarnings( wxCommandEvent& event ) { event.Skip(); } - virtual void OnInfo( wxCommandEvent& event ) { event.Skip(); } + virtual void onErrors( wxCommandEvent& event ) { event.Skip(); } + virtual void onWarnings( wxCommandEvent& event ) { event.Skip(); } + virtual void onInfo( wxCommandEvent& event ) { event.Skip(); } public: @@ -909,14 +909,12 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnToggleRunMinimized( wxCommandEvent& event ) { event.Skip(); } - virtual void OnToggleIgnoreErrors( wxCommandEvent& event ) { event.Skip(); } - virtual void OnErrorDialogShow( wxCommandEvent& event ) { event.Skip(); } - virtual void OnErrorDialogCancel( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpScheduleBatch( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnSaveBatchJob( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onToggleRunMinimized( wxCommandEvent& event ) { event.Skip(); } + virtual void onToggleIgnoreErrors( wxCommandEvent& event ) { event.Skip(); } + virtual void onHelpScheduleBatch( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onSaveBatchJob( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -949,10 +947,10 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnUseRecycler( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOK( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onUseRecycler( wxCommandEvent& event ) { event.Skip(); } + virtual void onOkay( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -985,10 +983,9 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnUseRecycler( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOK( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onOkay( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -1078,21 +1075,21 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnRestoreDialogs( wxCommandEvent& event ) { event.Skip(); } - virtual void OnShowLogFolder( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnToggleLogfilesLimit( wxCommandEvent& event ) { event.Skip(); } - virtual void OnChangeSoundFilePath( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSelectSoundCompareDone( wxCommandEvent& event ) { event.Skip(); } - virtual void OnPlayCompareDone( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSelectSoundSyncDone( wxCommandEvent& event ) { event.Skip(); } - virtual void OnPlaySyncDone( wxCommandEvent& event ) { event.Skip(); } - virtual void OnAddRow( wxCommandEvent& event ) { event.Skip(); } - virtual void OnRemoveRow( wxCommandEvent& event ) { event.Skip(); } - virtual void OnHelpExternalApps( wxHyperlinkEvent& event ) { event.Skip(); } - virtual void OnDefault( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onRestoreDialogs( wxCommandEvent& event ) { event.Skip(); } + virtual void onShowLogFolder( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onToggleLogfilesLimit( wxCommandEvent& event ) { event.Skip(); } + virtual void onChangeSoundFilePath( wxCommandEvent& event ) { event.Skip(); } + virtual void onSelectSoundCompareDone( wxCommandEvent& event ) { event.Skip(); } + virtual void onPlayCompareDone( wxCommandEvent& event ) { event.Skip(); } + virtual void onSelectSoundSyncDone( wxCommandEvent& event ) { event.Skip(); } + virtual void onPlaySyncDone( wxCommandEvent& event ) { event.Skip(); } + virtual void onAddRow( wxCommandEvent& event ) { event.Skip(); } + virtual void onRemoveRow( wxCommandEvent& event ) { event.Skip(); } + virtual void onHelpExternalApps( wxHyperlinkEvent& event ) { event.Skip(); } + virtual void onDefault( wxCommandEvent& event ) { event.Skip(); } + virtual void onOkay( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -1137,11 +1134,11 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnChangeSelectionFrom( wxCalendarEvent& event ) { event.Skip(); } - virtual void OnChangeSelectionTo( wxCalendarEvent& event ) { event.Skip(); } - virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onChangeSelectionFrom( wxCalendarEvent& event ) { event.Skip(); } + virtual void onChangeSelectionTo( wxCalendarEvent& event ) { event.Skip(); } + virtual void onOkay( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -1196,14 +1193,14 @@ protected: wxButton* m_buttonClose; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnDonate( wxCommandEvent& event ) { event.Skip(); } - virtual void OnShowDonationDetails( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOpenForum( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOpenHomepage( wxCommandEvent& event ) { event.Skip(); } - virtual void OnSendEmail( wxCommandEvent& event ) { event.Skip(); } - virtual void OnShowGpl( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOK( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onDonate( wxCommandEvent& event ) { event.Skip(); } + virtual void onShowDonationDetails( wxCommandEvent& event ) { event.Skip(); } + virtual void onOpenForum( wxCommandEvent& event ) { event.Skip(); } + virtual void onOpenHomepage( wxCommandEvent& event ) { event.Skip(); } + virtual void onSendEmail( wxCommandEvent& event ) { event.Skip(); } + virtual void onShowGpl( wxCommandEvent& event ) { event.Skip(); } + virtual void onOkay( wxCommandEvent& event ) { event.Skip(); } public: @@ -1230,7 +1227,7 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -1273,12 +1270,12 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnActivateOnline( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCopyUrl( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOfflineActivationEnter( wxCommandEvent& event ) { event.Skip(); } - virtual void OnActivateOffline( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onActivateOnline( wxCommandEvent& event ) { event.Skip(); } + virtual void onCopyUrl( wxCommandEvent& event ) { event.Skip(); } + virtual void onOfflineActivationEnter( wxCommandEvent& event ) { event.Skip(); } + virtual void onActivateOffline( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -1305,9 +1302,9 @@ protected: wxButton* m_buttonCancel; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onOkay( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } public: @@ -1344,11 +1341,11 @@ protected: wxButton* m_buttonClose; // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnShowAppBundle( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOpenSecuritySettings( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCheckBoxClick( wxCommandEvent& event ) { event.Skip(); } - virtual void OnOK( wxCommandEvent& event ) { event.Skip(); } + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onShowAppBundle( wxCommandEvent& event ) { event.Skip(); } + virtual void onOpenSecuritySettings( wxCommandEvent& event ) { event.Skip(); } + virtual void onCheckBoxClick( wxCommandEvent& event ) { event.Skip(); } + virtual void onOkay( wxCommandEvent& event ) { event.Skip(); } public: diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp index 9484af2c..3cb6aaaa 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.cpp +++ b/FreeFileSync/Source/ui/gui_status_handler.cpp @@ -43,8 +43,8 @@ StatusHandlerTemporaryPanel::StatusHandlerTemporaryPanel(MainDialog& dlg, mainDlg_.Update(); //don't wait until idle event! //register keys - mainDlg_.Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(StatusHandlerTemporaryPanel::OnKeyPressed), nullptr, this); - mainDlg_.m_buttonCancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusHandlerTemporaryPanel::OnAbortCompare), nullptr, this); + mainDlg_. Bind(wxEVT_CHAR_HOOK, &StatusHandlerTemporaryPanel::onLocalKeyEvent, this); + mainDlg_.m_buttonCancel->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &StatusHandlerTemporaryPanel::onAbortCompare, this); } @@ -128,8 +128,9 @@ StatusHandlerTemporaryPanel::~StatusHandlerTemporaryPanel() mainDlg_.auiMgr_.Update(); //unregister keys - mainDlg_.Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(StatusHandlerTemporaryPanel::OnKeyPressed), nullptr, this); - mainDlg_.m_buttonCancel->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusHandlerTemporaryPanel::OnAbortCompare), nullptr, this); + [[maybe_unused]] bool ubOk1 = mainDlg_. Unbind(wxEVT_CHAR_HOOK, &StatusHandlerTemporaryPanel::onLocalKeyEvent, this); + [[maybe_unused]] bool ubOk2 = mainDlg_.m_buttonCancel->Unbind(wxEVT_COMMAND_BUTTON_CLICKED, &StatusHandlerTemporaryPanel::onAbortCompare, this); + assert(ubOk1 && ubOk2); mainDlg_.compareStatus_->teardown(); @@ -313,20 +314,20 @@ void StatusHandlerTemporaryPanel::forceUiUpdateNoThrow() } -void StatusHandlerTemporaryPanel::OnKeyPressed(wxKeyEvent& event) +void StatusHandlerTemporaryPanel::onLocalKeyEvent(wxKeyEvent& event) { const int keyCode = event.GetKeyCode(); if (keyCode == WXK_ESCAPE) { wxCommandEvent dummy; - OnAbortCompare(dummy); + onAbortCompare(dummy); } event.Skip(); } -void StatusHandlerTemporaryPanel::OnAbortCompare(wxCommandEvent& event) +void StatusHandlerTemporaryPanel::onAbortCompare(wxCommandEvent& event) { userRequestAbort(); } diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h index 2a9e00d2..e8ed01e4 100644 --- a/FreeFileSync/Source/ui/gui_status_handler.h +++ b/FreeFileSync/Source/ui/gui_status_handler.h @@ -41,8 +41,8 @@ public: Result reportResults(); //noexcept!! private: - void OnKeyPressed(wxKeyEvent& event); - void OnAbortCompare(wxCommandEvent& event); //handle abort button click + void onLocalKeyEvent(wxKeyEvent& event); + void onAbortCompare(wxCommandEvent& event); //handle abort button click void showStatsPanel(); MainDialog& mainDlg_; diff --git a/FreeFileSync/Source/ui/log_panel.cpp b/FreeFileSync/Source/ui/log_panel.cpp index 03279804..2b9b91da 100644 --- a/FreeFileSync/Source/ui/log_panel.cpp +++ b/FreeFileSync/Source/ui/log_panel.cpp @@ -189,30 +189,29 @@ public: void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override { - GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, enabled && selected); - } - - void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override - { - wxRect rectTmp = rect; + GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, selected); //-------------- draw item separation line ----------------- + wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), fastFromDIP(1))); + const bool drawBottomLine = [&] //don't separate multi-line messages { - wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), fastFromDIP(1))); - const bool drawBottomLine = [&] //don't separate multi-line messages - { - if (std::optional<MessageView::LogEntryView> nextEntry = msgView_.getEntry(row + 1)) - return nextEntry->firstLine; - return true; - }(); + if (std::optional<MessageView::LogEntryView> nextEntry = msgView_.getEntry(row + 1)) + return nextEntry->firstLine; + return true; + }(); - if (drawBottomLine) - { - dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); - --rectTmp.height; - } - } + if (drawBottomLine) + dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); //-------------------------------------------------------- + } + + void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override + { + wxDCTextColourChanger textColor(dc); + if (enabled && selected) //accessibility: always set *both* foreground AND background colors! + textColor.Set(*wxBLACK); + + wxRect rectTmp = rect; if (std::optional<MessageView::LogEntryView> entry = msgView_.getEntry(row)) switch (static_cast<ColumnTypeLog>(colType)) @@ -326,12 +325,11 @@ LogPanel::LogPanel(wxWindow* parent) : LogPanelGenerated(parent) }); //support for CTRL + C - m_gridMessages->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(LogPanel::onGridButtonEvent), nullptr, this); + m_gridMessages->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridButtonEvent(event); }); - m_gridMessages->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(LogPanel::onMsgGridContext), nullptr, this); + m_gridMessages->Bind(EVENT_GRID_CONTEXT_MENU, [this](GridContextMenuEvent& event) { onMsgGridContext(event); }); - //enable dialog-specific key events - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(LogPanel::onLocalKeyEvent), nullptr, this); + Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //enable dialog-specific key events setLog(nullptr); } @@ -400,21 +398,21 @@ void LogPanel::updateGrid() m_gridMessages->Refresh(); //update MVC "view" } -void LogPanel::OnErrors(wxCommandEvent& event) +void LogPanel::onErrors(wxCommandEvent& event) { m_bpButtonErrors->toggle(); updateGrid(); } -void LogPanel::OnWarnings(wxCommandEvent& event) +void LogPanel::onWarnings(wxCommandEvent& event) { m_bpButtonWarnings->toggle(); updateGrid(); } -void LogPanel::OnInfo(wxCommandEvent& event) +void LogPanel::onInfo(wxCommandEvent& event) { m_bpButtonInfo->toggle(); updateGrid(); @@ -448,7 +446,7 @@ void LogPanel::onGridButtonEvent(wxKeyEvent& event) } -void LogPanel::onMsgGridContext(GridClickEvent& event) +void LogPanel::onMsgGridContext(GridContextMenuEvent& event) { const std::vector<size_t> selection = m_gridMessages->getSelectedRows(); @@ -463,7 +461,7 @@ void LogPanel::onMsgGridContext(GridClickEvent& event) menu.addItem(_("Copy") + L"\tCtrl+C", [this] { copySelectionToClipboard(); }, wxNullImage, !selection.empty()); menu.addSeparator(); - menu.addItem(_("Select all") + L"\tCtrl+A", [this] { m_gridMessages->selectAllRows(GridEventPolicy::ALLOW); }, wxNullImage, rowCount > 0); + menu.addItem(_("Select all") + L"\tCtrl+A", [this] { m_gridMessages->selectAllRows(GridEventPolicy::allow); }, wxNullImage, rowCount > 0); menu.popup(*m_gridMessages, event.mousePos_); } @@ -486,7 +484,7 @@ void LogPanel::onLocalKeyEvent(wxKeyEvent& event) //process key events without e { case 'A': m_gridMessages->SetFocus(); - m_gridMessages->selectAllRows(GridEventPolicy::ALLOW); + m_gridMessages->selectAllRows(GridEventPolicy::allow); return; // -> swallow event! don't allow default grid commands! //case 'C': -> already implemented by "Grid" class diff --git a/FreeFileSync/Source/ui/log_panel.h b/FreeFileSync/Source/ui/log_panel.h index f92b0e48..828d6447 100644 --- a/FreeFileSync/Source/ui/log_panel.h +++ b/FreeFileSync/Source/ui/log_panel.h @@ -27,11 +27,11 @@ private: MessageView& getDataView(); void updateGrid(); - void OnErrors (wxCommandEvent& event) override; - void OnWarnings(wxCommandEvent& event) override; - void OnInfo (wxCommandEvent& event) override; + void onErrors (wxCommandEvent& event) override; + void onWarnings(wxCommandEvent& event) override; + void onInfo (wxCommandEvent& event) override; void onGridButtonEvent(wxKeyEvent& event); - void onMsgGridContext (zen::GridClickEvent& event); + void onMsgGridContext (zen::GridContextMenuEvent& event); void onLocalKeyEvent (wxKeyEvent& event); void copySelectionToClipboard(); diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index bbe626ab..491b7321 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -134,11 +134,11 @@ public: folderSelectorLeft_ .setSiblingSelector(&folderSelectorRight_); folderSelectorRight_.setSiblingSelector(&folderSelectorLeft_); - folderSelectorLeft_ .Connect(EVENT_ON_FOLDER_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); - folderSelectorRight_.Connect(EVENT_ON_FOLDER_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog); + folderSelectorLeft_ .Bind(EVENT_ON_FOLDER_SELECTED, [&mainDialog](wxCommandEvent& event) { mainDialog.onDirSelected(event); }); + folderSelectorRight_.Bind(EVENT_ON_FOLDER_SELECTED, [&mainDialog](wxCommandEvent& event) { mainDialog.onDirSelected(event); }); - folderSelectorLeft_ .Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); - folderSelectorRight_.Connect(EVENT_ON_FOLDER_MANUAL_EDIT, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); + folderSelectorLeft_ .Bind(EVENT_ON_FOLDER_MANUAL_EDIT, [&mainDialog](wxCommandEvent& event) { mainDialog.onDirManualCorrection(event); }); + folderSelectorRight_.Bind(EVENT_ON_FOLDER_MANUAL_EDIT, [&mainDialog](wxCommandEvent& event) { mainDialog.onDirManualCorrection(event); }); } void setValues(const LocalPairConfig& lpc) @@ -583,48 +583,55 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, //wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) -> better than wxBLACK, but which background to use? } - auiMgr_.GetPane(m_gridOverview).MinSize(-1, -1); //we successfully tricked wxAuiManager into setting an initial Window size :> incomplete API anyone?? - auiMgr_.Update(); // + auiMgr_.GetPane(m_gridOverview).MinSize(fastFromDIP(100), fastFromDIP(100)); //we successfully tricked wxAuiManager into setting an + auiMgr_.Update(); //initial Window size :> incomplete API anyone?? + defaultPerspective_ = auiMgr_.SavePerspective(); //---------------------------------------------------------------------------------- //register view layout context menu - m_panelTopButtons->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this); - m_panelConfig ->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this); - m_panelViewFilter->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this); - m_panelStatusBar ->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this); + m_panelTopButtons->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onContextSetLayout(event); }); + m_panelConfig ->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onContextSetLayout(event); }); + m_panelViewFilter->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onContextSetLayout(event); }); + m_panelStatusBar ->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onContextSetLayout(event); }); //---------------------------------------------------------------------------------- //file grid: sorting - m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(MainDialog::onGridLabelLeftClickL), nullptr, this); - m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(MainDialog::onGridLabelLeftClickC), nullptr, this); - m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(MainDialog::onGridLabelLeftClickR), nullptr, this); + m_gridMainL->Bind(EVENT_GRID_COL_LABEL_MOUSE_LEFT, [this](GridLabelClickEvent& event) { onGridLabelLeftClickL(event); }); + m_gridMainC->Bind(EVENT_GRID_COL_LABEL_MOUSE_LEFT, [this](GridLabelClickEvent& event) { onGridLabelLeftClickC(event); }); + m_gridMainR->Bind(EVENT_GRID_COL_LABEL_MOUSE_LEFT, [this](GridLabelClickEvent& event) { onGridLabelLeftClickR(event); }); - m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(MainDialog::onGridLabelContextL), nullptr, this); - m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(MainDialog::onGridLabelContextC), nullptr, this); - m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(MainDialog::onGridLabelContextR), nullptr, this); + m_gridMainL->Bind(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, [this](GridLabelClickEvent& event) { onGridLabelContextL(event); }); + m_gridMainC->Bind(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, [this](GridLabelClickEvent& event) { onGridLabelContextC(event); }); + m_gridMainR->Bind(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, [this](GridLabelClickEvent& event) { onGridLabelContextR(event); }); //file grid: context menu - m_gridMainL->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextL), nullptr, this); - m_gridMainR->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextR), nullptr, this); + m_gridMainL->Bind(EVENT_GRID_CONTEXT_MENU, [this](GridContextMenuEvent& event) { onGridContextL(event); }); + m_gridMainR->Bind(EVENT_GRID_CONTEXT_MENU, [this](GridContextMenuEvent& event) { onGridContextR(event); }); + + m_gridMainL->Bind(EVENT_GRID_MOUSE_RIGHT_DOWN, [this](GridClickEvent& event) { onGridGroupContextL(event); }); + m_gridMainR->Bind(EVENT_GRID_MOUSE_RIGHT_DOWN, [this](GridClickEvent& event) { onGridGroupContextR(event); }); - m_gridMainL->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onGridDoubleClickL), nullptr, this); - m_gridMainR->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onGridDoubleClickR), nullptr, this); + m_gridMainL->Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onGridSelectL(event); }); + m_gridMainR->Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onGridSelectR(event); }); + + m_gridMainL->Bind(EVENT_GRID_MOUSE_LEFT_DOUBLE, [this](GridClickEvent& event) { onGridDoubleClickL(event); }); + m_gridMainR->Bind(EVENT_GRID_MOUSE_LEFT_DOUBLE, [this](GridClickEvent& event) { onGridDoubleClickR(event); }); //tree grid: - m_gridOverview->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onTreeGridContext), nullptr, this); - m_gridOverview->Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(MainDialog::onTreeGridSelection), nullptr, this); + m_gridOverview->Bind(EVENT_GRID_CONTEXT_MENU, [this](GridContextMenuEvent& event) { onTreeGridContext (event); }); + m_gridOverview->Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onTreeGridSelection(event); }); //cfg grid: - m_gridCfgHistory->Connect(EVENT_GRID_SELECT_RANGE, GridSelectEventHandler(MainDialog::onCfgGridSelection), nullptr, this); - m_gridCfgHistory->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onCfgGridDoubleClick), nullptr, this); - m_gridCfgHistory->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onCfgGridKeyEvent), nullptr, this); - m_gridCfgHistory->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onCfgGridContext), nullptr, this); - m_gridCfgHistory->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(MainDialog::onCfgGridLabelContext), nullptr, this); - m_gridCfgHistory->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(MainDialog::onCfgGridLabelLeftClick), nullptr, this); + m_gridCfgHistory->getMainWin().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); }); + m_gridCfgHistory->Bind(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, [this](GridLabelClickEvent& event) { onCfgGridLabelContext (event); }); + m_gridCfgHistory->Bind(EVENT_GRID_COL_LABEL_MOUSE_LEFT, [this](GridLabelClickEvent& event) { onCfgGridLabelLeftClick(event); }); //---------------------------------------------------------------------------------- - m_panelSearch->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::OnSearchPanelKeyPressed), nullptr, this); + m_panelSearch->Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onSearchPanelKeyPressed(event); }); //set tool tips with (non-translated!) short cut hint @@ -676,11 +683,10 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, wxMenuItem* newItem = new wxMenuItem(m_menuLanguages, wxID_ANY, ti.languageName); newItem->SetBitmap(loadImage(ti.languageFlag)); - m_menuLanguages->Bind(wxEVT_COMMAND_MENU_SELECTED, [this, langId = ti.languageID](wxCommandEvent&) { this->switchProgramLanguage(langId); }, newItem->GetId()); + m_menuLanguages->Bind(wxEVT_COMMAND_MENU_SELECTED, [this, langId = ti.languageID](wxCommandEvent&) { switchProgramLanguage(langId); }, newItem->GetId()); m_menuLanguages->Append(newItem); //pass ownership } - //set up layout items to toggle showing hidden panels m_menuItemShowMain ->SetItemLabel(replaceCpy(_("Show \"%x\""), L"%x", _("Main Bar"))); m_menuItemShowFolders ->SetItemLabel(replaceCpy(_("Show \"%x\""), L"%x", _("Folder Pairs"))); @@ -706,14 +712,15 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, setupLayoutMenuEvent(m_menuItemShowConfig, m_panelConfig); setupLayoutMenuEvent(m_menuItemShowOverview, m_gridOverview); - m_menuTools->Connect(wxEVT_MENU_OPEN, wxMenuEventHandler(MainDialog::onOpenMenuTools), nullptr, this); + m_menuTools->Bind(wxEVT_MENU_OPEN, [this](wxMenuEvent& event) { onOpenMenuTools(event); }); //show FreeFileSync update reminder if (!globalSettings.gui.lastOnlineVersion.empty() && haveNewerVersionOnline(globalSettings.gui.lastOnlineVersion)) { auto menu = new wxMenu(); wxMenuItem* newItem = new wxMenuItem(menu, wxID_ANY, _("&Show details")); - this->Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnMenuUpdateAvailable)); + Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& event) { onMenuUpdateAvailable(event); }, newItem->GetId()); + menu->Append(newItem); //pass ownership const std::wstring blackStar = utfTo<std::wstring>("★"); @@ -737,52 +744,48 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, setConfig(guiCfg, referenceFiles); //support for CTRL + C and DEL on grids - m_gridMainL->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventL), nullptr, this); - m_gridMainC->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventC), nullptr, this); - m_gridMainR->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventR), nullptr, this); + m_gridMainL->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridButtonEventL(event); }); + m_gridMainC->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridButtonEventC(event); }); + m_gridMainR->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridButtonEventR(event); }); - m_gridOverview->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onTreeButtonEvent), nullptr, this); + m_gridOverview->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onTreeButtonEvent(event); }); - //enable dialog-specific key events - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onLocalKeyEvent), nullptr, this); + Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //enable dialog-specific key events //drag and drop .ffs_gui and .ffs_batch on main dialog setupFileDrop(*this); - Connect(EVENT_DROP_FILE, FileDropEventHandler(MainDialog::onDialogFilesDropped), nullptr, this); - - //Connect(wxEVT_SIZE, wxSizeEventHandler(MainDialog::OnResize), nullptr, this); - //Connect(wxEVT_MOVE, wxSizeEventHandler(MainDialog::OnResize), nullptr, this); + Bind(EVENT_DROP_FILE, [this](FileDropEvent& event) { onDialogFilesDropped(event); }); //calculate witdh of folder pair manually (if scrollbars are visible) - m_panelTopLeft->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeLeftFolderWidth), nullptr, this); + m_panelTopLeft->Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { onResizeLeftFolderWidth(event); }); - m_panelTopLeft ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, this); - m_panelTopCenter->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, this); - m_panelTopRight ->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onTopFolderPairKeyEvent), nullptr, this); + m_panelTopLeft ->Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onTopFolderPairKeyEvent(event); }); + m_panelTopCenter->Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onTopFolderPairKeyEvent(event); }); + m_panelTopRight ->Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onTopFolderPairKeyEvent(event); }); //dynamically change sizer direction depending on size - m_panelTopButtons->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeTopButtonPanel), nullptr, this); - m_panelConfig ->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeConfigPanel), nullptr, this); - m_panelViewFilter->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeViewPanel), nullptr, this); + m_panelTopButtons->Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { onResizeTopButtonPanel(event); }); + m_panelConfig ->Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { onResizeConfigPanel (event); }); + m_panelViewFilter->Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { onResizeViewPanel (event); }); wxSizeEvent dummy3; - OnResizeTopButtonPanel(dummy3); // - OnResizeConfigPanel (dummy3); //call once on window creation - OnResizeViewPanel (dummy3); // + onResizeTopButtonPanel(dummy3); // + onResizeConfigPanel (dummy3); //call once on window creation + onResizeViewPanel (dummy3); // //event handler for manual (un-)checking of rows and setting of sync direction - m_gridMainC->Connect(EVENT_GRID_CHECK_ROWS, CheckRowsEventHandler (MainDialog::onCheckRows), nullptr, this); - m_gridMainC->Connect(EVENT_GRID_SYNC_DIRECTION, SyncDirectionEventHandler(MainDialog::onSetSyncDirection), nullptr, this); + m_gridMainC->Bind(EVENT_GRID_CHECK_ROWS, [this](CheckRowsEvent& event) { onCheckRows (event); }); + m_gridMainC->Bind(EVENT_GRID_SYNC_DIRECTION, [this](SyncDirectionEvent& event) { onSetSyncDirection(event); }); //mainly to update row label sizes... updateGui(); //register regular check for update on next idle event - Connect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), nullptr, this); + Bind(wxEVT_IDLE, &MainDialog::onRegularUpdateCheck, this); //asynchronous call to wxWindow::Layout(): fix superfluous frame on right and bottom when FFS is started in fullscreen mode - Connect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnLayoutWindowAsync), nullptr, this); - wxCommandEvent evtDummy; //call once before OnLayoutWindowAsync() - OnResizeLeftFolderWidth(evtDummy); // + Bind(wxEVT_IDLE, &MainDialog::onLayoutWindowAsync, this); + wxCommandEvent evtDummy; //call once before onLayoutWindowAsync() + onResizeLeftFolderWidth(evtDummy); // //scroll cfg history to last used position. We cannot do this earlier e.g. in setGlobalCfgOnInit() //1. setConfig() indirectly calls cfggrid::addAndSelect() which changes cfg history scroll position @@ -889,7 +892,7 @@ MainDialog::~MainDialog() for (wxMenuItem* item : detachedMenuItems_) delete item; //something's got to give - //no need for wxEventHandler::Disconnect() here; event sources are components of this window and are destroyed, too + //no need for wxEventHandler::Unbind(): event sources are components of this window and are destroyed, too } //------------------------------------------------------------------------------------------------------------------------------------- @@ -905,7 +908,7 @@ void MainDialog::onQueryEndSession() } -void MainDialog::OnClose(wxCloseEvent& event) +void MainDialog::onClose(wxCloseEvent& event) { //attention: system shutdown is handled in onQueryEndSession()! @@ -1086,8 +1089,9 @@ XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit() globalSettings.gui.mainDlg.treeGridColumnAttribs = convertColAttributes<ColAttributesTree>(m_gridOverview->getColumnConfig()); globalSettings.gui.mainDlg.treeGridShowPercentBar = treegrid::getShowPercentage(*m_gridOverview); - std::tie(globalSettings.gui.mainDlg.treeGridLastSortColumn, - globalSettings.gui.mainDlg.treeGridLastSortAscending) = treegrid::getDataView(*m_gridOverview).getSortDirection(); + const auto [sortCol, ascending] = treegrid::getDataView(*m_gridOverview).getSortConfig(); + globalSettings.gui.mainDlg.treeGridLastSortColumn = sortCol; + globalSettings.gui.mainDlg.treeGridLastSortAscending = ascending; //-------------------------------------------------------------------------------- //write list of configuration files @@ -1231,7 +1235,7 @@ bool selectionIncludesNonEqualItem(const std::vector<FileSystemObject*>& selecti void MainDialog::setSyncDirManually(const std::vector<FileSystemObject*>& selection, SyncDirection direction) { if (!selectionIncludesNonEqualItem(selection)) - return; //harmonize with onMainGridContextRim(): this function should be a no-op iff context menu option is disabled! + return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled! for (FileSystemObject* fsObj : selection) { @@ -1242,13 +1246,13 @@ void MainDialog::setSyncDirManually(const std::vector<FileSystemObject*>& select } -void MainDialog::setFilterManually(const std::vector<FileSystemObject*>& selection, bool setActive) +void MainDialog::setIncludedManually(const std::vector<FileSystemObject*>& selection, bool setActive) { //if hidefiltered is active, there should be no filtered elements on screen => current element was filtered out assert(m_bpButtonShowExcluded->isActive() || !setActive); if (selection.empty()) - return; //harmonize with onMainGridContextRim(): this function should be a no-op iff context menu option is disabled! + return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled! for (FileSystemObject* fsObj : selection) setActiveStatus(setActive, *fsObj); //works recursively for directories @@ -1345,7 +1349,7 @@ void MainDialog::copyToAlternateFolder(const std::vector<FileSystemObject*>& sel { if (std::all_of(selectionLeft .begin(), selectionLeft .end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty< LEFT_SIDE>(); }) && /**/std::all_of(selectionRight.begin(), selectionRight.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty<RIGHT_SIDE>(); })) - /**/return; //harmonize with onMainGridContextRim(): this function should be a no-op iff context menu option is disabled! + /**/return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled! FocusPreserver fp; @@ -1393,7 +1397,7 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec { if (std::all_of(selectionLeft .begin(), selectionLeft .end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty< LEFT_SIDE>(); }) && /**/std::all_of(selectionRight.begin(), selectionRight.end(), [](const FileSystemObject* fsObj) { return fsObj->isEmpty<RIGHT_SIDE>(); })) - /**/return; //harmonize with onMainGridContextRim(): this function should be a no-op iff context menu option is disabled! + /**/return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled! FocusPreserver fp; @@ -1540,10 +1544,10 @@ void invokeCommandLine(const Zstring& commandLinePhrase, //throw FileError if (const auto& [exitCode, output] = consoleExecute(cmdLine, timeoutMs); //throw SysError, SysErrorTimeOut exitCode != 0) - throw zen::SysError(formatSystemError(utfTo<std::string>(commandLinePhrase), replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output)); + throw SysError(formatSystemError(utfTo<std::string>(commandLinePhrase), replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output)); } catch (SysErrorTimeOut&) {} //child process not failed yet => probably fine :> - catch (const zen::SysError& e) { throw FileError(replaceCpy(_("Command %x failed."), L"%x", fmtPath(cmdLine)), e.toString()); } + catch (const SysError& e) { throw FileError(replaceCpy(_("Command %x failed."), L"%x", fmtPath(cmdLine)), e.toString()); } } } } @@ -1815,21 +1819,21 @@ void updateSizerOrientation(wxBoxSizer& sizer, wxWindow& window, double horizont } -void MainDialog::OnResizeTopButtonPanel(wxEvent& event) +void MainDialog::onResizeTopButtonPanel(wxEvent& event) { updateSizerOrientation(*bSizerTopButtons, *m_panelTopButtons, 0.5); event.Skip(); } -void MainDialog::OnResizeConfigPanel(wxEvent& event) +void MainDialog::onResizeConfigPanel(wxEvent& event) { updateSizerOrientation(*bSizerConfig, *m_panelConfig, 0.5); event.Skip(); } -void MainDialog::OnResizeViewPanel(wxEvent& event) +void MainDialog::onResizeViewPanel(wxEvent& event) { //we need something more fancy for the statistics: const int newOrientation = m_panelViewFilter->GetSize().GetWidth() > m_panelViewFilter->GetSize().GetHeight() ? wxHORIZONTAL : wxVERTICAL; //check window NOT sizer width! @@ -1856,7 +1860,7 @@ void MainDialog::OnResizeViewPanel(wxEvent& event) } -void MainDialog::OnResizeLeftFolderWidth(wxEvent& event) +void MainDialog::onResizeLeftFolderWidth(wxEvent& event) { //adapt left-shift display distortion caused by scrollbars for multiple folder pairs const int width = m_panelTopLeft->GetSize().GetWidth(); @@ -1920,7 +1924,7 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event) case WXK_SPACE: case WXK_NUMPAD_SPACE: if (!selection.empty()) - setFilterManually(selection, m_bpButtonShowExcluded->isActive() && !selection[0]->isActive()); + setIncludedManually(selection, m_bpButtonShowExcluded->isActive() && !selection[0]->isActive()); //always exclude items if "m_bpButtonShowExcluded is unchecked" => yes, it's possible to have already unchecked items in selection, so we need to overwrite: //e.g. select root node while the first item returned is not shown on grid! return; @@ -2012,7 +2016,7 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) case WXK_SPACE: case WXK_NUMPAD_SPACE: if (!selection.empty()) - setFilterManually(selection, m_bpButtonShowExcluded->isActive() && !selection[0]->isActive()); + setIncludedManually(selection, m_bpButtonShowExcluded->isActive() && !selection[0]->isActive()); return; case WXK_DELETE: @@ -2204,7 +2208,7 @@ void MainDialog::onTreeGridSelection(GridSelectEvent& event) } -void MainDialog::onTreeGridContext(GridClickEvent& event) +void MainDialog::onTreeGridContext(GridContextMenuEvent& event) { const std::vector<FileSystemObject*>& selection = getTreeSelection(); //referenced by lambdas! ContextMenu menu; @@ -2265,9 +2269,9 @@ void MainDialog::onTreeGridContext(GridClickEvent& event) addFilterMenu(_("&Exclude via filter:"), "filter_exclude_sicon", false); //---------------------------------------------------------------------------------------------------- if (m_bpButtonShowExcluded->isActive() && !selection.empty() && !selection[0]->isActive()) - menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, loadImage("checkbox_true")); + menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setIncludedManually(selection, true); }, loadImage("checkbox_true")); else - menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, loadImage("checkbox_false"), !selection.empty()); + menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setIncludedManually(selection, false); }, loadImage("checkbox_false"), !selection.empty()); //---------------------------------------------------------------------------------------------------- const bool selectionContainsItemsToSync = [&] { @@ -2309,24 +2313,50 @@ void MainDialog::onTreeGridContext(GridClickEvent& event) } -void MainDialog::onMainGridContextL(GridClickEvent& event) +void MainDialog::onGridContextRim(GridContextMenuEvent& event, bool leftSide) { - onMainGridContextRim(true /*leftSide*/, event); + const std::vector<FileSystemObject*> selection = getGridSelection(); //referenced by lambdas! + const std::vector<FileSystemObject*> selectionLeft = getGridSelection(true, false); + const std::vector<FileSystemObject*> selectionRight = getGridSelection(false, true); + + onGridContextRim(getGridSelection(), + getGridSelection(true, false), + getGridSelection(false, true), event.mousePos_, leftSide); } -void MainDialog::onMainGridContextR(GridClickEvent& event) +void MainDialog::onGridGroupContextRim(GridClickEvent& event, bool leftSide) { - onMainGridContextRim(false /*leftSide*/, event); + if (static_cast<HoverAreaGroup>(event.hoverArea_) == HoverAreaGroup::groupName) + if (const FileView::PathDrawInfo pdi = filegrid::getDataView(*m_gridMainC).getDrawInfo(event.row_); + pdi.folderGroupObj) + { + assert(dynamic_cast<FolderPair*>(pdi.folderGroupObj)); + FolderPair* groupFolder = static_cast<FolderPair*>(pdi.folderGroupObj); + + m_gridMainL->clearSelection(GridEventPolicy::deny); + m_gridMainC->clearSelection(GridEventPolicy::deny); + m_gridMainR->clearSelection(GridEventPolicy::deny); + + std::vector<FileSystemObject*> selectionLeft; + std::vector<FileSystemObject*> selectionRight; + (leftSide ? selectionLeft : selectionRight).push_back(groupFolder); + + onGridContextRim({groupFolder}, + selectionLeft, + selectionRight, event.mousePos_, leftSide); + return; //"swallow" event => suppress default context menu handling + } + + assert(static_cast<HoverAreaGroup>(event.hoverArea_) != HoverAreaGroup::groupName); + event.Skip(); } -void MainDialog::onMainGridContextRim(bool leftSide, GridClickEvent& event) +void MainDialog::onGridContextRim(const std::vector<FileSystemObject*>& selection, + const std::vector<FileSystemObject*>& selectionLeft, + const std::vector<FileSystemObject*>& selectionRight, const wxPoint& mousePos, bool leftSide) { - const std::vector<FileSystemObject*> selection = getGridSelection(); //referenced by lambdas! - const std::vector<FileSystemObject*> selectionLeft = getGridSelection(true, false); - const std::vector<FileSystemObject*> selectionRight = getGridSelection(false, true); - ContextMenu menu; auto getImage = [&](SyncDirection dir, SyncOperation soDefault) @@ -2334,9 +2364,9 @@ void MainDialog::onMainGridContextRim(bool leftSide, GridClickEvent& event) return mirrorIfRtl(getSyncOpImage(!selection.empty() && selection[0]->getSyncOperation() != SO_EQUAL ? selection[0]->testSyncOperation(dir) : soDefault)); }; + const wxImage opLeft = getImage(SyncDirection::left, SO_OVERWRITE_LEFT ); const wxImage opRight = getImage(SyncDirection::right, SO_OVERWRITE_RIGHT); const wxImage opNone = getImage(SyncDirection::none, SO_DO_NOTHING ); - const wxImage opLeft = getImage(SyncDirection::left, SO_OVERWRITE_LEFT ); wxString shortcutLeft = L"\tAlt+Left"; wxString shortcutRight = L"\tAlt+Right"; @@ -2347,8 +2377,8 @@ void MainDialog::onMainGridContextRim(bool leftSide, GridClickEvent& event) menu.addItem(_("Set direction:") + L" ->" + shortcutRight, [this, &selection] { setSyncDirManually(selection, SyncDirection::right); }, opRight, nonEqualSelected); menu.addItem(_("Set direction:") + L" -" L"\tAlt+Down", [this, &selection] { setSyncDirManually(selection, SyncDirection::none); }, opNone, nonEqualSelected); menu.addItem(_("Set direction:") + L" <-" + shortcutLeft, [this, &selection] { setSyncDirManually(selection, SyncDirection::left); }, opLeft, nonEqualSelected); - //Gtk needs a direction, "<-", because it has no context menu icons! - //Gtk requires "no spaces" for shortcut identifiers! + //GTK needs a direction, "<-", because it has no context menu icons! + //GTK does not allow spaces in shortcut identifiers! menu.addSeparator(); //---------------------------------------------------------------------------------------------------- auto addFilterMenu = [&](const wxString& label, const char* iconName, bool include) @@ -2393,9 +2423,9 @@ void MainDialog::onMainGridContextRim(bool leftSide, GridClickEvent& event) addFilterMenu(_("&Exclude via filter:"), "filter_exclude_sicon", false); //---------------------------------------------------------------------------------------------------- if (m_bpButtonShowExcluded->isActive() && !selection.empty() && !selection[0]->isActive()) - menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, loadImage("checkbox_true")); + menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setIncludedManually(selection, true); }, loadImage("checkbox_true")); else - menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, loadImage("checkbox_false"), !selection.empty()); + menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setIncludedManually(selection, false); }, loadImage("checkbox_false"), !selection.empty()); //---------------------------------------------------------------------------------------------------- const bool selectionContainsItemsToSync = [&] { @@ -2461,7 +2491,72 @@ void MainDialog::onMainGridContextRim(bool leftSide, GridClickEvent& event) menu.addSeparator(); menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selectionLeft, selectionRight, true /*moveToRecycler*/); }, wxNullImage, haveNonEmptyItemsL || haveNonEmptyItemsR); - menu.popup(leftSide ? *m_gridMainL : *m_gridMainR, event.mousePos_); + menu.popup(leftSide ? *m_gridMainL : *m_gridMainR, mousePos); +} + + +void MainDialog::onGridSelectRim(GridSelectEvent& event, bool leftSide) +{ + //group name clicked? => expand selection to all items below folder group + FileView& gridDataView = filegrid::getDataView(*m_gridMainC); + + if (event.mouseClick_) + if (static_cast<HoverAreaGroup>(event.mouseClick_->hoverArea_) == HoverAreaGroup::groupName) + if (const FileView::PathDrawInfo pdi = gridDataView.getDrawInfo(event.mouseClick_->row_); + pdi.folderGroupObj) + { + assert(dynamic_cast<FolderPair*>(pdi.folderGroupObj)); + FolderPair* groupFolder = static_cast<FolderPair*>(pdi.folderGroupObj); + + std::vector<size_t> groupRows; + + const size_t rowsTotal = gridDataView.rowsOnView(); + for (size_t row = 0; row < rowsTotal; ++row) + if (const FileSystemObject* fsObj = gridDataView.getFsObject(row)) + { + const bool insideGroupFolder = [&] + { + if (fsObj == groupFolder) + return true; + + for (const FileSystemObject* fsObj2 = fsObj;;) + { + const ContainerObject& parent = fsObj2->parent(); + if (&parent == groupFolder) + return true; + + fsObj2 = dynamic_cast<const FolderPair*>(&parent); + if (!fsObj2) + return false; + } + }(); + if (insideGroupFolder) + groupRows.push_back(row); + } + else assert(false); + //------------------------------------------------ + //convert groupRows into multiple range selections + size_t rowFirst = 0; + size_t rowLast = 0; + auto addSelection = [&] + { + if (rowFirst < rowLast) + (leftSide ? *m_gridMainL : *m_gridMainR).selectRange(rowFirst, rowLast, event.positive_, GridEventPolicy::deny); + }; + + for (size_t row : groupRows) + if (row == rowLast) + ++rowLast; + else + { + addSelection(); + rowFirst = row; + rowLast = row + 1; + } + addSelection(); + } + + event.Skip(); } @@ -2559,29 +2654,20 @@ void MainDialog::onGridLabelContextC(GridLabelClickEvent& event) ContextMenu menu; const bool actionView = m_bpButtonViewTypeSyncAction->isActive(); - menu.addRadio(_("Category") + (actionView ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::category); }, !actionView); + menu.addRadio(_("Category") + ( actionView ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::category); }, !actionView); menu.addRadio(_("Action") + (!actionView ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::action ); }, actionView); menu.popup(*this); } -void MainDialog::onGridLabelContextL(GridLabelClickEvent& event) -{ - onGridLabelContextRim(*m_gridMainL, static_cast<ColumnTypeRim>(event.colType_), true /*left*/); -} - - -void MainDialog::onGridLabelContextR(GridLabelClickEvent& event) -{ - onGridLabelContextRim(*m_gridMainR, static_cast<ColumnTypeRim>(event.colType_), false /*left*/); -} - - -void MainDialog::onGridLabelContextRim(Grid& grid, ColumnTypeRim type, bool left) +void MainDialog::onGridLabelContextRim(bool leftSide) { ContextMenu menu; //-------------------------------------------------------------------------------------------------------- + Grid& grid = leftSide ? *m_gridMainL : *m_gridMainR; + //const ColumnTypeRim colType = static_cast<ColumnTypeRim>(event.colType_); + auto toggleColumn = [&](ColumnType ct) { auto colAttr = grid.getColumnConfig(); @@ -2616,7 +2702,7 @@ void MainDialog::onGridLabelContextRim(Grid& grid, ColumnTypeRim type, bool left //---------------------------------------------------------------------------------------------- menu.addSeparator(); - auto& itemPathFormat = left ? globalCfg_.gui.mainDlg.itemPathFormatLeftGrid : globalCfg_.gui.mainDlg.itemPathFormatRightGrid; + auto& itemPathFormat = leftSide ? globalCfg_.gui.mainDlg.itemPathFormatLeftGrid : globalCfg_.gui.mainDlg.itemPathFormatRightGrid; auto setItemPathFormat = [&](ItemPathFormat fmt) { @@ -2645,9 +2731,9 @@ void MainDialog::onGridLabelContextRim(Grid& grid, ColumnTypeRim type, bool left { const XmlGlobalSettings defaultCfg; - grid.setColumnConfig(convertColAttributes(left ? defaultCfg.gui.mainDlg.columnAttribLeft : defaultCfg.gui.mainDlg.columnAttribRight, defaultCfg.gui.mainDlg.columnAttribLeft)); + grid.setColumnConfig(convertColAttributes(leftSide ? defaultCfg.gui.mainDlg.columnAttribLeft : defaultCfg.gui.mainDlg.columnAttribRight, defaultCfg.gui.mainDlg.columnAttribLeft)); - setItemPathFormat(left ? defaultCfg.gui.mainDlg.itemPathFormatLeftGrid : defaultCfg.gui.mainDlg.itemPathFormatRightGrid); + setItemPathFormat(leftSide ? defaultCfg.gui.mainDlg.itemPathFormatLeftGrid : defaultCfg.gui.mainDlg.itemPathFormatRightGrid); setIconSize(defaultCfg.gui.mainDlg.iconSize, defaultCfg.gui.mainDlg.showIcons); }; @@ -2719,7 +2805,7 @@ void MainDialog::resetLayout() } -void MainDialog::OnContextSetLayout(wxMouseEvent& event) +void MainDialog::onContextSetLayout(wxMouseEvent& event) { ContextMenu menu; @@ -2755,7 +2841,7 @@ void MainDialog::OnContextSetLayout(wxMouseEvent& event) } -void MainDialog::OnCompSettingsContext(wxEvent& event) +void MainDialog::onCompSettingsContext(wxEvent& event) { ContextMenu menu; @@ -2781,7 +2867,7 @@ void MainDialog::OnCompSettingsContext(wxEvent& event) } -void MainDialog::OnSyncSettingsContext(wxEvent& event) +void MainDialog::onSyncSettingsContext(wxEvent& event) { ContextMenu menu; @@ -2810,8 +2896,8 @@ void MainDialog::OnSyncSettingsContext(wxEvent& event) void MainDialog::onDialogFilesDropped(FileDropEvent& event) { - assert(!event.getPaths().empty()); - loadConfiguration(event.getPaths()); + assert(!event.itemPaths_.empty()); + loadConfiguration(event.itemPaths_); //event.Skip(); } @@ -2841,7 +2927,7 @@ void MainDialog::cfgHistoryRemoveObsolete(const std::vector<Zstring>& filePaths) availableFiles.push_back(runAsync([=] { return fileAvailable(filePath); })); //potentially slow network access => limit maximum wait time! - wait_for_all_timed(availableFiles.begin(), availableFiles.end(), std::chrono::seconds(1)); + waitForAllTimed(availableFiles.begin(), availableFiles.end(), std::chrono::seconds(1)); std::vector<Zstring> pathsToRemove; @@ -2911,7 +2997,7 @@ void MainDialog::updateUnsavedCfgStatus() } -void MainDialog::OnConfigSave(wxCommandEvent& event) +void MainDialog::onConfigSave(wxCommandEvent& event) { const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalNativePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); @@ -2944,18 +3030,6 @@ void MainDialog::OnConfigSave(wxCommandEvent& event) } -void MainDialog::OnConfigSaveAs(wxCommandEvent& event) -{ - trySaveConfig(nullptr); -} - - -void MainDialog::OnSaveAsBatchJob(wxCommandEvent& event) -{ - trySaveBatchConfig(nullptr); -} - - bool MainDialog::trySaveConfig(const Zstring* guiCfgPath) //return true if saved successfully { Zstring cfgFilePath; @@ -2968,11 +3042,11 @@ bool MainDialog::trySaveConfig(const Zstring* guiCfgPath) //return true if saved else { const Zstring defaultFilePath = activeConfigFiles_.size() == 1 && !equalNativePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstr("SyncSettings.ffs_gui"); - auto defaultFolder = utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); - auto defaultFileName = utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)); + auto defaultFolder = utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); + auto defaultFileName = utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all)); //attention: activeConfigFiles may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config! - defaultFileName = beforeLast(defaultFileName, L'.', IF_MISSING_RETURN_ALL) + L".ffs_gui"; + defaultFileName = beforeLast(defaultFileName, L'.', IfNotFoundReturn::all) + L".ffs_gui"; wxFileDialog filePicker(this, //put modal dialog on stack: creating this on freestore leads to memleak! wxString(), //message @@ -3051,11 +3125,11 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchCfgPath) updateUnsavedCfgStatus(); //nothing else to update on GUI! const Zstring defaultFilePath = !activeCfgFilePath.empty() ? activeCfgFilePath : Zstr("BatchRun.ffs_batch"); - auto defaultFolder = utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)); - auto defaultFileName = utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)); + auto defaultFolder = utfTo<wxString>(beforeLast(defaultFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); + auto defaultFileName = utfTo<wxString>(afterLast (defaultFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all)); //attention: activeConfigFiles may be a *.ffs_gui file! We don't want to overwrite it with a BATCH config! - defaultFileName = beforeLast(defaultFileName, L'.', IF_MISSING_RETURN_ALL) + L".ffs_batch"; + defaultFileName = beforeLast(defaultFileName, L'.', IfNotFoundReturn::all) + L".ffs_batch"; wxFileDialog filePicker(this, //put modal dialog on stack: creating this on freestore leads to memleak! wxString(), //message @@ -3103,7 +3177,7 @@ bool MainDialog::saveOldConfig() //return false on user abort switch (showQuestionDialog(this, DialogInfoType::info, PopupDialogCfg(). setTitle(utfTo<wxString>(activeCfgFilePath)). setMainInstructions(replaceCpy(_("Do you want to save changes to %x?"), L"%x", - fmtPath(afterLast(activeCfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)))). + fmtPath(afterLast(activeCfgFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all)))). setCheckBox(neverSaveChanges, _("Never save &changes"), QuestionButton2::yes), _("&Save"), _("Do&n't save"))) { @@ -3148,13 +3222,13 @@ bool MainDialog::saveOldConfig() //return false on user abort } -void MainDialog::OnConfigLoad(wxCommandEvent& event) +void MainDialog::onConfigLoad(wxCommandEvent& event) { const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalNativePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); wxFileDialog filePicker(this, wxString(), //message - utfTo<wxString>(beforeLast(activeCfgFilePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE)), //default folder + utfTo<wxString>(beforeLast(activeCfgFilePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)), //default folder wxString(), //default file name wxString(L"FreeFileSync (*.ffs_gui; *.ffs_batch)|*.ffs_gui;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", wxFD_OPEN | wxFD_MULTIPLE); @@ -3188,6 +3262,8 @@ void MainDialog::onCfgGridSelection(GridSelectEvent& event) //- if user cancelled saving old config //- there's an error loading new config cfggrid::addAndSelect(*m_gridCfgHistory, activeConfigFiles_, false /*scrollToSelection*/); + + event.Skip(); } @@ -3201,7 +3277,7 @@ void MainDialog::onCfgGridDoubleClick(GridClickEvent& event) } -void MainDialog::OnConfigNew(wxCommandEvent& event) +void MainDialog::onConfigNew(wxCommandEvent& event) { loadConfiguration({}); } @@ -3304,22 +3380,23 @@ void MainDialog::renameSelectedCfgHistoryItem() if (!loadConfiguration({ cfgPathOld })) return; //error/cancel - const Zstring fileName = afterLast(cfgPathOld, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); - /**/ Zstring folderPathPf = beforeLast(cfgPathOld, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + const Zstring fileName = afterLast(cfgPathOld, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + /**/ Zstring folderPathPf = beforeLast(cfgPathOld, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); if (!folderPathPf.empty()) folderPathPf += FILE_NAME_SEPARATOR; - const Zstring cfgNameOld = beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_ALL); - /**/ Zstring cfgExtPf = afterLast(fileName, Zstr('.'), IF_MISSING_RETURN_NONE); - if (!cfgExtPf.empty()) - cfgExtPf = Zstr('.') + cfgExtPf; + const Zstring cfgNameOld = beforeLast(fileName, Zstr('.'), IfNotFoundReturn::all); + /**/ Zstring cfgDotExt = afterLast(fileName, Zstr('.'), IfNotFoundReturn::none); + if (!cfgDotExt.empty()) + cfgDotExt = Zstr('.') + cfgDotExt; for (;;) { wxTextEntryDialog cfgRenameDlg(this, _("New name:"), _("Rename Configuration"), utfTo<wxString>(cfgNameOld)); wxTextValidator inputValidator(wxFILTER_EXCLUDE_CHAR_LIST); - inputValidator.SetCharExcludes(LR"(/\":*?<>|)"); //forbidden chars for file names (at least on Windows) + inputValidator.SetCharExcludes(LR"(<>:"/\|?*)"); //forbidden chars for file names (at least on Windows) + //https://docs.microsoft.com/de-de/windows/win32/fileio/naming-a-file#naming-conventions cfgRenameDlg.SetTextValidator(inputValidator); if (cfgRenameDlg.ShowModal() != wxID_OK) @@ -3329,7 +3406,7 @@ void MainDialog::renameSelectedCfgHistoryItem() if (cfgNameNew == cfgNameOld) return; - const Zstring cfgPathNew = folderPathPf + cfgNameNew + cfgExtPf; + const Zstring cfgPathNew = folderPathPf + cfgNameNew + cfgDotExt; try { if (cfgNameNew.empty()) //better error message + check than wxFILTER_EMPTY, e.g. trimCpy()! @@ -3359,6 +3436,15 @@ void MainDialog::onCfgGridKeyEvent(wxKeyEvent& event) const int keyCode = event.GetKeyCode(); switch (keyCode) { + case WXK_RETURN: + case WXK_NUMPAD_ENTER: + if (!activeConfigFiles_.empty()) + { + wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED); + m_buttonCompare->Command(dummy); //simulate click + } + break; + case WXK_DELETE: case WXK_NUMPAD_DELETE: deleteSelectedCfgHistoryItems(); @@ -3373,7 +3459,7 @@ void MainDialog::onCfgGridKeyEvent(wxKeyEvent& event) } -void MainDialog::onCfgGridContext(GridClickEvent& event) +void MainDialog::onCfgGridContext(GridContextMenuEvent& event) { ContextMenu menu; const std::vector<size_t> selectedRows = m_gridCfgHistory->getSelectedRows(); @@ -3404,9 +3490,9 @@ void MainDialog::onCfgGridContext(GridClickEvent& event) wxBitmap bmpSquare(this->GetCharHeight(), this->GetCharHeight()); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes { wxMemoryDC dc(bmpSquare); - dc.SetBrush(!col.Ok() ? wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW) : col); - dc.SetPen(wxPen(wxColor(0xdd, 0xdd, 0xdd), fastFromDIP(1))); //light grey - dc.DrawRectangle(wxPoint(), bmpSquare.GetSize()); + const wxColor borderCol(0xdd, 0xdd, 0xdd); //light grey + const wxColor fillCol = col.Ok() ? col : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + drawFilledRectangle(dc, wxRect(bmpSquare.GetSize()), fastFromDIP(1), borderCol, fillCol); } submenu.addItem(name, applyBackColor, bmpSquare.ConvertToImage(), !selectedRows.empty()); }; @@ -3519,7 +3605,7 @@ void MainDialog::onCheckRows(CheckRowsEvent& event) if (!selectedRows.empty()) { std::vector<FileSystemObject*> objects = filegrid::getDataView(*m_gridMainC).getAllFileRef(selectedRows); - setFilterManually(objects, event.setActive_); + setIncludedManually(objects, event.setActive_); } } @@ -3742,7 +3828,7 @@ void MainDialog::showConfigDialog(SyncConfigPanel panelToShow, int localPairInde } -void MainDialog::OnGlobalFilterContext(wxEvent& event) +void MainDialog::onGlobalFilterContext(wxEvent& event) { auto clearFilter = [&] { @@ -3771,13 +3857,13 @@ void MainDialog::OnGlobalFilterContext(wxEvent& event) } -void MainDialog::OnToggleViewType(wxCommandEvent& event) +void MainDialog::onToggleViewType(wxCommandEvent& event) { setGridViewType(m_bpButtonViewTypeSyncAction->isActive() ? GridViewType::category : GridViewType::action); } -void MainDialog::OnToggleViewButton(wxCommandEvent& event) +void MainDialog::onToggleViewButton(wxCommandEvent& event) { if (auto button = dynamic_cast<ToggleButton*>(event.GetEventObject())) { @@ -3814,7 +3900,7 @@ void MainDialog::setViewFilterDefault() } -void MainDialog::OnViewTypeContext(wxEvent& event) +void MainDialog::onViewTypeContext(wxEvent& event) { auto saveButtonDefault = [](const ToggleButton& tb, bool& defaultValue) { @@ -3863,7 +3949,7 @@ void MainDialog::updateGlobalFilterButton() } -void MainDialog::OnCompare(wxCommandEvent& event) +void MainDialog::onCompare(wxCommandEvent& event) { //wxBusyCursor dummy; -> redundant: progress already shown in progress dialog! @@ -3919,15 +4005,15 @@ void MainDialog::OnCompare(wxCommandEvent& event) return updateGui(); //refresh grid in ANY case! (also on abort) - filegrid::getDataView(*m_gridMainC ).setData(folderCmp_); //update view on data - treegrid::getDataView(*m_gridOverview).setData(folderCmp_); // + filegrid::setData(*m_gridMainC, folderCmp_); //update view on data + treegrid::setData(*m_gridOverview, folderCmp_); // updateGui(); - m_gridMainL->clearSelection(GridEventPolicy::ALLOW); - m_gridMainC->clearSelection(GridEventPolicy::ALLOW); - m_gridMainR->clearSelection(GridEventPolicy::ALLOW); + m_gridMainL->clearSelection(GridEventPolicy::allow); + m_gridMainC->clearSelection(GridEventPolicy::allow); + m_gridMainR->clearSelection(GridEventPolicy::allow); - m_gridOverview->clearSelection(GridEventPolicy::ALLOW); + m_gridOverview->clearSelection(GridEventPolicy::allow); //play (optional) sound notification if (!globalCfg_.soundFileCompareFinished.empty()) @@ -4024,8 +4110,8 @@ void MainDialog::clearGrid(ptrdiff_t pos) folderCmp_.erase(folderCmp_.begin() + pos); } - filegrid::getDataView(*m_gridMainC ).setData(folderCmp_); - treegrid::getDataView(*m_gridOverview).setData(folderCmp_); + filegrid::setData(*m_gridMainC, folderCmp_); + treegrid::setData(*m_gridOverview, folderCmp_); updateGui(); } @@ -4086,7 +4172,7 @@ void MainDialog::applyCompareConfig(bool setDefaultViewType) } -void MainDialog::OnStartSync(wxCommandEvent& event) +void MainDialog::onStartSync(wxCommandEvent& event) { if (folderCmp_.empty()) { @@ -4204,7 +4290,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) case FinalRequest::none: break; case FinalRequest::exit: - Destroy(); //don't use Close(): we don't want to show the prompt to save current config in OnClose() + Destroy(); //don't use Close(): we don't want to show the prompt to save current config in onClose() break; case FinalRequest::shutdown: //run *after* last sync stats were updated and saved! https://freefilesync.org/forum/viewtopic.php?t=5761 try @@ -4275,7 +4361,7 @@ void MainDialog::startSyncForSelecction(const std::vector<FileSystemObject*>& se } if (basePairsSelect.empty()) - return; //harmonize with onMainGridContextRim(): this function should be a no-op iff context menu option is disabled! + return; //harmonize with onGridContextRim(): this function should be a no-op iff context menu option is disabled! FocusPreserver fp; { @@ -4456,7 +4542,7 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s } -void MainDialog::OnShowLog(wxCommandEvent& event) +void MainDialog::onShowLog(wxCommandEvent& event) { const bool show = !auiMgr_.GetPane(m_panelLog).IsShown(); showLogPanel(show); @@ -4524,18 +4610,6 @@ void MainDialog::showLogPanel(bool show) } -void MainDialog::onGridDoubleClickL(GridClickEvent& event) -{ - onGridDoubleClickRim(event.row_, true /*leftSide*/); -} - - -void MainDialog::onGridDoubleClickR(GridClickEvent& event) -{ - onGridDoubleClickRim(event.row_, false /*leftSide*/); -} - - void MainDialog::onGridDoubleClickRim(size_t row, bool leftSide) { if (!globalCfg_.gui.externalApps.empty()) @@ -4557,16 +4631,16 @@ void MainDialog::onGridLabelLeftClickC(GridLabelClickEvent& event) { bool sortAscending = getDefaultSortDirection(colType); - if (auto sortInfo = filegrid::getDataView(*m_gridMainC).getSortInfo()) + if (auto sortInfo = filegrid::getDataView(*m_gridMainC).getSortConfig()) if (const ColumnTypeCenter* sortType = std::get_if<ColumnTypeCenter>(&sortInfo->sortCol)) if (*sortType == colType) sortAscending = !sortInfo->ascending; filegrid::getDataView(*m_gridMainC).sortView(colType, sortAscending); - m_gridMainL->clearSelection(GridEventPolicy::ALLOW); - m_gridMainC->clearSelection(GridEventPolicy::ALLOW); - m_gridMainR->clearSelection(GridEventPolicy::ALLOW); + m_gridMainL->clearSelection(GridEventPolicy::allow); + m_gridMainC->clearSelection(GridEventPolicy::allow); + m_gridMainR->clearSelection(GridEventPolicy::allow); updateGui(); //refresh gridDataView } @@ -4577,7 +4651,7 @@ void MainDialog::onGridLabelLeftClick(bool onLeft, ColumnTypeRim colType) { bool sortAscending = getDefaultSortDirection(colType); - if (auto sortInfo = filegrid::getDataView(*m_gridMainC).getSortInfo()) + if (auto sortInfo = filegrid::getDataView(*m_gridMainC).getSortConfig()) if (const ColumnTypeRim* sortType = std::get_if<ColumnTypeRim>(&sortInfo->sortCol)) if (*sortType == colType && sortInfo->onLeft == onLeft) sortAscending = !sortInfo->ascending; @@ -4586,13 +4660,14 @@ void MainDialog::onGridLabelLeftClick(bool onLeft, ColumnTypeRim colType) filegrid::getDataView(*m_gridMainC).sortView(colType, itemPathFormat, onLeft, sortAscending); - m_gridMainL->clearSelection(GridEventPolicy::ALLOW); - m_gridMainC->clearSelection(GridEventPolicy::ALLOW); - m_gridMainR->clearSelection(GridEventPolicy::ALLOW); + m_gridMainL->clearSelection(GridEventPolicy::allow); + m_gridMainC->clearSelection(GridEventPolicy::allow); + m_gridMainR->clearSelection(GridEventPolicy::allow); updateGui(); //refresh gridDataView } + void MainDialog::onGridLabelLeftClickL(GridLabelClickEvent& event) { onGridLabelLeftClick(true, static_cast<ColumnTypeRim>(event.colType_)); @@ -4605,7 +4680,7 @@ void MainDialog::onGridLabelLeftClickR(GridLabelClickEvent& event) } -void MainDialog::OnSwapSides(wxCommandEvent& event) +void MainDialog::onSwapSides(wxCommandEvent& event) { //swap directory names: LocalPairConfig lpc1st = firstFolderPair_->getValues(); @@ -4781,11 +4856,11 @@ void MainDialog::updateGridViewData() m_bpButtonShowUpdateRight->Hide(); m_bpButtonShowDoNothing ->Hide(); - updateFilterButton(*m_bpButtonShowLeftOnly, "cat_left_only", viewStats.leftOnly); - updateFilterButton(*m_bpButtonShowRightOnly, "cat_right_only", viewStats.rightOnly); - updateFilterButton(*m_bpButtonShowLeftNewer, "cat_left_newer", viewStats.leftNewer); + updateFilterButton(*m_bpButtonShowLeftOnly, "cat_left_only", viewStats.leftOnly); + updateFilterButton(*m_bpButtonShowRightOnly, "cat_right_only", viewStats.rightOnly); + updateFilterButton(*m_bpButtonShowLeftNewer, "cat_left_newer", viewStats.leftNewer); updateFilterButton(*m_bpButtonShowRightNewer, "cat_right_newer", viewStats.rightNewer); - updateFilterButton(*m_bpButtonShowDifferent, "cat_different", viewStats.different); + updateFilterButton(*m_bpButtonShowDifferent, "cat_different", viewStats.different); } const bool anyViewButtonShown = m_bpButtonShowExcluded ->IsShown() || @@ -4922,30 +4997,24 @@ void MainDialog::applySyncDirections() } -void MainDialog::OnMenuFindItem(wxCommandEvent& event) //CTRL + F -{ - showFindPanel(); -} - - -void MainDialog::OnSearchGridEnter(wxCommandEvent& event) +void MainDialog::onSearchGridEnter(wxCommandEvent& event) { startFindNext(true /*searchAscending*/); } -void MainDialog::OnHideSearchPanel(wxCommandEvent& event) +void MainDialog::onHideSearchPanel(wxCommandEvent& event) { hideFindPanel(); } -void MainDialog::OnSearchPanelKeyPressed(wxKeyEvent& event) +void MainDialog::onSearchPanelKeyPressed(wxKeyEvent& event) { switch (event.GetKeyCode()) { case WXK_RETURN: - case WXK_NUMPAD_ENTER: //catches ENTER keys while focus is on *any* part of m_panelSearch! Seems to obsolete OnSearchGridEnter()! + case WXK_NUMPAD_ENTER: //catches ENTER keys while focus is on *any* part of m_panelSearch! Seems to obsolete onSearchGridEnter()! startFindNext(true /*searchAscending*/); return; case WXK_ESCAPE: @@ -5008,7 +5077,7 @@ void MainDialog::startFindNext(bool searchAscending) //F3 or ENTER in m_textCtrl assert(result.second >= 0); filegrid::setScrollMaster(*grid); - grid->setGridCursor(result.second, GridEventPolicy::ALLOW); + grid->setGridCursor(result.second, GridEventPolicy::allow); focusIdAfterSearch_ = grid->getMainWin().GetId(); @@ -5026,7 +5095,7 @@ void MainDialog::startFindNext(bool searchAscending) //F3 or ENTER in m_textCtrl } -void MainDialog::OnTopFolderPairAdd(wxCommandEvent& event) +void MainDialog::onTopFolderPairAdd(wxCommandEvent& event) { insertAddFolderPair({ LocalPairConfig() }, 0); @@ -5034,7 +5103,7 @@ void MainDialog::OnTopFolderPairAdd(wxCommandEvent& event) } -void MainDialog::OnTopFolderPairRemove(wxCommandEvent& event) +void MainDialog::onTopFolderPairRemove(wxCommandEvent& event) { assert(!additionalFolderPairs_.empty()); @@ -5046,7 +5115,7 @@ void MainDialog::OnTopFolderPairRemove(wxCommandEvent& event) } -void MainDialog::OnLocalCompCfg(wxCommandEvent& event) +void MainDialog::onLocalCompCfg(wxCommandEvent& event) { const wxObject* const eventObj = event.GetEventObject(); //find folder pair originating the event for (auto it = additionalFolderPairs_.begin(); it != additionalFolderPairs_.end(); ++it) @@ -5058,7 +5127,7 @@ void MainDialog::OnLocalCompCfg(wxCommandEvent& event) } -void MainDialog::OnLocalSyncCfg(wxCommandEvent& event) +void MainDialog::onLocalSyncCfg(wxCommandEvent& event) { const wxObject* const eventObj = event.GetEventObject(); //find folder pair originating the event for (auto it = additionalFolderPairs_.begin(); it != additionalFolderPairs_.end(); ++it) @@ -5070,7 +5139,7 @@ void MainDialog::OnLocalSyncCfg(wxCommandEvent& event) } -void MainDialog::OnLocalFilterCfg(wxCommandEvent& event) +void MainDialog::onLocalFilterCfg(wxCommandEvent& event) { const wxObject* const eventObj = event.GetEventObject(); //find folder pair originating the event for (auto it = additionalFolderPairs_.begin(); it != additionalFolderPairs_.end(); ++it) @@ -5082,7 +5151,7 @@ void MainDialog::OnLocalFilterCfg(wxCommandEvent& event) } -void MainDialog::OnRemoveFolderPair(wxCommandEvent& event) +void MainDialog::onRemoveFolderPair(wxCommandEvent& event) { const wxObject* const eventObj = event.GetEventObject(); //find folder pair originating the event @@ -5095,7 +5164,7 @@ void MainDialog::OnRemoveFolderPair(wxCommandEvent& event) } -void MainDialog::OnShowFolderPairOptions(wxEvent& event) +void MainDialog::onShowFolderPairOptions(wxEvent& event) { const wxObject* const eventObj = event.GetEventObject(); //find folder pair originating the event @@ -5155,27 +5224,27 @@ void MainDialog::onAddFolderPairKeyEvent(wxKeyEvent& event) { case WXK_PAGEUP: //Alt + Page Up case WXK_NUMPAD_PAGEUP: - { - const ptrdiff_t pos = getAddFolderPairPos(); - if (pos >= 0) { - moveAddFolderPairUp(pos); - (pos == 0 ? m_folderPathLeft : additionalFolderPairs_[pos - 1]->m_folderPathLeft)->SetFocus(); + const ptrdiff_t pos = getAddFolderPairPos(); + if (pos >= 0) + { + moveAddFolderPairUp(pos); + (pos == 0 ? m_folderPathLeft : additionalFolderPairs_[pos - 1]->m_folderPathLeft)->SetFocus(); + } } - } - return; + return; case WXK_PAGEDOWN: //Alt + Page Down case WXK_NUMPAD_PAGEDOWN: - { - const ptrdiff_t pos = getAddFolderPairPos(); - if (0 <= pos && pos + 1 < makeSigned(additionalFolderPairs_.size())) { - moveAddFolderPairUp(pos + 1); - additionalFolderPairs_[pos + 1]->m_folderPathLeft->SetFocus(); + const ptrdiff_t pos = getAddFolderPairPos(); + if (0 <= pos && pos + 1 < makeSigned(additionalFolderPairs_.size())) + { + moveAddFolderPairUp(pos + 1); + additionalFolderPairs_[pos + 1]->m_folderPathLeft->SetFocus(); + } } - } - return; + return; } event.Skip(); @@ -5285,14 +5354,15 @@ void MainDialog::insertAddFolderPair(const std::vector<LocalPairConfig>& newPair newPair->m_panelLeft->SetMinSize({width, -1}); //register events - newPair->m_bpButtonFolderPairOptions->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxEventHandler(MainDialog::OnShowFolderPairOptions), nullptr, this); - newPair->m_bpButtonFolderPairOptions->Connect(wxEVT_RIGHT_DOWN, wxEventHandler(MainDialog::OnShowFolderPairOptions), nullptr, this); - newPair->m_bpButtonRemovePair ->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolderPair ), nullptr, this); - static_cast<FolderPairPanelGenerated*>(newPair)->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::onAddFolderPairKeyEvent), nullptr, this); + newPair->m_bpButtonFolderPairOptions->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onShowFolderPairOptions(event); }); + newPair->m_bpButtonFolderPairOptions->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onShowFolderPairOptions(event); }); + newPair->m_bpButtonRemovePair ->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onRemoveFolderPair (event); }); + + static_cast<FolderPairPanelGenerated*>(newPair)->Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onAddFolderPairKeyEvent(event); }); - newPair->m_bpButtonLocalCompCfg->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnLocalCompCfg ), nullptr, this); - newPair->m_bpButtonLocalSyncCfg->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnLocalSyncCfg ), nullptr, this); - newPair->m_bpButtonLocalFilter ->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnLocalFilterCfg), nullptr, this); + newPair->m_bpButtonLocalCompCfg->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onLocalCompCfg (event); }); + newPair->m_bpButtonLocalSyncCfg->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onLocalSyncCfg (event); }); + newPair->m_bpButtonLocalFilter ->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onLocalFilterCfg(event); }); //important: make sure panel has proper default height! newPair->GetSizer()->SetSizeHints(newPair); //~=Fit() + SetMinSize() @@ -5333,8 +5403,8 @@ void MainDialog::moveAddFolderPairUp(size_t pos) if (!folderCmp_.empty()) std::swap(folderCmp_[pos], folderCmp_[pos + 1]); //invariant: folderCmp is empty or matches number of all folder pairs - filegrid::getDataView(*m_gridMainC ).setData(folderCmp_); - treegrid::getDataView(*m_gridOverview).setData(folderCmp_); + filegrid::setData(*m_gridMainC, folderCmp_); + treegrid::setData(*m_gridOverview, folderCmp_); updateGui(); } } @@ -5380,14 +5450,13 @@ void MainDialog::setAddFolderPairs(const std::vector<LocalPairConfig>& newPairs) //######################################################################################################## -//menu events -void MainDialog::OnMenuOptions(wxCommandEvent& event) +void MainDialog::onMenuOptions(wxCommandEvent& event) { showOptionsDlg(this, globalCfg_); } -void MainDialog::OnMenuExportFileList(wxCommandEvent& event) +void MainDialog::onMenuExportFileList(wxCommandEvent& event) { //get a filepath wxFileDialog filePicker(this, //creating this on freestore leads to memleak! @@ -5470,15 +5539,14 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) try { - //write file - FileOutput fileOut(FileOutput::ACC_OVERWRITE, filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError + TempFileOutput fileOut(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError fileOut.write(&header[0], header.size()); //throw FileError, (X) //main grid: write rows one after the other instead of creating one big string: memory allocation might fail; think 1 million rows! /* - performance test case "export 600.000 rows" to CSV: - aproach 1. assemble single temporary string, then write file: 4.6s - aproach 2. write to buffered file output directly for each row: 6.4s + performance test case "export 600.000 rows" to CSV: + aproach 1. assemble single temporary string, then write file: 4.6s + aproach 2. write to buffered file output directly for each row: 6.4s */ std::string buffer; const size_t rowCount = m_gridMainL->getRowCount(); @@ -5506,7 +5574,7 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) fileOut.write(&buffer[0], buffer.size()); //throw FileError, (X) buffer.clear(); } - fileOut.finalize(); //throw FileError, (X) + fileOut.commit(); //throw FileError, (X) flashStatusInformation(_("File list exported")); } @@ -5518,19 +5586,19 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) } -void MainDialog::OnMenuCheckVersion(wxCommandEvent& event) +void MainDialog::onMenuCheckVersion(wxCommandEvent& event) { checkForUpdateNow(*this, globalCfg_.gui.lastOnlineVersion); } -void MainDialog::OnMenuUpdateAvailable(wxCommandEvent& event) +void MainDialog::onMenuUpdateAvailable(wxCommandEvent& event) { checkForUpdateNow(*this, globalCfg_.gui.lastOnlineVersion); //show changelog + handle Donation Edition auto-updater (including expiration) } -void MainDialog::OnMenuCheckVersionAutomatically(wxCommandEvent& event) +void MainDialog::onMenuCheckVersionAutomatically(wxCommandEvent& event) { if (updateCheckActive(globalCfg_.gui.lastUpdateCheck)) disableUpdateCheck(globalCfg_.gui.lastUpdateCheck); @@ -5549,10 +5617,11 @@ void MainDialog::OnMenuCheckVersionAutomatically(wxCommandEvent& event) } -void MainDialog::OnRegularUpdateCheck(wxIdleEvent& event) +void MainDialog::onRegularUpdateCheck(wxIdleEvent& event) { //execute just once per startup! - Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), nullptr, this); + [[maybe_unused]] bool ubOk = Unbind(wxEVT_IDLE, &MainDialog::onRegularUpdateCheck, this); + assert(ubOk); if (shouldRunAutomaticUpdateCheck(globalCfg_.gui.lastUpdateCheck)) { @@ -5570,10 +5639,11 @@ void MainDialog::OnRegularUpdateCheck(wxIdleEvent& event) } -void MainDialog::OnLayoutWindowAsync(wxIdleEvent& event) +void MainDialog::onLayoutWindowAsync(wxIdleEvent& event) { //execute just once per startup! - Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnLayoutWindowAsync), nullptr, this); + [[maybe_unused]] bool ubOk = Unbind(wxEVT_IDLE, &MainDialog::onLayoutWindowAsync, this); + assert(ubOk); //adjust folder pair distortion on startup @@ -5586,13 +5656,13 @@ void MainDialog::OnLayoutWindowAsync(wxIdleEvent& event) } -void MainDialog::OnMenuAbout(wxCommandEvent& event) +void MainDialog::onMenuAbout(wxCommandEvent& event) { showAboutDialog(this); } -void MainDialog::OnShowHelp(wxCommandEvent& event) +void MainDialog::onShowHelp(wxCommandEvent& event) { displayHelpEntry(L"freefilesync", this); } @@ -5608,7 +5678,7 @@ void MainDialog::switchProgramLanguage(wxLanguage langId) MainDialog::create(globalConfigFilePath_, &newGlobalCfg, getConfig(), activeConfigFiles_, false); //we don't use Close(): - //1. we don't want to show the prompt to save current config in OnClose() + //1. we don't want to show the prompt to save current config in onClose() //2. after getGlobalCfgBeforeExit() the old main dialog is invalid so we want to force deletion Destroy(); //alternative: Close(true /*force*/) } diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index d762f81f..ecd65c7a 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -28,9 +28,7 @@ namespace fff class FolderPairFirst; class FolderPairPanel; class CompareProgressPanel; -template <class GuiPanel> -class FolderPairCallback; -class PanelMoveWindow; +template <class GuiPanel> class FolderPairCallback; class MainDialog : public MainDialogGenerated @@ -100,7 +98,7 @@ private: //main method for putting gridDataView on UI: updates data respecting current view settings void updateGui(); //kitchen-sink update - void updateGuiDelayedIf(bool condition); // 400 ms delay + void updateGuiDelayedIf(bool condition); //400 ms delay void updateGridViewData(); // void updateStatistics(); // more fine-grained updaters @@ -110,8 +108,8 @@ private: std::vector<FileSystemObject*> getGridSelection(bool fromLeft = true, bool fromRight = true) const; std::vector<FileSystemObject*> getTreeSelection() const; - void setSyncDirManually(const std::vector<FileSystemObject*>& selection, SyncDirection direction); - void setFilterManually (const std::vector<FileSystemObject*>& selection, bool setIncluded); + void setSyncDirManually (const std::vector<FileSystemObject*>& selection, SyncDirection direction); + void setIncludedManually(const std::vector<FileSystemObject*>& selection, bool setIncluded); void copySelectionToClipboard(const std::vector<const zen::Grid*>& gridRefs); void copyToAlternateFolder(const std::vector<FileSystemObject*>& selectionLeft, @@ -131,37 +129,49 @@ private: void flashStatusInformation(const wxString& msg); //temporarily show different status (only valid for setStatusBarFileStats) //events - void onGridButtonEventL(wxKeyEvent& event) { onGridButtonEvent(event, *m_gridMainL, true); } - void onGridButtonEventC(wxKeyEvent& event) { onGridButtonEvent(event, *m_gridMainC, true); } - void onGridButtonEventR(wxKeyEvent& event) { onGridButtonEvent(event, *m_gridMainR, false); } + void onGridButtonEventL(wxKeyEvent& event) { onGridButtonEvent(event, *m_gridMainL, true /*leftSide*/); } + void onGridButtonEventC(wxKeyEvent& event) { onGridButtonEvent(event, *m_gridMainC, true /*leftSide*/); } + void onGridButtonEventR(wxKeyEvent& event) { onGridButtonEvent(event, *m_gridMainR, false /*leftSide*/); } void onGridButtonEvent (wxKeyEvent& event, zen::Grid& grid, bool leftSide); void onTreeButtonEvent (wxKeyEvent& event); - void OnContextSetLayout(wxMouseEvent& event); + void onContextSetLayout(wxMouseEvent& event); void onLocalKeyEvent (wxKeyEvent& event); - void OnCompSettingsContext (wxCommandEvent& event) override { OnCompSettingsContext(static_cast<wxEvent&>(event)); } - void OnCompSettingsContextMouse(wxMouseEvent& event) override { OnCompSettingsContext(static_cast<wxEvent&>(event)); } - void OnSyncSettingsContext (wxCommandEvent& event) override { OnSyncSettingsContext(static_cast<wxEvent&>(event)); } - void OnSyncSettingsContextMouse(wxMouseEvent& event) override { OnSyncSettingsContext(static_cast<wxEvent&>(event)); } - void OnGlobalFilterContext (wxCommandEvent& event) override { OnGlobalFilterContext(static_cast<wxEvent&>(event)); } - void OnGlobalFilterContextMouse(wxMouseEvent& event) override { OnGlobalFilterContext(static_cast<wxEvent&>(event)); } - void OnViewTypeContext (wxCommandEvent& event) override { OnViewTypeContext (static_cast<wxEvent&>(event)); } - void OnViewTypeContextMouse (wxMouseEvent& event) override { OnViewTypeContext (static_cast<wxEvent&>(event)); } + void onCompSettingsContext (wxCommandEvent& event) override { onCompSettingsContext(static_cast<wxEvent&>(event)); } + void onCompSettingsContextMouse(wxMouseEvent& event) override { onCompSettingsContext(static_cast<wxEvent&>(event)); } + void onSyncSettingsContext (wxCommandEvent& event) override { onSyncSettingsContext(static_cast<wxEvent&>(event)); } + void onSyncSettingsContextMouse(wxMouseEvent& event) override { onSyncSettingsContext(static_cast<wxEvent&>(event)); } + void onGlobalFilterContext (wxCommandEvent& event) override { onGlobalFilterContext(static_cast<wxEvent&>(event)); } + void onGlobalFilterContextMouse(wxMouseEvent& event) override { onGlobalFilterContext(static_cast<wxEvent&>(event)); } + void onViewTypeContext (wxCommandEvent& event) override { onViewTypeContext (static_cast<wxEvent&>(event)); } + void onViewTypeContextMouse (wxMouseEvent& event) override { onViewTypeContext (static_cast<wxEvent&>(event)); } - void OnCompSettingsContext(wxEvent& event); - void OnSyncSettingsContext(wxEvent& event); - void OnGlobalFilterContext(wxEvent& event); - void OnViewTypeContext (wxEvent& event); + void onCompSettingsContext(wxEvent& event); + void onSyncSettingsContext(wxEvent& event); + void onGlobalFilterContext(wxEvent& event); + void onViewTypeContext (wxEvent& event); void applyCompareConfig(bool setDefaultViewType); //context menu handler methods - void onMainGridContextL(zen::GridClickEvent& event); - void onMainGridContextR(zen::GridClickEvent& event); - void onMainGridContextRim(bool leftSide, zen::GridClickEvent& event); + void onGridSelectL(zen::GridSelectEvent& event) { onGridSelectRim(event, true /*leftSide*/); } + void onGridSelectR(zen::GridSelectEvent& event) { onGridSelectRim(event, false /*leftSide*/); } + void onGridSelectRim(zen::GridSelectEvent& event, bool leftSide); - void onTreeGridContext(zen::GridClickEvent& event); + void onGridContextL(zen::GridContextMenuEvent& event) { onGridContextRim(event, true /*leftSide*/); } + void onGridContextR(zen::GridContextMenuEvent& event) { onGridContextRim(event, false /*leftSide*/); } + void onGridContextRim(zen::GridContextMenuEvent& event, bool leftSide); + + void onGridGroupContextL(zen::GridClickEvent& event) { onGridGroupContextRim(event, true /*leftSide*/); } + void onGridGroupContextR(zen::GridClickEvent& event) { onGridGroupContextRim(event, false /*leftSide*/); } + void onGridGroupContextRim(zen::GridClickEvent& event, bool leftSide); + + void onGridContextRim(const std::vector<FileSystemObject*>& selection, + const std::vector<FileSystemObject*>& selectionLeft, + const std::vector<FileSystemObject*>& selectionRight, const wxPoint& mousePos, bool leftSide); + + void onTreeGridContext(zen::GridContextMenuEvent& event); void onTreeGridSelection(zen::GridSelectEvent& event); @@ -173,8 +183,8 @@ private: void onCheckRows (CheckRowsEvent& event); void onSetSyncDirection(SyncDirectionEvent& event); - void onGridDoubleClickL(zen::GridClickEvent& event); - void onGridDoubleClickR(zen::GridClickEvent& event); + void onGridDoubleClickL(zen::GridClickEvent& event) { onGridDoubleClickRim(event.row_, true /*leftSide*/); } + void onGridDoubleClickR(zen::GridClickEvent& event) { onGridDoubleClickRim(event.row_, false /*leftSide*/); } void onGridDoubleClickRim(size_t row, bool leftSide); void onGridLabelLeftClickL(zen::GridLabelClickEvent& event); @@ -182,48 +192,48 @@ private: void onGridLabelLeftClickR(zen::GridLabelClickEvent& event); void onGridLabelLeftClick(bool onLeft, ColumnTypeRim colType); - void onGridLabelContextL(zen::GridLabelClickEvent& event); + void onGridLabelContextL(zen::GridLabelClickEvent& event) { onGridLabelContextRim(true /*leftSide*/); } void onGridLabelContextC(zen::GridLabelClickEvent& event); - void onGridLabelContextR(zen::GridLabelClickEvent& event); - void onGridLabelContextRim(zen::Grid& grid, ColumnTypeRim type, bool left); + void onGridLabelContextR(zen::GridLabelClickEvent& event) { onGridLabelContextRim(false /*leftSide*/); } + void onGridLabelContextRim(bool leftSide); - void OnToggleViewType (wxCommandEvent& event) override; - void OnToggleViewButton(wxCommandEvent& event) override; + void onToggleViewType (wxCommandEvent& event) override; + void onToggleViewButton(wxCommandEvent& event) override; - void OnConfigNew (wxCommandEvent& event) override; - void OnConfigSave (wxCommandEvent& event) override; - void OnConfigSaveAs (wxCommandEvent& event) override; - void OnSaveAsBatchJob (wxCommandEvent& event) override; - void OnConfigLoad (wxCommandEvent& event) override; + void onConfigNew (wxCommandEvent& event) override; + void onConfigSave (wxCommandEvent& event) override; + void onConfigSaveAs (wxCommandEvent& event) override { trySaveConfig(nullptr); } + void onSaveAsBatchJob (wxCommandEvent& event) override { trySaveBatchConfig(nullptr); } + void onConfigLoad (wxCommandEvent& event) override; void onCfgGridSelection (zen::GridSelectEvent& event); void onCfgGridDoubleClick(zen::GridClickEvent& event); void onCfgGridKeyEvent (wxKeyEvent& event); - void onCfgGridContext (zen::GridClickEvent& event); + void onCfgGridContext (zen::GridContextMenuEvent& event); void onCfgGridLabelContext (zen::GridLabelClickEvent& event); void onCfgGridLabelLeftClick(zen::GridLabelClickEvent& event); void deleteSelectedCfgHistoryItems(); void renameSelectedCfgHistoryItem(); - void OnRegularUpdateCheck (wxIdleEvent& event); - void OnLayoutWindowAsync (wxIdleEvent& event); + void onRegularUpdateCheck(wxIdleEvent& event); + void onLayoutWindowAsync (wxIdleEvent& event); - void OnResizeLeftFolderWidth(wxEvent& event); - void OnResizeTopButtonPanel (wxEvent& event); - void OnResizeConfigPanel (wxEvent& event); - void OnResizeViewPanel (wxEvent& event); - void OnShowLog (wxCommandEvent& event) override; - void OnCompare (wxCommandEvent& event) override; - void OnStartSync (wxCommandEvent& event) override; - void OnSwapSides (wxCommandEvent& event) override; - void OnClose (wxCloseEvent& event) override; + void onResizeLeftFolderWidth(wxEvent& event); + void onResizeTopButtonPanel (wxEvent& event); + void onResizeConfigPanel (wxEvent& event); + void onResizeViewPanel (wxEvent& event); + void onShowLog (wxCommandEvent& event) override; + void onCompare (wxCommandEvent& event) override; + void onStartSync (wxCommandEvent& event) override; + void onSwapSides (wxCommandEvent& event) override; + void onClose (wxCloseEvent& event) override; void startSyncForSelecction(const std::vector<FileSystemObject*>& selection); - void OnCmpSettings (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::COMPARISON, -1); } - void OnConfigureFilter(wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::FILTER, -1); } - void OnSyncSettings (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::SYNC, -1); } + void onCmpSettings (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::COMPARISON, -1); } + void onConfigureFilter(wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::FILTER, -1); } + void onSyncSettings (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::SYNC, -1); } void showConfigDialog(SyncConfigPanel panelToShow, int localPairIndexToShow); @@ -237,18 +247,18 @@ private: void filterItems(const std::vector<FileSystemObject*>& selection, bool include); void addFilterPhrase(const Zstring& phrase, bool include, bool requireNewLine); - void OnTopFolderPairAdd (wxCommandEvent& event) override; - void OnTopFolderPairRemove(wxCommandEvent& event) override; - void OnRemoveFolderPair (wxCommandEvent& event); - void OnShowFolderPairOptions(wxEvent& event); + void onTopFolderPairAdd (wxCommandEvent& event) override; + void onTopFolderPairRemove(wxCommandEvent& event) override; + void onRemoveFolderPair (wxCommandEvent& event); + void onShowFolderPairOptions(wxEvent& event); - void OnTopLocalCompCfg (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::COMPARISON, 0); } - void OnTopLocalSyncCfg (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::SYNC, 0); } - void OnTopLocalFilterCfg(wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::FILTER, 0); } + void onTopLocalCompCfg (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::COMPARISON, 0); } + void onTopLocalSyncCfg (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::SYNC, 0); } + void onTopLocalFilterCfg(wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::FILTER, 0); } - void OnLocalCompCfg (wxCommandEvent& event); - void OnLocalSyncCfg (wxCommandEvent& event); - void OnLocalFilterCfg(wxCommandEvent& event); + void onLocalCompCfg (wxCommandEvent& event); + void onLocalSyncCfg (wxCommandEvent& event); + void onLocalFilterCfg(wxCommandEvent& event); void onTopFolderPairKeyEvent(wxKeyEvent& event); void onAddFolderPairKeyEvent(wxKeyEvent& event); @@ -262,22 +272,22 @@ private: void resetLayout(); - void OnSearchGridEnter(wxCommandEvent& event) override; - void OnHideSearchPanel(wxCommandEvent& event) override; - void OnSearchPanelKeyPressed(wxKeyEvent& event); + void onSearchGridEnter(wxCommandEvent& event) override; + void onHideSearchPanel(wxCommandEvent& event) override; + void onSearchPanelKeyPressed(wxKeyEvent& event); //menu events void onOpenMenuTools(wxMenuEvent& event); - void OnMenuOptions (wxCommandEvent& event) override; - void OnMenuExportFileList (wxCommandEvent& event) override; - void OnMenuResetLayout (wxCommandEvent& event) override { resetLayout(); } - void OnMenuFindItem (wxCommandEvent& event) override; - void OnMenuCheckVersion (wxCommandEvent& event) override; - void OnMenuCheckVersionAutomatically(wxCommandEvent& event) override; - void OnMenuUpdateAvailable(wxCommandEvent& event); - void OnMenuAbout (wxCommandEvent& event) override; - void OnShowHelp (wxCommandEvent& event) override; - void OnMenuQuit (wxCommandEvent& event) override { Close(); } + void onMenuOptions (wxCommandEvent& event) override; + void onMenuExportFileList (wxCommandEvent& event) override; + void onMenuResetLayout (wxCommandEvent& event) override { resetLayout(); } + void onMenuFindItem (wxCommandEvent& event) override { showFindPanel(); } //CTRL + F + void onMenuCheckVersion (wxCommandEvent& event) override; + void onMenuCheckVersionAutomatically(wxCommandEvent& event) override; + void onMenuUpdateAvailable(wxCommandEvent& event); + void onMenuAbout (wxCommandEvent& event) override; + void onShowHelp (wxCommandEvent& event) override; + void onMenuQuit (wxCommandEvent& event) override { Close(); } void switchProgramLanguage(wxLanguage langId); diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp index 218c0463..4882d010 100644 --- a/FreeFileSync/Source/ui/progress_indicator.cpp +++ b/FreeFileSync/Source/ui/progress_indicator.cpp @@ -44,7 +44,7 @@ constexpr std::chrono::seconds SPEED_ESTIMATE_SAMPLE_SKIP(1); constexpr std::chrono::milliseconds SPEED_ESTIMATE_UPDATE_INTERVAL(500); constexpr std::chrono::seconds GRAPH_TOTAL_TIME_UPDATE_INTERVAL(2); -const size_t PROGRESS_GRAPH_SAMPLE_SIZE_MAX = 2500000; //sizeof(single node) worst case ~ 3 * 8 byte ptr + 16 byte key/value = 40 byte +const size_t PROGRESS_GRAPH_SAMPLE_SIZE_MAX = 2'500'000; //sizeof(single node) worst case ~ 3 * 8 byte ptr + 16 byte key/value = 40 byte inline wxColor getColorBytes() { return { 111, 255, 99 }; } //light green inline wxColor getColorItems() { return { 127, 147, 255 }; } //light blue @@ -95,8 +95,8 @@ private: std::vector<CurvePoint> getPoints(double minX, double maxX, const wxSize& areaSizePx) const override { - const double yHigh = drawTop_ ? 3 : 1; //draw partially out of vertical bounds to not render top/bottom borders of the bars - const double yLow = drawTop_ ? 1 : -1; // + const double yLow = drawTop_ ? 1 : -1; //draw partially out of vertical bounds to not render top/bottom borders of the bars + const double yHigh = drawTop_ ? 3 : 1; // return { @@ -151,7 +151,7 @@ public: bool timerIsRunning() const { return !stopWatch_.isPaused(); } private: - //void OnToggleIgnoreErrors(wxCommandEvent& event) override { updateStaticGui(); } + //void onToggleIgnoreErrors(wxCommandEvent& event) override { updateStaticGui(); } void updateStaticGui(); @@ -459,16 +459,16 @@ private: //------ add artifical last sample value ------- if (!samples_.empty() && samples_.rbegin()->first < lastSample_.first) if (lastSample_.first <= timeX) - return CurvePoint(std::chrono::duration<double>(lastSample_.first).count(), lastSample_.second); + return CurvePoint{std::chrono::duration<double>(lastSample_.first).count(), lastSample_.second}; //-------------------------------------------------- //find first key > x, then go one step back: => samples must be a std::map, NOT std::multimap!!! auto it = samples_.upper_bound(timeX); if (it == samples_.begin()) - return {}; + return std::nullopt; //=> samples not empty in this context --it; - return CurvePoint(std::chrono::duration<double>(it->first).count(), it->second); + return CurvePoint{std::chrono::duration<double>(it->first).count(), it->second}; } std::optional<CurvePoint> getGreaterEq(double x) const override @@ -478,13 +478,13 @@ private: //------ add artifical last sample value ------- if (!samples_.empty() && samples_.rbegin()->first < lastSample_.first) if (samples_.rbegin()->first < timeX && timeX <= lastSample_.first) - return CurvePoint(std::chrono::duration<double>(lastSample_.first).count(), lastSample_.second); + return CurvePoint{std::chrono::duration<double>(lastSample_.first).count(), lastSample_.second}; //-------------------------------------------------- auto it = samples_.lower_bound(timeX); if (it == samples_.end()) - return {}; - return CurvePoint(std::chrono::duration<double>(it->first).count(), it->second); + return std::nullopt; + return CurvePoint{std::chrono::duration<double>(it->first).count(), it->second}; } std::map <std::chrono::nanoseconds, double> samples_; //[!] don't use std::multimap, see getLessEq() @@ -682,13 +682,12 @@ public: private: void onLocalKeyEvent (wxKeyEvent& event); void onParentKeyEvent(wxKeyEvent& event); - void OnOkay (wxCommandEvent& event); - void OnPause (wxCommandEvent& event); - void OnCancel (wxCommandEvent& event); - void OnClose (wxCloseEvent& event); - void OnIconize(wxIconizeEvent& event); - void OnMinimizeToTray(wxCommandEvent& event) { minimizeToTray(); } - //void OnToggleIgnoreErrors(wxCommandEvent& event) { updateStaticGui(); } + void onPause (wxCommandEvent& event); + void onCancel (wxCommandEvent& event); + void onClose(wxCloseEvent& event); + void onIconize(wxIconizeEvent& event); + void onMinimizeToTray(wxCommandEvent& event) { minimizeToTray(); } + //void onToggleIgnoreErrors(wxCommandEvent& event) { updateStaticGui(); } void showSummary(SyncResult syncResult, const SharedRef<const ErrorLog>& log); @@ -713,7 +712,7 @@ private: //status variables const Statistics* syncStat_; //valid only while sync is running bool paused_ = false; - bool okayPressed_ = false; + bool closePressed_ = false; //remaining time PerfCheck perf_{ WINDOW_REMAINING_TIME, WINDOW_BYTES_PER_SEC }; @@ -781,20 +780,21 @@ syncStat_(&syncStat) bSizer170->Add(&pnl_, 1, wxEXPAND); this->SetSizer(bSizer170); //pass ownership - //lifetime of event sources is subset of this instance's lifetime => no wxEvtHandler::Disconnect() needed - this->Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler (SyncProgressDialogImpl<TopLevelDialog>::OnClose)); - this->Connect(wxEVT_ICONIZE, wxIconizeEventHandler(SyncProgressDialogImpl<TopLevelDialog>::OnIconize)); - this->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::onLocalKeyEvent), nullptr, this); - pnl_.m_buttonClose->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnOkay ), NULL, this); - pnl_.m_buttonPause->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnPause ), NULL, this); - pnl_.m_buttonStop ->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnCancel), NULL, this); - pnl_.m_bpButtonMinimizeToTray->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnMinimizeToTray), NULL, this); + //lifetime of event sources is subset of this instance's lifetime => no wxEvtHandler::Unbind() needed + this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { onClose(event); }); + this->Bind(wxEVT_ICONIZE, [this](wxIconizeEvent& event) { onIconize(event); }); + this->Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); + + pnl_.m_buttonClose ->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { closePressed_ = true; }); + pnl_.m_buttonPause ->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onPause(event); }); + pnl_.m_buttonStop ->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onCancel(event); }); + pnl_.m_bpButtonMinimizeToTray->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](wxCommandEvent& event) { onMinimizeToTray(event); }); if (parentFrame_) - parentFrame_->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::onParentKeyEvent), nullptr, this); + parentFrame_->Bind(wxEVT_CHAR_HOOK, &SyncProgressDialogImpl::onParentKeyEvent, this); - assert(pnl_.m_buttonClose->GetId() == wxID_OK); //we cannot use wxID_CLOSE else Esc key won't work: yet another wxWidgets bug?? + assert(pnl_.m_buttonClose->GetId() == wxID_OK); //we cannot use wxID_CLOSE else ESC key won't work: yet another wxWidgets bug?? setRelativeFontSize(*pnl_.m_staticTextPhase, 1.5); @@ -862,9 +862,7 @@ syncStat_(&syncStat) wxBitmap bmpSquare(this->GetCharHeight(), this->GetCharHeight()); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes { wxMemoryDC dc(bmpSquare); - dc.SetBrush(fillCol); - dc.SetPen(wxPen(borderCol, fastFromDIP(1))); - dc.DrawRectangle(wxPoint(), bmpSquare.GetSize()); + drawFilledRectangle(dc, wxRect(bmpSquare.GetSize()), fastFromDIP(1), borderCol, fillCol); } return bmpSquare; }; @@ -1449,15 +1447,15 @@ auto SyncProgressDialogImpl<TopLevelDialog>::destroy(bool autoClose, bool restor assert(syncStat_); //ATTENTION: dialog may live a little longer, so watch callbacks! - //e.g. wxGTK calls OnIconize after wxWindow::Close() (better not ask why) and before physical destruction! => indirectly calls updateStaticGui(), which reads syncStat_!!! + //e.g. wxGTK calls onIconize after wxWindow::Close() (better not ask why) and before physical destruction! => indirectly calls updateStaticGui(), which reads syncStat_!!! syncStat_ = nullptr; } else { showSummary(syncResult, log); - //wait until user closes the dialog by pressing "okay" - while (!okayPressed_) + //wait until user closes the dialog by pressing "Close" + while (!closePressed_) { wxTheApp->Yield(); //refresh GUI *first* before sleeping! (remove flicker) std::this_thread::sleep_for(UI_UPDATE_INTERVAL); @@ -1468,7 +1466,8 @@ auto SyncProgressDialogImpl<TopLevelDialog>::destroy(bool autoClose, bool restor if (parentFrame_) { - parentFrame_->Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::onParentKeyEvent), nullptr, this); + [[maybe_unused]] bool ubOk = parentFrame_->Unbind(wxEVT_CHAR_HOOK, &SyncProgressDialogImpl::onParentKeyEvent, this); + assert(ubOk); parentFrame_->SetTitle(parentTitleBackup_); //restore title text @@ -1480,7 +1479,7 @@ auto SyncProgressDialogImpl<TopLevelDialog>::destroy(bool autoClose, bool restor // parentFrame_->Iconize(false); } } - //else: don't call "TransformProcessType": consider "switch to main dialog" option during silent batch run + //else: don't call transformAppType(): consider "switch to main dialog" option during silent batch run //------------------------------------------------------------------------ const bool autoCloseDialog = getOptionAutoCloseDialog(); @@ -1492,20 +1491,13 @@ auto SyncProgressDialogImpl<TopLevelDialog>::destroy(bool autoClose, bool restor template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnOkay(wxCommandEvent& event) -{ - okayPressed_ = true; -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnClose(wxCloseEvent& event) +void SyncProgressDialogImpl<TopLevelDialog>::onClose(wxCloseEvent& event) { assert(event.CanVeto()); //this better be true: if "this" is parent of a modal error dialog, there is NO way (in hell) we allow destruction here!!! //note: close cannot be prevented on Windows during system shutdown! => already handled via Application::onQueryEndSession() event.Veto(); - okayPressed_ = true; //"temporary" auto-close: preempt closing results dialog + closePressed_ = true; //"temporary" auto-close: preempt closing results dialog if (syncStat_) { @@ -1519,7 +1511,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::OnClose(wxCloseEvent& event) template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnCancel(wxCommandEvent& event) +void SyncProgressDialogImpl<TopLevelDialog>::onCancel(wxCommandEvent& event) { userRequestAbort_(); @@ -1530,7 +1522,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::OnCancel(wxCommandEvent& event) template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnPause(wxCommandEvent& event) +void SyncProgressDialogImpl<TopLevelDialog>::onPause(wxCommandEvent& event) { paused_ = !paused_; updateStaticGui(); //update status + pause button @@ -1538,7 +1530,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::OnPause(wxCommandEvent& event) template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnIconize(wxIconizeEvent& event) +void SyncProgressDialogImpl<TopLevelDialog>::onIconize(wxIconizeEvent& event) { /* propagate progress dialog minimize/maximize to parent @@ -1636,9 +1628,7 @@ SyncProgressDialog* SyncProgressDialog::create(const std::function<void()>& user { auto dlg = new SyncProgressDialogImpl<wxFrame>(wxDEFAULT_FRAME_STYLE, userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, jobNames, syncStartTime, ignoreErrors, automaticRetryCount, postSyncAction); - - //only top level windows should have an icon: - dlg->SetIcon(getFfsIcon()); + dlg->SetIcon(getFfsIcon()); //only top level windows should have an icon return dlg; } } diff --git a/FreeFileSync/Source/ui/search_grid.cpp b/FreeFileSync/Source/ui/search_grid.cpp index 25154725..8c102d97 100644 --- a/FreeFileSync/Source/ui/search_grid.cpp +++ b/FreeFileSync/Source/ui/search_grid.cpp @@ -7,7 +7,6 @@ #include "search_grid.h" #include <zen/zstring.h> #include <zen/utf.h> -//#include <zen/perf.h> using namespace zen; using namespace fff; @@ -15,8 +14,11 @@ using namespace fff; namespace { -template <bool respectCase> inline -void normalizeForSeach(std::wstring& str) +template <bool respectCase> +void normalizeForSeach(std::wstring& str); + +template <> inline +void normalizeForSeach<true /*respectCase*/>(std::wstring& str) { for (wchar_t& c : str) if (!isAsciiChar(c)) @@ -29,9 +31,8 @@ void normalizeForSeach(std::wstring& str) c = L'/'; } - template <> inline -void normalizeForSeach<false>(std::wstring& str) +void normalizeForSeach<false /*respectCase*/>(std::wstring& str) { for (wchar_t& c : str) if (!isAsciiChar(c)) diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index 69a1da25..654c6681 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -59,13 +59,13 @@ public: AboutDlg(wxWindow* parent); private: - void OnOK (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_OKAY); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnDonate(wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/donate.php"); } - void OnOpenHomepage(wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/"); } - void OnOpenForum (wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/forum/"); } - void OnSendEmail (wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"mailto:zenju@" L"freefilesync.org"); } - void OnShowGpl (wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://www.gnu.org/licenses/gpl-3.0"); } + void onOkay (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_OKAY); } + void onClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onDonate(wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/donate.php"); } + void onOpenHomepage(wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/"); } + void onOpenForum (wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/forum/"); } + void onSendEmail (wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"mailto:zenju@" L"freefilesync.org"); } + void onShowGpl (wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://www.gnu.org/licenses/gpl-3.0"); } void onLocalKeyEvent(wxKeyEvent& event); }; @@ -91,6 +91,9 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) build += SPACED_BULLET; build += LTR_MARK; //fix Arabic +#ifndef ZEN_BUILD_ARCH +#error include <zen/build_info.h> +#endif #if ZEN_BUILD_ARCH == ZEN_ARCH_32BIT build += L"32 Bit"; #else @@ -150,8 +153,7 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) //------------------------------------ - //enable dialog-specific key events - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(AboutDlg::onLocalKeyEvent), nullptr, this); + 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!!! @@ -183,29 +185,29 @@ public: CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, size_t& parallelOps, const std::wstring* parallelOpsDisabledReason); private: - void OnOkay (wxCommandEvent& event) override; - void OnCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onOkay (wxCommandEvent& event) override; + void onCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnGdriveUserAdd (wxCommandEvent& event) override; - void OnGdriveUserRemove(wxCommandEvent& event) override; - void OnGdriveUserSelect(wxCommandEvent& event) override; + void onGdriveUserAdd (wxCommandEvent& event) override; + void onGdriveUserRemove(wxCommandEvent& event) override; + void onGdriveUserSelect(wxCommandEvent& event) override; void gdriveUpdateDrivesAndSelect(const std::string& accountEmail, const Zstring& sharedDriveName); - void OnDetectServerChannelLimit(wxCommandEvent& event) override; - void OnToggleShowPassword(wxCommandEvent& event) override; - void OnBrowseCloudFolder (wxCommandEvent& event) override; - void OnHelpFtpPerformance(wxHyperlinkEvent& event) override { displayHelpEntry(L"ftp-setup", this); } + void onDetectServerChannelLimit(wxCommandEvent& event) override; + void onToggleShowPassword(wxCommandEvent& event) override; + void onBrowseCloudFolder (wxCommandEvent& event) override; + void onHelpFtpPerformance(wxHyperlinkEvent& event) override { displayHelpEntry(L"ftp-setup", this); } - void OnConnectionGdrive(wxCommandEvent& event) override { type_ = CloudType::gdrive; updateGui(); } - void OnConnectionSftp (wxCommandEvent& event) override { type_ = CloudType::sftp; updateGui(); } - void OnConnectionFtp (wxCommandEvent& event) override { type_ = CloudType::ftp; updateGui(); } + void onConnectionGdrive(wxCommandEvent& event) override { type_ = CloudType::gdrive; updateGui(); } + void onConnectionSftp (wxCommandEvent& event) override { type_ = CloudType::sftp; updateGui(); } + void onConnectionFtp (wxCommandEvent& event) override { type_ = CloudType::ftp; updateGui(); } - void OnAuthPassword(wxCommandEvent& event) override { sftpAuthType_ = SftpAuthType::password; updateGui(); } - void OnAuthKeyfile (wxCommandEvent& event) override { sftpAuthType_ = SftpAuthType::keyFile; updateGui(); } - void OnAuthAgent (wxCommandEvent& event) override { sftpAuthType_ = SftpAuthType::agent; updateGui(); } + void onAuthPassword(wxCommandEvent& event) override { sftpAuthType_ = SftpAuthType::password; updateGui(); } + void onAuthKeyfile (wxCommandEvent& event) override { sftpAuthType_ = SftpAuthType::keyFile; updateGui(); } + void onAuthAgent (wxCommandEvent& event) override { sftpAuthType_ = SftpAuthType::agent; updateGui(); } - void OnSelectKeyfile(wxCommandEvent& event) override; + void onSelectKeyfile(wxCommandEvent& event) override; void updateGui(); @@ -275,7 +277,7 @@ CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, size_t m_spinCtrlTimeout ->SetMinSize({fastFromDIP(70), -1}); // setupFileDrop(*m_panelAuth); - m_panelAuth->Connect(EVENT_DROP_FILE, FileDropEventHandler(CloudSetupDlg::onKeyFileDropped), nullptr, this); + m_panelAuth->Bind(EVENT_DROP_FILE, [this](FileDropEvent& event) { onKeyFileDropped(event); }); m_staticTextConnectionsLabelSub->SetLabel(L'(' + _("Connections") + L')'); @@ -392,7 +394,7 @@ CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, size_t } -void CloudSetupDlg::OnGdriveUserAdd(wxCommandEvent& event) +void CloudSetupDlg::onGdriveUserAdd(wxCommandEvent& event) { guiQueue_.processAsync([]() -> std::variant<std::string /*email*/, FileError> { @@ -423,7 +425,7 @@ void CloudSetupDlg::OnGdriveUserAdd(wxCommandEvent& event) } -void CloudSetupDlg::OnGdriveUserRemove(wxCommandEvent& event) +void CloudSetupDlg::onGdriveUserRemove(wxCommandEvent& event) { const int selPos = m_listBoxGdriveUsers->GetSelection(); assert(selPos != wxNOT_FOUND); @@ -449,7 +451,7 @@ void CloudSetupDlg::OnGdriveUserRemove(wxCommandEvent& event) } -void CloudSetupDlg::OnGdriveUserSelect(wxCommandEvent& event) +void CloudSetupDlg::onGdriveUserSelect(wxCommandEvent& event) { const int selPos = m_listBoxGdriveUsers->GetSelection(); assert(selPos != wxNOT_FOUND); @@ -505,7 +507,7 @@ void CloudSetupDlg::gdriveUpdateDrivesAndSelect(const std::string& accountEmail, } -void CloudSetupDlg::OnDetectServerChannelLimit(wxCommandEvent& event) +void CloudSetupDlg::onDetectServerChannelLimit(wxCommandEvent& event) { assert (type_ == CloudType::sftp); try @@ -520,7 +522,7 @@ void CloudSetupDlg::OnDetectServerChannelLimit(wxCommandEvent& event) } -void CloudSetupDlg::OnToggleShowPassword(wxCommandEvent& event) +void CloudSetupDlg::onToggleShowPassword(wxCommandEvent& event) { assert(type_ != CloudType::gdrive); if (m_checkBoxShowPassword->GetValue()) @@ -546,10 +548,9 @@ bool CloudSetupDlg::acceptFileDrop(const std::vector<Zstring>& shellItemPaths) void CloudSetupDlg::onKeyFileDropped(FileDropEvent& event) { //assert (type_ == CloudType::SFTP); -> no big deal if false - const auto& itemPaths = event.getPaths(); - if (!itemPaths.empty()) + if (!event.itemPaths_.empty()) { - m_textCtrlKeyfilePath->ChangeValue(utfTo<wxString>(itemPaths[0])); + m_textCtrlKeyfilePath->ChangeValue(utfTo<wxString>(event.itemPaths_[0])); sftpAuthType_ = SftpAuthType::keyFile; updateGui(); @@ -557,12 +558,12 @@ void CloudSetupDlg::onKeyFileDropped(FileDropEvent& event) } -void CloudSetupDlg::OnSelectKeyfile(wxCommandEvent& event) +void CloudSetupDlg::onSelectKeyfile(wxCommandEvent& event) { assert (type_ == CloudType::sftp && sftpAuthType_ == SftpAuthType::keyFile); wxFileDialog filePicker(this, wxString(), //message - beforeLast(m_textCtrlKeyfilePath->GetValue(), utfTo<wxString>(FILE_NAME_SEPARATOR), IF_MISSING_RETURN_NONE), //default folder + beforeLast(m_textCtrlKeyfilePath->GetValue(), utfTo<wxString>(FILE_NAME_SEPARATOR), IfNotFoundReturn::none), //default folder wxString(), //default file name _("All files") + L" (*.*)|*" + L"|" + L"OpenSSL PEM (*.pem)|*.pem" + @@ -702,7 +703,7 @@ AbstractPath CloudSetupDlg::getFolderPath() const } -void CloudSetupDlg::OnBrowseCloudFolder(wxCommandEvent& event) +void CloudSetupDlg::onBrowseCloudFolder(wxCommandEvent& event) { AbstractPath folderPath = getFolderPath(); //noexcept @@ -726,7 +727,7 @@ void CloudSetupDlg::OnBrowseCloudFolder(wxCommandEvent& event) } -void CloudSetupDlg::OnOkay(wxCommandEvent& event) +void CloudSetupDlg::onOkay(wxCommandEvent& event) { //------- parameter validation (BEFORE writing output!) ------- if (type_ == CloudType::sftp && sftpAuthType_ == SftpAuthType::keyFile) @@ -768,9 +769,9 @@ public: bool& overwriteIfExists); private: - void OnOK (wxCommandEvent& event) override; - void OnCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onOkay (wxCommandEvent& event) override; + void onCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } void onLocalKeyEvent(wxKeyEvent& event); @@ -813,15 +814,13 @@ CopyToDialog::CopyToDialog(wxWindow* parent, m_textCtrlFileList->SetMinSize({fastFromDIP(500), fastFromDIP(200)}); - /* - There is a nasty bug on wxGTK under Ubuntu: If a multi-line wxTextCtrl contains so many lines that scrollbars are shown, - it re-enables all windows that are supposed to be disabled during the current modal loop! - This only affects Ubuntu/wxGTK! No such issue on Debian/wxGTK or Suse/wxGTK - => another Unity problem like the following? - http://trac.wxwidgets.org/ticket/14823 "Menu not disabled when showing modal dialogs in wxGTK under Unity" - */ + /* There is a nasty bug on wxGTK under Ubuntu: If a multi-line wxTextCtrl contains so many lines that scrollbars are shown, + it re-enables all windows that are supposed to be disabled during the current modal loop! + This only affects Ubuntu/wxGTK! No such issue on Debian/wxGTK or Suse/wxGTK + => another Unity problem like the following? + http://trac.wxwidgets.org/ticket/14823 "Menu not disabled when showing modal dialogs in wxGTK under Unity" */ - const auto [itemList, itemCount] = getSelectedItemsAsString(rowsOnLeft, rowsOnRight); + const auto& [itemList, itemCount] = getSelectedItemsAsString(rowsOnLeft, rowsOnRight); const wxString header = _P("Copy the following item to another folder?", "Copy the following %x items to another folder?", itemCount); @@ -836,8 +835,7 @@ CopyToDialog::CopyToDialog(wxWindow* parent, m_checkBoxOverwriteIfExists->SetValue(overwriteIfExists); //----------------- /set config -------------------------------- - //enable dialog-specific key events - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(CopyToDialog::onLocalKeyEvent), nullptr, this); + 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!!! @@ -853,7 +851,7 @@ void CopyToDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events witho } -void CopyToDialog::OnOK(wxCommandEvent& event) +void CopyToDialog::onOkay(wxCommandEvent& event) { //------- parameter validation (BEFORE writing output!) ------- if (trimCpy(targetFolder->getPath()).empty()) @@ -900,10 +898,10 @@ public: bool& useRecycleBin); private: - void OnUseRecycler(wxCommandEvent& event) override { updateGui(); } - void OnOK (wxCommandEvent& event) override; - void OnCancel (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onUseRecycler(wxCommandEvent& event) override { updateGui(); } + void onOkay (wxCommandEvent& event) override; + void onCancel (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } void onLocalKeyEvent(wxKeyEvent& event); @@ -937,8 +935,7 @@ DeleteDialog::DeleteDialog(wxWindow* parent, updateGui(); - //enable dialog-specific key events - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(DeleteDialog::onLocalKeyEvent), nullptr, this); + 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!!! @@ -951,7 +948,7 @@ DeleteDialog::DeleteDialog(wxWindow* parent, void DeleteDialog::updateGui() { - const auto [itemList, itemCount] = getSelectedItemsAsString(rowsToDeleteOnLeft_, rowsToDeleteOnRight_); + const auto& [itemList, itemCount] = getSelectedItemsAsString(rowsToDeleteOnLeft_, rowsToDeleteOnRight_); wxString header; if (m_checkBoxUseRecycler->GetValue()) { @@ -990,7 +987,7 @@ void DeleteDialog::onLocalKeyEvent(wxKeyEvent& event) } -void DeleteDialog::OnOK(wxCommandEvent& event) +void DeleteDialog::onOkay(wxCommandEvent& event) { //additional safety net, similar to Windows Explorer: time delta between DEL and ENTER must be at least 50ms to avoid accidental deletion! if (std::chrono::steady_clock::now() < dlgStartTime_ + std::chrono::milliseconds(50)) //considers chrono-wrap-around! @@ -1024,9 +1021,9 @@ public: const SyncStatistics& st, bool& dontShowAgain); private: - void OnStartSync(wxCommandEvent& event) override; - void OnCancel (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onStartSync(wxCommandEvent& event) override; + void onCancel (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } void onLocalKeyEvent(wxKeyEvent& event); @@ -1067,7 +1064,7 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent, m_checkBoxDontShowAgain->SetValue(dontShowAgain); - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncConfirmationDlg::onLocalKeyEvent), nullptr, this); + Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //update preview of item count and bytes to be transferred: auto setValue = [](wxStaticText& txtControl, bool isZeroValue, const wxString& valueAsString, wxStaticBitmap& bmpControl, const char* imageName) @@ -1108,7 +1105,7 @@ void SyncConfirmationDlg::onLocalKeyEvent(wxKeyEvent& event) } -void SyncConfirmationDlg::OnStartSync(wxCommandEvent& event) +void SyncConfirmationDlg::onStartSync(wxCommandEvent& event) { dontShowAgainOut_ = m_checkBoxDontShowAgain->GetValue(); EndModal(ReturnSmallDlg::BUTTON_OKAY); @@ -1139,25 +1136,25 @@ public: OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalCfg); private: - void OnOkay (wxCommandEvent& event) override; - void OnRestoreDialogs(wxCommandEvent& event) override; - void OnDefault (wxCommandEvent& event) override; - void OnCancel (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnAddRow (wxCommandEvent& event) override; - void OnRemoveRow (wxCommandEvent& event) override; - void OnHelpExternalApps(wxHyperlinkEvent& event) override { displayHelpEntry(L"external-applications", this); } - void OnShowLogFolder (wxHyperlinkEvent& event) override; - void OnToggleLogfilesLimit(wxCommandEvent& event) override { updateGui(); } - - void OnSelectSoundCompareDone(wxCommandEvent& event) override { selectSound(*m_textCtrlSoundPathCompareDone); } - void OnSelectSoundSyncDone (wxCommandEvent& event) override { selectSound(*m_textCtrlSoundPathSyncDone); } + void onOkay (wxCommandEvent& event) override; + void onRestoreDialogs(wxCommandEvent& event) override; + void onDefault (wxCommandEvent& event) override; + void onCancel (wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onAddRow (wxCommandEvent& event) override; + void onRemoveRow (wxCommandEvent& event) override; + void onHelpExternalApps (wxHyperlinkEvent& event) override { displayHelpEntry(L"external-applications", this); } + void onShowLogFolder (wxHyperlinkEvent& event) override; + void onToggleLogfilesLimit(wxCommandEvent& event) override { updateGui(); } + + void onSelectSoundCompareDone(wxCommandEvent& event) override { selectSound(*m_textCtrlSoundPathCompareDone); } + void onSelectSoundSyncDone (wxCommandEvent& event) override { selectSound(*m_textCtrlSoundPathSyncDone); } void selectSound(wxTextCtrl& txtCtrl); - void OnChangeSoundFilePath(wxCommandEvent& event) override { updateGui(); } + void onChangeSoundFilePath(wxCommandEvent& event) override { updateGui(); } - void OnPlayCompareDone(wxCommandEvent& event) override { playSoundWithDiagnostics(trimCpy(m_textCtrlSoundPathCompareDone->GetValue())); } - void OnPlaySyncDone (wxCommandEvent& event) override { playSoundWithDiagnostics(trimCpy(m_textCtrlSoundPathSyncDone ->GetValue())); } + void onPlayCompareDone(wxCommandEvent& event) override { playSoundWithDiagnostics(trimCpy(m_textCtrlSoundPathCompareDone->GetValue())); } + void onPlaySyncDone (wxCommandEvent& event) override { playSoundWithDiagnostics(trimCpy(m_textCtrlSoundPathSyncDone ->GetValue())); } void playSoundWithDiagnostics(const wxString& filePath); void onResize(wxSizeEvent& event); @@ -1261,7 +1258,7 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) : updateGui(); //automatically fit column width to match total grid width - Connect(wxEVT_SIZE, wxSizeEventHandler(OptionsDlg::onResize), nullptr, this); + Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { onResize(event); }); wxSizeEvent dummy; onResize(dummy); @@ -1302,7 +1299,7 @@ void OptionsDlg::updateGui() } -void OptionsDlg::OnRestoreDialogs(wxCommandEvent& event) +void OptionsDlg::onRestoreDialogs(wxCommandEvent& event) { confirmDlgs_ = defaultCfg_.confirmDlgs; warnDlgs_ = defaultCfg_.warnDlgs; @@ -1313,9 +1310,9 @@ void OptionsDlg::OnRestoreDialogs(wxCommandEvent& event) void OptionsDlg::selectSound(wxTextCtrl& txtCtrl) { - wxString defaultFolderPath = beforeLast(txtCtrl.GetValue(), utfTo<wxString>(FILE_NAME_SEPARATOR), IF_MISSING_RETURN_NONE); + wxString defaultFolderPath = beforeLast(txtCtrl.GetValue(), utfTo<wxString>(FILE_NAME_SEPARATOR), IfNotFoundReturn::none); if (defaultFolderPath.empty()) - defaultFolderPath = utfTo<wxString>(beforeLast(getResourceDirPf(), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)); + defaultFolderPath = utfTo<wxString>(beforeLast(getResourceDirPf(), FILE_NAME_SEPARATOR, IfNotFoundReturn::all)); wxFileDialog filePicker(this, wxString(), //message @@ -1338,7 +1335,7 @@ void OptionsDlg::playSoundWithDiagnostics(const wxString& filePath) //wxSOUND_ASYNC: NO failure indication (on Windows)! //wxSound::Play(..., wxSOUND_SYNC) can return false, but does not provide details! //=> check file access manually first: - /*std::string stream = */ loadBinContainer<std::string>(utfTo<Zstring>(filePath), nullptr /*notifyUnbufferedIO*/); //throw FileError + [[maybe_unused]] std::string stream = getFileContent(utfTo<Zstring>(filePath), nullptr /*notifyUnbufferedIO*/); //throw FileError /*bool success = */ wxSound::Play(filePath, wxSOUND_ASYNC); } @@ -1346,7 +1343,7 @@ void OptionsDlg::playSoundWithDiagnostics(const wxString& filePath) } -void OptionsDlg::OnDefault(wxCommandEvent& event) +void OptionsDlg::onDefault(wxCommandEvent& event) { m_checkBoxFailSafe ->SetValue(defaultCfg_.failSafeFileCopy); m_checkBoxCopyLocked ->SetValue(defaultCfg_.copyLockedFiles); @@ -1374,7 +1371,7 @@ void OptionsDlg::OnDefault(wxCommandEvent& event) } -void OptionsDlg::OnOkay(wxCommandEvent& event) +void OptionsDlg::onOkay(wxCommandEvent& event) { //write settings only when okay-button is pressed (except hidden dialog reset)! globalCfgOut_.failSafeFileCopy = m_checkBoxFailSafe->GetValue(); @@ -1441,7 +1438,7 @@ std::vector<ExternalApp> OptionsDlg::getExtApp() const } -void OptionsDlg::OnAddRow(wxCommandEvent& event) +void OptionsDlg::onAddRow(wxCommandEvent& event) { const int selectedRow = m_gridCustomCommand->GetGridCursorRow(); if (0 <= selectedRow && selectedRow < m_gridCustomCommand->GetNumberRows()) @@ -1456,7 +1453,7 @@ void OptionsDlg::OnAddRow(wxCommandEvent& event) } -void OptionsDlg::OnRemoveRow(wxCommandEvent& event) +void OptionsDlg::onRemoveRow(wxCommandEvent& event) { if (m_gridCustomCommand->GetNumberRows() > 0) { @@ -1474,7 +1471,7 @@ void OptionsDlg::OnRemoveRow(wxCommandEvent& event) } -void OptionsDlg::OnShowLogFolder(wxHyperlinkEvent& event) +void OptionsDlg::onShowLogFolder(wxHyperlinkEvent& event) { try { @@ -1500,16 +1497,16 @@ public: SelectTimespanDlg(wxWindow* parent, time_t& timeFrom, time_t& timeTo); private: - void OnOkay (wxCommandEvent& event) override; - void OnCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onOkay (wxCommandEvent& event) override; + void onCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnChangeSelectionFrom(wxCalendarEvent& event) override + void onChangeSelectionFrom(wxCalendarEvent& event) override { if (m_calendarFrom->GetDate() > m_calendarTo->GetDate()) m_calendarTo->SetDate(m_calendarFrom->GetDate()); } - void OnChangeSelectionTo(wxCalendarEvent& event) override + void onChangeSelectionTo(wxCalendarEvent& event) override { if (m_calendarFrom->GetDate() > m_calendarTo->GetDate()) m_calendarFrom->SetDate(m_calendarTo->GetDate()); @@ -1550,8 +1547,7 @@ SelectTimespanDlg::SelectTimespanDlg(wxWindow* parent, time_t& timeFrom, time_t& m_calendarFrom->SetDate(timeFromTmp); m_calendarTo ->SetDate(timeToTmp ); - //enable dialog-specific key events - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SelectTimespanDlg::onLocalKeyEvent), nullptr, this); + 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!!! @@ -1567,7 +1563,7 @@ void SelectTimespanDlg::onLocalKeyEvent(wxKeyEvent& event) //process key events } -void SelectTimespanDlg::OnOkay(wxCommandEvent& event) +void SelectTimespanDlg::onOkay(wxCommandEvent& event) { wxDateTime from = m_calendarFrom->GetDate(); wxDateTime to = m_calendarTo ->GetDate(); @@ -1601,9 +1597,9 @@ public: CfgHighlightDlg(wxWindow* parent, int& cfgHistSyncOverdueDays); private: - void OnOkay (wxCommandEvent& event) override; - void OnCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onOkay (wxCommandEvent& event) override; + void onCancel(wxCommandEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } + void onClose (wxCloseEvent& event) override { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } //work around defunct keyboard focus on macOS (or is it wxMac?) => not needed for this dialog! //void onLocalKeyEvent(wxKeyEvent& event); @@ -1634,7 +1630,7 @@ CfgHighlightDlg::CfgHighlightDlg(wxWindow* parent, int& cfgHistSyncOverdueDays) } -void CfgHighlightDlg::OnOkay(wxCommandEvent& event) +void CfgHighlightDlg::onOkay(wxCommandEvent& event) { cfgHistSyncOverdueDaysOut_ = m_spinCtrlOverdueDays->GetValue(); EndModal(ReturnSmallDlg::BUTTON_OKAY); @@ -1657,12 +1653,12 @@ public: ActivationDlg(wxWindow* parent, const std::wstring& lastErrorMsg, const std::wstring& manualActivationUrl, std::wstring& manualActivationKey); private: - void OnActivateOnline (wxCommandEvent& event) override; - void OnActivateOffline(wxCommandEvent& event) override; - void OnOfflineActivationEnter(wxCommandEvent& event) override { OnActivateOffline(event); } - void OnCopyUrl (wxCommandEvent& event) override; - void OnCancel(wxCommandEvent& event) override { EndModal(static_cast<int>(ReturnActivationDlg::CANCEL)); } - void OnClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ReturnActivationDlg::CANCEL)); } + void onActivateOnline (wxCommandEvent& event) override; + void onActivateOffline(wxCommandEvent& event) override; + void onOfflineActivationEnter(wxCommandEvent& event) override { onActivateOffline(event); } + void onCopyUrl (wxCommandEvent& event) override; + void onCancel(wxCommandEvent& event) override { EndModal(static_cast<int>(ReturnActivationDlg::CANCEL)); } + void onClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ReturnActivationDlg::CANCEL)); } std::wstring& manualActivationKeyOut_; //in/out parameter }; @@ -1677,7 +1673,7 @@ ActivationDlg::ActivationDlg(wxWindow* parent, { setStandardButtonLayout(*bSizerStdButtons, StdButtons().setCancel(m_buttonCancel)); - SetTitle(std::wstring(L"FreeFileSync ") + ffsVersion + L" [" + _("Donation Edition") + L']'); + SetTitle(L"FreeFileSync " + utfTo<std::wstring>(ffsVersion) + L" [" + _("Donation Edition") + L']'); //setMainInstructionFont(*m_staticTextMain); @@ -1696,7 +1692,7 @@ ActivationDlg::ActivationDlg(wxWindow* parent, } -void ActivationDlg::OnCopyUrl(wxCommandEvent& event) +void ActivationDlg::onCopyUrl(wxCommandEvent& event) { if (wxClipboard::Get()->Open()) { @@ -1709,14 +1705,14 @@ void ActivationDlg::OnCopyUrl(wxCommandEvent& event) } -void ActivationDlg::OnActivateOnline(wxCommandEvent& event) +void ActivationDlg::onActivateOnline(wxCommandEvent& event) { manualActivationKeyOut_ = m_textCtrlOfflineActivationKey->GetValue(); EndModal(static_cast<int>(ReturnActivationDlg::ACTIVATE_ONLINE)); } -void ActivationDlg::OnActivateOffline(wxCommandEvent& event) +void ActivationDlg::onActivateOffline(wxCommandEvent& event) { manualActivationKeyOut_ = m_textCtrlOfflineActivationKey->GetValue(); EndModal(static_cast<int>(ReturnActivationDlg::ACTIVATE_OFFLINE)); @@ -1753,7 +1749,7 @@ public: } private: - void OnCancel(wxCommandEvent& event) override { cancelled_ = true; } + void onCancel(wxCommandEvent& event) override { cancelled_ = true; } void updateGui() { @@ -1769,7 +1765,7 @@ private: int64_t bytesCurrent_ = 0; const int64_t bytesTotal_; Zstring filePath_; - const int GAUGE_FULL_RANGE = 1000000; + const int GAUGE_FULL_RANGE = 1000'000; }; diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index 01799c81..72251758 100644 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -99,40 +99,40 @@ public: std::vector<Zstring>& commandHistory, size_t commandHistoryMax); private: - void OnOkay (wxCommandEvent& event) override; - void OnCancel(wxCommandEvent& event) override { EndModal(ReturnSyncConfig::BUTTON_CANCEL); } - void OnClose (wxCloseEvent& event) override { EndModal(ReturnSyncConfig::BUTTON_CANCEL); } + void onOkay (wxCommandEvent& event) override; + void onCancel(wxCommandEvent& event) override { EndModal(ReturnSyncConfig::BUTTON_CANCEL); } + void onClose (wxCloseEvent& event) override { EndModal(ReturnSyncConfig::BUTTON_CANCEL); } void onLocalKeyEvent(wxKeyEvent& event); void onListBoxKeyEvent(wxKeyEvent& event) override; - void OnSelectFolderPair(wxCommandEvent& event) override; + void onSelectFolderPair(wxCommandEvent& event) override; enum class ConfigTypeImage { - COMPARISON = 0, //used as zero-based wxImageList index! - COMPARISON_GREY, - FILTER, - FILTER_GREY, - SYNC, - SYNC_GREY, + compare = 0, //used as zero-based wxImageList index! + compareGrey, + filter, + filterGrey, + sync, + syncGrey, }; //------------- comparison panel ---------------------- - void OnHelpComparisonSettings(wxHyperlinkEvent& event) override { displayHelpEntry(L"comparison-settings", this); } - void OnHelpTimeShift (wxHyperlinkEvent& event) override { displayHelpEntry(L"daylight-saving-time", this); } - void OnHelpPerformance (wxHyperlinkEvent& event) override { displayHelpEntry(L"performance", this); } - - void OnToggleLocalCompSettings(wxCommandEvent& event) override { updateCompGui(); updateSyncGui(); /*affects sync settings, too!*/ } - void OnToggleIgnoreErrors (wxCommandEvent& event) override { updateMiscGui(); } - void OnToggleAutoRetry (wxCommandEvent& event) override { updateMiscGui(); } - - void OnCompByTimeSize (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::timeSize; updateCompGui(); updateSyncGui(); } // - void OnCompByContent (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::content; updateCompGui(); updateSyncGui(); } //affects sync settings, too! - void OnCompBySize (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::size; updateCompGui(); updateSyncGui(); } // - void OnCompByTimeSizeDouble (wxMouseEvent& event) override; - void OnCompBySizeDouble (wxMouseEvent& event) override; - void OnCompByContentDouble (wxMouseEvent& event) override; - void OnChangeCompOption (wxCommandEvent& event) override { updateCompGui(); } + void onHelpComparisonSettings(wxHyperlinkEvent& event) override { displayHelpEntry(L"comparison-settings", this); } + void onHelpTimeShift (wxHyperlinkEvent& event) override { displayHelpEntry(L"daylight-saving-time", this); } + void onHelpPerformance (wxHyperlinkEvent& event) override { displayHelpEntry(L"performance", this); } + + void onToggleLocalCompSettings(wxCommandEvent& event) override { updateCompGui(); updateSyncGui(); /*affects sync settings, too!*/ } + void onToggleIgnoreErrors (wxCommandEvent& event) override { updateMiscGui(); } + void onToggleAutoRetry (wxCommandEvent& event) override { updateMiscGui(); } + + void onCompByTimeSize (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::timeSize; updateCompGui(); updateSyncGui(); } // + void onCompByContent (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::content; updateCompGui(); updateSyncGui(); } //affects sync settings, too! + void onCompBySize (wxCommandEvent& event) override { localCmpVar_ = CompareVariant::size; updateCompGui(); updateSyncGui(); } // + void onCompByTimeSizeDouble(wxMouseEvent& event) override; + void onCompByContentDouble (wxMouseEvent& event) override; + void onCompBySizeDouble (wxMouseEvent& event) override; + void onChangeCompOption (wxCommandEvent& event) override { updateCompGui(); } std::optional<CompConfig> getCompConfig() const; void setCompConfig(const CompConfig* compCfg); @@ -145,9 +145,9 @@ private: std::map<AfsDevice, size_t> deviceParallelOps_; // //------------- filter panel -------------------------- - void OnHelpFilterSettings(wxHyperlinkEvent& event) override { displayHelpEntry(L"exclude-items", this); } - void OnChangeFilterOption(wxCommandEvent& event) override { updateFilterGui(); } - void OnFilterReset (wxCommandEvent& event) override { setFilterConfig(FilterConfig()); } + void onHelpFilterSettings(wxHyperlinkEvent& event) override { displayHelpEntry(L"exclude-items", this); } + void onChangeFilterOption(wxCommandEvent& event) override { updateFilterGui(); } + void onFilterReset (wxCommandEvent& event) override { setFilterConfig(FilterConfig()); } void onFilterKeyEvent(wxKeyEvent& event); @@ -160,45 +160,45 @@ private: EnumDescrList<UnitSize> enumSizeDescr_; //------------- synchronization panel ----------------- - void OnSyncTwoWay(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::twoWay; updateSyncGui(); } - void OnSyncMirror(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::mirror; updateSyncGui(); } - void OnSyncUpdate(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::update; updateSyncGui(); } - void OnSyncCustom(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::custom; updateSyncGui(); } - - void OnToggleLocalSyncSettings(wxCommandEvent& event) override { updateSyncGui(); } - void OnToggleDetectMovedFiles (wxCommandEvent& event) override { directionCfg_.detectMovedFiles = !directionCfg_.detectMovedFiles; updateSyncGui(); } //parameter NOT owned by checkbox! - void OnChanegVersioningStyle (wxCommandEvent& event) override { updateSyncGui(); } - void OnToggleVersioningLimit (wxCommandEvent& event) override { updateSyncGui(); } - - void OnSyncTwoWayDouble(wxMouseEvent& event) override; - void OnSyncMirrorDouble(wxMouseEvent& event) override; - void OnSyncUpdateDouble(wxMouseEvent& event) override; - void OnSyncCustomDouble(wxMouseEvent& event) override; - - void OnExLeftSideOnly (wxCommandEvent& event) override; - void OnExRightSideOnly(wxCommandEvent& event) override; - void OnLeftNewer (wxCommandEvent& event) override; - void OnRightNewer (wxCommandEvent& event) override; - void OnDifferent (wxCommandEvent& event) override; - void OnConflict (wxCommandEvent& event) override; - - void OnHelpDetectMovedFiles(wxHyperlinkEvent& event) override { displayHelpEntry(L"synchronization-settings", this); } - void OnHelpVersioning (wxHyperlinkEvent& event) override { displayHelpEntry(L"versioning", this); } - - void OnDeletionPermanent (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::permanent; updateSyncGui(); } - void OnDeletionRecycler (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::recycler; updateSyncGui(); } - void OnDeletionVersioning (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::versioning; updateSyncGui(); } - - void OnToggleMiscOption(wxCommandEvent& event) override { updateMiscGui(); } - void OnToggleMiscEmail (wxCommandEvent& event) override + void onSyncTwoWay(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::twoWay; updateSyncGui(); } + void onSyncMirror(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::mirror; updateSyncGui(); } + void onSyncUpdate(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::update; updateSyncGui(); } + void onSyncCustom(wxCommandEvent& event) override { directionCfg_.var = SyncVariant::custom; updateSyncGui(); } + + void onToggleLocalSyncSettings(wxCommandEvent& event) override { updateSyncGui(); } + void onToggleDetectMovedFiles (wxCommandEvent& event) override { directionCfg_.detectMovedFiles = !directionCfg_.detectMovedFiles; updateSyncGui(); } //parameter NOT owned by checkbox! + void onChanegVersioningStyle (wxCommandEvent& event) override { updateSyncGui(); } + void onToggleVersioningLimit (wxCommandEvent& event) override { updateSyncGui(); } + + void onSyncTwoWayDouble(wxMouseEvent& event) override; + void onSyncMirrorDouble(wxMouseEvent& event) override; + void onSyncUpdateDouble(wxMouseEvent& event) override; + void onSyncCustomDouble(wxMouseEvent& event) override; + + void onExLeftSideOnly (wxCommandEvent& event) override; + void onExRightSideOnly(wxCommandEvent& event) override; + void onLeftNewer (wxCommandEvent& event) override; + void onRightNewer (wxCommandEvent& event) override; + void onDifferent (wxCommandEvent& event) override; + void onConflict (wxCommandEvent& event) override; + + void onHelpDetectMovedFiles(wxHyperlinkEvent& event) override { displayHelpEntry(L"synchronization-settings", this); } + void onHelpVersioning (wxHyperlinkEvent& event) override { displayHelpEntry(L"versioning", this); } + + void onDeletionPermanent (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::permanent; updateSyncGui(); } + void onDeletionRecycler (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::recycler; updateSyncGui(); } + void onDeletionVersioning (wxCommandEvent& event) override { handleDeletion_ = DeletionPolicy::versioning; updateSyncGui(); } + + void onToggleMiscOption(wxCommandEvent& event) override { updateMiscGui(); } + void onToggleMiscEmail (wxCommandEvent& event) override { - OnToggleMiscOption(event); + onToggleMiscOption(event); if (event.IsChecked()) //optimize UX m_comboBoxEmail->SetFocus(); // } - void OnEmailAlways (wxCommandEvent& event) override { emailNotifyCondition_ = ResultsNotification::always; updateMiscGui(); } - void OnEmailErrorWarning(wxCommandEvent& event) override { emailNotifyCondition_ = ResultsNotification::errorWarning; updateMiscGui(); } - void OnEmailErrorOnly (wxCommandEvent& event) override { emailNotifyCondition_ = ResultsNotification::errorOnly; updateMiscGui(); } + void onEmailAlways (wxCommandEvent& event) override { emailNotifyCondition_ = ResultsNotification::always; updateMiscGui(); } + void onEmailErrorWarning(wxCommandEvent& event) override { emailNotifyCondition_ = ResultsNotification::errorWarning; updateMiscGui(); } + void onEmailErrorOnly (wxCommandEvent& event) override { emailNotifyCondition_ = ResultsNotification::errorOnly; updateMiscGui(); } std::optional<SyncConfig> getSyncConfig() const; void setSyncConfig(const SyncConfig* syncCfg); @@ -358,7 +358,7 @@ showMultipleCfgs_(showMultipleCfgs) addToImageList(loadImage("cfg_compare_sicon")); addToImageList(loadImage("cfg_filter_sicon")); addToImageList(loadImage("cfg_sync_sicon")); - assert(imgList->GetImageCount() == static_cast<int>(ConfigTypeImage::SYNC_GREY) + 1); + assert(imgList->GetImageCount() == static_cast<int>(ConfigTypeImage::syncGrey) + 1); m_notebook->AssignImageList(imgList.release()); //pass ownership @@ -403,8 +403,8 @@ showMultipleCfgs_(showMultipleCfgs) assert(!contains(m_buttonClear->GetLabel(), L"&C") && !contains(m_buttonClear->GetLabel(), L"&c")); //gazillionth wxWidgets bug on OS X: Command + C mistakenly hits "&C" access key! - m_textCtrlInclude->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(ConfigDialog::onFilterKeyEvent), nullptr, this); - m_textCtrlExclude->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(ConfigDialog::onFilterKeyEvent), nullptr, this); + m_textCtrlInclude->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onFilterKeyEvent(event); }); + m_textCtrlExclude->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onFilterKeyEvent(event); }); m_staticTextFilterDescr->Wrap(fastFromDIP(450)); @@ -495,7 +495,7 @@ showMultipleCfgs_(showMultipleCfgs) //----------------------------------------------------- //enable dialog-specific key events - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(ConfigDialog::onLocalKeyEvent), nullptr, this); + Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); assert(!m_listBoxFolderPair->IsSorted()); @@ -621,7 +621,7 @@ void ConfigDialog::onListBoxKeyEvent(wxKeyEvent& event) } -void ConfigDialog::OnSelectFolderPair(wxCommandEvent& event) +void ConfigDialog::onSelectFolderPair(wxCommandEvent& event) { assert(!m_listBoxFolderPair->HasMultipleSelection()); //single-choice! const int selPos = event.GetSelection(); @@ -639,27 +639,27 @@ void ConfigDialog::OnSelectFolderPair(wxCommandEvent& event) } -void ConfigDialog::OnCompByTimeSizeDouble(wxMouseEvent& event) +void ConfigDialog::onCompByTimeSizeDouble(wxMouseEvent& event) { wxCommandEvent dummy; - OnCompByTimeSize(dummy); - OnOkay(dummy); + onCompByTimeSize(dummy); + onOkay(dummy); } -void ConfigDialog::OnCompBySizeDouble(wxMouseEvent& event) +void ConfigDialog::onCompBySizeDouble(wxMouseEvent& event) { wxCommandEvent dummy; - OnCompBySize(dummy); - OnOkay(dummy); + onCompBySize(dummy); + onOkay(dummy); } -void ConfigDialog::OnCompByContentDouble(wxMouseEvent& event) +void ConfigDialog::onCompByContentDouble(wxMouseEvent& event) { wxCommandEvent dummy; - OnCompByContent(dummy); - OnOkay(dummy); + onCompByContent(dummy); + onOkay(dummy); } @@ -715,7 +715,7 @@ void ConfigDialog::updateCompGui() m_panelComparisonSettings->Enable(compOptionsEnabled); m_notebook->SetPageImage(static_cast<size_t>(SyncConfigPanel::COMPARISON), - static_cast<int>(compOptionsEnabled ? ConfigTypeImage::COMPARISON : ConfigTypeImage::COMPARISON_GREY)); + static_cast<int>(compOptionsEnabled ? ConfigTypeImage::compare : ConfigTypeImage::compareGrey)); //update toggle buttons -> they have no parameter-ownership at all! m_buttonByTimeSize->setActive(CompareVariant::timeSize == localCmpVar_ && compOptionsEnabled); @@ -800,7 +800,7 @@ void ConfigDialog::updateFilterGui() const FilterConfig activeCfg = getFilterConfig(); m_notebook->SetPageImage(static_cast<size_t>(SyncConfigPanel::FILTER), - static_cast<int>(!isNullFilter(activeCfg) ? ConfigTypeImage::FILTER: ConfigTypeImage::FILTER_GREY)); + static_cast<int>(!isNullFilter(activeCfg) ? ConfigTypeImage::filter: ConfigTypeImage::filterGrey)); m_bitmapInclude ->SetBitmap(greyScaleIfDisabled(loadImage("filter_include"), !NameFilter::isNull(activeCfg.includeFilter, FilterConfig().excludeFilter))); m_bitmapExclude ->SetBitmap(greyScaleIfDisabled(loadImage("filter_exclude"), !NameFilter::isNull(FilterConfig().includeFilter, activeCfg.excludeFilter))); @@ -811,39 +811,39 @@ void ConfigDialog::updateFilterGui() m_spinCtrlMinSize ->Enable(activeCfg.unitSizeMin != UnitSize::none); m_spinCtrlMaxSize ->Enable(activeCfg.unitSizeMax != UnitSize::none); - m_buttonClear->Enable(!(activeCfg == FilterConfig())); + m_buttonClear->Enable(activeCfg != FilterConfig()); } -void ConfigDialog::OnSyncTwoWayDouble(wxMouseEvent& event) +void ConfigDialog::onSyncTwoWayDouble(wxMouseEvent& event) { wxCommandEvent dummy; - OnSyncTwoWay(dummy); - OnOkay(dummy); + onSyncTwoWay(dummy); + onOkay(dummy); } -void ConfigDialog::OnSyncMirrorDouble(wxMouseEvent& event) +void ConfigDialog::onSyncMirrorDouble(wxMouseEvent& event) { wxCommandEvent dummy; - OnSyncMirror(dummy); - OnOkay(dummy); + onSyncMirror(dummy); + onOkay(dummy); } -void ConfigDialog::OnSyncUpdateDouble(wxMouseEvent& event) +void ConfigDialog::onSyncUpdateDouble(wxMouseEvent& event) { wxCommandEvent dummy; - OnSyncUpdate(dummy); - OnOkay(dummy); + onSyncUpdate(dummy); + onOkay(dummy); } -void ConfigDialog::OnSyncCustomDouble(wxMouseEvent& event) +void ConfigDialog::onSyncCustomDouble(wxMouseEvent& event) { wxCommandEvent dummy; - OnSyncCustom(dummy); - OnOkay(dummy); + onSyncCustom(dummy); + onOkay(dummy); } @@ -911,42 +911,42 @@ void toggleCustomSyncConfig(SyncDirectionConfig& directionCfg, SyncDirection& cu } -void ConfigDialog::OnExLeftSideOnly(wxCommandEvent& event) +void ConfigDialog::onExLeftSideOnly(wxCommandEvent& event) { toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.exLeftSideOnly); updateSyncGui(); } -void ConfigDialog::OnExRightSideOnly(wxCommandEvent& event) +void ConfigDialog::onExRightSideOnly(wxCommandEvent& event) { toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.exRightSideOnly); updateSyncGui(); } -void ConfigDialog::OnLeftNewer(wxCommandEvent& event) +void ConfigDialog::onLeftNewer(wxCommandEvent& event) { toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.leftNewer); updateSyncGui(); } -void ConfigDialog::OnRightNewer(wxCommandEvent& event) +void ConfigDialog::onRightNewer(wxCommandEvent& event) { toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.rightNewer); updateSyncGui(); } -void ConfigDialog::OnDifferent(wxCommandEvent& event) +void ConfigDialog::onDifferent(wxCommandEvent& event) { toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.different); updateSyncGui(); } -void ConfigDialog::OnConflict(wxCommandEvent& event) +void ConfigDialog::onConflict(wxCommandEvent& event) { toggleCustomSyncConfig(directionCfg_, directionCfg_.custom.conflict); updateSyncGui(); @@ -1058,7 +1058,7 @@ void ConfigDialog::updateSyncGui() m_panelSyncSettings->Enable(syncOptionsEnabled); m_notebook->SetPageImage(static_cast<size_t>(SyncConfigPanel::SYNC), - static_cast<int>(syncOptionsEnabled ? ConfigTypeImage::SYNC: ConfigTypeImage::SYNC_GREY)); + static_cast<int>(syncOptionsEnabled ? ConfigTypeImage::sync: ConfigTypeImage::syncGrey)); updateSyncDirectionIcons(directionCfg_, *m_bpButtonLeftOnly, @@ -1239,7 +1239,7 @@ void ConfigDialog::setMiscSyncOptions(const MiscSyncConfig& miscCfg) if (rowsToCreate >= 0) for (int i = 0; i < rowsToCreate; ++i) { - wxSpinCtrl* spinCtrlParallelOps = new wxSpinCtrl(m_scrolledWindowPerf, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 2000000000, 1); + 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?) spinCtrlParallelOps->Enable(enableExtraFeatures_); fgSizerPerf->Add(spinCtrlParallelOps, 0, wxALIGN_CENTER_VERTICAL); @@ -1524,7 +1524,7 @@ bool ConfigDialog::unselectFolderPairConfig(bool validateParams) } -void ConfigDialog::OnOkay(wxCommandEvent& event) +void ConfigDialog::onOkay(wxCommandEvent& event) { if (!unselectFolderPairConfig(true /*validateParams*/)) return; @@ -1536,7 +1536,7 @@ void ConfigDialog::OnOkay(wxCommandEvent& event) logFolderHistoryOut_ = m_logFolderPath ->getHistory()->getList(); commandHistoryOut_ = m_comboBoxPostSyncCommand->getHistory(); - emailHistoryOut_ = m_comboBoxEmail->getHistory(); + emailHistoryOut_ = m_comboBoxEmail ->getHistory(); EndModal(ReturnSyncConfig::BUTTON_OKAY); } diff --git a/FreeFileSync/Source/ui/tray_icon.cpp b/FreeFileSync/Source/ui/tray_icon.cpp index 07127f68..cf8445b3 100644 --- a/FreeFileSync/Source/ui/tray_icon.cpp +++ b/FreeFileSync/Source/ui/tray_icon.cpp @@ -129,7 +129,7 @@ class FfsTrayIcon::TaskBarImpl : public wxTaskBarIcon public: TaskBarImpl(const std::function<void()>& requestResume) : requestResume_(requestResume) { - Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxEventHandler(TaskBarImpl::OnDoubleClick), nullptr, this); + Bind(wxEVT_TASKBAR_LEFT_DCLICK, [this](wxTaskBarIconEvent& event) { onDoubleClick(event); }); //Windows User Experience Guidelines: show the context menu rather than doing *nothing* on single left clicks; however: //MSDN: "Double-clicking the left mouse button actually generates a sequence of four messages: WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, and WM_LBUTTONUP." @@ -140,11 +140,6 @@ public: void dontCallbackAnymore() { requestResume_ = nullptr; } private: - enum Selection - { - CONTEXT_RESTORE = 1 //wxWidgets: "A MenuItem ID of zero does not work under Mac" - }; - wxMenu* CreatePopupMenu() override { if (!requestResume_) @@ -152,36 +147,24 @@ private: wxMenu* contextMenu = new wxMenu; - wxMenuItem* defaultItem = new wxMenuItem(contextMenu, CONTEXT_RESTORE, _("&Restore")); + wxMenuItem* defaultItem = new wxMenuItem(contextMenu, wxID_ANY, _("&Restore")); //wxWidgets font mess-up: //1. font must be set *before* wxMenu::Append()! //2. don't use defaultItem->GetFont(); making it bold creates a huge font size for some reason contextMenu->Append(defaultItem); - //event handling - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TaskBarImpl::OnContextMenuSelection), nullptr, this); + contextMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& event) { if (requestResume_) requestResume_(); }, defaultItem->GetId()); return contextMenu; //ownership transferred to caller } - void OnContextMenuSelection(wxCommandEvent& event) - { - switch (static_cast<Selection>(event.GetId())) - { - case CONTEXT_RESTORE: - if (requestResume_) - requestResume_(); - break; - } - } - - void OnDoubleClick(wxEvent& event) + void onDoubleClick(wxEvent& event) { if (requestResume_) requestResume_(); } - //void OnLeftDownClick(wxEvent& event) + //void onLeftDownClick(wxEvent& event) //{ // //copied from wxTaskBarIconBase::OnRightButtonDown() // if (wxMenu* menu = CreatePopupMenu()) @@ -207,17 +190,15 @@ FfsTrayIcon::~FfsTrayIcon() { trayIcon_->dontCallbackAnymore(); //TaskBarImpl has longer lifetime than FfsTrayIcon: avoid callback! - /* - This is not working correctly on OS X! It seems both wxTaskBarIcon::RemoveIcon() and ~wxTaskBarIcon() are broken and do NOT immediately - remove the icon from the system tray! Only some time later in the event loop which called these functions they will be removed. - Maybe some system component has still shared ownership? Objective C auto release pools are freed at the end of the current event loop... - Anyway, wxWidgets fails to disconnect the wxTaskBarIcon event handlers before calling "[m_statusitem release]"! + /* This is not working correctly on OS X! It seems both wxTaskBarIcon::RemoveIcon() and ~wxTaskBarIcon() are broken and do NOT immediately + remove the icon from the system tray! Only some time later in the event loop which called these functions they will be removed. + Maybe some system component has still shared ownership? Objective C auto release pools are freed at the end of the current event loop... + Anyway, wxWidgets fails to disconnect the wxTaskBarIcon event handlers before calling "[m_statusitem release]"! - => !!!clicking on the icon after ~wxTaskBarIcon ran crashes the application!!! + => !!!clicking on the icon after ~wxTaskBarIcon ran crashes the application!!! - - if ~wxTaskBarIcon() ran from the SyncProgressDialog::updateGui() event loop (e.g. user manually clicking the icon) => icon removed on return - - if ~wxTaskBarIcon() ran from SyncProgressDialog::closeDirectly() => leaves the icon dangling until user closes this dialog and outter event loop runs! - */ + - if ~wxTaskBarIcon() ran from the SyncProgressDialog::updateGui() event loop (e.g. user manually clicking the icon) => icon removed on return + - if ~wxTaskBarIcon() ran from SyncProgressDialog::closeDirectly() => leaves the icon dangling until user closes this dialog and outter event loop runs! */ trayIcon_->RemoveIcon(); //required on Windows: unlike on OS X, wxPendingDelete does not kick in before main event loop! //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking) diff --git a/FreeFileSync/Source/ui/tree_grid.cpp b/FreeFileSync/Source/ui/tree_grid.cpp index 70c9aec1..cadf2d2a 100644 --- a/FreeFileSync/Source/ui/tree_grid.cpp +++ b/FreeFileSync/Source/ui/tree_grid.cpp @@ -34,6 +34,17 @@ inline wxColor getColorPercentBackground() { return { 0xf8, 0xf8, 0xf8 }; } } +TreeView::TreeView(FolderComparison& folderCmp, const SortInfo& si) : folderCmp_(folderCmp), currentSort_(si) +{ + //remove truly empty folder pairs as early as this: we want to distinguish single/multiple folder pair cases by looking at "folderCmp" + std::erase_if(folderCmp_, [](const std::shared_ptr<BaseFolderPair>& baseObj) + { + return AFS::isNullPath(baseObj->getAbstractPath< LEFT_SIDE>()) && + AFS::isNullPath(baseObj->getAbstractPath<RIGHT_SIDE>()); + }); +} + + inline void TreeView::compressNode(Container& cont) //remove single-element sub-trees -> gain clarity + usability (call *after* inclusion check!!!) { @@ -145,7 +156,7 @@ void calcPercentage(std::vector<std::pair<uint64_t, int*>>& workList) remainingPercent -= *percent; } assert(remainingPercent >= 0); - assert(remainingPercent < static_cast<int>(workList.size())); + assert(remainingPercent < std::ssize(workList)); //distribute remaining percent so that overall error is minimized as much as possible: remainingPercent = std::min(remainingPercent, static_cast<int>(workList.size())); @@ -169,9 +180,9 @@ struct TreeView::LessShortName bool operator()(const TreeLine& lhs, const TreeLine& rhs) const { //files last (irrespective of sort direction) - if (lhs.type == TreeView::TYPE_FILES) + if (lhs.type == NodeType::files) return false; - else if (rhs.type == TreeView::TYPE_FILES) + else if (rhs.type == NodeType::files) return true; if (lhs.type != rhs.type) // @@ -179,12 +190,12 @@ struct TreeView::LessShortName switch (lhs.type) { - case TreeView::TYPE_ROOT: + case NodeType::root: return makeSortDirection(LessNaturalSort() /*even on Linux*/, std::bool_constant<ascending>())(utfTo<Zstring>(static_cast<const RootNodeImpl*>(lhs.node)->displayName), utfTo<Zstring>(static_cast<const RootNodeImpl*>(rhs.node)->displayName)); - case TreeView::TYPE_DIRECTORY: + case NodeType::folder: { const auto* folderL = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(lhs.node)->objId)); const auto* folderR = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(rhs.node)->objId)); @@ -197,7 +208,7 @@ struct TreeView::LessShortName return makeSortDirection(LessNaturalSort(), std::bool_constant<ascending>())(folderL->getItemNameAny(), folderR->getItemNameAny()); } - case TreeView::TYPE_FILES: + case NodeType::files: break; } assert(false); @@ -213,10 +224,10 @@ void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeTree colu { switch (line.type) { - case TreeView::TYPE_ROOT: - case TreeView::TYPE_DIRECTORY: + case NodeType::root: + case NodeType::folder: return line.node->bytesGross; - case TreeView::TYPE_FILES: + case NodeType::files: return line.node->bytesNet; } assert(false); @@ -227,11 +238,11 @@ void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeTree colu { switch (line.type) { - case TreeView::TYPE_ROOT: - case TreeView::TYPE_DIRECTORY: + case NodeType::root: + case NodeType::folder: return line.node->itemCountGross; - case TreeView::TYPE_FILES: + case NodeType::files: return line.node->itemCountNet; } assert(false); @@ -264,21 +275,21 @@ void TreeView::getChildren(const Container& cont, unsigned int level, std::vecto for (const DirNodeImpl& subDir : cont.subDirs) { - output.push_back({ level, 0, &subDir, TreeView::TYPE_DIRECTORY }); + output.push_back({ level, 0, &subDir, NodeType::folder}); workList.emplace_back(subDir.bytesGross, &output.back().percent); } if (cont.firstFileId) { - output.push_back({ level, 0, &cont, TreeView::TYPE_FILES }); + output.push_back({ level, 0, &cont, NodeType::files }); workList.emplace_back(cont.bytesNet, &output.back().percent); } calcPercentage(workList); - if (sortAscending_) - sortSingleLevel<true>(output, sortColumn_); + if (currentSort_.ascending) + sortSingleLevel<true>(output, currentSort_.sortCol); else - sortSingleLevel<false>(output, sortColumn_); + sortSingleLevel<false>(output, currentSort_.sortCol); } @@ -289,15 +300,15 @@ void TreeView::applySubView(std::vector<RootNodeImpl>&& newView) { switch (tl.type) { - case TreeView::TYPE_ROOT: + case NodeType::root: return static_cast<const RootNodeImpl*>(tl.node)->baseFolder.get(); - case TreeView::TYPE_DIRECTORY: + case NodeType::folder: if (auto folder = dynamic_cast<const FolderPair*>(FileSystemObject::retrieve(static_cast<const DirNodeImpl*>(tl.node)->objId))) return folder; break; - case TreeView::TYPE_FILES: + case NodeType::files: break; //none!!! } return nullptr; @@ -334,16 +345,16 @@ void TreeView::applySubView(std::vector<RootNodeImpl>&& newView) for (const RootNodeImpl& root : folderCmpView_) { - flatTree_.push_back({ 0, 0, &root, TreeView::TYPE_ROOT }); + flatTree_.push_back({ 0, 0, &root, NodeType::root }); workList.emplace_back(root.bytesGross, &flatTree_.back().percent); } calcPercentage(workList); - if (sortAscending_) - sortSingleLevel<true>(flatTree_, sortColumn_); + if (currentSort_.ascending) + sortSingleLevel<true>(flatTree_, currentSort_.sortCol); else - sortSingleLevel<false>(flatTree_, sortColumn_); + sortSingleLevel<false>(flatTree_, currentSort_.sortCol); } //restore node expansion status @@ -397,8 +408,7 @@ void TreeView::updateView(Predicate pred) void TreeView::setSortDirection(ColumnTypeTree colType, bool ascending) //apply permanently! { - sortColumn_ = colType; - sortAscending_ = ascending; + currentSort_ = SortInfo{colType, ascending}; //reapply current view applySubView(std::move(folderCmpView_)); @@ -415,11 +425,11 @@ TreeView::NodeStatus TreeView::getStatus(size_t row) const //it's either reduced or empty switch (flatTree_[row].type) { - case TreeView::TYPE_DIRECTORY: - case TreeView::TYPE_ROOT: + case NodeType::root: + case NodeType::folder: return flatTree_[row].node->firstFileId || !flatTree_[row].node->subDirs.empty() ? TreeView::STATUS_REDUCED : TreeView::STATUS_EMPTY; - case TreeView::TYPE_FILES: + case NodeType::files: return TreeView::STATUS_EMPTY; } } @@ -441,11 +451,11 @@ void TreeView::expandNode(size_t row) switch (flatTree_[row].type) { - case TreeView::TYPE_ROOT: - case TreeView::TYPE_DIRECTORY: + case NodeType::root: + case NodeType::folder: getChildren(*flatTree_[row].node, flatTree_[row].level + 1, newLines); break; - case TreeView::TYPE_FILES: + case NodeType::files: break; } flatTree_.insert(flatTree_.begin() + row + 1, newLines.begin(), newLines.end()); @@ -594,21 +604,6 @@ void TreeView::applyFilterByAction(bool showExcluded, } -void TreeView::setData(FolderComparison& newData) -{ - std::vector<TreeLine >().swap(flatTree_); //free mem - std::vector<RootNodeImpl>().swap(folderCmpView_); // - folderCmp_ = newData; - - //remove truly empty folder pairs as early as this: we want to distinguish single/multiple folder pair cases by looking at "folderCmp" - std::erase_if(folderCmp_, [](const std::shared_ptr<BaseFolderPair>& baseObj) - { - return AFS::isNullPath(baseObj->getAbstractPath< LEFT_SIDE>()) && - AFS::isNullPath(baseObj->getAbstractPath<RIGHT_SIDE>()); - }); -} - - std::unique_ptr<TreeView::Node> TreeView::getLine(size_t row) const { if (row < flatTree_.size()) @@ -618,14 +613,14 @@ std::unique_ptr<TreeView::Node> TreeView::getLine(size_t row) const switch (flatTree_[row].type) { - case TreeView::TYPE_ROOT: + case NodeType::root: { const auto& root = *static_cast<const TreeView::RootNodeImpl*>(flatTree_[row].node); return std::make_unique<TreeView::RootNode>(percent, root.bytesGross, root.itemCountGross, getStatus(row), *root.baseFolder, root.displayName); } break; - case TreeView::TYPE_DIRECTORY: + case NodeType::folder: { const auto* dir = static_cast<const TreeView::DirNodeImpl*>(flatTree_[row].node); if (auto folder = dynamic_cast<FolderPair*>(FileSystemObject::retrieve(dir->objId))) @@ -633,7 +628,7 @@ std::unique_ptr<TreeView::Node> TreeView::getLine(size_t row) const } break; - case TreeView::TYPE_FILES: + case NodeType::files: { const auto* parentDir = flatTree_[row].node; if (FileSystemObject* firstFile = FileSystemObject::retrieve(parentDir->firstFileId)) @@ -667,30 +662,20 @@ wxColor getColorForLevel(size_t level) { switch (level % 12) { - case 0: - return { 0xcc, 0xcc, 0xff }; - case 1: - return { 0xcc, 0xff, 0xcc }; - case 2: - return { 0xff, 0xff, 0x99 }; - case 3: - return { 0xdd, 0xdd, 0xdd }; - case 4: - return { 0xff, 0xcc, 0xff }; - case 5: - return { 0x99, 0xff, 0xcc }; - case 6: - return { 0xcc, 0xcc, 0x99 }; - case 7: - return { 0xff, 0xcc, 0xcc }; - case 8: - return { 0xcc, 0xff, 0x99 }; - case 9: - return { 0xff, 0xff, 0xcc }; - case 10: - return { 0xcc, 0xff, 0xff }; - case 11: - return { 0xff, 0xcc, 0x99 }; + //*INDENT-OFF* + case 0: return { 0xcc, 0xcc, 0xff }; + case 1: return { 0xcc, 0xff, 0xcc }; + case 2: return { 0xff, 0xff, 0x99 }; + case 3: return { 0xdd, 0xdd, 0xdd }; + case 4: return { 0xff, 0xcc, 0xff }; + case 5: return { 0x99, 0xff, 0xcc }; + case 6: return { 0xcc, 0xcc, 0x99 }; + case 7: return { 0xff, 0xcc, 0xcc }; + case 8: return { 0xcc, 0xff, 0x99 }; + case 9: return { 0xff, 0xff, 0xcc }; + case 10: return { 0xcc, 0xff, 0xff }; + case 11: return { 0xff, 0xcc, 0x99 }; + //*INDENT-ON* } assert(false); return *wxBLACK; @@ -707,27 +692,36 @@ public: rootIcon_(loadImage("root_folder", widthNodeIcon_)), grid_(grid) { - grid.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GridDataTree::onKeyDown), nullptr, this); - grid.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler (GridDataTree::onMouseLeft ), nullptr, this); - grid.Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler (GridDataTree::onMouseLeftDouble ), nullptr, this); - grid.Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEventHandler(GridDataTree::onGridLabelContext ), nullptr, this); - grid.Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEventHandler(GridDataTree::onGridLabelLeftClick), nullptr, this); + grid.getMainWin().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); }); + grid.Bind(EVENT_GRID_COL_LABEL_MOUSE_LEFT, [this](GridLabelClickEvent& event) { onGridLabelLeftClick(event); }); } + void setData(FolderComparison& folderCmp) + { + const TreeView::SortInfo sortCfg = treeDataView_.ref().getSortConfig(); //preserve! + + treeDataView_ = makeSharedRef<TreeView>(); //clear old data view first! avoid memory peaks! + treeDataView_ = makeSharedRef<TreeView>(folderCmp, sortCfg); + } + + const TreeView& getDataView() const { return treeDataView_.ref(); } + /**/ TreeView& getDataView() { return treeDataView_.ref(); } + void setShowPercentage(bool value) { showPercentBar_ = value; grid_.Refresh(); } bool getShowPercentage() const { return showPercentBar_; } - TreeView& getDataView() { return treeDataView_; } - private: - size_t getRowCount() const override { return treeDataView_.linesTotal(); } + size_t getRowCount() const override { return getDataView().rowsTotal(); } std::wstring getToolTip(size_t row, ColumnType colType) const override { switch (static_cast<ColumnTypeTree>(colType)) { case ColumnTypeTree::folder: - if (std::unique_ptr<TreeView::Node> node = treeDataView_.getLine(row)) + if (std::unique_ptr<TreeView::Node> node = getDataView().getLine(row)) if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) { const std::wstring& dirLeft = AFS::getDisplayPath(root->baseFolder.getAbstractPath< LEFT_SIDE>()); @@ -749,7 +743,7 @@ private: std::wstring getValue(size_t row, ColumnType colType) const override { - if (std::unique_ptr<TreeView::Node> node = treeDataView_.getLine(row)) + if (std::unique_ptr<TreeView::Node> node = getDataView().getLine(row)) switch (static_cast<ColumnTypeTree>(colType)) { case ColumnTypeTree::folder: @@ -782,7 +776,7 @@ private: rectRemain.width -= getColumnGapLeft(); drawColumnLabelText(dc, rectRemain, getColumnLabel(colType), enabled); - if (const auto [sortCol, ascending] = treeDataView_.getSortDirection(); + if (const auto [sortCol, ascending] = getDataView().getSortConfig(); colTypeTree == sortCol) { const wxImage sortMarker = loadImage(ascending ? "sort_ascending" : "sort_descending"); @@ -794,11 +788,15 @@ private: enum class HoverAreaTree { - NODE, + node, }; void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override { + wxDCTextColourChanger textColor(dc); + if (enabled && selected) //accessibility: always set *both* foreground AND background colors! + textColor.Set(*wxBLACK); + //wxRect rectTmp= drawCellBorder(dc, rect); wxRect rectTmp = rect; @@ -810,9 +808,9 @@ private: if (static_cast<ColumnTypeTree>(colType) == ColumnTypeTree::folder) { - if (std::unique_ptr<TreeView::Node> node = treeDataView_.getLine(row)) + if (std::unique_ptr<TreeView::Node> node = getDataView().getLine(row)) { - ////clear first secion: + ////clear first section: //clearArea(dc, wxRect(rect.GetTopLeft(), wxSize( // node->level_ * widthLevelStep_ + gridGap_ + //width // (showPercentBar ? percentageBarWidth_ + 2 * gridGap_ : 0) + // @@ -831,25 +829,17 @@ private: //percentage bar if (showPercentBar_) { - const wxRect areaPerc(rectTmp.x, rectTmp.y + 2, percentageBarWidth_, rectTmp.height - 4); - { - //clear background - wxDCPenChanger dummy (dc, wxPen(getColorPercentBorder(), fastFromDIP(1))); - wxDCBrushChanger dummy2(dc, getColorPercentBackground()); - dc.DrawRectangle(areaPerc); - - //inner area - const wxColor brushCol = getColorForLevel(node->level_); - dc.SetPen (brushCol); - dc.SetBrush(brushCol); - - wxRect areaPercTmp = areaPerc; - areaPercTmp.Deflate(1); //do not include border - areaPercTmp.width = numeric::round(areaPercTmp.width * node->percent_ / 100.0); - dc.DrawRectangle(areaPercTmp); - } + wxRect areaPerc(rectTmp.x, rectTmp.y + fastFromDIP(2), percentageBarWidth_, rectTmp.height - fastFromDIP(4)); + //clear background + drawFilledRectangle(dc, areaPerc, fastFromDIP(1), getColorPercentBorder(), getColorPercentBackground()); + areaPerc.Deflate(fastFromDIP(1)); + + //inner area + wxRect areaPercTmp = areaPerc; + areaPercTmp.width = numeric::round(areaPercTmp.width * node->percent_ / 100.0); + clearArea(dc, areaPercTmp, getColorForLevel(node->level_)); - wxDCTextColourChanger textColor(dc, *wxBLACK); //accessibility: always set both foreground AND background colors! + wxDCTextColourChanger textColorPercent(dc, *wxBLACK); //accessibility: always set both foreground AND background colors! drawCellText(dc, areaPerc, numberTo<std::wstring>(node->percent_) + L"%", wxALIGN_CENTER); rectTmp.x += percentageBarWidth_ + 2 * gridGap_; @@ -865,12 +855,10 @@ private: wxRect rectStat(rectTmp.GetTopLeft(), wxSize(img.GetWidth(), img.GetHeight())); rectStat.y += (rectTmp.height - rectStat.height) / 2; - //clearArea(dc, rectStat, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - clearArea(dc, rectStat, *wxWHITE); //accessibility: always set both foreground AND background colors! drawBitmapRtlNoMirror(dc, img, rectStat, wxALIGN_CENTER); }; - const bool drawMouseHover = static_cast<HoverAreaTree>(rowHover) == HoverAreaTree::NODE; + const bool drawMouseHover = static_cast<HoverAreaTree>(rowHover) == HoverAreaTree::node; switch (node->status_) { case TreeView::STATUS_EXPANDED: @@ -910,7 +898,6 @@ private: if (rectTmp.width > 0) { - wxDCTextColourChanger textColor(dc); if (!isActive) textColor.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); @@ -948,7 +935,7 @@ private: if (static_cast<ColumnTypeTree>(colType) == ColumnTypeTree::folder) { - if (std::unique_ptr<TreeView::Node> node = treeDataView_.getLine(row)) + if (std::unique_ptr<TreeView::Node> node = getDataView().getLine(row)) return node->level_ * widthLevelStep_ + gridGap_ + (showPercentBar_ ? percentageBarWidth_ + 2 * gridGap_ : 0) + widthNodeStatus_ + gridGap_ + widthNodeIcon_ + gridGap_ + dc.GetTextExtent(getValue(row, colType)).GetWidth() + gridGap_; //additional gap from right @@ -960,12 +947,12 @@ private: 2 * gridGap_; //include gap from right! } - HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override + HoverArea getRowMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override { switch (static_cast<ColumnTypeTree>(colType)) { case ColumnTypeTree::folder: - if (std::unique_ptr<TreeView::Node> node = treeDataView_.getLine(row)) + if (std::unique_ptr<TreeView::Node> node = getDataView().getLine(row)) { const int tolerance = 2; const int nodeStatusXFirst = -tolerance + static_cast<int>(node->level_) * widthLevelStep_ + gridGap_ + (showPercentBar_ ? percentageBarWidth_ + 2 * gridGap_ : 0); @@ -973,7 +960,7 @@ private: // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() if (nodeStatusXFirst <= cellRelativePosX && cellRelativePosX < nodeStatusXLast) - return static_cast<HoverArea>(HoverAreaTree::NODE); + return static_cast<HoverArea>(HoverAreaTree::node); } break; @@ -981,7 +968,7 @@ private: case ColumnTypeTree::bytes: break; } - return HoverArea::NONE; + return HoverArea::none; } std::wstring getColumnLabel(ColumnType colType) const override @@ -1002,8 +989,8 @@ private: { switch (static_cast<HoverAreaTree>(event.hoverArea_)) { - case HoverAreaTree::NODE: - switch (treeDataView_.getStatus(event.row_)) + case HoverAreaTree::node: + switch (getDataView().getStatus(event.row_)) { case TreeView::STATUS_EXPANDED: return reduceNode(event.row_); @@ -1019,7 +1006,7 @@ private: void onMouseLeftDouble(GridClickEvent& event) { - switch (treeDataView_.getStatus(event.row_)) + switch (getDataView().getStatus(event.row_)) { case TreeView::STATUS_EXPANDED: return reduceNode(event.row_); @@ -1055,17 +1042,17 @@ private: { case WXK_LEFT: case WXK_NUMPAD_LEFT: - case WXK_NUMPAD_SUBTRACT: //https://docs.microsoft.com/en-us/previous-versions/windows/desktop/dnacc/guidelines-for-keyboard-user-interface-design#atg_keyboardshortcuts_windows_shortcut_keys - switch (treeDataView_.getStatus(row)) + case WXK_NUMPAD_SUBTRACT: //https://docs.microsoft.com/en-us/previous-versions/windows/desktop/dnacc/guidelines-for-keyboard-user-interface-design#windows-shortcut-keys + switch (getDataView().getStatus(row)) { case TreeView::STATUS_EXPANDED: return reduceNode(row); case TreeView::STATUS_REDUCED: case TreeView::STATUS_EMPTY: - const int parentRow = treeDataView_.getParent(row); + const int parentRow = getDataView().getParent(row); if (parentRow >= 0) - grid_.setGridCursor(parentRow, GridEventPolicy::ALLOW); + grid_.setGridCursor(parentRow, GridEventPolicy::allow); break; } return; //swallow event @@ -1073,10 +1060,10 @@ private: case WXK_RIGHT: case WXK_NUMPAD_RIGHT: case WXK_NUMPAD_ADD: - switch (treeDataView_.getStatus(row)) + switch (getDataView().getStatus(row)) { case TreeView::STATUS_EXPANDED: - grid_.setGridCursor(std::min(rowCount - 1, row + 1), GridEventPolicy::ALLOW); + grid_.setGridCursor(std::min(rowCount - 1, row + 1), GridEventPolicy::allow); break; case TreeView::STATUS_REDUCED: return expandNode(row); @@ -1147,31 +1134,31 @@ private: const auto colTypeTree = static_cast<ColumnTypeTree>(event.colType_); bool sortAscending = getDefaultSortDirection(colTypeTree); - const auto [sortCol, ascending] = treeDataView_.getSortDirection(); + const auto [sortCol, ascending] = getDataView().getSortConfig(); if (sortCol == colTypeTree) sortAscending = !ascending; - treeDataView_.setSortDirection(colTypeTree, sortAscending); - grid_.clearSelection(GridEventPolicy::ALLOW); + getDataView().setSortDirection(colTypeTree, sortAscending); + grid_.clearSelection(GridEventPolicy::allow); grid_.Refresh(); } void expandNode(size_t row) { - treeDataView_.expandNode(row); + getDataView().expandNode(row); grid_.Refresh(); //implicitly clears selection (changed row count after expand) - grid_.setGridCursor(row, GridEventPolicy::ALLOW); + grid_.setGridCursor(row, GridEventPolicy::allow); //grid_.autoSizeColumns(); -> doesn't look as good as expected } void reduceNode(size_t row) { - treeDataView_.reduceNode(row); + getDataView().reduceNode(row); grid_.Refresh(); - grid_.setGridCursor(row, GridEventPolicy::ALLOW); + grid_.setGridCursor(row, GridEventPolicy::allow); } - TreeView treeDataView_; + SharedRef<TreeView> treeDataView_ = makeSharedRef<TreeView>(); const int gridGap_ = fastFromDIP(TREE_GRID_GAP_SIZE_DIP); const int percentageBarWidth_ = fastFromDIP(PERCENTAGE_BAR_WIDTH_DIP); @@ -1202,6 +1189,14 @@ void treegrid::init(Grid& grid) } +void treegrid::setData(zen::Grid& grid, FolderComparison& folderCmp) +{ + if (auto* prov = dynamic_cast<GridDataTree*>(grid.getDataProvider())) + return prov->setData(folderCmp); + throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] treegrid was not initialized."); +} + + TreeView& treegrid::getDataView(Grid& grid) { if (auto* prov = dynamic_cast<GridDataTree*>(grid.getDataProvider())) diff --git a/FreeFileSync/Source/ui/tree_grid.h b/FreeFileSync/Source/ui/tree_grid.h index 1291cec0..9d735804 100644 --- a/FreeFileSync/Source/ui/tree_grid.h +++ b/FreeFileSync/Source/ui/tree_grid.h @@ -19,9 +19,14 @@ namespace fff class TreeView { public: - TreeView() {} + struct SortInfo + { + ColumnTypeTree sortCol = treeGridLastSortColumnDefault; + bool ascending = getDefaultSortDirection(treeGridLastSortColumnDefault); + }; - void setData(FolderComparison& newData); //set data, taking (partial) ownership + TreeView() {} + TreeView(FolderComparison& folderCmp, const SortInfo& si); //takes (shared) ownership //apply view filter: comparison results void applyFilterByCategory(bool showExcluded, @@ -90,7 +95,7 @@ public: }; std::unique_ptr<Node> getLine(size_t row) const; //return nullptr on error - size_t linesTotal() const { return flatTree_.size(); } + size_t rowsTotal() const { return flatTree_.size(); } void expandNode(size_t row); void reduceNode(size_t row); @@ -98,9 +103,12 @@ public: ptrdiff_t getParent(size_t row) const; //return < 0 if none void setSortDirection(ColumnTypeTree colType, bool ascending); //apply permanently! - std::pair<ColumnTypeTree, bool> getSortDirection() { return { sortColumn_, sortAscending_ }; } + SortInfo getSortConfig() { return currentSort_; } private: + TreeView (const TreeView&) = delete; + TreeView& operator=(const TreeView&) = delete; + struct DirNodeImpl; struct Container @@ -128,11 +136,11 @@ private: std::wstring displayName; }; - enum NodeType + enum class NodeType { - TYPE_ROOT, //-> RootNodeImpl - TYPE_DIRECTORY, //-> DirNodeImpl - TYPE_FILES //-> Container + root, //-> RootNodeImpl + folder, //-> DirNodeImpl + files //-> Container }; struct TreeLine @@ -140,7 +148,7 @@ private: unsigned int level = 0; int percent = 0; //[0, 100] const Container* node = nullptr; // - NodeType type = NodeType::TYPE_ROOT; //we increase size of "flatTree" using C-style types rather than have a polymorphic "folderCmpView" + NodeType type = NodeType::root; //we increase size of "flatTree" using C-style types rather than have a polymorphic "folderCmpView" }; static void compressNode(Container& cont); @@ -164,8 +172,7 @@ private: | */ std::vector<std::shared_ptr<BaseFolderPair>> folderCmp_; //full raw data - ColumnTypeTree sortColumn_ = treeGridLastSortColumnDefault; - bool sortAscending_ = getDefaultSortDirection(treeGridLastSortColumnDefault); + SortInfo currentSort_; }; @@ -173,6 +180,7 @@ namespace treegrid { void init(zen::Grid& grid); TreeView& getDataView(zen::Grid& grid); +void setData(zen::Grid& grid, FolderComparison& folderCmp); //takes (shared) ownership void setShowPercentage(zen::Grid& grid, bool value); bool getShowPercentage(const zen::Grid& grid); diff --git a/FreeFileSync/Source/ui/tree_grid_attr.h b/FreeFileSync/Source/ui/tree_grid_attr.h index 4a392c7a..eff14c4e 100644 --- a/FreeFileSync/Source/ui/tree_grid_attr.h +++ b/FreeFileSync/Source/ui/tree_grid_attr.h @@ -36,7 +36,7 @@ std::vector<ColAttributesTree> getTreeGridDefaultColAttribs() using namespace zen; return //harmonize with tree_view.cpp::onGridLabelContext() => expects stretched folder and non-stretched other columns! { - { ColumnTypeTree::folder, fastFromDIP(0 - 60 - 60), 1, true }, //stretch to full width and substract sum of fixed size widths + { ColumnTypeTree::folder, - 2 * fastFromDIP(60), 1, true }, //stretch to full width and substract sum of fixed size widths { ColumnTypeTree::itemCount, fastFromDIP(60), 0, true }, { ColumnTypeTree::bytes, fastFromDIP(60), 0, true }, //GTK needs a few pixels more width }; diff --git a/FreeFileSync/Source/ui/triple_splitter.cpp b/FreeFileSync/Source/ui/triple_splitter.cpp index 6e193529..b5b0a385 100644 --- a/FreeFileSync/Source/ui/triple_splitter.cpp +++ b/FreeFileSync/Source/ui/triple_splitter.cpp @@ -37,18 +37,18 @@ TripleSplitter::TripleSplitter(wxWindow* parent, sashSize_ (fastFromDIP(SASH_SIZE_DIP)), childWindowMinSize_(fastFromDIP(CHILD_WINDOW_MIN_SIZE_DIP)) { - Connect(wxEVT_PAINT, wxPaintEventHandler(TripleSplitter::onPaintEvent), nullptr, this); - Connect(wxEVT_SIZE, wxSizeEventHandler (TripleSplitter::onSizeEvent ), nullptr, this); + Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { wxPaintDC dc(this); drawSash(dc); }); + Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { updateWindowSizes(); event.Skip(); }); Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) {}); //https://wiki.wxwidgets.org/Flicker-Free_Drawing SetBackgroundStyle(wxBG_STYLE_PAINT); - Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(TripleSplitter::onMouseLeftDown ), nullptr, this); - Connect(wxEVT_LEFT_UP, wxMouseEventHandler(TripleSplitter::onMouseLeftUp ), nullptr, this); - Connect(wxEVT_MOTION, wxMouseEventHandler(TripleSplitter::onMouseMovement ), nullptr, this); - Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(TripleSplitter::onLeaveWindow ), nullptr, this); - Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(TripleSplitter::onMouseLeftDouble), nullptr, this); - Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(TripleSplitter::onMouseCaptureLost), nullptr, this); + Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) { onMouseLeftDown (event); }); + Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& event) { onMouseLeftUp (event); }); + Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { onMouseMovement (event); }); + Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { onLeaveWindow (event); }); + Bind(wxEVT_LEFT_DCLICK, [this](wxMouseEvent& event) { onMouseLeftDouble(event); }); + Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent& event) { onMouseCaptureLost(event); }); } diff --git a/FreeFileSync/Source/ui/triple_splitter.h b/FreeFileSync/Source/ui/triple_splitter.h index ea7974fe..17754d59 100644 --- a/FreeFileSync/Source/ui/triple_splitter.h +++ b/FreeFileSync/Source/ui/triple_splitter.h @@ -49,14 +49,6 @@ public: void setSashOffset(int off) { centerOffset_ = off; updateWindowSizes(); } private: - void onSizeEvent(wxSizeEvent& event) { updateWindowSizes(); event.Skip(); } - - void onPaintEvent(wxPaintEvent& event) - { - wxPaintDC dc(this); - drawSash(dc); - } - void updateWindowSizes(); int getCenterWidth() const; int getCenterPosX() const; //return normalized posX diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp index 0a8ac407..91199026 100644 --- a/FreeFileSync/Source/ui/version_check.cpp +++ b/FreeFileSync/Source/ui/version_check.cpp @@ -16,6 +16,7 @@ #include <zen/basic_math.h> #include <zen/file_error.h> #include <zen/http.h> +#include <zen/sys_version.h> #include <zen/thread.h> #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> @@ -79,15 +80,15 @@ bool fff::shouldRunAutomaticUpdateCheck(time_t lastUpdateCheck) std::wstring getIso639Language() { - assert(runningMainThread()); //this function is not thread-safe, consider wxWidgets usage + assert(runningOnMainThread()); //this function is not thread-safe, consider wxWidgets usage std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage())); - localeName = beforeFirst(localeName, L"@", IF_MISSING_RETURN_ALL); //the locale may contain an @, e.g. "sr_RS@latin"; see wxLocale::InitLanguagesDB() + localeName = beforeFirst(localeName, L"@", IfNotFoundReturn::all); //the locale may contain an @, e.g. "sr_RS@latin"; see wxLocale::InitLanguagesDB() if (!localeName.empty()) { - assert(beforeFirst(localeName, L"_", IF_MISSING_RETURN_ALL).size() == 2); - return beforeFirst(localeName, L"_", IF_MISSING_RETURN_ALL); + assert(beforeFirst(localeName, L"_", IfNotFoundReturn::all).size() == 2); + return beforeFirst(localeName, L"_", IfNotFoundReturn::all); } assert(false); return L"zz"; @@ -98,13 +99,13 @@ namespace { std::wstring getIso3166Country() { - assert(runningMainThread()); //this function is not thread-safe, consider wxWidgets usage + assert(runningOnMainThread()); //this function is not thread-safe, consider wxWidgets usage std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage())); - localeName = beforeFirst(localeName, L"@", IF_MISSING_RETURN_ALL); //the locale may contain an @, e.g. "sr_RS@latin"; see wxLocale::InitLanguagesDB() + localeName = beforeFirst(localeName, L"@", IfNotFoundReturn::all); //the locale may contain an @, e.g. "sr_RS@latin"; see wxLocale::InitLanguagesDB() if (contains(localeName, L"_")) - return afterFirst(localeName, L"_", IF_MISSING_RETURN_NONE); + return afterFirst(localeName, L"_", IfNotFoundReturn::none); assert(false); return L"ZZ"; } @@ -113,7 +114,7 @@ std::wstring getIso3166Country() //coordinate with get_latest_version_number.php std::vector<std::pair<std::string, std::string>> geHttpPostParameters(wxWindow& parent) { - assert(runningMainThread()); //this function is not thread-safe, e.g. consider wxWidgets usage in isPortableVersion() + assert(runningOnMainThread()); //this function is not thread-safe, e.g. consider wxWidgets usage in isPortableVersion() std::vector<std::pair<std::string, std::string>> params; params.emplace_back("ffs_version", ffsVersion); @@ -122,17 +123,12 @@ std::vector<std::pair<std::string, std::string>> geHttpPostParameters(wxWindow& params.emplace_back("os_name", "Linux"); - const wxLinuxDistributionInfo distribInfo = wxGetLinuxDistributionInfo(); - assert(contains(distribInfo.Release, L'.')); - std::vector<wxString> digits = split<wxString>(distribInfo.Release, L'.', SplitType::ALLOW_EMPTY); //e.g. "7.7.1908" - digits.resize(2); - //distribInfo.Id //e.g. "Ubuntu" - - const int osvMajor = stringTo<int>(digits[0]); - const int osvMinor = stringTo<int>(digits[1]); - - params.emplace_back("os_version", numberTo<std::string>(osvMajor) + "." + numberTo<std::string>(osvMinor)); + const OsVersion osv = getOsVersion(); + params.emplace_back("os_version", numberTo<std::string>(osv.major) + "." + numberTo<std::string>(osv.minor)); +#ifndef ZEN_BUILD_ARCH +#error include <zen/build_info.h> +#endif const char* osArch = ZEN_STRINGIZE_NUMBER(ZEN_BUILD_ARCH); params.emplace_back("os_arch", osArch); @@ -163,7 +159,7 @@ void showUpdateAvailableDialog(wxWindow* parent, const std::string& onlineVersio ffsUpdateCheckUserAgent, nullptr /*caCertFilePath*/, nullptr /*notifyUnbufferedIO*/).readAll(); //throw SysError updateDetailsMsg = utfTo<std::wstring>(buf); } - catch (const zen::SysError& e) { throw FileError(_("Failed to retrieve update information."), e.toString()); } + catch (const SysError& e) { throw FileError(_("Failed to retrieve update information."), e.toString()); } } catch (const FileError& e) //fall back to regular update info dialog: @@ -198,7 +194,7 @@ std::string getOnlineVersion(const std::vector<std::pair<std::string, std::strin std::vector<size_t> parseVersion(const std::string& version) { std::vector<size_t> output; - for (const std::string& digit : split(version, FFS_VERSION_SEPARATOR, SplitType::ALLOW_EMPTY)) + for (const std::string& digit : split(version, FFS_VERSION_SEPARATOR, SplitOnEmpty::allow)) output.push_back(stringTo<size_t>(digit)); return output; } @@ -245,7 +241,7 @@ void fff::checkForUpdateNow(wxWindow& parent, std::string& lastOnlineVersion) setTitle(_("Check for Program Updates")). setMainInstructions(_("FreeFileSync is up to date."))); } - catch (const zen::SysError& e) + catch (const SysError& e) { if (internetIsAlive()) { @@ -289,7 +285,7 @@ struct fff::UpdateCheckResultPrep std::shared_ptr<const UpdateCheckResultPrep> fff::automaticUpdateCheckPrepare(wxWindow& parent) { - assert(runningMainThread()); + assert(runningOnMainThread()); auto prep = std::make_shared<UpdateCheckResultPrep>(); prep->postParameters = geHttpPostParameters(parent); return prep; @@ -298,22 +294,22 @@ std::shared_ptr<const UpdateCheckResultPrep> fff::automaticUpdateCheckPrepare(wx struct fff::UpdateCheckResult { - UpdateCheckResult(const std::string& ver, const std::optional<zen::SysError>& err, bool alive) : onlineVersion(ver), error(err), internetIsAlive(alive) {} + UpdateCheckResult(const std::string& ver, const std::optional<SysError>& err, bool alive) : onlineVersion(ver), error(err), internetIsAlive(alive) {} std::string onlineVersion; - std::optional<zen::SysError> error; + std::optional<SysError> error; bool internetIsAlive = false; }; std::shared_ptr<const UpdateCheckResult> fff::automaticUpdateCheckRunAsync(const UpdateCheckResultPrep* resultPrep) { - //assert(!runningMainThread()); -> allow synchronous call, too + //assert(!runningOnMainThread()); -> allow synchronous call, too try { const std::string onlineVersion = getOnlineVersion(resultPrep->postParameters); //throw SysError return std::make_shared<UpdateCheckResult>(onlineVersion, std::nullopt, true); } - catch (const zen::SysError& e) + catch (const SysError& e) { return std::make_shared<UpdateCheckResult>("", e, internetIsAlive()); } @@ -322,7 +318,7 @@ std::shared_ptr<const UpdateCheckResult> fff::automaticUpdateCheckRunAsync(const void fff::automaticUpdateCheckEval(wxWindow* parent, time_t& lastUpdateCheck, std::string& lastOnlineVersion, const UpdateCheckResult* asyncResult) { - assert(runningMainThread()); + assert(runningOnMainThread()); const UpdateCheckResult& result = *asyncResult; diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index 29b4dab7..cbf84370 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace fff { -const char ffsVersion[] = "11.0"; //internal linkage! +const char ffsVersion[] = "11.1"; //internal linkage! const char FFS_VERSION_SEPARATOR = '.'; } diff --git a/libcurl/rest.cpp b/libcurl/rest.cpp index e17de3ea..60fcb8eb 100644 --- a/libcurl/rest.cpp +++ b/libcurl/rest.cpp @@ -5,7 +5,7 @@ // ***************************************************************************** #include "rest.h" -#include <zen/system.h> +#include <zen/sys_info.h> #include <zen/http.h> using namespace zen; diff --git a/wx+/app_main.h b/wx+/app_main.h index 570a2a9c..17a59d7b 100644 --- a/wx+/app_main.h +++ b/wx+/app_main.h @@ -21,16 +21,10 @@ bool globalWindowWasSet(); - //######################## implementation ######################## namespace impl { -inline -bool& refGlobalWindowStatus() -{ - static bool status = false; //external linkage! - return status; -} +inline bool haveGlobalWindow = false; } @@ -40,10 +34,12 @@ void setGlobalWindow(wxWindow* window) wxTheApp->SetTopWindow(window); wxTheApp->SetExitOnFrameDelete(true); - impl::refGlobalWindowStatus() = true; + impl::haveGlobalWindow = true; } -inline bool globalWindowWasSet() { return impl::refGlobalWindowStatus(); } + +inline +bool globalWindowWasSet() { return impl::haveGlobalWindow; } } #endif //APP_MAIN_H_08215601837818347575856 diff --git a/wx+/async_task.h b/wx+/async_task.h index 47660a6a..1571c917 100644 --- a/wx+/async_task.h +++ b/wx+/async_task.h @@ -114,7 +114,7 @@ private: class AsyncGuiQueue : private wxEvtHandler { public: - AsyncGuiQueue(int pollingMs = 50) : pollingMs_(pollingMs) { timer_.Connect(wxEVT_TIMER, wxEventHandler(AsyncGuiQueue::onTimerEvent), nullptr, this); } + AsyncGuiQueue(int pollingMs = 50) : pollingMs_(pollingMs) { timer_.Bind(wxEVT_TIMER, [this](wxTimerEvent& event) { onTimerEvent(event); }); } template <class Fun, class Fun2> void processAsync(Fun&& evalAsync, Fun2&& evalOnGui) diff --git a/wx+/bitmap_button.h b/wx+/bitmap_button.h index 508a72fc..8fe8e146 100644 --- a/wx+/bitmap_button.h +++ b/wx+/bitmap_button.h @@ -26,7 +26,7 @@ public: const wxSize& size = wxDefaultSize, long style = 0, const wxValidator& validator = wxDefaultValidator, - const wxString& name = wxButtonNameStr) : + const wxString& name = wxASCII_STR(wxButtonNameStr)) : wxBitmapButton(parent, id, wxNullBitmap, pos, size, style, validator, name) { SetLabel(label); @@ -98,9 +98,10 @@ wxBitmap renderSelectedButton(const wxSize& sz) wxBitmap bmp(sz); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes { wxMemoryDC dc(bmp); - dc.SetBrush(wxColor(0xcc, 0xe4, 0xf8)); //light blue - dc.SetPen (wxColor(0x79, 0xbc, 0xed)); //medium blue - dc.DrawRectangle(wxRect(bmp.GetSize())); + + const wxColor borderCol(0x79, 0xbc, 0xed); //medium blue + const wxColor innerCol (0xcc, 0xe4, 0xf8); //light blue + drawFilledRectangle(dc, wxRect(bmp.GetSize()), fastFromDIP(1), borderCol, innerCol); } return bmp; } @@ -116,7 +117,8 @@ wxBitmap renderPressedButton(const wxSize& sz) const wxColor colTo(0x11, 0x79, 0xfe); //light blue wxMemoryDC dc(bmp); - dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + dc.SetPen(*wxTRANSPARENT_PEN); //wxTRANSPARENT_PEN is about 2x faster than redundantly drawing with col! + wxRect rect(bmp.GetSize()); const int borderSize = fastFromDIP(3); @@ -125,10 +127,13 @@ wxBitmap renderPressedButton(const wxSize& sz) const wxColor colGradient((colFrom.Red () * (borderSize - i) + colTo.Red () * i) / borderSize, (colFrom.Green() * (borderSize - i) + colTo.Green() * i) / borderSize, (colFrom.Blue () * (borderSize - i) + colTo.Blue () * i) / borderSize); - dc.SetPen(colGradient); + dc.SetBrush(colGradient); dc.DrawRectangle(rect); rect.Deflate(1); } + + dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + dc.DrawRectangle(rect); } return bmp; } diff --git a/wx+/choice_enum.h b/wx+/choice_enum.h index e11b9991..626aa39a 100644 --- a/wx+/choice_enum.h +++ b/wx+/choice_enum.h @@ -104,7 +104,7 @@ Enum getEnumVal(const EnumDescrList<Enum>& mapping, const wxChoice& ctrl) { const int selectedPos = ctrl.GetSelection(); - if (0 <= selectedPos && selectedPos < static_cast<int>(mapping.descrList.size())) + if (0 <= selectedPos && selectedPos < std::ssize(mapping.descrList)) return mapping.descrList[selectedPos].first; else { @@ -117,7 +117,7 @@ template <class Enum> void updateTooltipEnumVal(const EnumDescrList<Enum>& mappi { const int selectedPos = ctrl.GetSelection(); - if (0 <= selectedPos && selectedPos < static_cast<int>(mapping.descrList.size())) + if (0 <= selectedPos && selectedPos < std::ssize(mapping.descrList)) { if (const auto& [text, tooltip] = mapping.descrList[selectedPos].second; !tooltip.empty()) diff --git a/wx+/context_menu.h b/wx+/context_menu.h index 7096da33..c53cec39 100644 --- a/wx+/context_menu.h +++ b/wx+/context_menu.h @@ -13,15 +13,14 @@ #include <wx/menu.h> #include <wx/app.h> -/* -A context menu supporting lambda callbacks! - -Usage: - ContextMenu menu; - menu.addItem(L"Some Label", [&]{ ...do something... }); -> capture by reference is fine, as long as captured variables have at least scope of ContextMenu::popup()! - ... - menu.popup(wnd); -*/ +/* A context menu supporting lambda callbacks! + + Usage: + ContextMenu menu; + menu.addItem(L"Some Label", [&]{ ...do something... }); -> capture by reference is fine, as long as captured variables have at least scope of ContextMenu::popup()! + ... + menu.popup(wnd); */ + namespace zen { class ContextMenu : private wxEvtHandler @@ -32,9 +31,11 @@ public: void addItem(const wxString& label, const std::function<void()>& command, const wxImage& img = wxNullImage, bool enabled = true) { wxMenuItem* newItem = new wxMenuItem(menu_.get(), wxID_ANY, label); //menu owns item! - if (img.IsOk()) newItem->SetBitmap(img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason + if (img.IsOk()) + newItem->SetBitmap(img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason menu_->Append(newItem); - if (!enabled) newItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason + if (!enabled) + newItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason commandList_[newItem->GetId()] = command; //defer event connection, this may be a submenu only! } @@ -42,7 +43,8 @@ public: { wxMenuItem* newItem = menu_->AppendCheckItem(wxID_ANY, label); newItem->Check(checked); - if (!enabled) newItem->Enable(false); + if (!enabled) + newItem->Enable(false); commandList_[newItem->GetId()] = command; } @@ -50,7 +52,8 @@ public: { wxMenuItem* newItem = menu_->AppendRadioItem(wxID_ANY, label); newItem->Check(selected); - if (!enabled) newItem->Enable(false); + if (!enabled) + newItem->Enable(false); commandList_[newItem->GetId()] = command; } @@ -65,16 +68,18 @@ public: submenu.menu_->SetNextHandler(menu_.get()); //on wxGTK submenu events are not propagated to their parent menu by default! wxMenuItem* newItem = new wxMenuItem(menu_.get(), wxID_ANY, label, L"", wxITEM_NORMAL, submenu.menu_.release()); //menu owns item, item owns submenu! - if (img.IsOk()) newItem->SetBitmap(img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason + if (img.IsOk()) + newItem->SetBitmap(img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason menu_->Append(newItem); - if (!enabled) newItem->Enable(false); + if (!enabled) + newItem->Enable(false); } void popup(wxWindow& wnd, const wxPoint& pos = wxDefaultPosition) //show popup menu + process lambdas { //eventually all events from submenu items will be received by this menu for (const auto& [itemId, command] : commandList_) - menu_->Connect(itemId, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(ContextMenu::onSelection), new GenericCommand(command) /*pass ownership*/, this); + menu_->Bind(wxEVT_COMMAND_MENU_SELECTED, [command /*clang bug*/= command](wxCommandEvent& event) { command(); }, itemId); wnd.PopupMenu(menu_.get(), pos); wxTheApp->ProcessPendingEvents(); //make sure lambdas are evaluated before going out of scope; @@ -85,20 +90,8 @@ private: ContextMenu (const ContextMenu&) = delete; ContextMenu& operator=(const ContextMenu&) = delete; - void onSelection(wxCommandEvent& event) - { - if (auto cmd = dynamic_cast<GenericCommand*>(event.m_callbackUserData)) - (cmd->fun_)(); - } - - struct GenericCommand : public wxObject - { - GenericCommand(const std::function<void()>& fun) : fun_(fun) {} - std::function<void()> fun_; - }; - std::unique_ptr<wxMenu> menu_ = std::make_unique<wxMenu>(); - std::map<int, std::function<void()>> commandList_; //(item id, command) + std::map<int /*item id*/, std::function<void()> /*command*/> commandList_; }; } @@ -31,20 +31,36 @@ namespace zen BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer) }; */ - inline void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) { if (rect.width > 0 && //clearArea() is surprisingly expensive rect.height > 0) - { - wxDCPenChanger dummy (dc, col); - wxDCBrushChanger dummy2(dc, col); - dc.DrawRectangle(rect); + { + //wxDC::DrawRectangle() just widens inner area if wxTRANSPARENT_PEN is used! + //bonus: wxTRANSPARENT_PEN is about 2x faster than redundantly drawing with col! + wxDCPenChanger dummy (dc, *wxTRANSPARENT_PEN); + wxDCBrushChanger dummy2(dc, col); + dc.DrawRectangle(rect); } } +//properly draw rectangle respecting high DPI (and avoiding wxPen position fuzzyness) +inline +void drawFilledRectangle(wxDC& dc, wxRect rect, int borderWidth, const wxColor& borderCol, const wxColor& innerCol) +{ + assert(borderCol.IsSolid() && innerCol.IsSolid()); + wxDCPenChanger graphPen (dc, *wxTRANSPARENT_PEN); + wxDCBrushChanger graphBrush(dc, borderCol); + dc.DrawRectangle(rect); + rect.Deflate(borderWidth); //attention, more wxWidgets design mistakes: behavior of wxRect::Deflate depends on object being const/non-const!!! + + dc.SetBrush(innerCol); + dc.DrawRectangle(rect); +} + + /* Standard DPI: Windows/Ubuntu: 96 x 96 macOS: wxWidgets uses DIP (note: wxScreenDC().GetPPI() returns 72 x 72 which is a lie; looks like 96 x 96) */ @@ -52,17 +68,14 @@ void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) inline int fastFromDIP(int d) //like wxWindow::FromDIP (but tied to primary monitor and buffered) { -#ifdef wxHAVE_DPI_INDEPENDENT_PIXELS //pulled from wx/window.h: https://github.com/wxWidgets/wxWidgets/blob/master/include/wx/window.h#L2029 - return d; //e.g. macOS, GTK3 -#else //https://github.com/wxWidgets/wxWidgets/blob/master/src/common/wincmn.cpp#L2865 - static_assert(GTK_MAJOR_VERSION == 2); +#ifndef wxHAVE_DPI_INDEPENDENT_PIXELS +#error why is wxHAVE_DPI_INDEPENDENT_PIXELS not defined? +#endif //GTK2 doesn't properly support high DPI: https://freefilesync.org/forum/viewtopic.php?t=6114 //=> requires general fix at wxWidgets-level - assert(wxTheApp); //only call after wxWidgets was initalized! - static const int dpiY = wxScreenDC().GetPPI().y; //perf: buffering for calls to ::GetDeviceCaps() needed!? - const int defaultDpi = 96; - return 1.0 * d * dpiY / defaultDpi + 0.49 /*round values like 1.5 down, e.g. 1 pixel on 150% scale*/; -#endif + + //https://github.com/wxWidgets/wxWidgets/blob/d9d05c2bb201078f5e762c42458ca2f74af5b322/include/wx/window.h#L2060 + return d; //e.g. macOS, GTK3 } @@ -74,8 +87,8 @@ class RecursiveDcClipper public: RecursiveDcClipper(wxDC& dc, const wxRect& r) : dc_(dc) { - if (auto it = clippingAreas.find(&dc); - it != clippingAreas.end()) + if (auto it = clippingAreas_.find(&dc); + it != clippingAreas_.end()) { oldRect_ = it->second; @@ -87,7 +100,7 @@ public: else { dc_.SetClippingRegion(r); - clippingAreas.emplace(&dc_, r); + clippingAreas_.emplace(&dc_, r); } } @@ -97,10 +110,10 @@ public: if (oldRect_) { dc_.SetClippingRegion(*oldRect_); - clippingAreas[&dc_] = *oldRect_; + clippingAreas_[&dc_] = *oldRect_; } else - clippingAreas.erase(&dc_); + clippingAreas_.erase(&dc_); } private: @@ -108,7 +121,7 @@ private: RecursiveDcClipper& operator=(const RecursiveDcClipper&) = delete; //associate "active" clipping area with each DC - inline static std::unordered_map<wxDC*, wxRect> clippingAreas; + inline static std::unordered_map<wxDC*, wxRect> clippingAreas_; std::optional<wxRect> oldRect_; wxDC& dc_; diff --git a/wx+/file_drop.cpp b/wx+/file_drop.cpp index 938f9dbd..42cfbd3d 100644 --- a/wx+/file_drop.cpp +++ b/wx+/file_drop.cpp @@ -13,7 +13,10 @@ using namespace zen; -const wxEventType zen::EVENT_DROP_FILE = wxNewEventType(); +namespace zen +{ +wxDEFINE_EVENT(EVENT_DROP_FILE, FileDropEvent); +} @@ -23,7 +26,7 @@ namespace class WindowDropTarget : public wxFileDropTarget { public: - WindowDropTarget(const wxWindow& dropWindow) : dropWindow_(dropWindow) {} + explicit WindowDropTarget(const wxWindow& dropWindow) : dropWindow_(dropWindow) {} private: wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult def) override diff --git a/wx+/file_drop.h b/wx+/file_drop.h index 0a1089fc..e5de8f95 100644 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -16,47 +16,29 @@ namespace zen { -//register simple file drop event (without issue of freezing dialogs and without wxFileDropTarget overdesign) -//CAVEAT: a drop target window must not be directly or indirectly contained within a wxStaticBoxSizer until the following wxGTK bug -//is fixed. According to wxWidgets release cycles this is expected to be: never http://trac.wxwidgets.org/ticket/2763 +/* register simple file drop event (without issue of freezing dialogs and without wxFileDropTarget overdesign) + CAVEAT: a drop target window must not be directly or indirectly contained within a wxStaticBoxSizer until the following wxGTK bug + is fixed. According to wxWidgets release cycles this is expected to be: never http://trac.wxwidgets.org/ticket/2763 -/* -1. setup a window to emit EVENT_DROP_FILE: - - simple file system paths: setupFileDrop - - any shell paths with validation: setupShellItemDrop + 1. setup a window to emit EVENT_DROP_FILE: + - simple file system paths: setupFileDrop + - any shell paths with validation: setupShellItemDrop -2. register events: -wnd.Connect (EVENT_DROP_FILE, FileDropEventHandler(MyDlg::OnFilesDropped), nullptr, this); -wnd.Disconnect(EVENT_DROP_FILE, FileDropEventHandler(MyDlg::OnFilesDropped), nullptr, this); + 2. register events: + wnd.Bind(EVENT_DROP_FILE, [this](FileDropEvent& event) { onFilesDropped(event); }); */ +struct FileDropEvent; +wxDECLARE_EVENT(EVENT_DROP_FILE, FileDropEvent); -3. do something: -void MyDlg::OnFilesDropped(FileDropEvent& event); -*/ -extern const wxEventType EVENT_DROP_FILE; - - -class FileDropEvent : public wxCommandEvent +struct FileDropEvent : public wxEvent { -public: - FileDropEvent(const std::vector<Zstring>& droppedPaths) : wxCommandEvent(EVENT_DROP_FILE), droppedPaths_(droppedPaths) { StopPropagation(); } - - const std::vector<Zstring>& getPaths() const { return droppedPaths_; } - -private: - wxEvent* Clone() const override { return new FileDropEvent(*this); } + explicit FileDropEvent(const std::vector<Zstring>& droppedPaths) : wxEvent(0 /*winid*/, EVENT_DROP_FILE), itemPaths_(droppedPaths) {} + FileDropEvent* Clone() const override { return new FileDropEvent(*this); } - const std::vector<Zstring> droppedPaths_; + const std::vector<Zstring> itemPaths_; }; -using FileDropEventFunction = void (wxEvtHandler::*)(FileDropEvent&); - -#define FileDropEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FileDropEventFunction, &func) - - - void setupFileDrop(wxWindow& dropWindow); } diff --git a/wx+/graph.cpp b/wx+/graph.cpp index f3805fca..7bd67504 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -10,6 +10,7 @@ #include <numeric> #include <zen/basic_math.h> #include <zen/scope_guard.h> +#include <zen/perf.h> #include "dc.h" using namespace zen; @@ -17,7 +18,10 @@ using namespace zen; //todo: support zoom via mouse wheel? -const wxEventType zen::wxEVT_GRAPH_SELECTION = wxNewEventType(); +namespace zen +{ +wxDEFINE_EVENT(EVENT_GRAPH_SELECTION, GraphSelectEvent); +} double zen::nextNiceNumber(double blockSize) //round to next number which is a convenient to read block size @@ -44,26 +48,18 @@ wxColor getDefaultColor(size_t pos) { switch (pos % 10) { - case 0: - return { 0, 69, 134 }; //blue - case 1: - return { 255, 66, 14 }; //red - case 2: - return { 255, 211, 32 }; //yellow - case 3: - return { 87, 157, 28 }; //green - case 4: - return { 126, 0, 33 }; //royal - case 5: - return { 131, 202, 255 }; //light blue - case 6: - return { 49, 64, 4 }; //dark green - case 7: - return { 174, 207, 0 }; //light green - case 8: - return { 75, 31, 111 }; //purple - case 9: - return { 255, 149, 14 }; //orange + //*INDENT-OFF* + case 0: return { 0, 69, 134 }; //blue + case 1: return { 255, 66, 14 }; //red + case 2: return { 255, 211, 32 }; //yellow + case 3: return { 87, 157, 28 }; //green + case 4: return { 126, 0, 33 }; //royal + case 5: return { 131, 202, 255 }; //light blue + case 6: return { 49, 64, 4 }; //dark green + case 7: return { 174, 207, 0 }; //light green + case 8: return { 75, 31, 111 }; //purple + case 9: return { 255, 149, 14 }; //orange + //*INDENT-ON* } assert(false); return *wxBLACK; @@ -228,7 +224,7 @@ void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Grap //add text shadow to improve readability: wxDCTextColourChanger textColor(dc, colorBack); - dc.DrawText(txt, drawPos + border + wxSize(fastFromDIP(1), fastFromDIP(1))); + dc.DrawText(txt, drawPos + border + wxSize(1, 1) /*better without fastFromDIP()?*/); textColor.Set(colorText); dc.DrawText(txt, drawPos + border); @@ -292,7 +288,7 @@ struct GetIntersectionX { const double deltaX = to.x - from.x; const double deltaY = to.y - from.y; - return numeric::isNull(deltaX) ? to : CurvePoint(x_, from.y + (x_ - from.x) / deltaX * deltaY); + return numeric::isNull(deltaX) ? to : CurvePoint{x_, from.y + (x_ - from.x) / deltaX * deltaY}; } private: @@ -306,7 +302,7 @@ struct GetIntersectionY { const double deltaX = to.x - from.x; const double deltaY = to.y - from.y; - return numeric::isNull(deltaY) ? to : CurvePoint(from.x + (y_ - from.y) / deltaY * deltaX, y_); + return numeric::isNull(deltaY) ? to : CurvePoint{from.x + (y_ - from.y) / deltaY * deltaX, y_}; } private: @@ -350,7 +346,7 @@ std::vector<CurvePoint> ContinuousCurveData::getPoints(double minX, double maxX, for (int i = posFrom; i <= posTo; ++i) { const double x = cvrtX.screenToReal(i); - points.emplace_back(x, getValue(x)); + points.emplace_back(CurvePoint{x, getValue(x)}); } } return points; @@ -375,7 +371,7 @@ std::vector<CurvePoint> SparseCurveData::getPoints(double minX, double maxX, con if (addSteps_) if (pt.y != points.back().y) - points.emplace_back(CurvePoint(pt.x, points.back().y)); //[!] aliasing parameter not yet supported via emplace_back: VS bug! => make copy + points.emplace_back(CurvePoint{pt.x, points.back().y}); //[!] aliasing parameter not yet supported via emplace_back: VS bug! => make copy } points.push_back(pt); }; @@ -393,9 +389,9 @@ std::vector<CurvePoint> SparseCurveData::getPoints(double minX, double maxX, con const int posGe = ptGe ? cvrtX.realToScreenRound(ptGe->x) : i - 1; assert(!ptLe || posLe <= i); //check for invalid return values assert(!ptGe || posGe >= i); // - /* - Breakdown of all combinations of posLe, posGe and expected action (n >= 1) - Note: For every empty x-range of at least one pixel, both next and previous points must be saved to keep the interpolating line stable!!! + + /* Breakdown of all combinations of posLe, posGe and expected action (n >= 1) + Note: For every empty x-range of at least one pixel, both next and previous points must be saved to keep the interpolating line stable!!! posLe | posGe | action +-------+-------+-------- @@ -410,8 +406,7 @@ std::vector<CurvePoint> SparseCurveData::getPoints(double minX, double maxX, con | none | i + n | save ptGe; jump to position posGe + 1 | i | i + n | save ptLe; if n == 1: continue; else: save ptGe; jump to position posGe + 1 | i - n | i + n | save ptLe, ptGe; jump to position posGe + 1 - +-------+-------+-------- - */ + +-------+-------+-------- */ if (posGe < i) { if (posLe == i) @@ -448,18 +443,18 @@ Graph2D::Graph2D(wxWindow* parent, long style, const wxString& name) : wxPanel(parent, winid, pos, size, style, name) { - Connect(wxEVT_PAINT, wxPaintEventHandler(Graph2D::onPaintEvent), nullptr, this); - Connect(wxEVT_SIZE, wxSizeEventHandler (Graph2D::onSizeEvent ), nullptr, this); + Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { onPaintEvent(event); }); + Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { Refresh(); event.Skip(); }); Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) {}); //https://wiki.wxwidgets.org/Flicker-Free_Drawing //SetDoubleBuffered(true); slow as hell! SetBackgroundStyle(wxBG_STYLE_PAINT); - Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(Graph2D::OnMouseLeftDown), nullptr, this); - Connect(wxEVT_MOTION, wxMouseEventHandler(Graph2D::OnMouseMovement), nullptr, this); - Connect(wxEVT_LEFT_UP, wxMouseEventHandler(Graph2D::OnMouseLeftUp), nullptr, this); - Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(Graph2D::OnMouseCaptureLost), nullptr, this); + Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) { onMouseLeftDown(event); }); + Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { onMouseMovement(event); }); + Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& event) { onMouseLeftUp (event); }); + Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent& event) { onMouseCaptureLost(event); }); } @@ -471,7 +466,7 @@ void Graph2D::onPaintEvent(wxPaintEvent& event) } -void Graph2D::OnMouseLeftDown(wxMouseEvent& event) +void Graph2D::onMouseLeftDown(wxMouseEvent& event) { activeSel_ = std::make_unique<MouseSelection>(*this, event.GetPosition()); @@ -481,7 +476,7 @@ void Graph2D::OnMouseLeftDown(wxMouseEvent& event) } -void Graph2D::OnMouseMovement(wxMouseEvent& event) +void Graph2D::onMouseMovement(wxMouseEvent& event) { if (activeSel_.get()) { @@ -491,7 +486,7 @@ void Graph2D::OnMouseMovement(wxMouseEvent& event) } -void Graph2D::OnMouseLeftUp(wxMouseEvent& event) +void Graph2D::onMouseLeftUp(wxMouseEvent& event) { if (activeSel_.get()) { @@ -510,7 +505,7 @@ void Graph2D::OnMouseLeftUp(wxMouseEvent& event) } -void Graph2D::OnMouseCaptureLost(wxMouseCaptureLostEvent& event) +void Graph2D::onMouseCaptureLost(wxMouseCaptureLostEvent& event) { activeSel_.reset(); Refresh(); @@ -546,15 +541,14 @@ void Graph2D::render(wxDC& dc) const //wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); const int xLabelHeight = attr_.xLabelHeight ? *attr_.xLabelHeight : GetCharHeight() + fastFromDIP(2) /*margin*/; - const int yLabelWidth = attr_.yLabelWidth ? *attr_.yLabelWidth : dc.GetTextExtent(L"1,23457e+07").x; - - /* - ----------------------- - | | x-label | - ----------------------- - |y-label | graph area | - |---------------------- - */ + const int yLabelWidth = attr_.yLabelWidth ? *attr_.yLabelWidth : dc.GetTextExtent(L"1.23457e+07").x; + + /* ----------------------- + | | x-label | + ----------------------- + |y-label | graph area | + |---------------------- */ + wxRect graphArea = clientRect; int xLabelPosY = clientRect.y; int yLabelPosX = clientRect.x; @@ -589,15 +583,9 @@ void Graph2D::render(wxDC& dc) const assert(attr_.labelposX == LABEL_X_NONE || attr_.labelFmtX); assert(attr_.labelposY == LABEL_Y_NONE || attr_.labelFmtY); - { - //paint graph background (excluding label area) - wxDCPenChanger dummy (dc, wxPen(getBorderColor(), fastFromDIP(1))); - wxDCBrushChanger dummy2(dc, attr_.colorBack); - //accessibility: consider system text and background colors; small drawback: color of graphs is NOT connected to the background! => responsibility of client to use correct colors - - dc.DrawRectangle(graphArea); - graphArea.Deflate(1, 1); //attention more wxWidgets design mistakes: behavior of wxRect::Deflate depends on object being const/non-const!!! - } + //paint graph background (excluding label area) + drawFilledRectangle(dc, graphArea, fastFromDIP(1), getBorderColor(), attr_.colorBack); + graphArea.Deflate(fastFromDIP(1)); //set label areas respecting graph area border! const wxRect xLabelArea(graphArea.x, xLabelPosY, graphArea.width, xLabelHeight); @@ -691,8 +679,8 @@ void Graph2D::render(wxDC& dc) const if (curves_[index].second.fillMode == CurveAttributes::FILL_CURVE) if (!cp.empty()) { - cp.emplace_back(CurvePoint(cp.back ().x, minY)); //add lower right and left corners - cp.emplace_back(CurvePoint(cp.front().x, minY)); //[!] aliasing parameter not yet supported via emplace_back: VS bug! => make copy + cp.emplace_back(CurvePoint{cp.back ().x, minY}); //add lower right and left corners + cp.emplace_back(CurvePoint{cp.front().x, minY}); //[!] aliasing parameter not yet supported via emplace_back: VS bug! => make copy oobMarker[index].back() = true; oobMarker[index].push_back(true); oobMarker[index].push_back(true); @@ -733,26 +721,26 @@ void Graph2D::render(wxDC& dc) const widen(&screenFromY, &screenToY); //save current selection as "double" coordinates - activeSel_->refSelection().from = CurvePoint(cvrtX.screenToReal(screenFromX), - cvrtY.screenToReal(screenFromY)); + activeSel_->refSelection().from = CurvePoint{cvrtX.screenToReal(screenFromX), + cvrtY.screenToReal(screenFromY)}; - activeSel_->refSelection().to = CurvePoint(cvrtX.screenToReal(screenToX), - cvrtY.screenToReal(screenToY)); + activeSel_->refSelection().to = CurvePoint{cvrtX.screenToReal(screenToX), + cvrtY.screenToReal(screenToY)}; } //#################### begin drawing #################### //1. draw colored area under curves for (auto it = curves_.begin(); it != curves_.end(); ++it) if (it->second.fillMode != CurveAttributes::FILL_NONE) - { - const std::vector<wxPoint>& points = drawPoints[it - curves_.begin()]; - if (points.size() >= 3) + if (const std::vector<wxPoint>& points = drawPoints[it - curves_.begin()]; + points.size() >= 3) { - wxDCBrushChanger dummy(dc, it->second.fillColor); - wxDCPenChanger dummy2(dc, wxPen(it->second.fillColor, fastFromDIP(1))); + //wxDC::DrawPolygon() draws *transparent* border if wxTRANSPARENT_PEN is used! + //unlike wxDC::DrawRectangle() which just widens inner area! + wxDCPenChanger dummy (dc, wxPen(it->second.fillColor, 1 /*[!] width*/)); + wxDCBrushChanger dummy2(dc, it->second.fillColor); dc.DrawPolygon(static_cast<int>(points.size()), &points[0]); } - } //2. draw all currently set mouse selections (including active selection) std::vector<SelectionBlock> allSelections = oldSel_; diff --git a/wx+/graph.h b/wx+/graph.h index f1f0c76c..a6e99200 100644 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -19,26 +19,19 @@ //elegant 2D graph as wxPanel specialization namespace zen { -/* -Example: - //init graph (optional) +/* //init graph (optional) m_panelGraph->setAttributes(Graph2D::MainAttributes(). setLabelX(Graph2D::LABEL_X_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()). setLabelY(Graph2D::LABEL_Y_RIGHT, 60, std::make_shared<LabelFormatterBytes>())); //set graph data std::shared_ptr<CurveData> curveDataBytes_ = ... - m_panelGraph->setCurve(curveDataBytes_, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0))); -*/ + m_panelGraph->setCurve(curveDataBytes_, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0))); */ struct CurvePoint { - CurvePoint() {} - CurvePoint(double xVal, double yVal) : x(xVal), y(yVal) {} double x = 0; double y = 0; }; -inline bool operator==(const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } -inline bool operator!=(const CurvePoint& lhs, const CurvePoint& rhs) { return !(lhs == rhs); } struct CurveData @@ -84,7 +77,7 @@ private: const size_t sz = getSize(); const size_t pos = std::min<ptrdiff_t>(std::floor(x), sz - 1); //[!] expect unsigned underflow if empty! if (pos < sz) - return CurvePoint(pos, getValue(pos)); + return CurvePoint{1.0 * pos, getValue(pos)}; return {}; } @@ -92,7 +85,7 @@ private: { const size_t pos = std::max<ptrdiff_t>(std::ceil(x), 0); //[!] use std::max with signed type! if (pos < getSize()) - return CurvePoint(pos, getValue(pos)); + return CurvePoint{1.0 * pos, getValue(pos)}; return {}; } }; @@ -131,12 +124,11 @@ struct DecimalNumberFormatter : public LabelFormatter }; //------------------------------------------------------------------------------------------------------------ +//example: wnd.Bind(EVENT_GRAPH_SELECTION, [this](GraphSelectEvent& event) { onGraphSelect(event); }); -//emit data selection event -//Usage: wnd.Connect(wxEVT_GRAPH_SELECTION, GraphSelectEventHandler(MyDlg::OnGraphSelection), nullptr, this); -// void MyDlg::OnGraphSelection(GraphSelectEvent& event); +struct GraphSelectEvent; +wxDECLARE_EVENT(EVENT_GRAPH_SELECTION, GraphSelectEvent); -extern const wxEventType wxEVT_GRAPH_SELECTION; struct SelectionBlock { @@ -144,23 +136,13 @@ struct SelectionBlock CurvePoint to; }; -class GraphSelectEvent : public wxCommandEvent +struct GraphSelectEvent : public wxEvent { -public: - GraphSelectEvent(const SelectionBlock& selBlock) : wxCommandEvent(wxEVT_GRAPH_SELECTION), selBlock_(selBlock) {} - wxEvent* Clone() const override { return new GraphSelectEvent(selBlock_); } - - SelectionBlock getSelection() { return selBlock_; } + explicit GraphSelectEvent(const SelectionBlock& selBlock) : wxEvent(0 /*winid*/, EVENT_GRAPH_SELECTION), selectBlock_(selBlock) {} + GraphSelectEvent* Clone() const override { return new GraphSelectEvent(*this); } -private: - SelectionBlock selBlock_; + SelectionBlock selectBlock_; }; - -using GraphSelectEventFunction = void (wxEvtHandler::*)(GraphSelectEvent&); - -#define GraphSelectEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GraphSelectEventFunction, &func) - //------------------------------------------------------------------------------------------------------------ class Graph2D : public wxPanel @@ -171,7 +153,7 @@ public: const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL | wxNO_BORDER, - const wxString& name = wxPanelNameStr); + const wxString& name = wxASCII_STR(wxPanelNameStr)); class CurveAttributes { @@ -288,6 +270,8 @@ public: std::map<PosCorner, wxString> cornerTexts; + //accessibility: consider system text and background colors; + //small drawback: color of graphs is NOT connected to the background! => responsibility of client to use correct colors wxColor colorText = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); wxColor colorBack = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -308,13 +292,12 @@ public: void clearSelection() { oldSel_.clear(); Refresh(); } private: - void OnMouseLeftDown(wxMouseEvent& event); - void OnMouseMovement(wxMouseEvent& event); - void OnMouseLeftUp (wxMouseEvent& event); - void OnMouseCaptureLost(wxMouseCaptureLostEvent& event); + void onMouseLeftDown(wxMouseEvent& event); + void onMouseMovement(wxMouseEvent& event); + void onMouseLeftUp (wxMouseEvent& event); + void onMouseCaptureLost(wxMouseCaptureLostEvent& event); void onPaintEvent(wxPaintEvent& event); - void onSizeEvent(wxSizeEvent& event) { Refresh(); event.Skip(); } void render(wxDC& dc) const; diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 3f1599f3..80c9aaf1 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -55,8 +55,7 @@ const int COLUMN_MOVE_MARKER_WIDTH_DIP = 3; const bool fillGapAfterColumns = true; //draw rows/column label to fill full window width; may become an instance variable some time? -/* -IsEnabled() vs IsThisEnabled() since wxWidgets 2.9.5: +/* IsEnabled() vs IsThisEnabled() since wxWidgets 2.9.5: void wxWindowBase::NotifyWindowOnEnableChange(), called from bool wxWindowBase::Enable(), fails to refresh child elements when disabling a IsTopLevel() dialog, e.g. when showing a modal dialog. @@ -73,8 +72,7 @@ The perfect solution would be a bool renderAsEnabled() { return "IsEnabled() but However "IsThisEnabled()" is good enough (same like the old IsEnabled() on wxWidgets 2.8.12) and it avoids this pathetic behavior on XP. (Similar problem on Win 7: e.g. directly click sync button without comparing first) -=> 2018-07-30: roll our own: -*/ +=> 2018-07-30: roll our own: */ bool renderAsEnabled(wxWindow& win) { if (win.IsTopLevel()) @@ -88,25 +86,39 @@ bool renderAsEnabled(wxWindow& win) } //---------------------------------------------------------------------------------------------------------------- -const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOUBLE = wxNewEventType(); -const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOWN = wxNewEventType(); -const wxEventType zen::EVENT_GRID_MOUSE_LEFT_UP = wxNewEventType(); -const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_DOWN = wxNewEventType(); -const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_UP = wxNewEventType(); -const wxEventType zen::EVENT_GRID_SELECT_RANGE = wxNewEventType(); -const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_LEFT = wxNewEventType(); -const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_RIGHT = wxNewEventType(); -const wxEventType zen::EVENT_GRID_COL_RESIZE = wxNewEventType(); +namespace zen +{ +wxDEFINE_EVENT(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEvent); +wxDEFINE_EVENT(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEvent); +wxDEFINE_EVENT(EVENT_GRID_MOUSE_RIGHT_DOWN, GridClickEvent); +wxDEFINE_EVENT(EVENT_GRID_SELECT_RANGE, GridSelectEvent); +wxDEFINE_EVENT(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEvent); +wxDEFINE_EVENT(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEvent); +wxDEFINE_EVENT(EVENT_GRID_COL_RESIZE, GridColumnResizeEvent); +wxDEFINE_EVENT(EVENT_GRID_CONTEXT_MENU, GridContextMenuEvent); +} //---------------------------------------------------------------------------------------------------------------- void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) { - drawCellBackground(dc, rect, enabled, selected, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + if (enabled) + { + if (selected) + dc.GradientFillLinear(rect, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST); + else + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + } + else + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); } void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) { + wxDCTextColourChanger textColor(dc); + if (enabled && selected) //accessibility: always set *both* foreground AND background colors! + textColor.Set(*wxBLACK); + wxRect rectTmp = drawCellBorder(dc, rect); rectTmp.x += getColumnGapLeft(); @@ -131,20 +143,6 @@ wxRect GridData::drawCellBorder(wxDC& dc, const wxRect& rect) //returns remainin } -void GridData::drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, const wxColor& backgroundColor) -{ - if (enabled) - { - if (selected) - dc.GradientFillLinear(rect, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST); - else - clearArea(dc, rect, backgroundColor); - } - else - clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); -} - - void GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& text, int alignment, const wxSize* textExtentHint) { /* Performance Notes (Windows): @@ -270,37 +268,37 @@ class Grid::SubWindow : public wxWindow { public: SubWindow(Grid& parent) : - wxWindow(&parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBORDER_NONE, wxPanelNameStr), + wxWindow(&parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBORDER_NONE, wxASCII_STR(wxPanelNameStr)), parent_(parent) { - Connect(wxEVT_PAINT, wxPaintEventHandler(SubWindow::onPaintEvent), nullptr, this); - Connect(wxEVT_SIZE, wxSizeEventHandler (SubWindow::onSizeEvent), nullptr, this); + Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { onPaintEvent(event); }); + Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { Refresh(); event.Skip(); }); Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) {}); //https://wiki.wxwidgets.org/Flicker-Free_Drawing //SetDoubleBuffered(true); slow as hell! SetBackgroundStyle(wxBG_STYLE_PAINT); - Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(SubWindow::onFocus), nullptr, this); - Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(SubWindow::onFocus), nullptr, this); - Connect(wxEVT_CHILD_FOCUS, wxEventHandler(SubWindow::onChildFocus), nullptr, this); + Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) { onFocus(event); }); + Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& event) { onFocus(event); }); + Bind(wxEVT_CHILD_FOCUS, [](wxChildFocusEvent& event) {}); //wxGTK::wxScrolledWindow automatically scrolls to child window when child gets focus -> prevent! - Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(SubWindow::onMouseLeftDown ), nullptr, this); - Connect(wxEVT_LEFT_UP, wxMouseEventHandler(SubWindow::onMouseLeftUp ), nullptr, this); - Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(SubWindow::onMouseLeftDouble), nullptr, this); - Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(SubWindow::onMouseRightDown ), nullptr, this); - Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(SubWindow::onMouseRightUp ), nullptr, this); - Connect(wxEVT_MOTION, wxMouseEventHandler(SubWindow::onMouseMovement ), nullptr, this); - Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(SubWindow::onLeaveWindow ), nullptr, this); - Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(SubWindow::onMouseWheel ), nullptr, this); - Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(SubWindow::onMouseCaptureLost), nullptr, this); + Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) { onMouseLeftDown (event); }); + Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& event) { onMouseLeftUp (event); }); + Bind(wxEVT_LEFT_DCLICK, [this](wxMouseEvent& event) { onMouseLeftDouble(event); }); + Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onMouseRightDown (event); }); + Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent& event) { onMouseRightUp (event); }); + Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { onMouseMovement (event); }); + Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { onLeaveWindow (event); }); + Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent& event) { onMouseWheel (event); }); + Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent& event) { onMouseCaptureLost(event); }); - Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(SubWindow::onKeyDown), nullptr, this); - Connect(wxEVT_KEY_UP, wxKeyEventHandler(SubWindow::onKeyUp ), nullptr, this); + Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event); }); + Bind(wxEVT_KEY_UP, [this](wxKeyEvent& event) { onKeyUp (event); }); assert(GetClientAreaOrigin() == wxPoint()); //generally assumed when dealing with coordinates below } - Grid& refParent() { return parent_; } + Grid& refParent() { return parent_; } const Grid& refParent() const { return parent_; } template <class T> @@ -337,7 +335,6 @@ private: virtual void render(wxDC& dc, const wxRect& rect) = 0; virtual void onFocus(wxFocusEvent& event) { event.Skip(); } - virtual void onChildFocus(wxEvent& event) {} //wxGTK::wxScrolledWindow automatically scrolls to child window when child gets focus -> prevent! virtual void onMouseLeftDown (wxMouseEvent& event) { event.Skip(); } virtual void onMouseLeftUp (wxMouseEvent& event) { event.Skip(); } @@ -362,19 +359,30 @@ private: void onMouseWheel(wxMouseEvent& event) { - /* - MSDN, WM_MOUSEWHEEL: "Sent to the focus window when the mouse wheel is rotated. - The DefWindowProc function propagates the message to the window's parent. - There should be no internal forwarding of the message, since DefWindowProc propagates - it up the parent chain until it finds a window that processes it." + /* MSDN, WM_MOUSEWHEEL: "Sent to the focus window when the mouse wheel is rotated. + The DefWindowProc function propagates the message to the window's parent. + There should be no internal forwarding of the message, since DefWindowProc propagates + it up the parent chain until it finds a window that processes it." - On OS X there is no such propagation! => we need a redirection (the same wxGrid implements) - */ + On OS X there is no such propagation! => we need a redirection (the same wxGrid implements) - //new wxWidgets 3.0 screw-up for GTK2: wxScrollHelperEvtHandler::ProcessEvent() ignores wxEVT_MOUSEWHEEL events - //thereby breaking the scenario of redirection to parent we need here (but also breaking their very own wxGrid sample) - //=> call wxScrolledWindow mouse wheel handler directly - parent_.HandleOnMouseWheel(event); + new wxWidgets 3.0 screw-up for GTK2: wxScrollHelperEvtHandler::ProcessEvent() ignores wxEVT_MOUSEWHEEL events + thereby breaking the scenario of redirection to parent we need here (but also breaking their very own wxGrid sample) + => call wxScrolledWindow mouse wheel handler directly */ + + //wxWidgets never ceases to amaze: multi-line scrolling is implemented maximally inefficient by repeating wxEVT_SCROLLWIN_LINEUP!! => WTF! + if (event.GetWheelAxis() == wxMOUSE_WHEEL_VERTICAL && //=> reimplement wxScrollHelperBase::HandleOnMouseWheel() in a non-retarded way + !event.IsPageScroll()) + { + mouseRotateRemainder_ += -event.GetWheelRotation(); + const int rotations = mouseRotateRemainder_ / event.GetWheelDelta(); + mouseRotateRemainder_ -= rotations * event.GetWheelDelta(); + + const int rowsDelta = rotations * event.GetLinesPerAction(); + parent_.scrollDelta(0, rowsDelta); + } + else + parent_.HandleOnMouseWheel(event); //if (!sendEventToParent(event)) // event.Skip(); @@ -392,14 +400,9 @@ private: render(dc, it.GetRect()); } - void onSizeEvent(wxSizeEvent& event) - { - Refresh(); - event.Skip(); - } - Grid& parent_; std::optional<wxBitmap> doubleBuffer_; + int mouseRotateRemainder_ = 0; }; //---------------------------------------------------------------------------------------------------------------- @@ -432,7 +435,7 @@ private: dc.DrawLine(clientRect.GetBottomLeft(), clientRect.GetBottomRight()); wxRect rectShrinked = clientRect; - rectShrinked.Deflate(1); + rectShrinked.Deflate(fastFromDIP(1)); dc.SetPen(wxPen(*wxWHITE, fastFromDIP(1))); //dc.DrawLine(clientRect.GetTopLeft(), clientRect.GetTopRight() + wxPoint(1, 0)); @@ -528,7 +531,7 @@ private: //label text wxRect textRect = rect; - textRect.Deflate(1); + textRect.Deflate(fastFromDIP(1)); GridData::drawCellText(dc, textRect, formatRow(row), wxALIGN_CENTRE); @@ -657,7 +660,7 @@ private: const int clientWidth = GetClientSize().GetWidth(); //need reliable, stable width in contrast to rect.width if (totalWidth < clientWidth) - drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(clientWidth - totalWidth, colLabelHeight)), absWidths.size(), ColumnType::NONE, enabled); + drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(clientWidth - totalWidth, colLabelHeight)), absWidths.size(), ColumnType::none, enabled); } } @@ -758,7 +761,7 @@ private: const int bestWidth = refParent().getBestColumnSize(action->col); //return -1 on error if (bestWidth >= 0) { - refParent().setColumnWidth(bestWidth, action->col, GridEventPolicy::ALLOW); + refParent().setColumnWidth(bestWidth, action->col, GridEventPolicy::allow); refParent().Refresh(); //refresh main grid as well! } } @@ -773,12 +776,12 @@ private: const int newWidth = activeResizing_->getStartWidth() + event.GetPosition().x - activeResizing_->getStartPosX(); //set width tentatively - refParent().setColumnWidth(newWidth, col, GridEventPolicy::ALLOW); + refParent().setColumnWidth(newWidth, col, GridEventPolicy::allow); //check if there's a small gap after last column, if yes, fill it const int gapWidth = GetClientSize().GetWidth() - refParent().getColWidthsSum(GetClientSize().GetWidth()); if (std::abs(gapWidth) < fastFromDIP(COLUMN_FILL_GAP_TOLERANCE_DIP)) - refParent().setColumnWidth(newWidth + gapWidth, col, GridEventPolicy::ALLOW); + refParent().setColumnWidth(newWidth + gapWidth, col, GridEventPolicy::allow); refParent().Refresh(); //refresh columns on main grid as well! } @@ -815,8 +818,8 @@ private: const std::wstring toolTip = [&] { const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); - const ColumnType colType = refParent().getColumnAtPos(absPos.x).colType; //returns ColumnType::NONE if no column at x position! - if (colType != ColumnType::NONE) + const ColumnType colType = refParent().getColumnAtPos(absPos.x).colType; //returns ColumnType::none if no column at x position! + if (colType != ColumnType::none) if (auto prov = refParent().getDataProvider()) return prov->getToolTip(colType); return std::wstring(); @@ -846,7 +849,7 @@ private: else //notify right click (on free space after last column) if (fillGapAfterColumns) - sendEventToParent(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, ColumnType::NONE)); + sendEventToParent(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, ColumnType::none)); event.Skip(); } @@ -859,7 +862,7 @@ private: //---------------------------------------------------------------------------------------------------------------- namespace { -const wxEventType EVENT_GRID_HAS_SCROLLED = wxNewEventType(); //internal to Grid::MainWin::ScrollWindow() +wxDEFINE_EVENT(EVENT_GRID_HAS_SCROLLED, wxCommandEvent); } //---------------------------------------------------------------------------------------------------------------- @@ -872,7 +875,7 @@ public: rowLabelWin_(rowLabelWin), colLabelWin_(colLabelWin) { - Connect(EVENT_GRID_HAS_SCROLLED, wxEventHandler(MainWin::onRequestWindowUpdate), nullptr, this); + Bind(EVENT_GRID_HAS_SCROLLED, [this](wxCommandEvent& event) { onRequestWindowUpdate(event); }); } ~MainWin() { assert(!gridUpdatePending_); } @@ -951,7 +954,7 @@ private: } else if (highlight_.row == row) return highlight_.rowHover; - return HoverArea::NONE; + return HoverArea::none; } bool drawAsSelected(size_t row) const @@ -976,10 +979,14 @@ private: { if (auto prov = refParent().getDataProvider()) { + wxClientDC dc(this); + dc.SetFont(GetFont()); + + const ptrdiff_t rowCount = refParent().getRowCount(); const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! - const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::none if no column at x position! + const HoverArea rowHover = 0 <= row && row < rowCount ? prov->getRowMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth) : HoverArea::none; const wxPoint mousePos = GetPosition() + event.GetPosition(); //client is interested in all double-clicks, even those outside of the grid! @@ -992,40 +999,58 @@ private: { if (auto prov = refParent().getDataProvider()) { + onMouseMovement(event); //update highlight in obscure cases (e.g. right-click while context menu is open) + + wxClientDC dc(this); + dc.SetFont(GetFont()); + + const ptrdiff_t rowCount = refParent().getRowCount(); const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! - const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::none if no column at x position! + const HoverArea rowHover = 0 <= row && row < rowCount ? prov->getRowMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth) : HoverArea::none; const wxPoint mousePos = GetPosition() + event.GetPosition(); - //row < 0 possible!!! Pressing "Menu Key" simulates mouse-right-button down + up at position 0xffff/0xffff! + + assert(row >= 0); + //row < 0 was possible in older wxWidgets: https://github.com/wxWidgets/wxWidgets/commit/2c69d27c0d225d3a331c773da466686153185320#diff-9f11c8f2cb1f734f7c0c1071aba491a5 + //=> pressing "Menu Key" simulated mouse-right-button down + up at position 0xffff/0xffff! GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, row, rowHover, mousePos); - if (!sendEventToParent(mouseEvent)) //allow client to swallow event! + + freezeMouseHighlight_ = true; //e.g. while showing context menu + const bool processed = sendEventToParent(mouseEvent); //allow client to swallow event! + freezeMouseHighlight_ = false; + + if (!processed) { if (wxWindow::FindFocus() != this) //doesn't seem to happen automatically for right mouse button SetFocus(); - if (row >= 0) - if (!event.RightDown() || !refParent().isSelected(row)) //do NOT start a new selection if user right-clicks on a selected area! + if (event.RightDown() && (row < 0 || refParent().isSelected(row))) //=> open context menu *immediately* and do *not* start a new selection + sendEventToParent(GridContextMenuEvent(mousePos)); + else if (row >= 0) + { + if (event.ControlDown()) + activeSelection_ = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row) /*positive*/, false /*gridWasCleared*/, mouseEvent); + else if (event.ShiftDown()) { - if (event.ControlDown()) - activeSelection_ = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row) /*positive*/, false /*gridWasCleared*/, mouseEvent); - else if (event.ShiftDown()) - { - refParent().clearSelection(GridEventPolicy::DENY); - activeSelection_ = std::make_unique<MouseSelection>(*this, selectionAnchor_, true /*positive*/, true /*gridWasCleared*/, mouseEvent); - } - else - { - refParent().clearSelection(GridEventPolicy::DENY); - activeSelection_ = std::make_unique<MouseSelection>(*this, row, true /*positive*/, true /*gridWasCleared*/, mouseEvent); - //DO NOT emit range event for clearing selection! would be inconsistent with keyboard handling (moving cursor neither emits range event) - //and is also harmful when range event is considered a final action - //e.g. cfg grid would prematurely show a modal dialog after changed config - } + refParent().clearSelection(GridEventPolicy::deny); + activeSelection_ = std::make_unique<MouseSelection>(*this, selectionAnchor_, true /*positive*/, true /*gridWasCleared*/, mouseEvent); } - Refresh(); + else + { + refParent().clearSelection(GridEventPolicy::deny); + activeSelection_ = std::make_unique<MouseSelection>(*this, row, true /*positive*/, true /*gridWasCleared*/, mouseEvent); + //DO NOT emit range event for clearing selection! would be inconsistent with keyboard handling (moving cursor neither emits range event) + //and is also harmful when range event is considered a final action + //e.g. cfg grid would prematurely show a modal dialog after changed config + } + } } + + //update mouse highlight (in case it was frozen above) + event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)! + onMouseMovement(event); } event.Skip(); //allow changing focus } @@ -1056,29 +1081,39 @@ private: const ptrdiff_t rowTo = activeSelection_->getCurrentRow(); const bool positive = activeSelection_->isPositiveSelect(); const GridClickEvent mouseClick = activeSelection_->getFirstClick(); + assert((mouseClick.GetEventType() == EVENT_GRID_MOUSE_RIGHT_DOWN) == event.RightUp()); activeSelection_.reset(); //release mouse capture *before* sending the event (which might show a modal popup dialog requiring the mouse!!!) - refParent().selectRange(rowFrom, rowTo, positive, &mouseClick, GridEventPolicy::ALLOW); - } + refParent().selectRange(rowFrom, rowTo, positive, &mouseClick, GridEventPolicy::allow); - if (auto prov = refParent().getDataProvider()) - { - //this one may point to row which is not in visible area! - const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); - const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! - const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); - const wxPoint mousePos = GetPosition() + event.GetPosition(); - //notify click event after the range selection! e.g. this makes sure the selection is applied before showing a context menu - sendEventToParent(GridClickEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, row, rowHover, mousePos)); + if (mouseClick.GetEventType() == EVENT_GRID_MOUSE_RIGHT_DOWN) + sendEventToParent(GridContextMenuEvent(mouseClick.mousePos_)); } - //update highlight_ and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) +#if 0 + if (!event.RightUp()) + if (auto prov = refParent().getDataProvider()) + { + wxClientDC dc(this); + dc.SetFont(GetFont()); + + //this one may point to row which is not in visible area! + const ptrdiff_t rowCount = refParent().getRowCount(); + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::none if no column at x position! + const HoverArea rowHover = 0 <= row && row < rowCount ? prov->getRowMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth) : HoverArea::none; + const wxPoint mousePos = GetPosition() + event.GetPosition(); + //notify click event after the range selection! e.g. this makes sure the selection is applied before showing a context menu + sendEventToParent(GridClickEvent(EVENT_GRID_MOUSE_LEFT_UP, row, rowHover, mousePos)); + } +#endif + + //update mouse highlight and tooltip: macOS no mouse movement event is generated after a mouse button click (unlike on Windows) event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)! onMouseMovement(event); - Refresh(); event.Skip(); //allow changing focus } @@ -1087,12 +1122,12 @@ private: if (activeSelection_) { if (activeSelection_->gridWasCleared()) - refParent().clearSelection(GridEventPolicy::ALLOW); //see onMouseDown(); selection is "completed" => emit GridSelectEvent + refParent().clearSelection(GridEventPolicy::allow); //see onMouseDown(); selection is "completed" => emit GridSelectEvent activeSelection_.reset(); + Refresh(); } - highlight_.row = -1; - Refresh(); + updateMouseHover({-1, HoverArea::none}); //event.Skip(); -> we DID handle it! } @@ -1100,15 +1135,17 @@ private: { if (auto prov = refParent().getDataProvider()) { + wxClientDC dc(this); + dc.SetFont(GetFont()); + const ptrdiff_t rowCount = refParent().getRowCount(); const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! - const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::none if no column at x position! const std::wstring toolTip = [&] { - if (cpi.colType != ColumnType::NONE && 0 <= row && row < rowCount) + if (cpi.colType != ColumnType::none && 0 <= row && row < rowCount) return prov->getToolTip(row, cpi.colType); return std::wstring(); }(); @@ -1118,22 +1155,17 @@ private: activeSelection_->evalMousePos(); //call on both mouse movement + timer event! else { - refreshHighlight(highlight_); - highlight_.row = row; - highlight_.rowHover = rowHover; - refreshHighlight(highlight_); //multiple Refresh() calls are condensed into single one! + const HoverArea rowHover = 0 <= row && row < rowCount ? prov->getRowMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth) : HoverArea::none; + updateMouseHover({row, rowHover}); } } event.Skip(); } - void onLeaveWindow(wxMouseEvent& event) override //wxEVT_LEAVE_WINDOW does not respect mouse capture! + void onLeaveWindow(wxMouseEvent& event) override { - if (!activeSelection_) - { - refreshHighlight(highlight_); - highlight_.row = -1; - } + if (!activeSelection_) //wxEVT_LEAVE_WINDOW does not respect mouse capture! + updateMouseHover({-1, HoverArea::none}); event.Skip(); } @@ -1148,9 +1180,10 @@ private: wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positive), gridWasCleared_(gridWasCleared), firstClick_(firstClick) { wnd_.CaptureMouse(); - timer_.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this); + timer_.Bind(wxEVT_TIMER, [this](wxTimerEvent& event) { evalMousePos(); }); timer_.Start(100); //timer interval in ms evalMousePos(); + wnd_.Refresh(); } ~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); } @@ -1179,6 +1212,7 @@ private: int pixelsPerUnitY = 0; wnd_.refParent().GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY); + assert(pixelsPerUnitY > 0); if (pixelsPerUnitY <= 0) return; @@ -1211,6 +1245,7 @@ private: const wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed); const ptrdiff_t newRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + assert(newRow >= 0); if (newRow >= 0) if (rowCurrent_ != newRow) { @@ -1220,8 +1255,6 @@ private: } private: - void onTimer(wxEvent& event) { evalMousePos(); } - MainWin& wnd_; const size_t rowStart_; ptrdiff_t rowCurrent_; @@ -1237,7 +1270,9 @@ private: struct MouseHighlight { ptrdiff_t row = -1; - HoverArea rowHover = HoverArea::NONE; + HoverArea rowHover = HoverArea::none; + + bool operator==(const MouseHighlight&) const = default; }; void ScrollWindow(int dx, int dy, const wxRect* rect) override @@ -1266,7 +1301,7 @@ private: assert(gridUpdatePending_); ZEN_ON_SCOPE_EXIT(gridUpdatePending_ = false); - refParent().updateWindowSizes(false); //row label width has changed -> do *not* update scrollbars: recursion on wxGTK! -> still a problem, now that we're called async?? + refParent().updateWindowSizes(false); //row label width has changed -> do *not* update scrollbars: recursion on wxGTK! -> still a problem, now that this function is called async?? rowLabelWin_.Update(); //update while dragging scroll thumb } @@ -1278,18 +1313,27 @@ private: RefreshRect(cellArea, false); } - void refreshHighlight(const MouseHighlight& hl) + void updateMouseHover(const MouseHighlight& hl) { - const ptrdiff_t rowCount = refParent().getRowCount(); - if (0 <= hl.row && hl.row < rowCount && hl.rowHover != HoverArea::NONE) //no highlight_? => NOP! - refreshRow(hl.row); + if (highlight_ != hl && !freezeMouseHighlight_) + { + const ptrdiff_t rowCount = refParent().getRowCount(); + if (0 <= highlight_.row && highlight_.row < rowCount && highlight_.rowHover != HoverArea::none) //no highlight_? => NOP! + refreshRow(highlight_.row); + + highlight_ = hl; + + if (0 <= highlight_.row && highlight_.row < rowCount && highlight_.rowHover != HoverArea::none) //no highlight_? => NOP! + refreshRow(highlight_.row); + } } RowLabelWin& rowLabelWin_; ColLabelWin& colLabelWin_; std::unique_ptr<MouseSelection> activeSelection_; //bound while user is selecting with mouse - MouseHighlight highlight_; //current mouse highlight_ (superseded by activeSelection_ if available) + MouseHighlight highlight_; //current mouse highlight + bool freezeMouseHighlight_ = false; ptrdiff_t cursorRow_ = 0; size_t selectionAnchor_ = 0; @@ -1326,12 +1370,11 @@ Grid::Grid(wxWindow* parent, assert(GetClientSize() == GetSize() && GetWindowBorderSize() == wxSize()); //borders are NOT allowed for Grid //reason: updateWindowSizes() wants to use "GetSize()" as a "GetClientSize()" including scrollbars - Connect(wxEVT_PAINT, wxPaintEventHandler(Grid::onPaintEvent), nullptr, this); - Connect(wxEVT_SIZE, wxSizeEventHandler (Grid::onSizeEvent ), nullptr, this); + Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { wxPaintDC dc(this); }); + Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { updateWindowSizes(); event.Skip(); }); Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) {}); //https://wiki.wxwidgets.org/Flicker-Free_Drawing - Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(Grid::onKeyDown), nullptr, this); - Connect(wxEVT_KEY_UP, wxKeyEventHandler(Grid::onKeyUp ), nullptr, this); + Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event); }); } @@ -1522,12 +1565,12 @@ wxSize Grid::GetSizeAvailableForScrollTarget(const wxSize& size) //lame hard-coded numbers (from Ubuntu 19.10) and openSuse //=> let's have a *close* eye on scrollbar fluctuation! - assert(scrollBarSizeTmp.x == 0 || - scrollBarSizeTmp.x == 6 || scrollBarSizeTmp.x == 13 || //Ubuntu 19.10 - scrollBarSizeTmp.x == 16); //openSuse - assert(scrollBarSizeTmp.y == 0 || - scrollBarSizeTmp.y == 6 || scrollBarSizeTmp.y == 13 || //Ubuntu 19.10 - scrollBarSizeTmp.y == 16); //openSuse + assert(scrollBarSizeTmp.x == 0 || + scrollBarSizeTmp.x == 6 || scrollBarSizeTmp.x == 13 || //Ubuntu 19.10 + scrollBarSizeTmp.x == 16); //openSuse + assert(scrollBarSizeTmp.y == 0 || + scrollBarSizeTmp.y == 6 || scrollBarSizeTmp.y == 13 || //Ubuntu 19.10 + scrollBarSizeTmp.y == 16); //openSuse #else #error unknown GTK version! #endif @@ -1550,35 +1593,6 @@ wxSize Grid::GetSizeAvailableForScrollTarget(const wxSize& size) } -void Grid::onPaintEvent(wxPaintEvent& event) { wxPaintDC dc(this); } - - -void Grid::onKeyUp(wxKeyEvent& event) -{ - int keyCode = event.GetKeyCode(); - if (event.ShiftDown() && keyCode == WXK_F10) //== alias for menu key - keyCode = WXK_WINDOWS_MENU; - - switch (keyCode) - { - case WXK_MENU: // - case WXK_WINDOWS_MENU: //simulate right mouse click at cursor(+1) position - { - const int cursorNextPosY = rowLabelWin_->getRowHeight() * std::min(getRowCount(), mainWin_->getCursor() + 1); - const int clientPosMainWinY = std::clamp(CalcScrolledPosition(wxPoint(0, cursorNextPosY)).y, //absolute -> client coordinates - 0, mainWin_->GetClientSize().GetHeight() - 1); - const wxPoint mousePos = mainWin_->GetPosition() + wxPoint(0, clientPosMainWinY); //mainWin_-relative to Grid-relative - - GridClickEvent clickEvent(EVENT_GRID_MOUSE_RIGHT_UP, -1, HoverArea::NONE, mousePos); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->ProcessEvent(clickEvent); - } - return; - } - event.Skip(); -} - - void Grid::onKeyDown(wxKeyEvent& event) { int keyCode = event.GetKeyCode(); @@ -1600,7 +1614,7 @@ void Grid::onKeyDown(wxKeyEvent& event) if (rowCount > 0) { row = std::clamp<ptrdiff_t>(row, 0, rowCount - 1); - setGridCursor(row, GridEventPolicy::ALLOW); + setGridCursor(row, GridEventPolicy::allow); } }; @@ -1616,16 +1630,16 @@ void Grid::onKeyDown(wxKeyEvent& event) switch (keyCode) { case WXK_MENU: // - case WXK_WINDOWS_MENU: //simulate right mouse click at cursor(+1) position + case WXK_WINDOWS_MENU: //simulate right mouse click at cursor row (+1) position { const int cursorNextPosY = rowLabelWin_->getRowHeight() * std::min(getRowCount(), mainWin_->getCursor() + 1); const int clientPosMainWinY = std::clamp(CalcScrolledPosition(wxPoint(0, cursorNextPosY)).y, //absolute -> client coordinates 0, mainWin_->GetClientSize().GetHeight() - 1); const wxPoint mousePos = mainWin_->GetPosition() + wxPoint(0, clientPosMainWinY); //mainWin_-relative to Grid-relative - GridClickEvent clickEvent(EVENT_GRID_MOUSE_RIGHT_DOWN, -1, HoverArea::NONE, mousePos); + GridContextMenuEvent contextEvent(mousePos); if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->ProcessEvent(clickEvent); + evtHandler->ProcessEvent(contextEvent); } return; @@ -1716,12 +1730,12 @@ void Grid::onKeyDown(wxKeyEvent& event) case 'A': //Ctrl + A - select all if (event.ControlDown()) - selectRange(0, rowCount, true /*positive*/, nullptr /*mouseInitiated*/, GridEventPolicy::ALLOW); + selectRange(0, rowCount, true /*positive*/, nullptr /*mouseClick*/, GridEventPolicy::allow); break; case WXK_NUMPAD_ADD: //CTRL + '+' - auto-size all if (event.ControlDown()) - autoSizeColumns(GridEventPolicy::ALLOW); + autoSizeColumns(GridEventPolicy::allow); return; } @@ -1743,60 +1757,41 @@ void Grid::showRowLabel(bool show) } -void Grid::selectRow(size_t row, GridEventPolicy rangeEventPolicy) -{ - selection_.selectRow(row); - mainWin_->Refresh(); - - if (rangeEventPolicy == GridEventPolicy::ALLOW) - { - GridSelectEvent selEvent(row, row + 1, true, nullptr /*mouseClick*/); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->ProcessEvent(selEvent); - } -} - - -void Grid::selectAllRows(GridEventPolicy rangeEventPolicy) +void Grid::selectRange(size_t rowFirst, size_t rowLast, bool positive, GridEventPolicy rangeEventPolicy) { - selection_.selectAll(); + selection_.selectRange(rowFirst, rowLast, positive); mainWin_->Refresh(); - if (rangeEventPolicy == GridEventPolicy::ALLOW) + if (rangeEventPolicy == GridEventPolicy::allow) { - GridSelectEvent selEvent(0, getRowCount(), true /*positive*/, nullptr /*mouseClick*/); + GridSelectEvent selEvent(rowFirst, rowLast, positive, nullptr /*mouseClick*/); if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->ProcessEvent(selEvent); + /*bool processed = */evtHandler->ProcessEvent(selEvent); } } -void Grid::clearSelection(GridEventPolicy rangeEventPolicy) -{ - selection_.clear(); - mainWin_->Refresh(); - - if (rangeEventPolicy == GridEventPolicy::ALLOW) - { - GridSelectEvent unselectionEvent(0, getRowCount(), false /*positive*/, nullptr /*mouseClick*/); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->ProcessEvent(unselectionEvent); - } -} +void Grid::selectRow(size_t row, GridEventPolicy rangeEventPolicy) { selectRange(row, row + 1, true /*positive*/, rangeEventPolicy); } +void Grid::selectAllRows (GridEventPolicy rangeEventPolicy) { selectRange(0, selection_.gridSize(), true /*positive*/, rangeEventPolicy); } +void Grid::clearSelection (GridEventPolicy rangeEventPolicy) { selectRange(0, selection_.gridSize(), false /*positive*/, rangeEventPolicy); } void Grid::scrollDelta(int deltaX, int deltaY) { - wxPoint scrollPos = GetViewStart(); + const wxPoint scrollPosOld = GetViewStart(); - scrollPos.x += deltaX; - scrollPos.y += deltaY; + wxPoint scrollPosNew = scrollPosOld; + scrollPosNew.x += deltaX; + scrollPosNew.y += deltaY; - scrollPos.x = std::max(0, scrollPos.x); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"! - scrollPos.y = std::max(0, scrollPos.y); // + scrollPosNew.x = std::max(0, scrollPosNew.x); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"! + scrollPosNew.y = std::max(0, scrollPosNew.y); // - Scroll(scrollPos); //internally calls wxWindows::Update()! - updateWindowSizes(); //may show horizontal scroll bar if row column gets wider + if (scrollPosNew != scrollPosOld) + { + Scroll(scrollPosNew); //internally calls wxWindows::Update()! + updateWindowSizes(); //may show horizontal scroll bar if row column gets wider + } } @@ -1826,7 +1821,7 @@ void Grid::Refresh(bool eraseBackground, const wxRect* rect) updateWindowSizes(); } - if (selection_.maxSize() != rowCountNew) //clear selection only when needed (consider setSelectedRows()) + if (selection_.gridSize() != rowCountNew) //clear selection only when needed (consider setSelectedRows()) selection_.init(rowCountNew); wxScrolledWindow::Refresh(eraseBackground, rect); @@ -1850,7 +1845,7 @@ void Grid::setColumnConfig(const std::vector<Grid::ColAttributes>& attr) for (const ColAttributes& ca : attr) { assert(ca.stretch >= 0); - assert(ca.type != ColumnType::NONE); + assert(ca.type != ColumnType::none); if (ca.visible) visCols.push_back({ ca.type, ca.offset, std::max(ca.stretch, 0) }); @@ -2003,7 +1998,7 @@ ColumnType Grid::colToType(size_t col) const { if (col < visibleCols_.size()) return visibleCols_[col].type; - return ColumnType::NONE; + return ColumnType::none; } @@ -2022,7 +2017,7 @@ Grid::ColumnPosInfo Grid::getColumnAtPos(int posX) const return { cw.type, posX + cw.width - accWidth, cw.width }; } } - return { ColumnType::NONE, 0, 0 }; + return { ColumnType::none, 0, 0 }; } @@ -2066,7 +2061,7 @@ void Grid::setGridCursor(size_t row, GridEventPolicy rangeEventPolicy) makeRowVisible(row); selection_.clear(); //clear selection, do NOT fire event - selectRange(row, row, true /*positive*/, nullptr /*mouseInitiated*/, rangeEventPolicy); //set new selection + fire event + selectRange(row, row, true /*positive*/, nullptr /*mouseClick*/, rangeEventPolicy); //set new selection + fire event } @@ -2078,7 +2073,7 @@ void Grid::selectWithCursor(ptrdiff_t row) //emits GridSelectEvent makeRowVisible(row); selection_.clear(); //clear selection, do NOT fire event - selectRange(anchorRow, row, true /*positive*/, nullptr /*mouseInitiated*/, GridEventPolicy::ALLOW); //set new selection + fire event + selectRange(anchorRow, row, true /*positive*/, nullptr /*mouseClick*/, GridEventPolicy::allow); //set new selection + fire event } @@ -2138,11 +2133,11 @@ void Grid::selectRange(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const selection_.selectRange(rowFirst, rowLast, positive); mainWin_->Refresh(); - if (rangeEventPolicy == GridEventPolicy::ALLOW) + if (rangeEventPolicy == GridEventPolicy::allow) { GridSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseClick); if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->ProcessEvent(selectionEvent); + /*bool processed = */evtHandler->ProcessEvent(selectionEvent); } } @@ -2243,7 +2238,7 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve if (visibleCols_[col2].stretch > 0) //normalize stretched columns only visibleCols_[col2].offset = std::max(visibleCols_[col2].offset, fastFromDIP(COLUMN_MIN_WIDTH_DIP) - stretchedWidths[col2]); - if (columnResizeEventPolicy == GridEventPolicy::ALLOW) + if (columnResizeEventPolicy == GridEventPolicy::allow) { GridColumnResizeEvent sizeEvent(vcRs.offset, vcRs.type); if (wxEvtHandler* evtHandler = GetEventHandler()) @@ -19,28 +19,33 @@ //a user-friendly, extensible and high-performance grid control namespace zen { -enum class ColumnType { NONE = -1 }; //user-defiend column type -enum class HoverArea { NONE = -1 }; //user-defined area for mouse selections for a given row (may span multiple columns or split a single column into multiple areas) - -//wxContextMenuEvent? => automatically generated by wxWidgets when right mouse down/up is not handled; even OS-dependent in which case event is generated -//=> inappropriate! client decides when to show context! => simulate right mouse click when WXK_WINDOWS_MENU button is pressed -//=> same behavior as earlier wxWidgets: https://github.com/wxWidgets/wxWidgets/commit/2c69d27c0d225d3a331c773da466686153185320#diff-9f11c8f2cb1f734f7c0c1071aba491a5 +enum class ColumnType { none = -1 }; //user-defiend column type +enum class HoverArea { none = -1 }; //user-defined area for mouse selections for a given row (may span multiple columns or split a single column into multiple areas) //------------------------ events ------------------------------------------------ -extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOUBLE; // -extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOWN; // -extern const wxEventType EVENT_GRID_MOUSE_LEFT_UP; //generates: GridClickEvent -extern const wxEventType EVENT_GRID_MOUSE_RIGHT_DOWN; // -extern const wxEventType EVENT_GRID_MOUSE_RIGHT_UP; // +//example: wnd.Bind(EVENT_GRID_COL_LABEL_LEFT_CLICK, [this](GridClickEvent& event) { onGridLeftClick(event); }); + +struct GridClickEvent; +struct GridSelectEvent; +struct GridLabelClickEvent; +struct GridColumnResizeEvent; +struct GridContextMenuEvent; + +wxDECLARE_EVENT(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEvent); +wxDECLARE_EVENT(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEvent); +wxDECLARE_EVENT(EVENT_GRID_MOUSE_RIGHT_DOWN, GridClickEvent); -extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridSelectEvent +wxDECLARE_EVENT(EVENT_GRID_SELECT_RANGE, GridSelectEvent); //NOTE: neither first nor second row need to match EVENT_GRID_MOUSE_LEFT_DOWN/EVENT_GRID_MOUSE_LEFT_UP: user holding SHIFT; moving out of window... -extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridLabelClickEvent -extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_RIGHT; // -extern const wxEventType EVENT_GRID_COL_RESIZE; //generates: GridColumnResizeEvent +wxDECLARE_EVENT(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridLabelClickEvent); +wxDECLARE_EVENT(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridLabelClickEvent); +wxDECLARE_EVENT(EVENT_GRID_COL_RESIZE, GridColumnResizeEvent); + +//wxContextMenuEvent? => generated by wxWidgets when right mouse down/up is not handled; even OS-dependent in which case event is generated +//=> inappropriate! we know better when to show context! +wxDECLARE_EVENT(EVENT_GRID_CONTEXT_MENU, GridContextMenuEvent); -//example: wnd.Connect(EVENT_GRID_COL_LABEL_LEFT_CLICK, GridClickEventHandler(MyDlg::OnLeftClick), nullptr, this); struct GridClickEvent : public wxEvent { @@ -49,7 +54,7 @@ struct GridClickEvent : public wxEvent GridClickEvent* Clone() const override { return new GridClickEvent(*this); } const ptrdiff_t row_; //-1 for invalid position, >= rowCount if out of range - const HoverArea hoverArea_; //may be HoverArea::NONE + const HoverArea hoverArea_; //may be HoverArea::none const wxPoint mousePos_; //client coordinates }; @@ -71,7 +76,7 @@ struct GridLabelClickEvent : public wxEvent GridLabelClickEvent(wxEventType et, ColumnType colType) : wxEvent(0 /*winid*/, et), colType_(colType) {} GridLabelClickEvent* Clone() const override { return new GridLabelClickEvent(*this); } - const ColumnType colType_; //may be ColumnType::NONE + const ColumnType colType_; //may be ColumnType::none }; struct GridColumnResizeEvent : public wxEvent @@ -83,16 +88,13 @@ struct GridColumnResizeEvent : public wxEvent const int offset_; }; -using GridClickEventFunction = void (wxEvtHandler::*)(GridClickEvent&); -using GridSelectEventFunction = void (wxEvtHandler::*)(GridSelectEvent&); -using GridLabelClickEventFunction = void (wxEvtHandler::*)(GridLabelClickEvent&); -using GridColumnResizeEventFunction = void (wxEvtHandler::*)(GridColumnResizeEvent&); - -#define GridClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func) -#define GridSelectEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridSelectEventFunction, &func) -#define GridLabelClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridLabelClickEventFunction, &func) -#define GridColumnResizeEventHandler(func)(wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridColumnResizeEventFunction, &func) +struct GridContextMenuEvent : public wxEvent +{ + explicit GridContextMenuEvent(const wxPoint& mousePos) : wxEvent(0 /*winid*/, EVENT_GRID_CONTEXT_MENU), mousePos_(mousePos) {} + GridContextMenuEvent* Clone() const override { return new GridContextMenuEvent(*this); } + const wxPoint mousePos_; //client coordinates +}; //------------------------------------------------------------------------------------------------------------ class Grid; @@ -110,8 +112,8 @@ public: virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected); //default implementation virtual void renderCell (wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover); virtual int getBestSize (wxDC& dc, size_t row, ColumnType colType); //must correspond to renderCell()! + virtual HoverArea getRowMouseHover (wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) { return HoverArea::none; } virtual std::wstring getToolTip (size_t row, ColumnType colType) const { return std::wstring(); } - virtual HoverArea getRowMouseHover (size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) { return HoverArea::NONE; } //label area: virtual std::wstring getColumnLabel(ColumnType colType) const = 0; @@ -126,7 +128,6 @@ public: static void drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& text, int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, const wxSize* textExtentHint = nullptr); //returns text extent static wxRect drawCellBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle - static void drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, const wxColor& backgroundColor); static wxRect drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted); //returns inner rectangle static void drawColumnLabelText (wxDC& dc, const wxRect& rect, const std::wstring& text, bool enabled); @@ -135,8 +136,8 @@ public: enum class GridEventPolicy { - ALLOW, - DENY + allow, + deny }; @@ -148,7 +149,7 @@ public: const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL | wxNO_BORDER, - const wxString& name = wxPanelNameStr); + const wxString& name = wxASCII_STR(wxPanelNameStr)); size_t getRowCount() const; @@ -156,7 +157,7 @@ public: struct ColAttributes { - ColumnType type = ColumnType::NONE; + ColumnType type = ColumnType::none; //first, client width is partitioned according to all available stretch factors, then "offset_" is added //universal model: a non-stretched column has stretch factor 0 with the "offset" becoming identical to final width! int offset = 0; @@ -185,9 +186,11 @@ public: void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical); std::vector<size_t> getSelectedRows() const { return selection_.get(); } + void selectRow(size_t row, GridEventPolicy rangeEventPolicy); void selectAllRows (GridEventPolicy rangeEventPolicy); //turn off range selection event when calling this function in an event handler to avoid recursion! void clearSelection(GridEventPolicy rangeEventPolicy); // + void selectRange(size_t rowFirst, size_t rowLast, bool positive, GridEventPolicy rangeEventPolicy); //select [rowFirst, rowLast) void scrollDelta(int deltaX, int deltaY); //in scroll units @@ -201,7 +204,7 @@ public: struct ColumnPosInfo { - ColumnType colType = ColumnType::NONE; //ColumnType::NONE no column at x position! + ColumnType colType = ColumnType::none; //ColumnType::none no column at x position! int cellRelativePosX = 0; int colWidth = 0; }; @@ -226,10 +229,7 @@ public: //############################################################################################################ private: - void onPaintEvent(wxPaintEvent& event); - void onSizeEvent(wxSizeEvent& event) { updateWindowSizes(); event.Skip(); } void onKeyDown(wxKeyEvent& event); - void onKeyUp (wxKeyEvent& event); void updateWindowSizes(bool updateScrollbar = true); @@ -256,7 +256,7 @@ private: public: void init(size_t rowCount) { selected_.resize(rowCount); clear(); } - size_t maxSize() const { return selected_.size(); } + size_t gridSize() const { return selected_.size(); } std::vector<size_t> get() const { @@ -267,9 +267,7 @@ private: return result; } - void selectRow(size_t row) { selectRange(row, row + 1, true); } - void selectAll () { selectRange(0, selected_.size(), true); } - void clear () { selectRange(0, selected_.size(), false); } + void clear() { selectRange(0, selected_.size(), false); } bool isSelected(size_t row) const { return row < selected_.size() ? selected_[row] != 0 : false; } @@ -291,14 +289,14 @@ private: struct VisibleColumn { - ColumnType type = ColumnType::NONE; + ColumnType type = ColumnType::none; int offset = 0; int stretch = 0; //>= 0 }; struct ColumnWidth { - ColumnType type = ColumnType::NONE; + ColumnType type = ColumnType::none; int width = 0; }; std::vector<ColumnWidth> getColWidths() const; // @@ -332,7 +330,7 @@ private: void moveColumn(size_t colFrom, size_t colTo); ptrdiff_t clientPosToMoveTargetColumn(const wxPoint& pos) const; //return < 0 on error - ColumnType colToType(size_t col) const; //returns ColumnType::NONE on error + ColumnType colToType(size_t col) const; //returns ColumnType::none on error /* Grid window layout: _______________________________ diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index d09e5dfa..b89abe6d 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -128,7 +128,7 @@ private: Protected<std::vector<std::pair<std::string, ImageHolder>>> result_; using TaskType = FunctionReturnTypeT<decltype(&getScalerTask)>; - std::optional<ThreadGroup<TaskType>> threadGroup_{ ThreadGroup<TaskType>(std::max<int>(std::thread::hardware_concurrency(), 1), "xBRZ Scaler") }; + std::optional<ThreadGroup<TaskType>> threadGroup_{ ThreadGroup<TaskType>(std::max<int>(std::thread::hardware_concurrency(), 1), Zstr("xBRZ Scaler")) }; //hardware_concurrency() == 0 if "not computable or well defined" }; @@ -182,7 +182,7 @@ ImageBuffer::ImageBuffer(const Zstring& zipPath) //throw FileError try //to load from ZIP first: { //wxFFileInputStream/wxZipInputStream loads in junks of 512 bytes => WTF!!! => implement sane file loading: - const std::string rawStream = loadBinContainer<std::string>(zipPath, nullptr /*notifyUnbufferedIO*/); //throw FileError + const std::string rawStream = getFileContent(zipPath, nullptr /*notifyUnbufferedIO*/); //throw FileError wxMemoryInputStream memStream(rawStream.c_str(), rawStream.size()); //does not take ownership wxZipInputStream zipStream(memStream, wxConvUTF8); //do NOT rely on wxConvLocal! On failure shows unhelpful popup "Cannot convert from the charset 'Unknown encoding (-1)'!" @@ -195,11 +195,11 @@ ImageBuffer::ImageBuffer(const Zstring& zipPath) //throw FileError } catch (FileError&) //fall back to folder { - traverseFolder(beforeLast(zipPath, Zstr(".zip"), IF_MISSING_RETURN_NONE), [&](const FileInfo& fi) + traverseFolder(beforeLast(zipPath, Zstr(".zip"), IfNotFoundReturn::none), [&](const FileInfo& fi) { if (endsWith(fi.fullPath, Zstr(".png"))) { - std::string stream = loadBinContainer<std::string>(fi.fullPath, nullptr /*notifyUnbufferedIO*/); //throw FileError + std::string stream = getFileContent(fi.fullPath, nullptr /*notifyUnbufferedIO*/); //throw FileError streams.emplace_back(fi.itemName, std::move(stream)); } }, nullptr, nullptr, [](const std::wstring& errorMsg) { throw FileError(errorMsg); }); @@ -227,7 +227,7 @@ ImageBuffer::ImageBuffer(const Zstring& zipPath) //throw FileError //=> there's only one type of wxImage: with alpha channel, no mask!!! convertToVanillaImage(img); - const std::string imageName = utfTo<std::string>(beforeLast(fileName, Zstr("."), IF_MISSING_RETURN_NONE)); + const std::string imageName = utfTo<std::string>(beforeLast(fileName, Zstr("."), IfNotFoundReturn::none)); imagesRaw_.emplace(imageName, img); if (hqScaler_) @@ -311,7 +311,7 @@ std::unique_ptr<ImageBuffer> globalImageBuffer; void zen::imageResourcesInit(const Zstring& zipPath) //throw FileError { - assert(runningMainThread()); //wxWidgets is not thread-safe! + assert(runningOnMainThread()); //wxWidgets is not thread-safe! assert(!globalImageBuffer); globalImageBuffer = std::make_unique<ImageBuffer>(zipPath); //throw FileError } @@ -319,7 +319,7 @@ void zen::imageResourcesInit(const Zstring& zipPath) //throw FileError void zen::ImageResourcesCleanup() { - assert(runningMainThread()); //wxWidgets is not thread-safe! + assert(runningOnMainThread()); //wxWidgets is not thread-safe! assert(globalImageBuffer); globalImageBuffer.reset(); } @@ -327,7 +327,7 @@ void zen::ImageResourcesCleanup() const wxImage& zen::loadImage(const std::string& name, int maxWidth /*optional*/, int maxHeight /*optional*/) { - assert(runningMainThread()); //wxWidgets is not thread-safe! + assert(runningOnMainThread()); //wxWidgets is not thread-safe! assert(globalImageBuffer); if (globalImageBuffer) return globalImageBuffer->getImage(name, maxWidth, maxHeight); diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index 19ba6ba6..4569a2a7 100644 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -70,7 +70,7 @@ void copySubImage(const wxImage& src, wxPoint srcPos, void copyImageLayover(const wxImage& src, - /**/ wxImage& trg, wxPoint trgPos) + /**/ wxImage& trg, wxPoint trgPos) { const int srcWidth = src.GetWidth (); const int srcHeight = src.GetHeight(); @@ -119,7 +119,7 @@ std::vector<std::pair<wxString, wxSize>> getTextExtentInfo(const wxString& text, dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evaluated on OS X, wxWidgets 2.9.5, so apply it to the DC directly! std::vector<std::pair<wxString, wxSize>> lineInfo; //text + extent - for (const wxString& line : split(text, L'\n', SplitType::ALLOW_EMPTY)) + for (const wxString& line : split(text, L'\n', SplitOnEmpty::allow)) lineInfo.emplace_back(line, line.empty() ? wxSize() : dc.GetTextExtent(line)); return lineInfo; diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp index bdb904f2..ffde9824 100644 --- a/wx+/popup_dlg.cpp +++ b/wx+/popup_dlg.cpp @@ -173,7 +173,7 @@ public: else m_checkBoxCustom->Hide(); - Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(StandardPopupDialog::OnKeyPressed), nullptr, this); //dialog-specific local key events + Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //dialog-specific local key events //------------------------------------------------------------------------------ StdButtons stdBtns; @@ -225,31 +225,31 @@ public: } private: - void OnClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ConfirmationButton3::cancel)); } - void OnCancel(wxCommandEvent& event) override { EndModal(static_cast<int>(ConfirmationButton3::cancel)); } + void onClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ConfirmationButton3::cancel)); } + void onCancel(wxCommandEvent& event) override { EndModal(static_cast<int>(ConfirmationButton3::cancel)); } - void OnButtonAccept(wxCommandEvent& event) override + void onButtonAccept(wxCommandEvent& event) override { if (checkBoxValue_) *checkBoxValue_ = m_checkBoxCustom->GetValue(); EndModal(static_cast<int>(ConfirmationButton3::accept)); } - void OnButtonAcceptAll(wxCommandEvent& event) override + void onButtonAcceptAll(wxCommandEvent& event) override { if (checkBoxValue_) *checkBoxValue_ = m_checkBoxCustom->GetValue(); EndModal(static_cast<int>(ConfirmationButton3::acceptAll)); } - void OnButtonDecline(wxCommandEvent& event) override + void onButtonDecline(wxCommandEvent& event) override { if (checkBoxValue_) *checkBoxValue_ = m_checkBoxCustom->GetValue(); EndModal(static_cast<int>(ConfirmationButton3::decline)); } - void OnKeyPressed(wxKeyEvent& event) + void onLocalKeyEvent(wxKeyEvent& event) { switch (event.GetKeyCode()) { @@ -257,7 +257,7 @@ private: case WXK_NUMPAD_ENTER: { wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED); - OnButtonAccept(dummy); + onButtonAccept(dummy); return; } @@ -268,7 +268,7 @@ private: event.Skip(); } - void OnCheckBoxClick(wxCommandEvent& event) override { updateGui(); event.Skip(); } + void onCheckBoxClick(wxCommandEvent& event) override { updateGui(); event.Skip(); } void updateGui() { diff --git a/wx+/popup_dlg_generated.cpp b/wx+/popup_dlg_generated.cpp index 7fc05d53..b9da4c38 100644 --- a/wx+/popup_dlg_generated.cpp +++ b/wx+/popup_dlg_generated.cpp @@ -11,91 +11,91 @@ PopupDialogGenerated::PopupDialogGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) { - this->SetSizeHints( wxSize( -1,-1 ), wxDefaultSize ); - this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + this->SetSizeHints( wxSize( -1, -1 ), wxDefaultSize ); + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); - wxBoxSizer* bSizer24; - bSizer24 = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* bSizer24; + bSizer24 = new wxBoxSizer( wxVERTICAL ); - m_panel33 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - m_panel33->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_panel33 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panel33->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - wxBoxSizer* bSizer165; - bSizer165 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer165; + bSizer165 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapMsgType = new wxStaticBitmap( m_panel33, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 ); - bSizer165->Add( m_bitmapMsgType, 0, wxALL, 10 ); + m_bitmapMsgType = new wxStaticBitmap( m_panel33, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); + bSizer165->Add( m_bitmapMsgType, 0, wxALL, 10 ); - wxBoxSizer* bSizer16; - bSizer16 = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* bSizer16; + bSizer16 = new wxBoxSizer( wxVERTICAL ); - bSizer16->Add( 0, 10, 0, 0, 5 ); + bSizer16->Add( 0, 10, 0, 0, 5 ); - m_staticTextMain = new wxStaticText( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextMain->Wrap( -1 ); - bSizer16->Add( m_staticTextMain, 0, wxRIGHT, 10 ); + m_staticTextMain = new wxStaticText( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextMain->Wrap( -1 ); + bSizer16->Add( m_staticTextMain, 0, wxRIGHT, 10 ); - bSizer16->Add( 0, 5, 0, 0, 5 ); + bSizer16->Add( 0, 5, 0, 0, 5 ); - m_textCtrlTextDetail = new wxTextCtrl( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxBORDER_NONE ); - bSizer16->Add( m_textCtrlTextDetail, 1, wxEXPAND, 5 ); + m_textCtrlTextDetail = new wxTextCtrl( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxBORDER_NONE ); + bSizer16->Add( m_textCtrlTextDetail, 1, wxEXPAND, 5 ); - bSizer165->Add( bSizer16, 1, wxEXPAND, 5 ); + bSizer165->Add( bSizer16, 1, wxEXPAND, 5 ); - m_panel33->SetSizer( bSizer165 ); - m_panel33->Layout(); - bSizer165->Fit( m_panel33 ); - bSizer24->Add( m_panel33, 1, wxEXPAND, 5 ); + m_panel33->SetSizer( bSizer165 ); + m_panel33->Layout(); + bSizer165->Fit( m_panel33 ); + bSizer24->Add( m_panel33, 1, wxEXPAND, 5 ); - m_staticline6 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer24->Add( m_staticline6, 0, wxEXPAND, 5 ); + m_staticline6 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer24->Add( m_staticline6, 0, wxEXPAND, 5 ); - wxBoxSizer* bSizer25; - bSizer25 = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* bSizer25; + bSizer25 = new wxBoxSizer( wxVERTICAL ); - m_checkBoxCustom = new wxCheckBox( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - bSizer25->Add( m_checkBoxCustom, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 ); + m_checkBoxCustom = new wxCheckBox( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizer25->Add( m_checkBoxCustom, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 ); - bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); + bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); - m_buttonAccept = new wxButton( this, wxID_YES, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_buttonAccept = new wxButton( this, wxID_YES, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); - m_buttonAccept->SetDefault(); - bSizerStdButtons->Add( m_buttonAccept, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + m_buttonAccept->SetDefault(); + bSizerStdButtons->Add( m_buttonAccept, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - m_buttonAcceptAll = new wxButton( this, wxID_YESTOALL, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); - bSizerStdButtons->Add( m_buttonAcceptAll, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); + m_buttonAcceptAll = new wxButton( this, wxID_YESTOALL, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + bSizerStdButtons->Add( m_buttonAcceptAll, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); - m_buttonDecline = new wxButton( this, wxID_NO, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); - bSizerStdButtons->Add( m_buttonDecline, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); + m_buttonDecline = new wxButton( this, wxID_NO, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + bSizerStdButtons->Add( m_buttonDecline, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); - m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); - bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); + m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); - bSizer25->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 ); + bSizer25->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 ); - bSizer24->Add( bSizer25, 0, wxEXPAND, 5 ); + bSizer24->Add( bSizer25, 0, wxEXPAND, 5 ); - this->SetSizer( bSizer24 ); - this->Layout(); - bSizer24->Fit( this ); + this->SetSizer( bSizer24 ); + this->Layout(); + bSizer24->Fit( this ); - this->Centre( wxBOTH ); + this->Centre( wxBOTH ); - // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( PopupDialogGenerated::OnClose ) ); - m_checkBoxCustom->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnCheckBoxClick ), NULL, this ); - m_buttonAccept->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonAccept ), NULL, this ); - m_buttonAcceptAll->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonAcceptAll ), NULL, this ); - m_buttonDecline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonDecline ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnCancel ), NULL, this ); + // Connect Events + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( PopupDialogGenerated::onClose ) ); + m_checkBoxCustom->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onCheckBoxClick ), NULL, this ); + m_buttonAccept->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onButtonAccept ), NULL, this ); + m_buttonAcceptAll->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onButtonAcceptAll ), NULL, this ); + m_buttonDecline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onButtonDecline ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::onCancel ), NULL, this ); } PopupDialogGenerated::~PopupDialogGenerated() diff --git a/wx+/popup_dlg_generated.h b/wx+/popup_dlg_generated.h index 8191ca0f..52bd593c 100644 --- a/wx+/popup_dlg_generated.h +++ b/wx+/popup_dlg_generated.h @@ -38,34 +38,34 @@ /////////////////////////////////////////////////////////////////////////////// class PopupDialogGenerated : public wxDialog { - private: +private: - protected: - wxPanel* m_panel33; - wxStaticBitmap* m_bitmapMsgType; - wxStaticText* m_staticTextMain; - wxTextCtrl* m_textCtrlTextDetail; - wxStaticLine* m_staticline6; - wxCheckBox* m_checkBoxCustom; - wxBoxSizer* bSizerStdButtons; - wxButton* m_buttonAccept; - wxButton* m_buttonAcceptAll; - wxButton* m_buttonDecline; - wxButton* m_buttonCancel; +protected: + wxPanel* m_panel33; + wxStaticBitmap* m_bitmapMsgType; + wxStaticText* m_staticTextMain; + wxTextCtrl* m_textCtrlTextDetail; + wxStaticLine* m_staticline6; + wxCheckBox* m_checkBoxCustom; + wxBoxSizer* bSizerStdButtons; + wxButton* m_buttonAccept; + wxButton* m_buttonAcceptAll; + wxButton* m_buttonDecline; + wxButton* m_buttonCancel; - // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnCheckBoxClick( wxCommandEvent& event ) { event.Skip(); } - virtual void OnButtonAccept( wxCommandEvent& event ) { event.Skip(); } - virtual void OnButtonAcceptAll( wxCommandEvent& event ) { event.Skip(); } - virtual void OnButtonDecline( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + // Virtual event handlers, overide them in your derived class + virtual void onClose( wxCloseEvent& event ) { event.Skip(); } + virtual void onCheckBoxClick( wxCommandEvent& event ) { event.Skip(); } + virtual void onButtonAccept( wxCommandEvent& event ) { event.Skip(); } + virtual void onButtonAcceptAll( wxCommandEvent& event ) { event.Skip(); } + virtual void onButtonDecline( wxCommandEvent& event ) { event.Skip(); } + virtual void onCancel( wxCommandEvent& event ) { event.Skip(); } - public: +public: - PopupDialogGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); - ~PopupDialogGenerated(); + PopupDialogGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + ~PopupDialogGenerated(); }; @@ -33,7 +33,7 @@ wxImage mirrorIfRtl(const wxImage& img); //---------------------- implementation ------------------------ namespace impl { -//don't use wxDC::DrawLabel: +//don't use wxDC::DrawLabel: // - expensive GetTextExtent() call even when passing an empty string!!! // - 1-off alignment bugs! inline diff --git a/wx+/toggle_button.h b/wx+/toggle_button.h index 0a359c5c..560ad77e 100644 --- a/wx+/toggle_button.h +++ b/wx+/toggle_button.h @@ -24,7 +24,7 @@ public: const wxSize& size = wxDefaultSize, long style = 0, const wxValidator& validator = wxDefaultValidator, - const wxString& name = wxButtonNameStr) : + const wxString& name = wxASCII_STR(wxButtonNameStr)) : wxBitmapButton(parent, id, bitmap, pos, size, style, validator, name) {} //wxButton constructor @@ -35,7 +35,7 @@ public: const wxSize& size = wxDefaultSize, long style = 0, const wxValidator& validator = wxDefaultValidator, - const wxString& name = wxButtonNameStr) : + const wxString& name = wxASCII_STR(wxButtonNameStr)) : wxBitmapButton(parent, id, wxNullBitmap, pos, size, style, validator, name) { SetLabel(label); diff --git a/zen/basic_math.h b/zen/basic_math.h index 0a226555..0e30c276 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -7,14 +7,11 @@ #ifndef BASIC_MATH_H_3472639843265675 #define BASIC_MATH_H_3472639843265675 +#include <cassert> #include <algorithm> -#include <iterator> -#include <limits> #include <cmath> -#include <functional> -#include <cassert> + #include <numbers> #include "type_traits.h" -#include "legacy_compiler.h" namespace numeric @@ -22,7 +19,7 @@ namespace numeric template <class T> T abs(T value); template <class T> auto dist(T a, T b); template <class T> int sign(T value); //returns one of {-1, 0, 1} -template <class T> bool isNull(T value); +template <class T> bool isNull(T value); //...definitively fishy... template <class T, class InputIterator> //precondition: range must be sorted! auto nearMatch(const T& val, InputIterator first, InputIterator last); @@ -168,6 +165,7 @@ int64_t round(double d) { assert(d - 0.5 >= std::numeric_limits<int64_t>::min() && //if double is larger than what int can represent: d + 0.5 <= std::numeric_limits<int64_t>::max()); //=> undefined behavior! + return static_cast<int64_t>(d < 0 ? d - 0.5 : d + 0.5); } diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index d02e229e..62493084 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -57,14 +57,12 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError ZEN_ON_SCOPE_FAIL( ::close(pimpl_->notifDescr); ); //set non-blocking mode - bool initSuccess = false; - { - int flags = ::fcntl(pimpl_->notifDescr, F_GETFL); - if (flags != -1) - initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) == 0; - } - if (!initSuccess) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "fcntl"); + const int flags = ::fcntl(pimpl_->notifDescr, F_GETFL); + if (flags == -1) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "fcntl(F_GETFL)"); + + if (::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != 0) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "fcntl(F_SETFL, O_NONBLOCK)"); //add watches for (const Zstring& subDirPath : fullFolderList) diff --git a/zen/error_log.h b/zen/error_log.h index 8604f127..6d9f80ae 100644 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -90,7 +90,7 @@ ErrorLog::Stats ErrorLog::getStats() const ++count.error; break; } - assert(static_cast<int>(entries_.size()) == count.info + count.warning + count.error); + assert(std::ssize(entries_) == count.info + count.warning + count.error); return count; } diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 10713ce5..7d3fbfc5 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -84,7 +84,7 @@ std::optional<Zstring> zen::getParentFolderPath(const Zstring& itemPath) if (comp->relPath.empty()) return {}; - const Zstring parentRelPath = beforeLast(comp->relPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + const Zstring parentRelPath = beforeLast(comp->relPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); if (parentRelPath.empty()) return comp->rootPath; return appendSeparator(comp->rootPath) + parentRelPath; @@ -123,7 +123,7 @@ std::optional<ItemType> zen::itemStillExists(const Zstring& itemPath) //throw Fi // ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE, // ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH => not reliable - const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); + const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); assert(!itemName.empty()); const std::optional<ItemType> parentType = itemStillExists(*parentPath); //throw FileError @@ -316,8 +316,11 @@ void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool r if (ec == EXDEV) throw ErrorMoveUnsupported(errorMsg, errorDescr); - if (ec == EEXIST) + + assert(!replaceExisting || ec != EEXIST); + if (!replaceExisting && ec == EEXIST) throw ErrorTargetExisting(errorMsg, errorDescr); + throw FileError(errorMsg, errorDescr); }; @@ -518,16 +521,16 @@ void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTarget auto getErrorMsg = [&] { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)); }; //don't allow creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/ - const Zstring dirName = afterLast(dirPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); + const Zstring dirName = afterLast(dirPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); if (std::all_of(dirName.begin(), dirName.end(), [](Zchar c) { return c == Zstr('.'); })) /**/throw FileError(getErrorMsg(), replaceCpy<std::wstring>(L"Invalid folder name %x.", L"%x", fmtPath(dirName))); - #if 0 //not appreciated: https://freefilesync.org/forum/viewtopic.php?t=7509 +#if 0 //not appreciated: https://freefilesync.org/forum/viewtopic.php?t=7509 //not critical, but will visually confuse user sooner or later: if (startsWith(dirName, Zstr(' ')) || endsWith (dirName, Zstr(' '))) throw FileError(getErrorMsg(), replaceCpy<std::wstring>(L"Folder name %x starts/ends with space character.", L"%x", fmtPath(dirName))); - #endif +#endif const mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777, default for newly created directories @@ -638,14 +641,10 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target throw FileError(errorMsg, errorDescr); } - ZEN_ON_SCOPE_FAIL( try { removeFilePlain(targetFile); } - catch (FileError&) {} ); - //place guard AFTER ::open() and BEFORE lifetime of FileOutput: - //=> don't delete file that existed previously!!! FileOutput fileOut(fdTarget, targetFile, IOCallbackDivider(notifyUnbufferedIO, totalUnbufferedIO)); //pass ownership - //fileOut.preAllocateSpaceBestEffort(sourceInfo.st_size); //throw FileError - //=> perf: seems like no real benefit... + //preallocate disk space + reduce fragmentation (perf: no real benefit) + fileOut.reserveSpace(sourceInfo.st_size); //throw FileError bufferedStreamCopy(fileIn, fileOut); //throw FileError, (ErrorFileLocked), X @@ -659,6 +658,10 @@ 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.finalize(); //throw FileError, (X) essentially a close() since buffers were already flushed + //========================================================================================================== + //take fileOut ownership => from this point on, WE are responsible for calling removeFilePlain() on failure!! + //=========================================================================================================== + std::optional<FileError> errorModTime; try { diff --git a/zen/file_id_def.h b/zen/file_id_def.h index 55ee77f5..d2d104d5 100644 --- a/zen/file_id_def.h +++ b/zen/file_id_def.h @@ -31,8 +31,9 @@ struct FileId //always available on Linux, and *generally* available on Windows) } VolumeId volumeId = 0; FileIndex fileIndex = 0; + + bool operator==(const FileId&) const = default; }; -inline bool operator==(const FileId& lhs, const FileId& rhs) { return lhs.volumeId == rhs.volumeId && lhs.fileIndex == rhs.fileIndex; } inline diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 80a83724..4c6602cc 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -5,7 +5,6 @@ // ***************************************************************************** #include "file_io.h" -#include "file_access.h" #include <sys/stat.h> #include <fcntl.h> //open @@ -14,12 +13,9 @@ using namespace zen; - const FileBase::FileHandle FileBase::invalidHandleValue_ = -1; - - FileBase::~FileBase() { - if (fileHandle_ != invalidHandleValue_) + if (hFile_ != invalidFileHandle_) try { close(); //throw FileError @@ -30,13 +26,11 @@ FileBase::~FileBase() void FileBase::close() //throw FileError { - if (fileHandle_ == invalidHandleValue_) + if (hFile_ == invalidFileHandle_) throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"Contract error: close() called more than once."); - ZEN_ON_SCOPE_EXIT(fileHandle_ = invalidHandleValue_); - - //no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional! + ZEN_ON_SCOPE_EXIT(hFile_ = invalidFileHandle_); - if (::close(fileHandle_) != 0) + if (::close(hFile_) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "close"); } @@ -72,10 +66,10 @@ FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileErro //else: let ::open() fail for errors like "not existing" //don't use O_DIRECT: https://yarchive.net/comp/linux/o_direct.html - const FileBase::FileHandle fileHandle = ::open(filePath.c_str(), O_RDONLY | O_CLOEXEC); - if (fileHandle == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle + const int fdFile = ::open(filePath.c_str(), O_RDONLY | O_CLOEXEC); + if (fdFile == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), "open"); - return fileHandle; //pass ownership + return fdFile; //pass ownership } } @@ -90,7 +84,7 @@ FileInput::FileInput(const Zstring& filePath, const IOCallback& notifyUnbuffered { //optimize read-ahead on input file: if (::posix_fadvise(getHandle(), 0, 0, POSIX_FADV_SEQUENTIAL) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), "posix_fadvise"); + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), "posix_fadvise(POSIX_FADV_SEQUENTIAL)"); } @@ -168,13 +162,13 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, Erro namespace { -FileBase::FileHandle openHandleForWrite(const Zstring& filePath, FileOutput::AccessFlag access) //throw FileError, ErrorTargetExisting +FileBase::FileHandle openHandleForWrite(const Zstring& filePath) //throw FileError, ErrorTargetExisting { //checkForUnsupportedType(filePath); -> not needed, open() + O_WRONLY should fail fast - const FileBase::FileHandle fileHandle = ::open(filePath.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | (access == FileOutput::ACC_CREATE_NEW ? O_EXCL : O_TRUNC), - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666 - if (fileHandle == -1) + const int fdFile = ::open(filePath.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | /*access == FileOutput::ACC_OVERWRITE ? O_TRUNC : */ O_EXCL, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666 + if (fdFile == -1) { const int ec = errno; //copy before making other system calls! const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath)); @@ -186,27 +180,30 @@ FileBase::FileHandle openHandleForWrite(const Zstring& filePath, FileOutput::Acc throw FileError(errorMsg, errorDescr); } - return fileHandle; //pass ownership + return fdFile; //pass ownership } } FileOutput::FileOutput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO) : - FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} + FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) +{ +} -FileOutput::FileOutput(AccessFlag access, const Zstring& filePath, const IOCallback& notifyUnbufferedIO) : - FileBase(openHandleForWrite(filePath, access), filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} //throw FileError, ErrorTargetExisting +FileOutput::FileOutput(const Zstring& filePath, const IOCallback& notifyUnbufferedIO) : + FileBase(openHandleForWrite(filePath), filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} //throw FileError, ErrorTargetExisting FileOutput::~FileOutput() { - notifyUnbufferedIO_ = nullptr; //no call-backs during destruction!!! - try + + if (getHandle() != invalidFileHandle_) //not finalized => clean up garbage { - flushBuffers(); //throw FileError, (X) + //"deleting while handle is open" == FILE_FLAG_DELETE_ON_CLOSE + if (::unlink(getFilePath().c_str()) != 0) + assert(false); } - catch (...) { assert(false); } } @@ -288,20 +285,44 @@ void FileOutput::flushBuffers() //throw FileError, X void FileOutput::finalize() //throw FileError, X { flushBuffers(); //throw FileError, X - //~FileBase() calls this one, too, but we want to propagate errors if any: - close(); //throw FileError + close(); //throw FileError + //~FileBase() calls this one, too, but we want to propagate errors if any } -void FileOutput::preAllocateSpaceBestEffort(uint64_t expectedSize) //throw FileError +void FileOutput::reserveSpace(uint64_t expectedSize) //throw FileError { - const FileHandle fh = getHandle(); - //don't use potentially inefficient ::posix_fallocate! - const int rv = ::fallocate(fh, //int fd, - 0, //int mode, - 0, //off_t offset - expectedSize); //off_t len - if (rv != 0) - return; //may fail with EOPNOTSUPP, unlike posix_fallocate + //NTFS: "If you set the file allocation info [...] the file contents will be forced into nonresident data, even if it would have fit inside the MFT." + if (expectedSize < 1024) //https://www.sciencedirect.com/topics/computer-science/master-file-table + return; + + //don't use ::posix_fallocate! horribly inefficient if FS doesn't support it + changes file size + //FALLOC_FL_KEEP_SIZE => allocate only, file size is NOT changed! + if (::fallocate(getHandle(), //int fd, + FALLOC_FL_KEEP_SIZE, //int mode, + 0, //off_t offset + expectedSize) != 0) //off_t len + if (errno != EOPNOTSUPP) //possible, unlike with posix_fallocate() + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "fallocate"); + +} + +std::string zen::getFileContent(const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +{ + FileInput streamIn(filePath, notifyUnbufferedIO); //throw FileError, ErrorFileLocked + return bufferedLoad<std::string>(streamIn); //throw FileError, X +} + + +void zen::setFileContent(const Zstring& filePath, const std::string& byteStream, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +{ + TempFileOutput fileOut(filePath, notifyUnbufferedIO); //throw FileError + if (!byteStream.empty()) + { + //preallocate disk space + reduce fragmentation + fileOut.reserveSpace(byteStream.size()); //throw FileError + fileOut.write(&byteStream[0], byteStream.size()); //throw FileError, X + } + fileOut.commit(); //throw FileError, X } diff --git a/zen/file_io.h b/zen/file_io.h index b47c6077..81e1e7cc 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -8,46 +8,47 @@ #define FILE_IO_H_89578342758342572345 #include "file_error.h" +#include "file_access.h" #include "serialize.h" +#include "crc.h" +#include "guid.h" namespace zen { const char LINE_BREAK[] = "\n"; //since OS X Apple uses newline, too -/* -OS-buffered file IO optimized for +/* OS-buffered file IO optimized for - sequential read/write accesses - better error reporting - long path support - - follows symlinks - */ + - follows symlinks */ class FileBase { public: - const Zstring& getFilePath() const { return filePath_; } - using FileHandle = int; + static const int invalidFileHandle_ = -1; - FileHandle getHandle() { return fileHandle_; } + FileHandle getHandle() { return hFile_; } //Windows: use 64kB ?? https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc938632%28v=technet.10%29 //Linux: use st_blksize? //macOS: use f_iosize? static size_t getBlockSize() { return 128 * 1024; }; + const Zstring& getFilePath() const { return filePath_; } + protected: - FileBase(FileHandle handle, const Zstring& filePath) : fileHandle_(handle), filePath_(filePath) {} + FileBase(FileHandle handle, const Zstring& filePath) : hFile_(handle), filePath_(filePath) {} ~FileBase(); void close(); //throw FileError -> optional, but good place to catch errors when closing stream! - static const FileHandle invalidHandleValue_; private: FileBase (const FileBase&) = delete; FileBase& operator=(const FileBase&) = delete; - FileHandle fileHandle_ = invalidHandleValue_; + FileHandle hFile_ = invalidFileHandle_; const Zstring filePath_; }; @@ -75,54 +76,65 @@ private: class FileOutput : public FileBase { public: - enum AccessFlag - { - ACC_OVERWRITE, - ACC_CREATE_NEW - }; - FileOutput(AccessFlag access, const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, ErrorTargetExisting + FileOutput( const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, ErrorTargetExisting FileOutput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //takes ownership! ~FileOutput(); - void preAllocateSpaceBestEffort(uint64_t expectedSize); //throw FileError + void reserveSpace(uint64_t expectedSize); //throw FileError void write(const void* buffer, size_t bytesToWrite); //throw FileError, X void flushBuffers(); //throw FileError, X + //caveat: does NOT flush OS or hard disk buffers like e.g. FlushFileBuffers()! + void finalize(); /*= flushBuffers() + close()*/ //throw FileError, X private: size_t tryWrite(const void* buffer, size_t bytesToWrite); //throw FileError; may return short! CONTRACT: bytesToWrite > 0 IOCallback notifyUnbufferedIO_; //throw X - std::vector<std::byte> memBuf_ = std::vector<std::byte>(getBlockSize()); size_t bufPos_ = 0; size_t bufPosEnd_ = 0; }; - //----------------------------------------------------------------------------------------------- - //native stream I/O convenience functions: -template <class BinContainer> inline -BinContainer loadBinContainer(const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +class TempFileOutput { - FileInput streamIn(filePath, notifyUnbufferedIO); //throw FileError, ErrorFileLocked - return bufferedLoad<BinContainer>(streamIn); //throw FileError, X -} +public: + TempFileOutput( const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) : //throw FileError + filePath_(filePath), + tmpFile_(tmpFilePath_, notifyUnbufferedIO) {} //throw FileError, (ErrorTargetExisting) + void reserveSpace(uint64_t expectedSize) { tmpFile_.reserveSpace(expectedSize); } //throw FileError -template <class BinContainer> inline -void saveBinContainer(const Zstring& filePath, const BinContainer& buffer, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X -{ - FileOutput fileOut(FileOutput::ACC_OVERWRITE, filePath, notifyUnbufferedIO); //throw FileError, (ErrorTargetExisting) - if (!buffer.empty()) + void write(const void* buffer, size_t bytesToWrite) { tmpFile_.write(buffer, bytesToWrite); } //throw FileError, X + + void commit() //throw FileError, X { - /*snake oil?*/ fileOut.preAllocateSpaceBestEffort(buffer.size()); //throw FileError - fileOut.write(&buffer[0], buffer.size()); //throw FileError, X + tmpFile_.finalize(); //throw FileError, X + + //take ownership: + ZEN_ON_SCOPE_FAIL( try { removeFilePlain(tmpFilePath_); /*throw FileError*/ } + catch (FileError&) {}); + + //operation finished: move temp file transactionally + moveAndRenameItem(tmpFilePath_, filePath_, true /*replaceExisting*/); //throw FileError, (ErrorMoveUnsupported), (ErrorTargetExisting) } - fileOut.finalize(); //throw FileError, X -} + +private: + //generate (hopefully) unique file name to avoid clashing with unrelated tmp file + const Zstring filePath_; + const Zstring shortGuid_ = printNumber<Zstring>(Zstr("%04x"), static_cast<unsigned int>(getCrc16(generateGUID()))); + const Zstring tmpFilePath_ = filePath_ + Zstr('.') + shortGuid_ + Zstr(".tmp"); + FileOutput tmpFile_; +}; + + +[[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 } #endif //FILE_IO_H_89578342758342572345 diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 28e62236..0afc28ee 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -8,7 +8,6 @@ #include "file_error.h" - #include <cstddef> //offsetof #include <unistd.h> //::pathconf() #include <sys/stat.h> #include <dirent.h> diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index eeebda53..4984c1d7 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -73,12 +73,12 @@ std::wstring zen::formatFilesizeShort(int64_t size) namespace { -enum UnitRemTime +enum class UnitRemTime { - URT_SEC, - URT_MIN, - URT_HOUR, - URT_DAY + sec, + min, + hour, + day }; @@ -86,13 +86,13 @@ std::wstring formatUnitTime(int val, UnitRemTime unit) { switch (unit) { - case URT_SEC: + case UnitRemTime::sec: return _P("1 sec", "%x sec", val); - case URT_MIN: + case UnitRemTime::min: return _P("1 min", "%x min", val); - case URT_HOUR: + case UnitRemTime::hour: return _P("1 hour", "%x hours", val); - case URT_DAY: + case UnitRemTime::day: return _P("1 day", "%x days", val); } assert(false); @@ -131,18 +131,18 @@ std::wstring zen::formatRemainingTime(double timeInSec) //determine preferred unit double timeInUnit = timeInSec; if (timeInUnit <= 60) - return roundToBlock(timeInUnit, URT_SEC, steps60, 1, URT_SEC, steps60); + return roundToBlock(timeInUnit, UnitRemTime::sec, steps60, 1, UnitRemTime::sec, steps60); timeInUnit /= 60; if (timeInUnit <= 60) - return roundToBlock(timeInUnit, URT_MIN, steps60, 60, URT_SEC, steps60); + return roundToBlock(timeInUnit, UnitRemTime::min, steps60, 60, UnitRemTime::sec, steps60); timeInUnit /= 60; if (timeInUnit <= 24) - return roundToBlock(timeInUnit, URT_HOUR, steps24, 60, URT_MIN, steps60); + return roundToBlock(timeInUnit, UnitRemTime::hour, steps24, 60, UnitRemTime::min, steps60); timeInUnit /= 24; - return roundToBlock(timeInUnit, URT_DAY, steps10, 24, URT_HOUR, steps24); + return roundToBlock(timeInUnit, UnitRemTime::day, steps10, 24, UnitRemTime::hour, steps24); //note: for 10% granularity steps10 yields a valid blocksize only up to timeInUnit == 100! //for larger time sizes this results in a finer granularity than expected: 10 days -> should not be a problem considering "usual" remaining time for synchronization } @@ -164,28 +164,9 @@ std::wstring zen::formatFraction(double fraction) std::wstring zen::formatNumber(int64_t n) { - //we have to include thousands separator ourselves; this doesn't work for all countries (e.g india), but is better than nothing - - //::setlocale (LC_ALL, ""); -> implicitly called by wxLocale - const lconv& localInfo = *::localeconv(); //always bound according to doc - const std::wstring& thousandSep = utfTo<std::wstring>(localInfo.thousands_sep); - - // THOUSANDS_SEPARATOR = std::use_facet<std::numpunct<wchar_t>>(std::locale("")).thousands_sep(); - why not working? - // DECIMAL_POINT = std::use_facet<std::numpunct<wchar_t>>(std::locale("")).decimal_point(); - - std::wstring number = numberTo<std::wstring>(n); - - size_t i = number.size(); - for (;;) - { - if (i <= 3) - break; - i -= 3; - if (!isDigit(number[i - 1])) //stop on +, - signs - break; - number.insert(i, thousandSep); - } - return number; + //::setlocale (LC_ALL, ""); -> see localization.cpp::wxWidgetsLocale + static_assert(sizeof(long long int) == sizeof(n)); + return printNumber<std::wstring>(L"%'lld", n); //considers grouping (') } diff --git a/zen/globals.h b/zen/globals.h index 876d2598..635909f7 100644 --- a/zen/globals.h +++ b/zen/globals.h @@ -10,19 +10,18 @@ #include <atomic> #include <memory> #include "scope_guard.h" +#include "legacy_compiler.h" namespace zen { -/* -Solve static destruction order fiasco by providing shared ownership and serialized access to global variables +/* 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! -=> 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" - => "solved" by FunStatGlobal, but we can't have "too many" of these... */ + => 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! + => 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" + => "solved" by FunStatGlobal, but we can't have "too many" of these... */ class PodSpinMutex { public: @@ -32,7 +31,7 @@ public: bool isLocked(); private: - std::atomic_flag flag_; /* => avoid potential contention with worker thread during Global<> construction! + std::atomic_flag flag_{}; /* => avoid potential contention with worker thread during Global<> construction! - "For an atomic_flag with static storage duration, this guarantees static initialization:" => just what the doctor ordered! - "[default initialization] initializes std::atomic_flag to clear state" - since C++20 => - "std::atomic_flag is [...] guaranteed to be lock-free" @@ -40,21 +39,25 @@ private: }; +#define GLOBAL_RUN_ONCE(X) \ + struct ZEN_CONCAT(GlobalInitializer, __LINE__) \ + { \ + ZEN_CONCAT(GlobalInitializer, __LINE__)() { X; } \ + } ZEN_CONCAT(globalInitializer, __LINE__) + + template <class T> class Global //don't use for function-scope statics! { public: - Global() + consteval2 Global() {}; //demand static zero-initialization! + + ~Global() { static_assert(std::is_trivially_destructible_v<Pod>, "this memory needs to live forever"); - assert(!pod_.spinLock.isLocked()); //we depend on static zero-initialization! - assert(!pod_.inst); // + set(nullptr); } - explicit Global(std::unique_ptr<T>&& newInst) { set(std::move(newInst)); } - - ~Global() { set(nullptr); } - std::shared_ptr<T> get() //=> return std::shared_ptr to let instance life time be handled by caller (MT usage!) { pod_.spinLock.lock(); @@ -83,8 +86,8 @@ 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; + //serialize access: can't use std::mutex: has non-trival destructor + std::shared_ptr<T>* inst = nullptr; } pod_; }; @@ -94,9 +97,9 @@ private: struct CleanUpEntry { using CleanUpFunction = void (*)(void* callbackData); - CleanUpFunction cleanUpFun; - void* callbackData; - CleanUpEntry* prev; + CleanUpFunction cleanUpFun = nullptr; + void* callbackData = nullptr; + CleanUpEntry* prev = nullptr; }; void registerGlobalForDestruction(CleanUpEntry& entry); @@ -105,15 +108,31 @@ template <class T> class FunStatGlobal { public: - //No FunStatGlobal() or ~FunStatGlobal()! + consteval2 FunStatGlobal() {}; //demand static zero-initialization! - std::shared_ptr<T> get() + //No ~FunStatGlobal()! + + void initOnce(std::unique_ptr<T> (*getInitialValue)()) { static_assert(std::is_trivially_destructible_v<FunStatGlobal>, "this class must not generate code for magic statics!"); pod_.spinLock.lock(); ZEN_ON_SCOPE_EXIT(pod_.spinLock.unlock()); + if (!pod_.cleanUpEntry.cleanUpFun) + { + assert(!pod_.inst); + if (std::unique_ptr<T> newInst = (*getInitialValue)()) + pod_.inst = new std::shared_ptr<T>(std::move(newInst)); + registerDestruction(); + } + } + + std::shared_ptr<T> get() + { + pod_.spinLock.lock(); + ZEN_ON_SCOPE_EXIT(pod_.spinLock.unlock()); + if (pod_.inst) return *pod_.inst; return nullptr; @@ -134,20 +153,6 @@ public: delete tmpInst; } - void initOnce(std::unique_ptr<T> (*getInitialValue)()) - { - pod_.spinLock.lock(); - ZEN_ON_SCOPE_EXIT(pod_.spinLock.unlock()); - - if (!pod_.cleanUpEntry.cleanUpFun) - { - assert(!pod_.inst); - if (std::unique_ptr<T> newInst = (*getInitialValue)()) - pod_.inst = new std::shared_ptr<T>(std::move(newInst)); - registerDestruction(); - } - } - private: //call while holding pod_.spinLock void registerDestruction() @@ -171,7 +176,7 @@ private: { 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; + std::shared_ptr<T>* inst = nullptr; CleanUpEntry cleanUpEntry; } pod_; }; @@ -206,7 +211,7 @@ void registerGlobalForDestruction(CleanUpEntry& entry) } //------------------------------------------------------------------------------------------ -#if __cpp_lib_atomic_wait +#ifdef __cpp_lib_atomic_wait #error implement + rewiew improvements #endif @@ -222,7 +227,7 @@ inline void PodSpinMutex::lock() { while (!tryLock()) -#if __cpp_lib_atomic_wait +#ifdef __cpp_lib_atomic_wait flag_.wait(true, std::memory_order_relaxed); #else ; @@ -234,7 +239,7 @@ inline void PodSpinMutex::unlock() { flag_.clear(std::memory_order_release); -#if __cpp_lib_atomic_wait +#ifdef __cpp_lib_atomic_wait flag_.notify_one(); #endif } @@ -20,11 +20,11 @@ std::string generateGUID() //creates a 16-byte GUID { std::string guid(16, '\0'); -#ifndef __GLIBC__ -#error Where is GLIB? +#ifndef __GLIBC_PREREQ +#error Where is Glibc? #endif -#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25) //getentropy() requires glibc 2.25 (ldd --version) PS: CentOS 7 is on 2.17 +#if __GLIBC_PREREQ(2, 25) //getentropy() requires Glibc 2.25 (ldd --version) PS: CentOS 7 is on 2.17 if (::getentropy(&guid[0], guid.size()) != 0) //"The maximum permitted value for the length argument is 256" throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" + utfTo<std::string>(formatSystemError("getentropy", errno))); diff --git a/zen/http.cpp b/zen/http.cpp index 57d61221..5d389719 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -31,9 +31,9 @@ public: //may be sending large POST: call back first if (notifyUnbufferedIO_) notifyUnbufferedIO_(0); //throw X - const Zstring urlFmt = afterFirst(url, Zstr("://"), IF_MISSING_RETURN_NONE); - const Zstring server = beforeFirst(urlFmt, Zstr('/'), IF_MISSING_RETURN_ALL); - const Zstring page = Zstr('/') + afterFirst(urlFmt, Zstr('/'), IF_MISSING_RETURN_NONE); + const Zstring urlFmt = afterFirst(url, Zstr("://"), IfNotFoundReturn::none); + const Zstring server = beforeFirst(urlFmt, Zstr('/'), IfNotFoundReturn::all); + const Zstring page = Zstr('/') + afterFirst(urlFmt, Zstr('/'), IfNotFoundReturn::none); const bool useTls = [&] { @@ -100,8 +100,8 @@ public: if (contains(buf, headerDelim)) { - headBuf = beforeFirst(buf, headerDelim, IF_MISSING_RETURN_NONE); - const std::string bodyBuf = afterFirst (buf, headerDelim, IF_MISSING_RETURN_NONE); + headBuf = beforeFirst(buf, headerDelim, IfNotFoundReturn::none); + const std::string bodyBuf = afterFirst (buf, headerDelim, IfNotFoundReturn::none); //put excess bytes into instance buffer for body retrieval assert(bufPos_ == 0 && bufPosEnd_ == 0); bufPosEnd_ = bodyBuf.size(); @@ -112,18 +112,18 @@ public: break; } //parse header - const std::string statusBuf = beforeFirst(headBuf, "\r\n", IF_MISSING_RETURN_ALL); - const std::string headersBuf = afterFirst (headBuf, "\r\n", IF_MISSING_RETURN_NONE); + const std::string statusBuf = beforeFirst(headBuf, "\r\n", IfNotFoundReturn::all); + const std::string headersBuf = afterFirst (headBuf, "\r\n", IfNotFoundReturn::none); - const std::vector<std::string> statusItems = split(statusBuf, ' ', SplitType::ALLOW_EMPTY); //HTTP-Version SP Status-Code SP Reason-Phrase CRLF + const std::vector<std::string> statusItems = split(statusBuf, ' ', SplitOnEmpty::allow); //HTTP-Version SP Status-Code SP Reason-Phrase CRLF if (statusItems.size() < 2 || !startsWith(statusItems[0], "HTTP/")) throw SysError(L"Invalid HTTP response: \"" + utfTo<std::wstring>(statusBuf) + L'"'); statusCode_ = stringTo<int>(statusItems[1]); - for (const std::string& line : split(headersBuf, "\r\n", SplitType::SKIP_EMPTY)) - responseHeaders_[trimCpy(beforeFirst(line, ':', IF_MISSING_RETURN_ALL))] = - /**/ trimCpy(afterFirst (line, ':', IF_MISSING_RETURN_NONE)); + for (const std::string& line : split(headersBuf, "\r\n", SplitOnEmpty::skip)) + responseHeaders_[trimCpy(beforeFirst(line, ':', IfNotFoundReturn::all))] = + /**/ trimCpy(afterFirst (line, ':', IfNotFoundReturn::none)); //try to get "Content-Length" header if available if (const std::string* value = getHeader("Content-Length")) @@ -332,9 +332,9 @@ std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const st { std::vector<std::pair<std::string, std::string>> output; - for (const std::string& nvPair : split(str, '&', SplitType::SKIP_EMPTY)) - output.emplace_back(urldecode(beforeFirst(nvPair, '=', IF_MISSING_RETURN_ALL)), - urldecode(afterFirst (nvPair, '=', IF_MISSING_RETURN_NONE))); + for (const std::string& nvPair : split(str, '&', SplitOnEmpty::skip)) + output.emplace_back(urldecode(beforeFirst(nvPair, '=', IfNotFoundReturn::all)), + urldecode(afterFirst (nvPair, '=', IfNotFoundReturn::none))); return output; } @@ -465,17 +465,17 @@ bool zen::isValidEmail(const std::string& email) //https://en.wikipedia.org/wiki/Email_address#Syntax //https://tools.ietf.org/html/rfc3696 => note errata! https://www.rfc-editor.org/errata_search.php?rfc=3696 //https://tools.ietf.org/html/rfc5321 - std::string local = beforeLast(email, '@', IF_MISSING_RETURN_NONE); - std::string domain = afterLast(email, '@', IF_MISSING_RETURN_NONE); + std::string local = beforeLast(email, '@', IfNotFoundReturn::none); + std::string domain = afterLast(email, '@', IfNotFoundReturn::none); //consider: "t@st"@email.com t\@st@email.com" auto stripComments = [](std::string& part) { if (startsWith(part, '(')) - part = afterFirst(part, ')', IF_MISSING_RETURN_NONE); + part = afterFirst(part, ')', IfNotFoundReturn::none); if (endsWith(part, ')')) - part = beforeLast(part, '(', IF_MISSING_RETURN_NONE); + part = beforeLast(part, '(', IfNotFoundReturn::none); }; stripComments(local); stripComments(domain); @@ -488,7 +488,7 @@ bool zen::isValidEmail(const std::string& email) const bool quoted = (startsWith(local, '"') && endsWith(local, '"')) || contains(local, '\\'); //e.g. "t\@st@email.com" if (!quoted) //I'm not going to parse and validate this! - for (const std::string& comp : split(local, '.', SplitType::ALLOW_EMPTY)) + for (const std::string& comp : split(local, '.', SplitOnEmpty::allow)) if (comp.empty() || !std::all_of(comp.begin(), comp.end(), [](char c) { const char printable[] = "!#$%&'*+-/=?^_`{|}~"; @@ -505,7 +505,7 @@ bool zen::isValidEmail(const std::string& email) if (!contains(domain, '.')) return false; - for (const std::string& comp : split(domain, '.', SplitType::ALLOW_EMPTY)) + for (const std::string& comp : split(domain, '.', SplitOnEmpty::allow)) if (comp.empty() || comp.size() > 63 || !std::all_of(comp.begin(), comp.end(), [](char c) { return isAsciiAlpha(c) ||isDigit(c) || !isAsciiChar(c) || c == '-'; })) return false; @@ -57,26 +57,21 @@ std::shared_ptr<const TranslationHandler> getTranslator(); //######################## implementation ############################## namespace impl { -inline -FunStatGlobal<const TranslationHandler>& refGlobalTranslationHandler() -{ - //getTranslator() may be called even after static objects of this translation unit are destroyed! - static FunStatGlobal<const TranslationHandler> inst; //external linkage even in header! - return inst; -} +//getTranslator() may be called even after static objects of this translation unit are destroyed! +inline constinit2 Global<const TranslationHandler> globalTranslationHandler; } inline std::shared_ptr<const TranslationHandler> getTranslator() { - return impl::refGlobalTranslationHandler().get(); + return impl::globalTranslationHandler.get(); } inline void setTranslator(std::unique_ptr<const TranslationHandler>&& newHandler) { - impl::refGlobalTranslationHandler().set(std::move(newHandler)); + impl::globalTranslationHandler.set(std::move(newHandler)); } @@ -26,14 +26,18 @@ struct JsonValue array, }; - explicit JsonValue() {} + /**/ JsonValue() {} explicit JsonValue(Type t) : type(t) {} explicit JsonValue(bool b) : type(Type::boolean), primVal(b ? "true" : "false") {} explicit JsonValue(int num) : type(Type::number), primVal(numberTo<std::string>(num)) {} explicit JsonValue(int64_t num) : type(Type::number), primVal(numberTo<std::string>(num)) {} explicit JsonValue(double num) : type(Type::number), primVal(numberTo<std::string>(num)) {} explicit JsonValue(std::string str) : type(Type::string), primVal(std::move(str)) {} //unifying assignment - explicit JsonValue(const void*) = delete; //catch usage errors e.g. const char* -> JsonValue(bool) + explicit JsonValue(const char* str) : type(Type::string), primVal(str) {} + explicit JsonValue(const void*) = delete; //catch usage errors e.g. const int* -> JsonValue(bool) + //explicit JsonValue(std::initializer_list<JsonValue> initList) : type(Type::array), arrayVal(initList) {} => empty list is ambiguous + explicit JsonValue(std::vector<JsonValue> initList) : type(Type::array), arrayVal(std::move(initList)) {} //unifying assignment + Type type = Type::null; std::string primVal; //for primitive types @@ -97,28 +101,28 @@ std::string jsonEscape(const std::string& str) for (const char c : str) switch (c) { - //*INDENT-OFF* - case '"': output += "\\\""; break; //escaping mandatory - case '\\': output += "\\\\"; break; // - - case '\b': output += "\\b"; break; // - case '\f': output += "\\f"; break; // - case '\n': output += "\\n"; break; //prefer compact escaping - case '\r': output += "\\r"; break; // - case '\t': output += "\\t"; break; // - - default: - if (static_cast<unsigned char>(c) < 32) - { - const auto [high, low] = hexify(c); - output += "\\u00"; - output += high; - output += low; - } - else - output += c; - break; - //*INDENT-ON* + //*INDENT-OFF* + case '"': output += "\\\""; break; //escaping mandatory + case '\\': output += "\\\\"; break; // + + case '\b': output += "\\b"; break; // + case '\f': output += "\\f"; break; // + case '\n': output += "\\n"; break; //prefer compact escaping + case '\r': output += "\\r"; break; // + case '\t': output += "\\t"; break; // + + default: + if (static_cast<unsigned char>(c) < 32) + { + const auto [high, low] = hexify(c); + output += "\\u00"; + output += high; + output += low; + } + else + output += c; + break; + //*INDENT-ON* } return output; } @@ -133,7 +137,7 @@ std::string jsonUnescape(const std::string& str) { if (!utf16Buf.empty()) { - impl::UtfDecoder<impl::Char16> decoder(utf16Buf.c_str(), utf16Buf.size()); + UtfDecoder<impl::Char16> decoder(utf16Buf.c_str(), utf16Buf.size()); while (std::optional<impl::CodePoint> cp = decoder.getNext()) impl::codePointToUtf<char>(*cp, [&](char c) { output += c; }); utf16Buf.clear(); @@ -161,34 +165,34 @@ std::string jsonUnescape(const std::string& str) const char c2 = *it; switch (c2) { - //*INDENT-OFF* - case '"': - case '\\': - case '/': writeOut(c2); break; - case 'b': writeOut('\b'); break; - case 'f': writeOut('\f'); break; - case 'n': writeOut('\n'); break; - case 'r': writeOut('\r'); break; - case 't': writeOut('\t'); break; - default: - if (c2 == 'u' && - str.end() - it >= 5 && - isHexDigit(it[1]) && - isHexDigit(it[2]) && - isHexDigit(it[3]) && - isHexDigit(it[4])) - { - utf16Buf += static_cast<impl::Char16>(static_cast<unsigned char>(unhexify(it[1], it[2])) * 256 + - static_cast<unsigned char>(unhexify(it[3], it[4]))); - it += 4; - } - else //unknown escape sequence! - { - writeOut(c); - writeOut(c2); - } - break; - //*INDENT-ON* + //*INDENT-OFF* + case '"': + case '\\': + case '/': writeOut(c2); break; + case 'b': writeOut('\b'); break; + case 'f': writeOut('\f'); break; + case 'n': writeOut('\n'); break; + case 'r': writeOut('\r'); break; + case 't': writeOut('\t'); break; + default: + if (c2 == 'u' && + str.end() - it >= 5 && + isHexDigit(it[1]) && + isHexDigit(it[2]) && + isHexDigit(it[3]) && + isHexDigit(it[4])) + { + utf16Buf += static_cast<impl::Char16>(static_cast<unsigned char>(unhexify(it[1], it[2])) * 256 + + static_cast<unsigned char>(unhexify(it[3], it[4]))); + it += 4; + } + else //unknown escape sequence! + { + writeOut(c); + writeOut(c2); + } + break; + //*INDENT-ON* } } else diff --git a/zen/legacy_compiler.cpp b/zen/legacy_compiler.cpp index 416993ed..66125b0f 100644 --- a/zen/legacy_compiler.cpp +++ b/zen/legacy_compiler.cpp @@ -5,7 +5,7 @@ // ***************************************************************************** #include "legacy_compiler.h" -#if __cpp_lib_to_chars +#ifdef __cpp_lib_to_chars #error get rid of workarounds #endif diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h index 16e22d03..82c404d8 100644 --- a/zen/legacy_compiler.h +++ b/zen/legacy_compiler.h @@ -7,9 +7,6 @@ #ifndef LEGACY_COMPILER_H_839567308565656789 #define LEGACY_COMPILER_H_839567308565656789 - #include <numbers> //C++20 - - //https://en.cppreference.com/w/cpp/feature_test @@ -18,12 +15,12 @@ //https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html namespace std { - +} //--------------------------------------------------------------------------------- - - -} +//constinit, consteval + #define constinit2 constinit //GCC has it + #define consteval2 consteval // namespace zen diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp index f2b48fd9..1d0c4bf2 100644 --- a/zen/open_ssl.cpp +++ b/zen/open_ssl.cpp @@ -374,7 +374,7 @@ void zen::verifySignature(const std::string& message, const std::string& signatu namespace { -std::wstring formatSslErrorCode(int ec) +std::wstring getSslErrorLiteral(int ec) { switch (ec) { @@ -392,7 +392,7 @@ std::wstring formatSslErrorCode(int ec) ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_CLIENT_HELLO_CB); default: - return replaceCpy<std::wstring>(L"SSL error %x", L"%x", numberTo<std::wstring>(ec)); + return L"SSL error " + numberTo<std::wstring>(ec); } } @@ -532,7 +532,7 @@ public: const int rv = ::SSL_connect(ssl_); //implicitly calls SSL_set_connect_state() if (rv != 1) - throw SysError(formatLastOpenSSLError("SSL_connect") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv))); + throw SysError(formatLastOpenSSLError("SSL_connect") + L' ' + getSslErrorLiteral(::SSL_get_error(ssl_, rv))); if (caCertFilePath) { @@ -579,7 +579,7 @@ public: if ((sslError == SSL_ERROR_SYSCALL && ::ERR_peek_last_error() == 0)) //EOF: only expected for HTTP/1.0 return 0; #endif - throw SysError(formatLastOpenSSLError("SSL_read_ex") + L' ' + formatSslErrorCode(sslError)); + throw SysError(formatLastOpenSSLError("SSL_read_ex") + L' ' + getSslErrorLiteral(sslError)); } assert(bytesReceived > 0); //SSL_read_ex() considers EOF an error! if (bytesReceived > bytesToRead) //better safe than sorry @@ -596,7 +596,7 @@ public: size_t bytesWritten = 0; const int rv = ::SSL_write_ex(ssl_, buffer, bytesToWrite, &bytesWritten); if (rv != 1) - throw SysError(formatLastOpenSSLError("SSL_write_ex") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv))); + throw SysError(formatLastOpenSSLError("SSL_write_ex") + L' ' + getSslErrorLiteral(::SSL_get_error(ssl_, rv))); if (bytesWritten > bytesToWrite) throw SysError(formatSystemError("SSL_write_ex", L"", L"Buffer overflow.")); @@ -657,22 +657,22 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: auto itLine = lines.begin(); if (itLine == lines.end() || !startsWith(*itLine, "PuTTY-User-Key-File-2: ")) throw SysError(L"Unknown key file format"); - const std::string algorithm = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + const std::string algorithm = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; if (itLine == lines.end() || !startsWith(*itLine, "Encryption: ")) throw SysError(L"Unknown key encryption"); - const std::string keyEncryption = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + const std::string keyEncryption = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; if (itLine == lines.end() || !startsWith(*itLine, "Comment: ")) throw SysError(L"Invalid key comment"); - const std::string comment = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + const std::string comment = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; if (itLine == lines.end() || !startsWith(*itLine, "Public-Lines: ")) throw SysError(L"Invalid key: invalid public lines"); - size_t pubLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE)); + size_t pubLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IfNotFoundReturn::none)); ++itLine; std::string publicBlob64; @@ -684,7 +684,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (itLine == lines.end() || !startsWith(*itLine, "Private-Lines: ")) throw SysError(L"Invalid key: invalid private lines"); - size_t privLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE)); + size_t privLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IfNotFoundReturn::none)); ++itLine; std::string privateBlob64; @@ -696,7 +696,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (itLine == lines.end() || !startsWith(*itLine, "Private-MAC: ")) throw SysError(L"Invalid key: MAC missing"); - const std::string macHex = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + const std::string macHex = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; //----------- unpack key file elements --------------------- @@ -945,7 +945,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: algorithm == "ecdsa-sha2-nistp384" || algorithm == "ecdsa-sha2-nistp521") { - const std::string algoShort = afterLast(algorithm, '-', IF_MISSING_RETURN_NONE); + const std::string algoShort = afterLast(algorithm, '-', IfNotFoundReturn::none); if (extractStringPub() != algoShort) throw SysError(L"Invalid public key stream (header)"); @@ -32,7 +32,7 @@ namespace zen // => wxStopWatch implementation uses QueryPerformanceCounter: https://github.com/wxWidgets/wxWidgets/blob/17d72a48ffd4d8ff42eed070ac48ee2de50ceabd/src/common/stopwatch.cpp // => whatever the problem was, it's almost certainly not caused by QueryPerformanceCounter(): // MSDN: "How often does QPC roll over? Not less than 100 years from the most recent system boot" -// https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#How_often_does_QPC_roll_over +// https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#general-faq-about-qpc-and-tsc // // => using the system clock is problematic: https://freefilesync.org/forum/viewtopic.php?t=5280 // diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 9c463546..b1f2c0fd 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -11,7 +11,6 @@ #include <gio/gio.h> #include "scope_guard.h" - using namespace zen; @@ -31,8 +30,15 @@ bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError if (!type) return false; - //implement same behavior as in Windows: if recycler is not existing, delete permanently - if (error && error->code == G_IO_ERROR_NOT_SUPPORTED) + /* g_file_trash() can fail with different error codes/messages when trash is unavailable: + Debian 8 (GLib 2.42): G_IO_ERROR_NOT_SUPPORTED: Unable to find or create trash directory + CentOS 7 (GLib 2.56): G_IO_ERROR_FAILED: Unable to find or create trash directory for file.txt + master (GLib 2.64): G_IO_ERROR_NOT_SUPPORTED: Trashing on system internal mounts is not supported + https://gitlab.gnome.org/GNOME/glib/blob/master/gio/glocalfile.c#L2042 */ + const bool trashUnavailable = error && + ((error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_SUPPORTED) || + startsWith(error->message, "Unable to find or create trash directory")); + if (trashUnavailable) //implement same behavior as on Windows: if recycler is not existing, delete permanently { if (*type == ItemType::folder) removeDirectoryPlainRecursion(itemPath); //throw FileError @@ -42,13 +48,9 @@ bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError } throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)), - formatSystemError("g_file_trash", - error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"", - error ? utfTo<std::wstring>(error->message) : L"Unknown error.")); - //g_quark_to_string(error->domain) + formatGlibError("g_file_trash", error)); } return true; - } diff --git a/zen/recycler.h b/zen/recycler.h index ad96aa53..deb03a1c 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -14,24 +14,21 @@ namespace zen { -/* --------------------- -|Recycle Bin Access| --------------------- +/* -------------------- + |Recycle Bin Access| + -------------------- -Windows -------- --> Recycler API (IFileOperation) always available --> COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize + Windows + ------- + -> Recycler API (IFileOperation) always available + -> COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize -Linux ------ -Compiler flags: `pkg-config --cflags gio-2.0` -Linker flags: `pkg-config --libs gio-2.0` - -Already included in package "gtk+-2.0"! -*/ + Linux + ----- + Compiler flags: `pkg-config --cflags gio-2.0` + Linker flags: `pkg-config --libs gio-2.0` + Already included in package "gtk+-2.0"! */ //move a file or folder to Recycle Bin (deletes permanently if recycler is not available) -> crappy semantics, but we have no choice thanks to Windows' design diff --git a/zen/ring_buffer.h b/zen/ring_buffer.h index a8d629c6..240262fa 100644 --- a/zen/ring_buffer.h +++ b/zen/ring_buffer.h @@ -28,6 +28,8 @@ public: } RingBuffer& operator=(RingBuffer&& tmp) noexcept { swap(tmp); return *this; } //noexcept *required* to support move for reallocations in std::vector and std::swap!!! + ~RingBuffer() { clear(); } + using value_type = T; using reference = T&; using const_reference = const T&; @@ -35,8 +37,6 @@ public: size_t size() const { return size_; } bool empty() const { return size_ == 0; } - ~RingBuffer() { clear(); } - reference front() { checkInvariants(); assert(!empty()); return getBufPtr()[bufStart_]; } const_reference front() const { checkInvariants(); assert(!empty()); return getBufPtr()[bufStart_]; } @@ -184,7 +184,6 @@ public: Iterator& operator++() { ++offset_; return *this; } Iterator& operator+=(ptrdiff_t offset) { offset_ += offset; return *this; } inline friend bool operator==(const Iterator& lhs, const Iterator& rhs) { assert(lhs.container_ == rhs.container_); return lhs.offset_ == rhs.offset_; } - inline friend bool operator!=(const Iterator& lhs, const Iterator& rhs) { return !(lhs == rhs); } inline friend ptrdiff_t operator-(const Iterator& lhs, const Iterator& rhs) { return lhs.offset_ - rhs.offset_; } inline friend Iterator operator+(const Iterator& lhs, ptrdiff_t offset) { Iterator tmp(lhs); return tmp += offset; } Value& operator* () const { return (*container_)[offset_]; } diff --git a/zen/scope_guard.h b/zen/scope_guard.h index e97d3f0a..61422eb4 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -16,17 +16,16 @@ namespace zen { -//Scope Guard -/* - auto guardAio = zen::makeGuard<ScopeGuardRunMode::onExit>([&] { ::CloseHandle(hDir); }); - ... - guardAio.dismiss(); - -Scope Exit: - ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); - ZEN_ON_SCOPE_FAIL(UndoPreviousWork()); - ZEN_ON_SCOPE_SUCCESS(NotifySuccess()); -*/ +/* Scope Guard + + auto guardAio = zen::makeGuard<ScopeGuardRunMode::onExit>([&] { ::CloseHandle(hDir); }); + ... + guardAio.dismiss(); + + Scope Exit: + ZEN_ON_SCOPE_EXIT (CleanUp()); + ZEN_ON_SCOPE_FAIL (UndoPreviousWork()); + ZEN_ON_SCOPE_SUCCESS(NotifySuccess()); */ enum class ScopeGuardRunMode { diff --git a/zen/shell_execute.cpp b/zen/shell_execute.cpp index 90ccfdf3..241b9786 100644 --- a/zen/shell_execute.cpp +++ b/zen/shell_execute.cpp @@ -89,7 +89,7 @@ std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cm THROW_LAST_SYS_ERROR("open"); auto guardTmpFile = makeGuard<ScopeGuardRunMode::onExit>([&] { ::close(fdTempFile); }); - //"deleting while handles are open" == FILE_FLAG_DELETE_ON_CLOSE + //"deleting while handle is open" == FILE_FLAG_DELETE_ON_CLOSE if (::unlink(tempFilePath.c_str()) != 0) THROW_LAST_SYS_ERROR("unlink"); @@ -158,11 +158,16 @@ std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cm guardFdLifeSignW.dismiss(); ::close(fdLifeSignW); //[!] make sure we get EOF when fd is closed by child! - if (::fcntl(fdLifeSignR, F_SETFL, O_NONBLOCK) != 0) - THROW_LAST_SYS_ERROR("fcntl(O_NONBLOCK)"); + const int flags = ::fcntl(fdLifeSignR, F_GETFL); + if (flags == -1) + THROW_LAST_SYS_ERROR("fcntl(F_GETFL)"); + + if (::fcntl(fdLifeSignR, F_SETFL, flags | O_NONBLOCK) == -1) + THROW_LAST_SYS_ERROR("fcntl(F_SETFL, O_NONBLOCK)"); + const auto endTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(*timeoutMs); - for (;;) //EINTR handling? => allow interrupt!? + for (;;) //EINTR handling? => allow interruption!? { //read until EAGAIN char buf[16]; diff --git a/zen/string_base.h b/zen/string_base.h index 615c7d2c..1052de56 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -8,23 +8,19 @@ #define STRING_BASE_H_083217454562342526 #include <algorithm> -#include <cassert> -#include <cstdint> #include <atomic> - #include <compare> #include "string_tools.h" + //Zbase - a policy based string class optimizing performance and flexibility namespace zen { -/* -Allocator Policy: ------------------ +/* Allocator Policy: + ----------------- void* allocate(size_t size) //throw std::bad_alloc void deallocate(void* ptr) - size_t calcCapacity(size_t length) -*/ + size_t calcCapacity(size_t length) */ class AllocatorOptimalSpeed //exponential growth + min size { protected: @@ -45,20 +41,18 @@ protected: static size_t calcCapacity(size_t length) { return length; } }; -/* -Storage Policy: ---------------- -template <typename Char, //Character Type - class AP> //Allocator Policy +/* Storage Policy: + --------------- + template <typename Char, //Character Type + class AP> //Allocator Policy - Char* create(size_t size) - Char* create(size_t size, size_t minCapacity) - Char* clone(Char* ptr) - void destroy(Char* ptr) //must handle "destroy(nullptr)"! - bool canWrite(const Char* ptr, size_t minCapacity) //needs to be checked before writing to "ptr" - size_t length(const Char* ptr) - void setLength(Char* ptr, size_t newLength) -*/ + Char* create(size_t size) + Char* create(size_t size, size_t minCapacity) + Char* clone(Char* ptr) + void destroy(Char* ptr) //must handle "destroy(nullptr)"! + bool canWrite(const Char* ptr, size_t minCapacity) //needs to be checked before writing to "ptr" + size_t length(const Char* ptr) + void setLength(Char* ptr, size_t newLength) */ template <class Char, //Character Type class AP> //Allocator Policy @@ -135,6 +129,12 @@ protected: { assert(size <= minCapacity); + if (minCapacity == 0) //perf: avoid memory allocation for empty string + { + ++globalEmptyString.descr.refCount; + return &globalEmptyString.nullTerm; + } + const size_t newCapacity = AP::calcCapacity(minCapacity); assert(newCapacity >= minCapacity); @@ -186,20 +186,30 @@ protected: private: struct Descriptor { - Descriptor(size_t len, size_t cap) : + constexpr Descriptor(size_t len, size_t cap) : length (static_cast<uint32_t>(len)), capacity(static_cast<uint32_t>(cap)) { static_assert(decltype(refCount)::is_always_lock_free); } - std::atomic<uint32_t> refCount { 1 }; //std:atomic is uninitialized by default! + std::atomic<uint32_t> refCount{1}; //std:atomic is uninitialized by default! uint32_t length; const uint32_t capacity; //allocated size without null-termination }; static Descriptor* descr( Char* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; } static const Descriptor* descr(const Char* ptr) { return reinterpret_cast<const Descriptor*>(ptr) - 1; } + + struct GlobalEmptyString + { + Descriptor descr{0 /*length*/, 0 /*capacity*/}; + Char nullTerm = 0; + }; + static_assert(offsetof(GlobalEmptyString, nullTerm) - offsetof(GlobalEmptyString, descr) == sizeof(Descriptor), "no gap!"); + static_assert(std::is_trivially_destructible_v<GlobalEmptyString>, "this memory needs to live forever"); + + inline static constinit2 GlobalEmptyString globalEmptyString; //constinit: dodge static initialization order fiasco! }; @@ -331,7 +341,6 @@ template <class Char, template <class> class SP> inline Zbase<Char, SP> operator template <class Char, template <class> class SP> inline Zbase<Char, SP>::Zbase() { - //resist the temptation to avoid this allocation by referencing a static global: NO performance advantage, MT issues! rawStr_ = this->create(0); rawStr_[0] = 0; } @@ -612,6 +621,7 @@ Zbase<Char, SP>& Zbase<Char, SP>::append(InputIterator first, InputIterator last } +//don't use unifying assignment but save one move-construction in the r-value case instead! template <class Char, template <class> class SP> inline Zbase<Char, SP>& Zbase<Char, SP>::operator=(const Zbase<Char, SP>& str) { @@ -623,10 +633,14 @@ Zbase<Char, SP>& Zbase<Char, SP>::operator=(const Zbase<Char, SP>& str) template <class Char, template <class> class SP> inline Zbase<Char, SP>& Zbase<Char, SP>::operator=(Zbase<Char, SP>&& tmp) noexcept { - swap(tmp); //don't use unifying assignment but save one move-construction in the r-value case instead! + //don't use swap() but end rawStr_ life time immediately + this->destroy(rawStr_); + rawStr_ = tmp.rawStr_; + tmp.rawStr_ = nullptr; return *this; } + template <class Char, template <class> class SP> inline void Zbase<Char, SP>::pop_back() { diff --git a/zen/string_tools.h b/zen/string_tools.h index cfdb27bd..2c33a4f8 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -19,7 +19,7 @@ #include "legacy_compiler.h" //<charconv> (without compiler crashes) -//enhance arbitray string class with useful non-member functions: +//enhance *any* string class with useful non-member functions: namespace zen { template <class Char> bool isWhiteSpace(Char c); @@ -53,23 +53,22 @@ template <class S, class T, typename = std::enable_if_t<IsStringLikeV<S>>> bool }; -enum FailureReturnVal +enum class IfNotFoundReturn { - IF_MISSING_RETURN_ALL, - IF_MISSING_RETURN_NONE + all, + none }; +template <class S, class T> S afterLast (const S& str, const T& term, IfNotFoundReturn infr); +template <class S, class T> S beforeLast (const S& str, const T& term, IfNotFoundReturn infr); +template <class S, class T> S afterFirst (const S& str, const T& term, IfNotFoundReturn infr); +template <class S, class T> S beforeFirst(const S& str, const T& term, IfNotFoundReturn infr); -template <class S, class T> S afterLast (const S& str, const T& term, FailureReturnVal rv); -template <class S, class T> S beforeLast (const S& str, const T& term, FailureReturnVal rv); -template <class S, class T> S afterFirst (const S& str, const T& term, FailureReturnVal rv); -template <class S, class T> S beforeFirst(const S& str, const T& term, FailureReturnVal rv); - -enum class SplitType +enum class SplitOnEmpty { - ALLOW_EMPTY, - SKIP_EMPTY + allow, + skip }; -template <class S, class T> std::vector<S> split(const S& str, const T& delimiter, SplitType st); +template <class S, class T> std::vector<S> split(const S& str, const T& delimiter, SplitOnEmpty soe); template <class S> S trimCpy(S str, bool fromLeft = true, bool fromRight = true); template <class S> void trim (S& str, bool fromLeft = true, bool fromRight = true); @@ -303,7 +302,7 @@ bool contains(const S& str, const T& term) template <class S, class T> inline -S afterLast(const S& str, const T& term, FailureReturnVal rv) +S afterLast(const S& str, const T& term, IfNotFoundReturn infr) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); @@ -316,7 +315,7 @@ S afterLast(const S& str, const T& term, FailureReturnVal rv) const auto* it = searchLast(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) - return rv == IF_MISSING_RETURN_ALL ? str : S(); + return infr == IfNotFoundReturn::all ? str : S(); it += termLen; return S(it, strLast - it); @@ -324,7 +323,7 @@ S afterLast(const S& str, const T& term, FailureReturnVal rv) template <class S, class T> inline -S beforeLast(const S& str, const T& term, FailureReturnVal rv) +S beforeLast(const S& str, const T& term, IfNotFoundReturn infr) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); @@ -337,14 +336,14 @@ S beforeLast(const S& str, const T& term, FailureReturnVal rv) const auto* it = searchLast(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) - return rv == IF_MISSING_RETURN_ALL ? str : S(); + return infr == IfNotFoundReturn::all ? str : S(); return S(strFirst, it - strFirst); } template <class S, class T> inline -S afterFirst(const S& str, const T& term, FailureReturnVal rv) +S afterFirst(const S& str, const T& term, IfNotFoundReturn infr) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); @@ -357,7 +356,7 @@ S afterFirst(const S& str, const T& term, FailureReturnVal rv) const auto* it = std::search(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) - return rv == IF_MISSING_RETURN_ALL ? str : S(); + return infr == IfNotFoundReturn::all ? str : S(); it += termLen; return S(it, strLast - it); @@ -365,7 +364,7 @@ S afterFirst(const S& str, const T& term, FailureReturnVal rv) template <class S, class T> inline -S beforeFirst(const S& str, const T& term, FailureReturnVal rv) +S beforeFirst(const S& str, const T& term, IfNotFoundReturn infr) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); @@ -378,21 +377,21 @@ S beforeFirst(const S& str, const T& term, FailureReturnVal rv) auto it = std::search(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) - return rv == IF_MISSING_RETURN_ALL ? str : S(); + return infr == IfNotFoundReturn::all ? str : S(); return S(strFirst, it - strFirst); } template <class S, class T> inline -std::vector<S> split(const S& str, const T& delimiter, SplitType st) +std::vector<S> split(const S& str, const T& delimiter, SplitOnEmpty soe) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t delimLen = strLength(delimiter); assert(delimLen > 0); if (delimLen == 0) { - if (str.empty() && st == SplitType::SKIP_EMPTY) + if (str.empty() && soe == SplitOnEmpty::skip) return {}; return { str }; } @@ -408,7 +407,7 @@ std::vector<S> split(const S& str, const T& delimiter, SplitType st) { const auto* const blockEnd = std::search(blockStart, strLast, delimFirst, delimLast); - if (blockStart != blockEnd || st == SplitType::ALLOW_EMPTY) + if (blockStart != blockEnd || soe == SplitOnEmpty::allow) output.emplace_back(blockStart, blockEnd - blockStart); if (blockEnd == strLast) @@ -566,7 +565,7 @@ int saferPrintf(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const template <class S, class T, class Num> inline S printNumber(const T& format, const Num& number) //format a single number using ::sprintf { -#if __cpp_lib_format +#ifdef __cpp_lib_format #error refactor #endif diff --git a/zen/string_traits.h b/zen/string_traits.h index 76d601b3..333c92c7 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -7,33 +7,31 @@ #ifndef STRING_TRAITS_H_813274321443234 #define STRING_TRAITS_H_813274321443234 -#include <string_view> #include <cstring> //strlen +#include <string_view> #include "type_traits.h" //uniform access to string-like types, both classes and character arrays namespace zen { -/* -IsStringLikeV<>: - IsStringLikeV<const wchar_t*> //equals "true" - IsStringLikeV<const int*> //equals "false" - -GetCharTypeT<>: - GetCharTypeT<std::wstring> //equals wchar_t - GetCharTypeT<wchar_t[5]> //equals wchar_t - -strLength(): - strLength(str); //equals str.length() - strLength(array); //equals cStringLength(array) - -strBegin(): -> not null-terminated! -> may be nullptr if length is 0! - std::wstring str(L"dummy"); - char array[] = "dummy"; - strBegin(str); //returns str.c_str() - strBegin(array); //returns array -*/ +/* IsStringLikeV<>: + IsStringLikeV<const wchar_t*> //equals "true" + IsStringLikeV<const int*> //equals "false" + + GetCharTypeT<>: + GetCharTypeT<std::wstring> //equals wchar_t + GetCharTypeT<wchar_t[5]> //equals wchar_t + + strLength(): + strLength(str); //equals str.length() + strLength(array); //equals cStringLength(array) + + strBegin(): -> not null-terminated! -> may be nullptr if length is 0! + std::wstring str(L"dummy"); + char array[] = "dummy"; + strBegin(str); //returns str.c_str() + strBegin(array); //returns array */ //reference a sub-string for consumption by zen string_tools diff --git a/zen/sys_error.cpp b/zen/sys_error.cpp index f9747d45..fa7352f0 100644 --- a/zen/sys_error.cpp +++ b/zen/sys_error.cpp @@ -5,24 +5,11 @@ // ***************************************************************************** #include "sys_error.h" - #include <cstring> + #include <gio/gio.h> using namespace zen; - - -std::wstring zen::getSystemErrorDescription(ErrorCode ec) //return empty string on error -{ - const ErrorCode currentError = getLastError(); //not necessarily == ec - ZEN_ON_SCOPE_EXIT(errno = currentError); - - std::wstring errorMsg; - errorMsg = utfTo<std::wstring>(::strerror(ec)); - return trimCpy(errorMsg); //Windows messages seem to end with a space... -} - - namespace { std::wstring formatSystemErrorCode(ErrorCode ec) @@ -168,6 +155,109 @@ std::wstring formatSystemErrorCode(ErrorCode ec) } +std::wstring zen::formatGlibError(const std::string& functionName, GError* error) +{ + if (!error) + return formatSystemError(functionName, L"", _("Error description not available.") + L" null GError"); + + if (error->domain == G_FILE_ERROR) //"values corresponding to errno codes" + return formatSystemError(functionName, error->code); + + std::wstring errorCode; + if (error->domain == G_IO_ERROR) + errorCode = [&]() -> std::wstring + { + switch (error->code) //GIOErrorEnum: https://gitlab.gnome.org/GNOME/glib/-/blob/master/gio/gioenums.h#L530 + { + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_FAILED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_FOUND); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_EXISTS); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_IS_DIRECTORY); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_DIRECTORY); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_EMPTY); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_REGULAR_FILE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_SYMBOLIC_LINK); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_MOUNTABLE_FILE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_FILENAME_TOO_LONG); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_INVALID_FILENAME); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_TOO_MANY_LINKS); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NO_SPACE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_INVALID_ARGUMENT); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PERMISSION_DENIED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_SUPPORTED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_MOUNTED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_ALREADY_MOUNTED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_CLOSED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_CANCELLED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PENDING); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_READ_ONLY); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_CANT_CREATE_BACKUP); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_WRONG_ETAG); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_TIMED_OUT); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_WOULD_RECURSE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_BUSY); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_WOULD_BLOCK); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_HOST_NOT_FOUND); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_WOULD_MERGE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_FAILED_HANDLED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_TOO_MANY_OPEN_FILES); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_INITIALIZED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_ADDRESS_IN_USE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PARTIAL_INPUT); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_INVALID_DATA); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_DBUS_ERROR); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_HOST_UNREACHABLE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NETWORK_UNREACHABLE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_CONNECTION_REFUSED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PROXY_FAILED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PROXY_AUTH_FAILED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PROXY_NEED_AUTH); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PROXY_NOT_ALLOWED); +#ifndef GLIB_CHECK_VERSION //e.g Debian 8 (GLib 2.42) CentOS 7 (GLib 2.56) +#error Where is GLib? +#endif +#if GLIB_CHECK_VERSION(2, 44, 0) + static_assert(G_IO_ERROR_BROKEN_PIPE == G_IO_ERROR_CONNECTION_CLOSED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_CONNECTION_CLOSED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_CONNECTED); +#endif +#if GLIB_CHECK_VERSION(2, 48, 0) + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_MESSAGE_TOO_LARGE); +#endif + default: + return replaceCpy<std::wstring>(L"GIO error %x", L"%x", numberTo<std::wstring>(error->code)); + } + }(); + else + { + //g-file-error-quark => g-file-error + //g-io-error-quark => g-io-error + std::wstring domain = utfTo<std::wstring>(::g_quark_to_string(error->domain)); //e.g. "g-io-error-quark" + if (endsWith(domain, L"-quark")) + domain = beforeLast(domain, L"-", IfNotFoundReturn::none); + + errorCode = domain + L' ' + numberTo<std::wstring>(error->code); //e.g. "g-io-error 15" + } + + const std::wstring errorMsg = utfTo<std::wstring>(error->message); //e.g. "Unable to find or create trash directory for file.txt" + + return formatSystemError(functionName, errorCode, errorMsg); +} + + + +std::wstring zen::getSystemErrorDescription(ErrorCode ec) //return empty string on error +{ + const ErrorCode currentError = getLastError(); //not necessarily == ec + ZEN_ON_SCOPE_EXIT(errno = currentError); + + std::wstring errorMsg; + 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... +} + + std::wstring zen::formatSystemError(const std::string& functionName, ErrorCode ec) { return formatSystemError(functionName, formatSystemErrorCode(ec), getSystemErrorDescription(ec)); @@ -176,10 +266,10 @@ std::wstring zen::formatSystemError(const std::string& functionName, ErrorCode e std::wstring zen::formatSystemError(const std::string& functionName, const std::wstring& errorCode, const std::wstring& errorMsg) { - std::wstring output = errorCode; + std::wstring output = trimCpy(errorCode); const std::wstring errorMsgFmt = trimCpy(errorMsg); - if (!errorCode.empty() && !errorMsgFmt.empty()) + if (!output.empty() && !errorMsgFmt.empty()) output += L": "; output += errorMsgFmt; diff --git a/zen/sys_error.h b/zen/sys_error.h index 01df17ab..f4b867be 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -7,11 +7,11 @@ #ifndef SYS_ERROR_H_3284791347018951324534 #define SYS_ERROR_H_3284791347018951324534 -//#include <string> #include "scope_guard.h" // #include "utf.h" //not used by this header, but the "rest of the world" needs it! #include "i18n.h" // + #include <glib.h> #include <cerrno> @@ -24,6 +24,7 @@ ErrorCode getLastError(); std::wstring formatSystemError(const std::string& functionName, const std::wstring& errorCode, const std::wstring& errorMsg); std::wstring formatSystemError(const std::string& functionName, ErrorCode ec); + std::wstring formatGlibError(const std::string& functionName, GError* error); //A low-level exception class giving (non-translated) detail information only - same conceptional level like "GetLastError()"! @@ -45,6 +46,12 @@ private: do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false) +/* Example: ASSERT_SYSERROR(expr); + + Equivalent to: + if (!expr) + throw zen::SysError(L"Assertion failed: \"expr\""); */ +#define ASSERT_SYSERROR(expr) ASSERT_SYSERROR_IMPL(expr, #expr) //throw SysError @@ -61,6 +68,17 @@ std::wstring getSystemErrorDescription(ErrorCode ec); //return empty string on e std::wstring getSystemErrorDescription(long long) = delete; + + +namespace impl +{ +inline bool validateBool(bool b) { return b; } +inline bool validateBool(void* b) { return b; } +bool validateBool(int) = delete; //catch unintended bool conversions, e.g. HRESULT +} +#define ASSERT_SYSERROR_IMPL(expr, exprStr) \ + { if (!impl::validateBool(expr)) \ + throw zen::SysError(L"Assertion failed: \"" L ## exprStr L"\""); } } #endif //SYS_ERROR_H_3284791347018951324534 diff --git a/zen/system.cpp b/zen/sys_info.cpp index 23e2c343..da5cc61f 100644 --- a/zen/system.cpp +++ b/zen/sys_info.cpp @@ -4,9 +4,10 @@ // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** -#include "system.h" -#include "file_access.h" +#include "sys_info.h" #include "crc.h" +#include "file_access.h" +#include "sys_version.h" #include "symlink_target.h" #include "file_io.h" @@ -14,9 +15,9 @@ #include <net/if.h> //IFF_LOOPBACK #include <netpacket/packet.h> //sockaddr_ll + #include <unistd.h> //getuid() #include <pwd.h> //getpwuid_r() - #include "shell_execute.h" using namespace zen; @@ -53,7 +54,7 @@ ComputerModel zen::getComputerModel() //throw FileError return std::wstring(); try { - const std::string stream = loadBinContainer<std::string>(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError + const std::string stream = getFileContent(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError return utfTo<std::wstring>(trimCpy(stream)); } catch (const FileError& e) { throw SysError(replaceCpy(e.toString(), L"\n\n", L'\n')); } //errors should be further enriched by context info => SysError @@ -96,36 +97,36 @@ std::wstring zen::getOsDescription() //throw FileError { try { - //"lsb_release" not available on some systems: https://freefilesync.org/forum/viewtopic.php?t=7191 - // => use /etc/os-release: https://www.freedesktop.org/software/systemd/man/os-release.html - std::string releaseInfo; - try - { - releaseInfo = loadBinContainer<std::string>("/etc/os-release", nullptr /*notifyUnbufferedIO*/); //throw FileError - } - catch (const FileError& e) { throw SysError(replaceCpy(e.toString(), L"\n\n", L'\n')); } //errors should be further enriched by context info => SysError + const OsVersionDetail verDetail = getOsVersionDetail(); //throw SysError + return trimCpy(verDetail.osName + L' ' + verDetail.osVersionRaw); //e.g. "CentOS 7.8.2003" - std::string osName; - std::string osVersion; - for (const std::string& line : split(releaseInfo, '\n', SplitType::SKIP_EMPTY)) //throw FileError - if (startsWith(line, "NAME=")) - osName = afterFirst(line, '=', IF_MISSING_RETURN_NONE); - else if (startsWith(line, "VERSION_ID=")) - osVersion = afterFirst(line, '=', IF_MISSING_RETURN_NONE); + } + catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); } +} - trim(osName, true, true, [](char c) { return c == '"' || c == '\''; }); - trim(osVersion, true, true, [](char c) { return c == '"' || c == '\''; }); - if (osName.empty()) throw SysError(formatSystemError("/etc/os-release", L"", L"NAME missing.")); - //VERSION_ID usually available, except for Arch Linux: https://freefilesync.org/forum/viewtopic.php?t=7276 - //PRETTY_NAME? too wordy! e.g. "Fedora 17 (Beefy Miracle)" - return utfTo<std::wstring>(trimCpy(osName + ' ' + osVersion)); //e.g. "CentOS Linux 7" +Zstring zen::getDesktopPath() //throw FileError +{ + try + { + const char* path = ::getenv("HOME"); //no extended error reporting + if (!path) + throw SysError(L"Cannot find HOME environment variable."); + + return appendSeparator(path) + "Desktop"; + } + catch (const SysError& e) + { + throw FileError(_("Cannot get process information."), e.toString() ); } - catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); } -} +} +Zstring zen::getProcessPath() //throw FileError +{ + return getSymlinkRawContent("/proc/self/exe").targetPath; //throw FileError +} diff --git a/zen/system.h b/zen/sys_info.h index f10a6a40..9cd328bb 100644 --- a/zen/system.h +++ b/zen/sys_info.h @@ -28,6 +28,9 @@ ComputerModel getComputerModel(); //throw FileError std::wstring getOsDescription(); //throw FileError +Zstring getDesktopPath(); //throw FileError +Zstring getProcessPath(); //throw FileError + } #endif //SYSTEM_H_4189731847832147508915 diff --git a/zen/sys_version.cpp b/zen/sys_version.cpp new file mode 100644 index 00000000..46918315 --- /dev/null +++ b/zen/sys_version.cpp @@ -0,0 +1,91 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "sys_version.h" + #include <iostream> + #include "file_io.h" + #include "shell_execute.h" + +using namespace zen; + + +OsVersionDetail zen::getOsVersionDetail() //throw SysError +{ + /* prefer lsb_release: lsb_release Distributor ID: Debian + 1. terser OS name Release: 8.11 + 2. detailed version number + /etc/os-release NAME="Debian GNU/Linux" + VERSION_ID="8" */ + std::wstring osName; + std::wstring osVersion; + try + { + if (const auto [exitCode, output] = consoleExecute("lsb_release --id -s", std::nullopt); //throw SysError + exitCode != 0) + throw SysError(formatSystemError("lsb_release --id", replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output)); + else + osName = trimCpy(output); + + if (const auto [exitCode, output] = consoleExecute("lsb_release --release -s", std::nullopt); //throw SysError + exitCode != 0) + throw SysError(formatSystemError("lsb_release --release", replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output)); + else + osVersion = trimCpy(output); + } + //lsb_release not available on some systems: https://freefilesync.org/forum/viewtopic.php?t=7191 + catch (SysError&) // => fall back to /etc/os-release: https://www.freedesktop.org/software/systemd/man/os-release.html + { + std::string releaseInfo; + try + { + releaseInfo = getFileContent("/etc/os-release", nullptr /*notifyUnbufferedIO*/); //throw FileError + } + catch (const FileError& e) { throw SysError(replaceCpy(e.toString(), L"\n\n", L'\n')); } //errors should be further enriched by context info => SysError + + for (const std::string& line : split(releaseInfo, '\n', SplitOnEmpty::skip)) + if (startsWith(line, "NAME=")) + osName = utfTo<std::wstring>(afterFirst(line, '=', IfNotFoundReturn::none)); + else if (startsWith(line, "VERSION_ID=")) + osVersion = utfTo<std::wstring>(afterFirst(line, '=', IfNotFoundReturn::none)); + //PRETTY_NAME? too wordy! e.g. "Fedora 17 (Beefy Miracle)" + + trim(osName, true, true, [](char c) { return c == L'"' || c == L'\''; }); + trim(osVersion, true, true, [](char c) { return c == L'"' || c == L'\''; }); + } + + if (osName.empty()) + throw SysError(L"Operating system release could not be determined."); //should never happen! + //osVersion is usually available, except for Arch Linux: https://freefilesync.org/forum/viewtopic.php?t=7276 + // lsb_release Release is "rolling" + // etc/os-release: VERSION_ID is missing + + std::vector<std::wstring> verDigits = split<std::wstring>(osVersion, L'.', SplitOnEmpty::allow); //e.g. "7.7.1908" + verDigits.resize(2); + + return OsVersionDetail + { + { + stringTo<int>(verDigits[0]), + stringTo<int>(verDigits[1]) + }, + osVersion, osName + }; +} + + +OsVersion zen::getOsVersion() +{ + try + { + static const OsVersionDetail verDetail = getOsVersionDetail(); //throw SysError + return verDetail.version; + } + catch (const SysError& e) + { + std::cerr << utfTo<std::string>(e.toString()) << '\n'; + return {}; //sigh, it's a jungle out there: https://freefilesync.org/forum/viewtopic.php?t=7276 + } +} diff --git a/zen/sys_version.h b/zen/sys_version.h new file mode 100644 index 00000000..4381ad67 --- /dev/null +++ b/zen/sys_version.h @@ -0,0 +1,37 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef WIN_VER_H_238470348254325 +#define WIN_VER_H_238470348254325 + +#include "file_error.h" + + +namespace zen +{ +struct OsVersion //keep it a POD, so that the global version constants can be used during static initialization +{ + int major = 0; + int minor = 0; + + std::strong_ordering operator<=>(const OsVersion&) const = default; +}; + + +struct OsVersionDetail +{ + OsVersion version; + std::wstring osVersionRaw; + std::wstring osName; +}; +OsVersionDetail getOsVersionDetail(); //throw SysError + +OsVersion getOsVersion(); + + +} + +#endif //WIN_VER_H_238470348254325 diff --git a/zen/thread.cpp b/zen/thread.cpp index 6b763f39..89fa0233 100644 --- a/zen/thread.cpp +++ b/zen/thread.cpp @@ -6,52 +6,32 @@ #include "thread.h" #include <sys/prctl.h> - #include <unistd.h> - #include <sys/syscall.h> using namespace zen; -void zen::setCurrentThreadName(const char* threadName) +void zen::setCurrentThreadName(const Zstring& threadName) { - ::prctl(PR_SET_NAME, threadName, 0, 0, 0); + ::prctl(PR_SET_NAME, threadName.c_str(), 0, 0, 0); } namespace { -uint64_t getThreadIdNative() -{ - const pid_t tid = ::syscall(SYS_gettid); //no-fail - //"Invalid thread and process IDs": https://devblogs.microsoft.com/oldnewthing/20040223-00/?p=40503 - //if (tid == 0) -> not sure this holds on Linux, too! - // throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to get thread ID."); - static_assert(sizeof(uint64_t) >= sizeof(tid)); - return tid; -} - - -const uint64_t globalMainThreadId = getThreadId(); //avoid code-gen for "magic static"! -} - - -uint64_t zen::getThreadId() -{ - thread_local const uint64_t tid = getThreadIdNative(); //buffer to get predictable perf characteristics - return tid; +//don't make this a function-scope static (avoid code-gen for "magic static") +const std::thread::id globalMainThreadId = std::this_thread::get_id(); } -uint64_t zen::getMainThreadId() +bool zen::runningOnMainThread() { - //don't make this a function-scope static (avoid code-gen for "magic static") - if (globalMainThreadId == 0) //might be called during static initialization - return getThreadId(); + if (globalMainThreadId == std::thread::id()) //called during static initialization! + return true; - return globalMainThreadId; + return std::this_thread::get_id() == globalMainThreadId; } diff --git a/zen/thread.h b/zen/thread.h index 99e61e1f..1bea95ea 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -12,81 +12,89 @@ #include "scope_guard.h" #include "ring_buffer.h" #include "string_tools.h" +#include "zstring.h" namespace zen { class InterruptionStatus; +//migrate towards https://en.cppreference.com/w/cpp/thread/jthread class InterruptibleThread { public: InterruptibleThread() {} - InterruptibleThread (InterruptibleThread&&) noexcept = default; - InterruptibleThread& operator=(InterruptibleThread&&) noexcept = default; + InterruptibleThread (InterruptibleThread&& ) noexcept = default; + InterruptibleThread& operator=(InterruptibleThread&& tmp) noexcept //don't use swap() but end stdThread_ life time immediately + { + if (joinable()) + { + requestStop(); + join(); + } + stdThread_ = std::move(tmp.stdThread_); + intStatus_ = std::move(tmp.intStatus_); + return *this; + } template <class Function> - InterruptibleThread(Function&& f); + explicit InterruptibleThread(Function&& f); + + ~InterruptibleThread() + { + if (joinable()) + { + requestStop(); + join(); + } + } bool joinable () const { return stdThread_.joinable(); } - void interrupt(); + void requestStop(); void join () { stdThread_.join(); } void detach () { stdThread_.detach(); } - template <class Rep, class Period> - bool tryJoinFor(const std::chrono::duration<Rep, Period>& relTime) - { - if (threadCompleted_.wait_for(relTime) != std::future_status::ready) - return false; - - stdThread_.join(); //runs thread-local destructors => this better be fast!!! - return true; - } - private: std::thread stdThread_; std::shared_ptr<InterruptionStatus> intStatus_ = std::make_shared<InterruptionStatus>(); - std::future<void> threadCompleted_; }; + +class ThreadStopRequest {}; + //context of worker thread: -void interruptionPoint(); //throw ThreadInterruption +void interruptionPoint(); //throw ThreadStopRequest template<class Predicate> -void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred); //throw ThreadInterruption +void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred); //throw ThreadStopRequest template <class Rep, class Period> -void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime); //throw ThreadInterruption - -void setCurrentThreadName(const char* threadName); +void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime); //throw ThreadStopRequest -uint64_t getThreadId(); //simple integer thread id, unlike boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754 -uint64_t getMainThreadId(); +void setCurrentThreadName(const Zstring& threadName); -inline bool runningMainThread() { return getThreadId() == getMainThreadId(); } +bool runningOnMainThread(); //------------------------------------------------------------------------------------------ -/* -std::async replacement without crappy semantics: - 1. guaranteed to run asynchronously - 2. does not follow C++11 [futures.async], Paragraph 5, where std::future waits for thread in destructor - -Example: - Zstring dirPath = ... - auto ft = zen::runAsync([=]{ return zen::dirExists(dirPath); }); - if (ft.wait_for(std::chrono::milliseconds(200)) == std::future_status::ready && ft.get()) - //dir existing -*/ +/* std::async replacement without crappy semantics: + 1. guaranteed to run asynchronously + 2. does not follow C++11 [futures.async], Paragraph 5, where std::future waits for thread in destructor + + Example: + Zstring dirPath = ... + auto ft = zen::runAsync([=]{ return zen::dirExists(dirPath); }); + if (ft.wait_for(std::chrono::milliseconds(200)) == std::future_status::ready && ft.get()) + //dir existing */ template <class Function> auto runAsync(Function&& fun); //wait for all with a time limit: return true if *all* results are available! //TODO: use std::when_all when available template<class InputIterator, class Duration> -bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration); +bool waitForAllTimed(InputIterator first, InputIterator last, const Duration& wait_duration); template<typename T> inline -bool isReady(const std::future<T>& f) { return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } +bool isReady(const std::future<T>& f) { assert(f.valid()); return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } //------------------------------------------------------------------------------------------ //wait until first job is successful or all failed @@ -115,13 +123,13 @@ private: //------------------------------------------------------------------------------------------ //value associated with mutex and guaranteed protected access: -//TODO: use std::synchronized_value when available +//TODO: use std::synchronized_value when available http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0290r2.html template <class T> class Protected { public: Protected() {} - Protected(T& value) : value_(value) {} + explicit Protected(T& value) : value_(value) {} //Protected(T&& tmp ) : value_(std::move(tmp)) {} <- wait until needed template <class Function> @@ -145,26 +153,24 @@ template <class Function> class ThreadGroup { public: - ThreadGroup(size_t threadCountMax, const std::string& groupName) : threadCountMax_(threadCountMax), groupName_(groupName) + 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__)); } + 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 + ~ThreadGroup() { - for (InterruptibleThread& w : worker_) w.interrupt(); //interrupt all first, then join - for (InterruptibleThread& w : worker_) detach_ ? w.detach() : w.join(); - } + for (InterruptibleThread& w : worker_) + w.requestStop(); //stop *all* at the same time before join! - ThreadGroup(ThreadGroup&& tmp) noexcept : - worker_ (std::move(tmp.worker_)), - workLoad_ (std::move(tmp.workLoad_)), - detach_ (tmp.detach_), - threadCountMax_(tmp.threadCountMax_), - groupName_ (std::move(tmp.groupName_)) { tmp.worker_.clear(); /*just in case: make sure destructor is no-op!*/ } - - ThreadGroup& operator=(ThreadGroup&& tmp) noexcept { swap(tmp); return *this; } //noexcept *required* to support move for reallocations in std::vector and std::swap!!! + if (detach_) //detach() without requestStop() doesn't make sense + for (InterruptibleThread& w : worker_) + w.detach(); + } //context of controlling OR worker thread, non-blocking: - void run(Function&& wi /*should throw ThreadInterruption when needed*/, bool insertFront = false) + void run(Function&& wi /*should throw ThreadStopRequest when needed*/, bool insertFront = false) { { std::lock_guard dummy(workLoad_->lock); @@ -214,22 +220,22 @@ private: void addWorkerThread() { - std::string threadName = groupName_ + '[' + numberTo<std::string>(worker_.size() + 1) + '/' + numberTo<std::string>(threadCountMax_) + ']'; + Zstring threadName = groupName_ + Zstr('[') + numberTo<Zstring>(worker_.size() + 1) + Zstr('/') + numberTo<Zstring>(threadCountMax_) + Zstr(']'); - worker_.emplace_back([wl = workLoad_, threadName = std::move(threadName)] //don't capture "this"! consider detach() and swap() + worker_.emplace_back([wl = workLoad_, threadName = std::move(threadName)] //don't capture "this"! consider detach() and move operations { - setCurrentThreadName(threadName.c_str()); + setCurrentThreadName(threadName); std::unique_lock dummy(wl->lock); for (;;) { - interruptibleWait(wl->conditionNewTask, dummy, [&tasks = wl->tasks] { return !tasks.empty(); }); //throw ThreadInterruption + interruptibleWait(wl->conditionNewTask, dummy, [&tasks = wl->tasks] { return !tasks.empty(); }); //throw ThreadStopRequest Function task = std::move(wl->tasks. front()); //noexcept thanks to move /**/ wl->tasks.pop_front(); // dummy.unlock(); - task(); //throw ThreadInterruption? + task(); //throw ThreadStopRequest? dummy.lock(); if (--(wl->tasksPending) == 0) @@ -239,22 +245,14 @@ private: callbacks.swap(wl->onCompletionCallbacks); dummy.unlock(); - for (const auto& cb : callbacks) cb(); //noexcept! + for (const auto& cb : callbacks) + cb(); //noexcept! dummy.lock(); } } }); } - void swap(ThreadGroup& other) - { - std::swap(worker_, other.worker_); - std::swap(workLoad_, other.workLoad_); - std::swap(detach_, other.detach_); - std::swap(threadCountMax_, other.threadCountMax_); - std::swap(groupName_, other.groupName_); - } - struct WorkLoad { std::mutex lock; @@ -268,7 +266,7 @@ private: std::shared_ptr<WorkLoad> workLoad_ = std::make_shared<WorkLoad>(); bool detach_ = false; size_t threadCountMax_; - std::string groupName_; + Zstring groupName_; }; @@ -313,12 +311,12 @@ auto runAsync(Function&& fun) template<class InputIterator, class Duration> inline -bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& duration) +bool waitForAllTimed(InputIterator first, InputIterator last, const Duration& duration) { const std::chrono::steady_clock::time_point stopTime = std::chrono::steady_clock::now() + duration; for (; first != last; ++first) - if (first->wait_until(stopTime) != std::future_status::ready) - return false; //time elapsed + if (first->wait_until(stopTime) == std::future_status::timeout) + return false; return true; } @@ -360,7 +358,7 @@ private: std::mutex lockResult_; size_t jobsFinished_ = 0; // - std::optional<T> result_; //our condition is: "have result" or "jobsFinished_ == jobsTotal" + std::optional<T> result_; //our condition is: "have result" or "jobsFinished_ == jobsTotal" std::condition_variable conditionJobDone_; }; @@ -390,16 +388,13 @@ std::optional<T> AsyncFirstResult<T>::get() const { return asyncResult_->getResu //------------------------------------------------------------------------------------------ -class ThreadInterruption {}; - - class InterruptionStatus { public: //context of InterruptibleThread instance: - void interrupt() + void requestStop() { - interrupted_ = true; + stopRequested_ = true; { std::lock_guard dummy(lockSleep_); //needed! makes sure the following signal is not lost! @@ -414,34 +409,34 @@ public: } //context of worker thread: - void checkInterruption() //throw ThreadInterruption + void throwIfStopped() //throw ThreadStopRequest { - if (interrupted_) - throw ThreadInterruption(); + if (stopRequested_) + throw ThreadStopRequest(); } //context of worker thread: template<class Predicate> - void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadInterruption + void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadStopRequest { setConditionVar(&cv); ZEN_ON_SCOPE_EXIT(setConditionVar(nullptr)); - //"interrupted_" is not protected by cv's mutex => signal may get lost!!! e.g. after condition was checked but before the wait begins + //"stopRequested_" is not protected by cv's mutex => signal may get lost!!! e.g. after condition was checked but before the wait begins //=> add artifical time out to mitigate! CPU: 0.25% vs 0% for longer time out! - while (!cv.wait_for(lock, std::chrono::milliseconds(1), [&] { return this->interrupted_ || pred(); })) + while (!cv.wait_for(lock, std::chrono::milliseconds(1), [&] { return this->stopRequested_ || pred(); })) ; - checkInterruption(); //throw ThreadInterruption + throwIfStopped(); //throw ThreadStopRequest } //context of worker thread: template <class Rep, class Period> - void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadInterruption + void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadStopRequest { std::unique_lock lock(lockSleep_); - if (conditionSleepInterruption_.wait_for(lock, relTime, [this] { return static_cast<bool>(this->interrupted_); })) - throw ThreadInterruption(); + if (conditionSleepInterruption_.wait_for(lock, relTime, [this] { return static_cast<bool>(this->stopRequested_); })) + throw ThreadStopRequest(); } private: @@ -451,7 +446,7 @@ private: activeCondition_ = cv; } - std::atomic<bool> interrupted_{ false }; //std:atomic is uninitialized by default!!! + std::atomic<bool> stopRequested_{ false }; //std:atomic is uninitialized by default!!! //"The default constructor is trivial: no initialization takes place other than zero initialization of static and thread-local objects." std::condition_variable* activeCondition_ = nullptr; @@ -464,43 +459,40 @@ private: namespace impl { -inline -InterruptionStatus*& refThreadLocalInterruptionStatus() -{ - //thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine: - thread_local InterruptionStatus* threadLocalInterruptionStatus = nullptr; - return threadLocalInterruptionStatus; -} +//thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine: +inline thread_local InterruptionStatus* threadLocalInterruptionStatus = nullptr; } + //context of worker thread: inline -void interruptionPoint() //throw ThreadInterruption +void interruptionPoint() //throw ThreadStopRequest { - assert(impl::refThreadLocalInterruptionStatus()); - if (impl::refThreadLocalInterruptionStatus()) - impl::refThreadLocalInterruptionStatus()->checkInterruption(); //throw ThreadInterruption + assert(impl::threadLocalInterruptionStatus); + if (impl::threadLocalInterruptionStatus) + impl::threadLocalInterruptionStatus->throwIfStopped(); //throw ThreadStopRequest } //context of worker thread: template<class Predicate> inline -void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadInterruption +void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadStopRequest { - assert(impl::refThreadLocalInterruptionStatus()); - if (impl::refThreadLocalInterruptionStatus()) - impl::refThreadLocalInterruptionStatus()->interruptibleWait(cv, lock, pred); + assert(impl::threadLocalInterruptionStatus); + if (impl::threadLocalInterruptionStatus) + impl::threadLocalInterruptionStatus->interruptibleWait(cv, lock, pred); else cv.wait(lock, pred); } + //context of worker thread: template <class Rep, class Period> inline -void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadInterruption +void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadStopRequest { - assert(impl::refThreadLocalInterruptionStatus()); - if (impl::refThreadLocalInterruptionStatus()) - impl::refThreadLocalInterruptionStatus()->interruptibleSleep(relTime); + assert(impl::threadLocalInterruptionStatus); + if (impl::threadLocalInterruptionStatus) + impl::threadLocalInterruptionStatus->interruptibleSleep(relTime); else std::this_thread::sleep_for(relTime); } @@ -509,29 +501,24 @@ void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //thr template <class Function> inline InterruptibleThread::InterruptibleThread(Function&& f) { - std::promise<void> pFinished; - threadCompleted_ = pFinished.get_future(); - stdThread_ = std::thread([f = std::forward<Function>(f), - intStatus = this->intStatus_, - pFinished = std::move(pFinished)]() mutable + intStatus = this->intStatus_]() mutable { - assert(!impl::refThreadLocalInterruptionStatus()); - impl::refThreadLocalInterruptionStatus() = intStatus.get(); - ZEN_ON_SCOPE_EXIT(impl::refThreadLocalInterruptionStatus() = nullptr); - ZEN_ON_SCOPE_EXIT(pFinished.set_value()); + assert(!impl::threadLocalInterruptionStatus); + impl::threadLocalInterruptionStatus = intStatus.get(); + ZEN_ON_SCOPE_EXIT(impl::threadLocalInterruptionStatus = nullptr); try { - f(); //throw ThreadInterruption + f(); //throw ThreadStopRequest } - catch (ThreadInterruption&) {} + catch (ThreadStopRequest&) {} }); } inline -void InterruptibleThread::interrupt() { intStatus_->interrupt(); } +void InterruptibleThread::requestStop() { intStatus_->requestStop(); } } #endif //THREAD_H_7896323423432235246427 @@ -21,12 +21,9 @@ struct TimeComp //replaces std::tm and SYSTEMTIME int hour = 0; //0-23 int minute = 0; //0-59 int second = 0; //0-60 (including leap second) + + bool operator==(const TimeComp&) const = default; }; -inline bool operator==(const TimeComp& lhs, const TimeComp& rhs) -{ - return lhs.second == rhs.second && lhs.minute == rhs.minute && lhs.hour == rhs.hour && lhs.day == rhs.day && lhs.month == rhs.month && lhs.year == rhs.year; -} -inline bool operator!=(const TimeComp& lhs, const TimeComp& rhs) { return !(lhs == rhs); } TimeComp getLocalTime(time_t utc = std::time(nullptr)); //convert time_t (UTC) to local time components, returns TimeComp() on error time_t localToTimeT(const TimeComp& tc); //convert local time components to time_t (UTC), returns -1 on error @@ -199,6 +196,8 @@ TimeComp getCompileTime() } + + inline Zstring formatTime(const Zchar* format, const TimeComp& tc) { @@ -299,11 +299,10 @@ private: const CodePoint* it_; const CodePoint* last_; }; - +} template <class CharType> -using UtfDecoder = UtfDecoderImpl<CharType, sizeof(CharType)>; -} +using UtfDecoder = impl::UtfDecoderImpl<CharType, sizeof(CharType)>; //------------------------------------------------------------------------------------------- @@ -325,7 +324,7 @@ template <class UtfString> inline size_t unicodeLength(const UtfString& str) //return number of code points (+ correctly handle broken UTF encoding) { size_t uniLen = 0; - impl::UtfDecoder<GetCharTypeT<UtfString>> decoder(strBegin(str), strLength(str)); + UtfDecoder<GetCharTypeT<UtfString>> decoder(strBegin(str), strLength(str)); while (decoder.getNext()) ++uniLen; return uniLen; @@ -344,7 +343,7 @@ UtfString getUnicodeSubstring(const UtfString& str, size_t uniPosFirst, size_t u UtfDecoder<CharType> decoder(strBegin(str), strLength(str)); for (size_t uniPos = 0; std::optional<CodePoint> cp = decoder.getNext(); ++uniPos) //[!] declaration in condition part of the for-loop - if (uniPosFirst <= uniPos) + if (uniPos >= uniPosFirst) { if (uniPos >= uniPosLast) break; diff --git a/zen/warn_static.h b/zen/warn_static.h index d5f78b5d..86b4ec32 100644 --- a/zen/warn_static.h +++ b/zen/warn_static.h @@ -7,12 +7,10 @@ #ifndef WARN_STATIC_H_08724567834560832745 #define WARN_STATIC_H_08724567834560832745 -/* - Portable Compile-Time Warning +/* Portable Compile-Time Warning ----------------------------- Usage: - warn_static("my message") -*/ + warn_static("my message") */ #define ZEN_STRINGIZE_STRING(NUM) #NUM #define ZEN_STRINGIZE_NUMBER(NUM) ZEN_STRINGIZE_STRING(NUM) diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp index dba890ee..6f17dc08 100644 --- a/zen/zlib_wrap.cpp +++ b/zen/zlib_wrap.cpp @@ -16,7 +16,7 @@ using namespace zen; namespace { -std::wstring formatZlibStatusCode(int sc) +std::wstring getZlibErrorLiteral(int sc) { switch (sc) { @@ -31,7 +31,7 @@ std::wstring formatZlibStatusCode(int sc) ZEN_CHECK_CASE_FOR_CONSTANT(Z_VERSION_ERROR); default: - return replaceCpy<std::wstring>(L"zlib status %x", L"%x", numberTo<std::wstring>(sc)); + return replaceCpy<std::wstring>(L"zlib error %x", L"%x", numberTo<std::wstring>(sc)); } } } @@ -45,37 +45,37 @@ size_t zen::impl::zlib_compressBound(size_t len) size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_t trgLen, int level) //throw SysError { - uLongf bufferSize = static_cast<uLong>(trgLen); + uLongf bufSize = static_cast<uLong>(trgLen); const int rv = ::compress2(static_cast<Bytef*>(trg), //Bytef* dest, - &bufferSize, //uLongf* destLen, + &bufSize, //uLongf* destLen, static_cast<const Bytef*>(src), //const Bytef* source, static_cast<uLong>(srcLen), //uLong sourceLen, level); //int level // Z_OK: success // Z_MEM_ERROR: not enough memory // Z_BUF_ERROR: not enough room in the output buffer - if (rv != Z_OK || bufferSize > trgLen) - throw SysError(formatSystemError("zlib compress2", formatZlibStatusCode(rv), L"")); + if (rv != Z_OK || bufSize > trgLen) + throw SysError(formatSystemError("zlib compress2", getZlibErrorLiteral(rv), L"")); - return bufferSize; + return bufSize; } size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen) //throw SysError { - uLongf bufferSize = static_cast<uLong>(trgLen); + uLongf bufSize = static_cast<uLong>(trgLen); const int rv = ::uncompress(static_cast<Bytef*>(trg), //Bytef* dest, - &bufferSize, //uLongf* destLen, + &bufSize, //uLongf* destLen, static_cast<const Bytef*>(src), //const Bytef* source, static_cast<uLong>(srcLen)); //uLong sourceLen // Z_OK: success // Z_MEM_ERROR: not enough memory // Z_BUF_ERROR: not enough room in the output buffer // Z_DATA_ERROR: input data was corrupted or incomplete - if (rv != Z_OK || bufferSize > trgLen) - throw SysError(formatSystemError("zlib uncompress", formatZlibStatusCode(rv), L"")); + if (rv != Z_OK || bufSize > trgLen) + throw SysError(formatSystemError("zlib uncompress", getZlibErrorLiteral(rv), L"")); - return bufferSize; + return bufSize; } @@ -98,7 +98,7 @@ public: memLevel, //int memLevel Z_DEFAULT_STRATEGY); //int strategy if (rv != Z_OK) - throw SysError(formatSystemError("zlib deflateInit2", formatZlibStatusCode(rv), L"")); + throw SysError(formatSystemError("zlib deflateInit2", getZlibErrorLiteral(rv), L"")); } ~Impl() @@ -133,7 +133,7 @@ public: if (rv == Z_STREAM_END) return bytesToRead - gzipStream_.avail_out; if (rv != Z_OK) - throw SysError(formatSystemError("zlib deflate", formatZlibStatusCode(rv), L"")); + throw SysError(formatSystemError("zlib deflate", getZlibErrorLiteral(rv), L"")); if (gzipStream_.avail_out == 0) return bytesToRead; diff --git a/zen/zstring.cpp b/zen/zstring.cpp index b78bc118..06c839e3 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -16,11 +16,14 @@ using namespace zen; Zstring getUpperCase(const Zstring& str) { + assert(str.find(Zchar('\0')) == Zstring::npos); //don't expect embedded nulls! + //fast pre-check: if (isAsciiString(str)) //perf: in the range of 3.5ns { Zstring output = str; - for (Zchar& c : output) c = asciiToUpper(c); + for (Zchar& c : output) + c = asciiToUpper(c); return output; } @@ -31,7 +34,7 @@ Zstring getUpperCase(const Zstring& str) Zstring output; output.reserve(strNorm.size()); - impl::UtfDecoder<char> decoder(strNorm.c_str(), strNorm.size()); + UtfDecoder<char> decoder(strNorm.c_str(), strNorm.size()); while (const std::optional<impl::CodePoint> cp = decoder.getNext()) impl::codePointToUtf<char>(::g_unichar_toupper(*cp), [&](char c) { output += c; }); //don't use std::towupper: *incomplete* and locale-dependent! @@ -77,6 +80,7 @@ Zstring replaceCpyAsciiNoCase(const Zstring& str, const Zstring& oldTerm, const if (oldTerm.empty()) return str; + //assert(isAsciiString(oldTerm)); Zstring output; for (size_t pos = 0;;) @@ -139,8 +143,8 @@ int compareNoCaseUtf8(const char* lhs, size_t lhsLen, const char* rhs, size_t rh //- wcsncasecmp: https://opensource.apple.com/source/Libc/Libc-763.12/string/wcsncasecmp-fbsd.c // => re-implement comparison based on g_unichar_tolower() to avoid memory allocations - impl::UtfDecoder<char> decL(lhs, lhsLen); - impl::UtfDecoder<char> decR(rhs, rhsLen); + UtfDecoder<char> decL(lhs, lhsLen); + UtfDecoder<char> decR(rhs, rhsLen); for (;;) { const std::optional<impl::CodePoint> cpL = decL.getNext(); diff --git a/zen/zstring.h b/zen/zstring.h index adfd671b..607d3859 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -24,10 +24,10 @@ using Zstringc = zen::Zbase<char>; //using Zstringw = zen::Zbase<wchar_t>; -//Caveat: don't expect input/output string sizes to match: -// - different UTF-8 encoding length of upper-case chars -// - different number of upper case chars (e.g. "ߢ => "SS" on macOS) -// - output is Unicode-normalized +/* Caveat: don't expect input/output string sizes to match: + - different UTF-8 encoding length of upper-case chars + - different number of upper case chars (e.g. "ߢ => "SS" on macOS) + - output is Unicode-normalized */ Zstring getUpperCase(const Zstring& str); //Windows, Linux: precomposed @@ -50,10 +50,9 @@ struct ZstringNoCase //use as STL container key: avoid needless upper-case conve ZstringNoCase(const Zstring& str) : upperCase(getUpperCase(str)) {} Zstring upperCase; - std::strong_ordering operator<=>(const ZstringNoCase& other) const = default; + std::strong_ordering operator<=>(const ZstringNoCase&) const = default; }; - //------------------------------------------------------------------------------------------ //Compare *local* file paths: @@ -112,14 +111,15 @@ Zstring appendPaths(const Zstring& basePath, const Zstring& relPath, Zchar pathS return basePath + relPath; } + inline Zstring nativeAppendPaths(const Zstring& basePath, const Zstring& relPath) { return appendPaths(basePath, relPath, FILE_NAME_SEPARATOR); } inline Zstring getFileExtension(const Zstring& filePath) { - //const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, zen::IF_MISSING_RETURN_ALL); - //return afterLast(fileName, Zstr('.'), zen::IF_MISSING_RETURN_NONE); + //const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + //return afterLast(fileName, Zstr('.'), zen::IfNotFoundReturn::none); auto it = zen::findLast(filePath.begin(), filePath.end(), FILE_NAME_SEPARATOR); if (it == filePath.end()) @@ -141,7 +141,7 @@ const wchar_t EN_DASH = L'\u2013'; const wchar_t* const SPACED_DASH = L" \u2013 "; //using 'EN DASH' const wchar_t LTR_MARK = L'\u200E'; //UTF-8: E2 80 8E const wchar_t RTL_MARK = L'\u200F'; //UTF-8: E2 80 8F -const wchar_t ELLIPSIS = L'\u2026'; //"..." +const wchar_t* const ELLIPSIS = L"\u2026"; //"..." const wchar_t MULT_SIGN = L'\u00D7'; //fancy "x" //const wchar_t NOBREAK_SPACE = L'\u00A0'; diff --git a/zenXml/zenxml/dom.h b/zenXml/zenxml/dom.h index 35226fc7..b49db90c 100644 --- a/zenXml/zenxml/dom.h +++ b/zenXml/zenxml/dom.h @@ -157,7 +157,6 @@ public: PtrIter& operator++() { ++it_; return *this; } PtrIter operator++(int) { PtrIter tmp(*this); operator++(); return tmp; } inline friend bool operator==(const PtrIter& lhs, const PtrIter& rhs) { return lhs.it_ == rhs.it_; } - inline friend bool operator!=(const PtrIter& lhs, const PtrIter& rhs) { return !(lhs == rhs); } T& operator* () const { return AccessPolicy::template objectRef<T>(it_); } T* operator->() const { return &AccessPolicy::template objectRef<T>(it_); } private: diff --git a/zenXml/zenxml/xml.h b/zenXml/zenxml/xml.h index 4058f7bf..f7340b39 100644 --- a/zenXml/zenxml/xml.h +++ b/zenXml/zenxml/xml.h @@ -91,12 +91,12 @@ void saveXml(const XmlDoc& doc, const Zstring& filePath) //throw FileError try //only update XML file if there are changes { if (getFileSize(filePath) == stream.size()) //throw FileError - if (loadBinContainer<std::string>(filePath, nullptr /*notifyUnbufferedIO*/) == stream) //throw FileError + if (getFileContent(filePath, nullptr /*notifyUnbufferedIO*/) == stream) //throw FileError return; } catch (FileError&) {} - saveBinContainer(filePath, stream, nullptr /*notifyUnbufferedIO*/); //throw FileError + setFileContent(filePath, stream, nullptr /*notifyUnbufferedIO*/); //throw FileError } |