diff options
author | B Stack <bgstack15@gmail.com> | 2020-11-02 12:45:42 +0000 |
---|---|---|
committer | B Stack <bgstack15@gmail.com> | 2020-11-02 12:45:42 +0000 |
commit | 3162cbdd63eaf7e930c8b2ebe604e10ecb369d08 (patch) | |
tree | 9939cdd1735bf15e97ad6700419c0604cac7c59e | |
parent | Merge branch '11.2' into 'master' (diff) | |
parent | add upstream 11.3 (diff) | |
download | FreeFileSync-11.3.tar.gz FreeFileSync-11.3.tar.bz2 FreeFileSync-11.3.zip |
Merge branch '11.3' into 'master'11.3
add upstream 11.3
See merge request opensource-tracking/FreeFileSync!27
59 files changed, 1448 insertions, 1414 deletions
@@ -5,7 +5,7 @@ the ones mentioned below. The remaining issues that are yet to be fixed are list ---------------- -| libcurl 7.72 | +| libcurl 7.73 | ---------------- __________________________________________________________________________________________________________ /lib/ftp.c @@ -46,9 +46,9 @@ https://github.com/curl/curl/issues/4342 __________________________________________________________________________________________________________ ------------------ -| libssh2 1.9.0 | ------------------ +-------------------------- +| libssh2 1.9.0-20201014 | +-------------------------- __________________________________________________________________________________________________________ src/session.c memory leak: https://github.com/libssh2/libssh2/issues/28 @@ -62,149 +62,6 @@ move the following constants from src/sftp.h to include/libssh2_sftp.h: #define MAX_SFTP_READ_SIZE 30000 __________________________________________________________________________________________________________ -src/transport.c -https://github.com/libssh2/libssh2/pull/443 - -- if (encrypted && compressed) -+ if (encrypted && compressed && session->local.comp_abstract) - -__________________________________________________________________________________________________________ -src/openssl.cpp -properly support ssh-ed25519: https://github.com/libssh2/libssh2/pull/416 - -+static int -+gen_publickey_from_ed_evp(LIBSSH2_SESSION* session, -+ unsigned char** method, -+ size_t* method_len, -+ unsigned char** pubkeydata, -+ size_t* pubkeydata_len, -+ EVP_PKEY* pk) -+{ -+ const char methodName[] = "ssh-ed25519"; -+ unsigned char* methodBuf = NULL; -+ unsigned char* pubKeyBuf = NULL; -+ size_t pubKeyLen = 0; -+ size_t edKeyLen = 0; -+ unsigned char* bufPos = NULL; -+ -+ _libssh2_debug(session, -+ LIBSSH2_TRACE_AUTH, -+ "Computing public key from ED private key envelop"); -+ -+ methodBuf = LIBSSH2_ALLOC(session, sizeof(methodName) - 1); -+ if (!methodBuf) -+ { -+ _libssh2_error(session, LIBSSH2_ERROR_ALLOC, -+ "Unable to allocate memory for private key data"); -+ goto cleanup; -+ } -+ -+ if (EVP_PKEY_get_raw_public_key(pk, NULL, &edKeyLen) != 1) -+ { -+ _libssh2_error(session, LIBSSH2_ERROR_PROTO, -+ "EVP_PKEY_get_raw_public_key failed"); -+ goto cleanup; -+ } -+ -+ pubKeyLen = 4 + sizeof(methodName) - 1 + 4 + edKeyLen; -+ bufPos = pubKeyBuf = LIBSSH2_ALLOC(session, pubKeyLen); -+ if (!pubKeyBuf) -+ { -+ _libssh2_error(session, LIBSSH2_ERROR_ALLOC, -+ "Unable to allocate memory for private key data"); -+ goto cleanup; -+ } -+ -+ _libssh2_store_str(&bufPos, methodName, sizeof(methodName) - 1); -+ _libssh2_store_u32(&bufPos, edKeyLen); -+ -+ if (EVP_PKEY_get_raw_public_key(pk, bufPos, &edKeyLen) != 1) -+ { -+ _libssh2_error(session, LIBSSH2_ERROR_PROTO, -+ "EVP_PKEY_get_raw_public_key failed"); -+ goto cleanup; -+ } -+ -+ memcpy(methodBuf, methodName, sizeof(methodName) - 1); -+ *method = methodBuf; -+ *method_len = sizeof(methodName) - 1; -+ *pubkeydata = pubKeyBuf; -+ *pubkeydata_len = pubKeyLen; -+ return 0; -+ -+cleanup: -+ if (methodBuf) -+ LIBSSH2_FREE(session, methodBuf); -+ if (pubKeyBuf) -+ LIBSSH2_FREE(session, pubKeyBuf); -+ return -1; -+} -+ -static int -gen_publickey_from_ed25519_openssh_priv_data(LIBSSH2_SESSION* session, - struct string_buf* decrypted, - unsigned char** method, - size_t* method_len, - unsigned char** pubkeydata, - - - - -int -_libssh2_ed25519_new_private_frommemory(libssh2_ed25519_ctx** ed_ctx, - LIBSSH2_SESSION* session, - const char* filedata, - size_t filedata_len, - unsigned const char* passphrase) -{ -+ libssh2_ed25519_ctx* ctx = NULL; -+ -+ _libssh2_init_if_needed(); -+ -+ ctx = _libssh2_ed25519_new_ctx(); -+ if (!ctx) -+ return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, -+ "Unable to allocate memory for ed25519 key"); -+ -+ if (read_private_key_from_memory((void**)&ctx->private_key, (pem_read_bio_func)&PEM_read_bio_PrivateKey, -+ filedata, filedata_len, passphrase) == 0) -+ { -+ if (EVP_PKEY_id(ctx->private_key) != EVP_PKEY_ED25519) -+ { -+ _libssh2_ed25519_free(ctx); -+ return _libssh2_error(session, LIBSSH2_ERROR_PROTO, -+ "Private key is not an ed25519 key"); -+ } -+ -+ *ed_ctx = ctx; -+ return 0; -+ } -+ _libssh2_ed25519_free(ctx); - - return read_openssh_private_key_from_memory((void**)ed_ctx, session, - "ssh-ed25519", - filedata, filedata_len, - passphrase); - - - -#ifdef HAVE_OPAQUE_STRUCTS - pktype = EVP_PKEY_id(pk); -#else - pktype = pk->type; -#endif - - switch (pktype) - { -+#if LIBSSH2_ED25519 -+ case EVP_PKEY_ED25519 : -+ st = gen_publickey_from_ed_evp(session, method, method_len, -+ pubkeydata, pubkeydata_len, pk); -+ break; -+#endif /* LIBSSH2_ED25519 */ - case EVP_PKEY_RSA : - st = gen_publickey_from_rsa_evp(session, method, method_len, -__________________________________________________________________________________________________________ ------------------- diff --git a/Changelog.txt b/Changelog.txt index 404b7cb7..c25ef6d8 100755 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,5 +1,20 @@ -FreeFileSync 11.2 ------------------ +FreeFileSync 11.3 [2020-11-01] +------------------------------ +Enhanced main grid color scheme +Mouse-highlight for file selection +Added file create/delete indicators +Show file list tooltip for missing items +Click folder name and scroll to group start +Log failure to create application default config folder +Added tooltips and fixed help link context menu +Fixed tooltip not updated when scrolling (macOS, Linux) +Move error dialogs to foreground during batch sync +Align context menu popup positions +Updated translation files + + +FreeFileSync 11.2 [2020-10-02] +------------------------------ Improved grid layout with file icons hidden Improved rendering of inactive and disabled grid items Remember last user-selected paths for file and folder pickers @@ -1470,7 +1485,7 @@ Allow CTRL + C to copy selection to clipboard on overview panel Consider current view filter for file selection on overview panel Work around silent failure to set modification times on NTFS volumes (Linux) Avoid main dialog flash when closing progress dialog (Linux) -Do not show middle grid tool tip when dragging outside visible area +Do not show middle grid tooltip when dragging outside visible area Reduced file accesses when loading XML files Simplified structure of GlobalSettings.xml Allow to change default exclusion filter via GlobalSettings.xml: "DefaultExclusionFilter" @@ -1494,7 +1509,7 @@ Implemented file icon support for sync preview (OS X) RealTimeSync exit via menu working again Restore main dialog even if "close progress dialog" is selected Show full path when failing to create directory on not existing target drive -Middle grid tool tip shown correctly again (SUSE Linux/X11) +Middle grid tooltip shown correctly again (SUSE Linux/X11) Prevent process hang when manually writing to directory history (Linux and OS X, wxWidgets 2.9.4) Resolved crash after showing help dialog (OS X) Properly handle non-ASCII characters for external commands (OS X) @@ -1643,7 +1658,7 @@ Apply hidden attribute to lock file Fixed potential "access denied" problem when updating the database file Show errors when saving configuration files during exit (ignore for batch mode) Mark begin of comparison phase in the log file -More detailed tool tip describing items that differ in attributes only +More detailed tooltip describing items that differ in attributes only Added Scottish Gaelic translation @@ -1707,7 +1722,7 @@ New context menu filter option: exclude by short name Use clicked-on row rather than anchor when determining action for shift-selection Refresh grid after pressing "CTRL + A" Add base folder pairs to CSV export -Show full path in tool tip if multiple folder pairs are used +Show full path in tooltip if multiple folder pairs are used Show child dialogs on same monitor as parent dialog on multiple monitor systems Added statistics at beginning of batch log file Fixed batch mode final speed statistic and reset graph after binary comparison @@ -1735,7 +1750,7 @@ Reenabled global shortcut F8 to toggle data shown in middle grid Unified error handling on failure to create log directory Do not close batch creation dialog after save Tree view: compress and filter root nodes the same way as regular folder nodes -Fixed wrong tool tip being shown if directory name changes +Fixed wrong tooltip being shown if directory name changes Date range selector does not trim year field anymore Show action "do nothing" on mouse-hover for conflicts in middle grid Fixed "Windows Error Code 59: An unexpected network error occurred" @@ -1754,7 +1769,7 @@ Even more pedantic user interface fine-tuning Compiles and runs on openSuse 12.1 Fixed grid page-up/down keys scrolling twice (Linux, wxGTK 2.9.3) Fixed unwanted grid scrolling when toggling middle column (Linux, wxGTK 2.9.3) -Fixed middle grid tool tip occasionally going blank (Linux) +Fixed middle grid tooltip occasionally going blank (Linux) Support single shift-click to check/set direction of multiple rows Removed gtkmm dependency (Linux) Installer remembers all settings for next installation (local installation only) @@ -1975,7 +1990,7 @@ Improved color theme support Fixed crash on certain system text color settings Fixed progress numbers for manual deletion Allow aborting manual deletion via escape key -Use relative name for file tool tip +Use relative name for file tooltip Automatically redirect arrow keys to main grid More tolerant directory creation (operation not supported/wrong parameter) More tolerant file move: ignore existing files (user-defined deletion directory) @@ -2018,7 +2033,7 @@ Correctly report message "nothing to sync" in batch mode Removed libjpg-8 dependency (Linux) Fixed loading correct maximized position on multi-screen desktop RealTimeSync: Removed blank icons in ALT-TAB list during execution of command line -Show RealTimeSync job name as systray tool tip +Show RealTimeSync job name as systray tooltip Last used configurations as sorted list without size limitation Remove redundant configuration when merging multiple ffs_gui/ffs_batch files Warning if folder is modified that is part of multiple folder pairs @@ -2032,7 +2047,7 @@ FreeFileSync 3.12 [2010-11-28] Allow empty folder pairs without complaining Automatically exclude database and lock files from all (sub-)directories (not only from base) Resize grid columns on both sides in parallel -Fixed tool tip foreground text color (Linux) +Fixed tooltip foreground text color (Linux) Search via CTRL + F and F3 now as global hotkeys Fully portable use of directory locking (Windows/Linux, 32/64 bit) RealTimeSync: Treat missing network path the same as missing local path @@ -2046,7 +2061,7 @@ Fixed moving buttons in synchronization dialog Allow deleting currently selected item from list of last used folders (not before wxWidgets 2.9.1) Avoid losing focus after manually deleting a file Preserve custom changes to sync directions after manually deleting a file -Handle empty tool tips correctly (Linux) +Handle empty tooltips correctly (Linux) Updated translation files @@ -2060,7 +2075,7 @@ FreeFileSync 3.10 [2010-09-19] ------------------------------ Automatically solve daylight saving time and time zone shift issues on FAT/FAT32 (finally) Instantly resolve abandoned directory locks associated with local computer -Show expanded directory name as tool tip and label text (resolves macros and relative paths) +Show expanded directory name as tooltip and label text (resolves macros and relative paths) Do not copy relative file attributes for base target directories that are created implicitly Move dialogs by clicking (almost) anywhere RealTimeSync: ignore request for device removal on Samba shares @@ -2069,7 +2084,7 @@ Correctly handle window position on multi-screen desktop Disabled warning "database not yet existing" RealTimeSync: replaced delay by minimum idle time Maximum number of folder pairs configurable via GlobalSettings.xml (XML node <FolderPairsMax>) -Added tool tips to display long filenames on main grid +Added tooltips to display long filenames on main grid Keep application responsive when deleting large directories Vista/Windows 7: harmonize modification times shown on main grid with Windows Explorer Changed background color to avoid unreadable texts in combination with certain color themes @@ -2101,7 +2116,7 @@ FreeFileSync 3.8 [2010-06-20] ----------------------------- New options handling Symlinks: ignore/direct/follow => warning: new database format for <Automatic> mode Fixed crash when starting sync for Windows XP SP2 -Prevent tool tip from stealing focus +Prevent tooltip from stealing focus Show associated file icons (Linux) Run folder existence checks in separate thread (faster network share access) Write <Automatic> mode database file even if both sides are already in sync @@ -2271,10 +2286,10 @@ FreeFileSync 2.2 [2009-08-16] New user-defined recycle bin directory Possibility to create synchronization directories automatically (if not existing) Support for relative directory names (e.g. \foo, ..\bar) respecting current working directory -New tool tip in middle grid showing detailed information (including conflicts) +New tooltip in middle grid showing detailed information (including conflicts) Status feedback and new abort button for manual deletion Options to add/remove folder pairs in batch dialog -Added tool tip showing progress for silent batch mode +Added tooltip showing progress for silent batch mode New view filter buttons in synchronization preview Revisioned handling of symbolic links (Linux/Windows) GUI optimizations removing flicker diff --git a/FreeFileSync/Build/Resources/Icons.zip b/FreeFileSync/Build/Resources/Icons.zip Binary files differindex b64eada9..9c64e2ff 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 ed116235..cfb06090 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 f9bd706b..7ecd42f8 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 Jul 22 03:12:14 2020 GMT +## Certificate data from Mozilla as of: Wed Oct 14 03:12:15 2020 GMT ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates @@ -14,7 +14,7 @@ ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.28. -## SHA256: cc6408bd4be7fbfb8699bdb40ccb7f6de5780d681d87785ea362646e4dad5e8e +## SHA256: a831d3bc63ba1f65478afe28038742b7150c0c2efd243ac342b64792a75d2038 ## @@ -448,36 +448,6 @@ KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- -Taiwan GRCA -=========== ------BEGIN CERTIFICATE----- -MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG -EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X -DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv -dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN -w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5 -BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O -1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO -htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov -J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7 -Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t -B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB -O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8 -lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV -HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2 -09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ -TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj -Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2 -Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU -D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz -DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk -Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk -7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ -CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy -+fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS ------END CERTIFICATE----- - DigiCert Assured ID Root CA =========================== -----BEGIN CERTIFICATE----- @@ -806,29 +776,6 @@ FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- -OISTE WISeKey Global Root GA CA -=============================== ------BEGIN CERTIFICATE----- -MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE -BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG -A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH -bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD -VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw -IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5 -IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9 -Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg -Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD -d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ -/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R -LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ -KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm -MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4 -+vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa -hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY -okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= ------END CERTIFICATE----- - Certigna ======== -----BEGIN CERTIFICATE----- @@ -1709,30 +1656,6 @@ P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== -----END CERTIFICATE----- -EE Certification Centre Root CA -=============================== ------BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG -EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy -dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw -MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB -UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy -ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM -TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2 -rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw -93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN -P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T -AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ -MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF -BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj -xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM -lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u -uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU -3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM -dcGWxZ0= ------END CERTIFICATE----- - D-TRUST Root Class 3 CA 2 2009 ============================== -----BEGIN CERTIFICATE----- @@ -3445,3 +3368,68 @@ Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N 0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= -----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29 +zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf +LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq +stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o +WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+ +OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40 +Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE +uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm ++9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj +ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H +PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H +ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla +4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R +vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd +zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O +856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH +Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu +3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp index 7a642545..37a5aa19 100644 --- a/FreeFileSync/Source/RealTimeSync/application.cpp +++ b/FreeFileSync/Source/RealTimeSync/application.cpp @@ -20,7 +20,6 @@ #include "../ffs_paths.h" #include "../return_codes.h" #include "../fatal_error.h" -#include "../help_provider.h" #include <gtk/gtk.h> @@ -121,7 +120,7 @@ bool Application::OnInit() int Application::OnExit() { fff::releaseWxLocale(); - ImageResourcesCleanup(); + imageResourcesCleanup(); return wxApp::OnExit(); } diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp index 4e0e24bd..4fe86a4f 100644 --- a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp +++ b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp @@ -123,7 +123,9 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr bSizer152->Add( m_staticText11, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 2 ); - m_hyperlink243 = new wxHyperlinkCtrl( this, wxID_ANY, _("Show examples"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink243 = new wxHyperlinkCtrl( this, wxID_ANY, _("Show examples"), wxT("https://freefilesync.org/manual.php?topic=realtimesync"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink243->SetToolTip( _("https://freefilesync.org/manual.php?topic=realtimesync") ); + bSizer152->Add( m_hyperlink243, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); @@ -300,7 +302,6 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr 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 ); diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.h b/FreeFileSync/Source/RealTimeSync/gui_generated.h index 51b92712..297b6291 100644 --- a/FreeFileSync/Source/RealTimeSync/gui_generated.h +++ b/FreeFileSync/Source/RealTimeSync/gui_generated.h @@ -95,7 +95,6 @@ protected: 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(); } diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp index 56a65649..257f4073 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp @@ -17,7 +17,6 @@ #include "config.h" #include "tray_menu.h" #include "app_icon.h" -#include "../help_provider.h" #include "../icon_buffer.h" #include "../ffs_paths.h" #include "../version/version.h" @@ -82,7 +81,7 @@ MainDialog::MainDialog(const Zstring& cfgFileName) : m_bpButtonRemoveTopFolder->Hide(); m_panelMainFolder->Layout(); - m_bitmapBatch ->SetBitmap(loadImage("file_batch_sicon")); + m_bitmapBatch ->SetBitmap(loadImage("cfg_batch_sicon")); m_bitmapFolders->SetBitmap(fff::IconBuffer::genericDirIcon(fff::IconBuffer::SIZE_SMALL)); m_bitmapConsole->SetBitmap(loadImage("command_line", fastFromDIP(20))); @@ -174,13 +173,6 @@ void MainDialog::onQueryEndSession() } - -void MainDialog::onShowHelp(wxCommandEvent& event) -{ - fff::displayHelpEntry(L"realtimesync", this); -} - - void MainDialog::onMenuAbout(wxCommandEvent& event) { wxString build = utfTo<wxString>(fff::ffsVersion); @@ -257,7 +249,7 @@ void MainDialog::onConfigSave(wxCommandEvent& event) //attention: activeConfigFile_ may be an imported *.ffs_batch file! We don't want to overwrite it with a RTS config! defaultFileName = beforeLast(defaultFileName, Zstr('.'), IfNotFoundReturn::all) + Zstr(".ffs_real"); - wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), utfTo<wxString>(defaultFileName), + wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), utfTo<wxString>(defaultFileName), wxString(L"RealTimeSync (*.ffs_real)|*.ffs_real") + L"|" +_("All files") + L" (*.*)|*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (fileSelector.ShowModal() != wxID_OK) @@ -318,10 +310,11 @@ void MainDialog::setLastUsedConfig(const Zstring& filepath) void MainDialog::onConfigLoad(wxCommandEvent& event) { const Zstring activeCfgFilePath = !equalNativePath(activeConfigFile_, lastRunConfigPath_) ? activeConfigFile_ : Zstring(); + //better: use last user-selected config path instead! std::optional<Zstring> defaultFolderPath = getParentFolderPath(activeCfgFilePath); - wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), wxString() /*default file name*/, + wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), wxString() /*default file name*/, wxString(L"RealTimeSync (*.ffs_real; *.ffs_batch)|*.ffs_real;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", wxFD_OPEN); if (fileSelector.ShowModal() != wxID_OK) diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.h b/FreeFileSync/Source/RealTimeSync/main_dlg.h index 5de4cc26..8ce409a6 100644 --- a/FreeFileSync/Source/RealTimeSync/main_dlg.h +++ b/FreeFileSync/Source/RealTimeSync/main_dlg.h @@ -37,8 +37,7 @@ 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 onShowHelp (wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/manual.php?topic=realtimesync"); } void onMenuAbout (wxCommandEvent& event) override; void onAddFolder (wxCommandEvent& event) override; void onRemoveFolder (wxCommandEvent& event); diff --git a/FreeFileSync/Source/afs/abstract.cpp b/FreeFileSync/Source/afs/abstract.cpp index 59b3c52e..dac36d17 100644 --- a/FreeFileSync/Source/afs/abstract.cpp +++ b/FreeFileSync/Source/afs/abstract.cpp @@ -59,10 +59,10 @@ std::optional<AbstractPath> AFS::getParentPath(const AbstractPath& ap) std::optional<AfsPath> AFS::getParentPath(const AfsPath& afsPath) { - if (afsPath.value.empty()) - return {}; + if (!afsPath.value.empty()) + return AfsPath(beforeLast(afsPath.value, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); - return AfsPath(beforeLast(afsPath.value, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); + return {}; } diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h index 79b7077e..353a5ece 100644 --- a/FreeFileSync/Source/afs/abstract.h +++ b/FreeFileSync/Source/afs/abstract.h @@ -517,7 +517,6 @@ void AbstractFileSystem::moveAndRenameItem(const AbstractPath& pathFrom, const A } - inline void AbstractFileSystem::copyNewFolder(const AbstractPath& apSource, const AbstractPath& apTarget, bool copyFilePermissions) //throw FileError { diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp index 2b63e321..40aae38f 100644 --- a/FreeFileSync/Source/application.cpp +++ b/FreeFileSync/Source/application.cpp @@ -25,7 +25,6 @@ #include "ui/main_dlg.h" #include "base_tools.h" #include "config.h" -#include "help_provider.h" #include "fatal_error.h" #include "log_file.h" @@ -144,7 +143,7 @@ bool Application::OnInit() int Application::OnExit() { releaseWxLocale(); - ImageResourcesCleanup(); + imageResourcesCleanup(); teardownAfs(); return wxApp::OnExit(); } diff --git a/FreeFileSync/Source/base/algorithm.cpp b/FreeFileSync/Source/base/algorithm.cpp index 3322f120..b4676594 100644 --- a/FreeFileSync/Source/base/algorithm.cpp +++ b/FreeFileSync/Source/base/algorithm.cpp @@ -777,7 +777,7 @@ void fff::redetermineSyncDirection(const std::vector<std::pair<BaseFolderPair*, RedetermineTwoWay::execute(*baseFolder, *lastSyncState); else //default fallback { - std::wstring msg = _("Setting default synchronization directions: Old files will be overwritten with newer files."); + std::wstring msg = _("Setting directions for first synchronization: Old files will be overwritten with newer files."); if (directCfgs.size() > 1) msg += L'\n' + AFS::getDisplayPath(baseFolder->getAbstractPath< LEFT_SIDE>()) + L' ' + getVariantNameWithSymbol(dirCfg.var) + L' ' + AFS::getDisplayPath(baseFolder->getAbstractPath<RIGHT_SIDE>()); diff --git a/FreeFileSync/Source/base/cmp_filetime.h b/FreeFileSync/Source/base/cmp_filetime.h index 3f4a3e90..04633df7 100644 --- a/FreeFileSync/Source/base/cmp_filetime.h +++ b/FreeFileSync/Source/base/cmp_filetime.h @@ -58,11 +58,11 @@ bool sameFileTime(time_t lhs, time_t rhs, int tolerance, const std::vector<unsig enum class TimeResult { - EQUAL, - LEFT_NEWER, - RIGHT_NEWER, - LEFT_INVALID, - RIGHT_INVALID + equal, + leftNewer, + rightNewer, + leftInvalid, + rightInvalid }; @@ -73,20 +73,20 @@ TimeResult compareFileTime(time_t lhs, time_t rhs, int tolerance, const std::vec static const time_t oneYearFromNow = std::time(nullptr) + 365 * 24 * 3600; if (sameFileTime(lhs, rhs, tolerance, ignoreTimeShiftMinutes)) //last write time may differ by up to 2 seconds (NTFS vs FAT32) - return TimeResult::EQUAL; + return TimeResult::equal; //check for erroneous dates if (lhs < 0 || lhs > oneYearFromNow) //earlier than Jan 1st 1970 or more than one year in future - return TimeResult::LEFT_INVALID; + return TimeResult::leftInvalid; if (rhs < 0 || rhs > oneYearFromNow) - return TimeResult::RIGHT_INVALID; + return TimeResult::rightInvalid; //regular time comparison if (lhs < rhs) - return TimeResult::RIGHT_NEWER; + return TimeResult::rightNewer; else - return TimeResult::LEFT_NEWER; + return TimeResult::leftNewer; } } diff --git a/FreeFileSync/Source/base/comparison.cpp b/FreeFileSync/Source/base/comparison.cpp index 69b85dac..6fc06145 100644 --- a/FreeFileSync/Source/base/comparison.cpp +++ b/FreeFileSync/Source/base/comparison.cpp @@ -289,7 +289,7 @@ void categorizeSymlinkByTime(SymlinkPair& symlink) switch (compareFileTime(symlink.getLastWriteTime<LEFT_SIDE>(), symlink.getLastWriteTime<RIGHT_SIDE>(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift())) { - case TimeResult::EQUAL: + case TimeResult::equal: //Caveat: //1. SYMLINK_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp //2. harmonize with "bool stillInSync()" in algorithm.cpp @@ -301,19 +301,19 @@ void categorizeSymlinkByTime(SymlinkPair& symlink) symlink.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(symlink)); break; - case TimeResult::LEFT_NEWER: + case TimeResult::leftNewer: symlink.setCategory<FILE_LEFT_NEWER>(); break; - case TimeResult::RIGHT_NEWER: + case TimeResult::rightNewer: symlink.setCategory<FILE_RIGHT_NEWER>(); break; - case TimeResult::LEFT_INVALID: + case TimeResult::leftInvalid: symlink.setCategoryConflict(getConflictInvalidDate<LEFT_SIDE>(symlink)); break; - case TimeResult::RIGHT_INVALID: + case TimeResult::rightInvalid: symlink.setCategoryConflict(getConflictInvalidDate<RIGHT_SIDE>(symlink)); break; } @@ -337,7 +337,7 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareByTimeSize(const Resolv switch (compareFileTime(file->getLastWriteTime<LEFT_SIDE>(), file->getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance_, fpConfig.ignoreTimeShiftMinutes)) { - case TimeResult::EQUAL: + case TimeResult::equal: //Caveat: //1. FILE_EQUAL may only be set if short names match in case: InSyncFolder's mapping tables use short name as a key! see db_file.cpp //2. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile @@ -354,19 +354,19 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::compareByTimeSize(const Resolv file->setCategoryConflict(getConflictSameDateDiffSize(*file)); //same date, different filesize break; - case TimeResult::LEFT_NEWER: + case TimeResult::leftNewer: file->setCategory<FILE_LEFT_NEWER>(); break; - case TimeResult::RIGHT_NEWER: + case TimeResult::rightNewer: file->setCategory<FILE_RIGHT_NEWER>(); break; - case TimeResult::LEFT_INVALID: + case TimeResult::leftInvalid: file->setCategoryConflict(getConflictInvalidDate<LEFT_SIDE>(*file)); break; - case TimeResult::RIGHT_INVALID: + case TimeResult::rightInvalid: file->setCategoryConflict(getConflictInvalidDate<RIGHT_SIDE>(*file)); break; } @@ -950,7 +950,7 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv excludefilterFailedRead += relPath.upperCase + Zstr('\n'); //exclude item AND (potential) child items! //somewhat obscure, but it's possible on Linux file systems to have a backslash as part of a file name - //=> avoid misinterpretation when parsing the filter phrase in PathFilter (see path_filter.cpp::addFilterEntry()) + //=> avoid misinterpretation when parsing the filter phrase in PathFilter (see path_filter.cpp::parseFilterPhrase()) if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(excludefilterFailedRead, Zstr('/'), Zstr('?')); if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(excludefilterFailedRead, Zstr('\\'), Zstr('?')); diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp index b6ca33c7..d61de963 100644 --- a/FreeFileSync/Source/base/dir_lock.cpp +++ b/FreeFileSync/Source/base/dir_lock.cpp @@ -15,7 +15,7 @@ #include <zen/file_io.h> #include <zen/sys_info.h> - #include <iostream> //std::cout + #include <iostream> //std::cerr #include <fcntl.h> //open() #include <unistd.h> //close() #include <signal.h> //kill() diff --git a/FreeFileSync/Source/base/path_filter.cpp b/FreeFileSync/Source/base/path_filter.cpp index 36e75bfb..65add3da 100644 --- a/FreeFileSync/Source/base/path_filter.cpp +++ b/FreeFileSync/Source/base/path_filter.cpp @@ -28,21 +28,51 @@ std::strong_ordering fff::operator<=>(const FilterRef& lhs, const FilterRef& rhs } -namespace +std::vector<Zstring> fff::splitByDelimiter(const Zstring& filterPhrase) { -//constructing Zstrings of these in addFilterEntry becomes perf issue for large filter lists => use global POD! -const Zchar sepAsterisk[] = Zstr("/*"); -const Zchar asteriskSep[] = Zstr("*/"); -static_assert(FILE_NAME_SEPARATOR == '/'); + //delimiters may be FILTER_ITEM_SEPARATOR or '\n' + std::vector<Zstring> output; + + 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()) + output.push_back(std::move(entry)); + } + return output; +} -void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& masksFileFolder, std::vector<Zstring>& masksFolder) + +namespace { - //normalize filter input: 1. ignore Unicode normalization form 2. ignore case 3. ignore path separator - Zstring filterFmt = getUpperCase(filterPhrase); - if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(filterFmt, Zstr('/'), FILE_NAME_SEPARATOR); - if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(filterFmt, Zstr('\\'), FILE_NAME_SEPARATOR); - /* phrase | action +void parseFilterPhrase(const Zstring& filterPhrase, std::vector<Zstring>& masksFileFolder, std::vector<Zstring>& masksFolder) +{ + const Zstring sepAsterisk = Zstr("/*"); + const Zstring asteriskSep = Zstr("*/"); + static_assert(FILE_NAME_SEPARATOR == '/'); + + auto processTail = [&](const Zstring& phrase) + { + if (endsWith(phrase, FILE_NAME_SEPARATOR) || //only relevant for folder filtering + endsWith(phrase, sepAsterisk)) // abc\* + { + const Zstring dirPhrase = beforeLast(phrase, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); + if (!dirPhrase.empty()) + masksFolder.push_back(dirPhrase); + } + else if (!phrase.empty()) + masksFileFolder.push_back(phrase); + }; + + for (const Zstring& itemPhrase : splitByDelimiter(filterPhrase)) + { + //normalize filter input: 1. ignore Unicode normalization form 2. ignore case 3. ignore path separator + Zstring phraseFmt = getUpperCase(itemPhrase); + if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(phraseFmt, Zstr('/'), FILE_NAME_SEPARATOR); + if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(phraseFmt, Zstr('\\'), FILE_NAME_SEPARATOR); + /* phrase | action +---------+-------- | \blah | remove \ | \*blah | remove \ @@ -61,26 +91,15 @@ void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& masksFile | blah\* | remove \*; folder only | blah*\* | remove \*; folder only +---------+-------- */ - auto processTail = [&masksFileFolder, &masksFolder](const Zstring& phrase) - { - if (endsWith(phrase, FILE_NAME_SEPARATOR) || //only relevant for folder filtering - endsWith(phrase, sepAsterisk)) // abc\* + + if (startsWith(phraseFmt, FILE_NAME_SEPARATOR)) // \abc + processTail(afterFirst(phraseFmt, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); + else { - const Zstring dirPhrase = beforeLast(phrase, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); - if (!dirPhrase.empty()) - masksFolder.push_back(dirPhrase); + processTail(phraseFmt); + if (startsWith(phraseFmt, asteriskSep)) // *\abc + processTail(afterFirst(phraseFmt, asteriskSep, IfNotFoundReturn::none)); } - else if (!phrase.empty()) - masksFileFolder.push_back(phrase); - }; - - if (startsWith(filterFmt, FILE_NAME_SEPARATOR)) // \abc - processTail(afterFirst(filterFmt, FILE_NAME_SEPARATOR, IfNotFoundReturn::none)); - else - { - processTail(filterFmt); - if (startsWith(filterFmt, asteriskSep)) // *\abc - processTail(afterFirst(filterFmt, asteriskSep, IfNotFoundReturn::none)); } } @@ -101,13 +120,11 @@ const Char* cStringFind(const Char* str, Char ch) //= strchr(), wcschr() } -/* -struct FullMatch -{ - static bool matchesMaskEnd (const Zchar* path) { return *path == 0; } - static bool matchesMaskStar(const Zchar* path) { return true; } -}; -*/ +/* struct FullMatch + { + static bool matchesMaskEnd (const Zchar* path) { return *path == 0; } + static bool matchesMaskStar(const Zchar* path) { return true; } + }; */ struct ParentFolderMatch //strict match of parent folder path! { @@ -221,44 +238,27 @@ bool matchesMaskBegin(const Zstring& name, const std::vector<Zstring>& masks) } } - -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, 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()) - output.push_back(std::move(entry)); - } - - return output; -} - //################################################################################################# NameFilter::NameFilter(const Zstring& includePhrase, const Zstring& excludePhrase) { //setup include/exclude filters for files and directories - for (const Zstring& entry : splitByDelimiter(includePhrase)) addFilterEntry(entry, includeMasksFileFolder, includeMasksFolder); - for (const Zstring& entry : splitByDelimiter(excludePhrase)) addFilterEntry(entry, excludeMasksFileFolder, excludeMasksFolder); + parseFilterPhrase(includePhrase, includeMasksFileFolder, includeMasksFolder); + parseFilterPhrase(excludePhrase, excludeMasksFileFolder, excludeMasksFolder); removeDuplicates(includeMasksFileFolder); - removeDuplicates(includeMasksFolder); + removeDuplicates(includeMasksFolder ); removeDuplicates(excludeMasksFileFolder); - removeDuplicates(excludeMasksFolder); + removeDuplicates(excludeMasksFolder ); } void NameFilter::addExclusion(const Zstring& excludePhrase) { - for (const Zstring& entry : splitByDelimiter(excludePhrase)) addFilterEntry(entry, excludeMasksFileFolder, excludeMasksFolder); + parseFilterPhrase(excludePhrase, excludeMasksFileFolder, excludeMasksFolder); removeDuplicates(excludeMasksFileFolder); - removeDuplicates(excludeMasksFolder); + removeDuplicates(excludeMasksFolder ); } @@ -270,7 +270,7 @@ bool NameFilter::passFileFilter(const Zstring& relFilePath) const const Zstring& pathFmt = getUpperCase(relFilePath); if (matchesMask<AnyMatch >(pathFmt, excludeMasksFileFolder) || //either full match on file or partial match on any parent folder - matchesMask<ParentFolderMatch>(pathFmt, excludeMasksFolder)) //partial match on any parent folder only + matchesMask<ParentFolderMatch>(pathFmt, excludeMasksFolder)) //partial match on any parent folder only return false; return matchesMask<AnyMatch >(pathFmt, includeMasksFileFolder) || @@ -291,34 +291,31 @@ bool NameFilter::passDirFilter(const Zstring& relDirPath, bool* childItemMightMa { if (childItemMightMatch) *childItemMightMatch = false; //perf: no need to traverse deeper; subfolders/subfiles would be excluded by filter anyway! - /* - Attention: the design choice that "childItemMightMatch" is optional implies that the filter must provide correct results no matter if this - value is considered by the client! - In particular, if *childItemMightMatch == false, then any filter evaluations for child items must also return "false"! - This is not a problem for folder traversal which stops at the first *childItemMightMatch == false anyway, but other code continues recursing further, - e.g. the database update code in db_file.cpp recurses unconditionally without filter check! It's possible to construct edge cases with incorrect - behavior if "childItemMightMatch" were not optional: - 1. two folders including a subfolder with some files are in sync with up-to-date database files - 2. deny access to this subfolder on both sides and start sync ignoring errors - 3. => database entries of this subfolder are incorrectly deleted! (if sub-folder is excluded, but child items are not!) - */ + + /* Attention: the design choice that "childItemMightMatch" is optional implies that the filter must provide correct results no matter if this + value is considered by the client! + In particular, if *childItemMightMatch == false, then any filter evaluations for child items must also return "false"! + This is not a problem for folder traversal which stops at the first *childItemMightMatch == false anyway, but other code continues recursing further, + e.g. the database update code in db_file.cpp recurses unconditionally without filter check! It's possible to construct edge cases with incorrect + behavior if "childItemMightMatch" were not optional: + 1. two folders including a subfolder with some files are in sync with up-to-date database files + 2. deny access to this subfolder on both sides and start sync ignoring errors + 3. => database entries of this subfolder are incorrectly deleted! (if sub-folder is excluded, but child items are not!) */ return false; } - if (!matchesMask<AnyMatch>(pathFmt, includeMasksFileFolder) && - !matchesMask<AnyMatch>(pathFmt, includeMasksFolder)) + if (matchesMask<AnyMatch>(pathFmt, includeMasksFileFolder) || + matchesMask<AnyMatch>(pathFmt, includeMasksFolder)) + return true; + + if (childItemMightMatch) { - if (childItemMightMatch) - { - const Zstring& childPathBegin = pathFmt + FILE_NAME_SEPARATOR; + const Zstring& childPathBegin = pathFmt + FILE_NAME_SEPARATOR; - *childItemMightMatch = matchesMaskBegin(childPathBegin, includeMasksFileFolder) || //might match a file or folder in subdirectory - matchesMaskBegin(childPathBegin, includeMasksFolder); // - } - return false; + *childItemMightMatch = matchesMaskBegin(childPathBegin, includeMasksFileFolder) || //might match a file or folder in subdirectory + matchesMaskBegin(childPathBegin, includeMasksFolder ); // } - - return true; + return false; } diff --git a/FreeFileSync/Source/base/path_filter.h b/FreeFileSync/Source/base/path_filter.h index a972bfe7..2760f427 100644 --- a/FreeFileSync/Source/base/path_filter.h +++ b/FreeFileSync/Source/base/path_filter.h @@ -87,10 +87,11 @@ private: friend class CombinedFilter; std::strong_ordering compareSameType(const PathFilter& other) const override; - std::vector<Zstring> includeMasksFileFolder; // - std::vector<Zstring> includeMasksFolder; //upper-case + Unicode-normalized by construction - std::vector<Zstring> excludeMasksFileFolder; // - std::vector<Zstring> excludeMasksFolder; // + //upper-case + Unicode-normalized by construction: + std::vector<Zstring> includeMasksFileFolder; + std::vector<Zstring> includeMasksFolder; + std::vector<Zstring> excludeMasksFileFolder; + std::vector<Zstring> excludeMasksFolder; }; diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp index 0020f081..788fede3 100644 --- a/FreeFileSync/Source/base/synchronization.cpp +++ b/FreeFileSync/Source/base/synchronization.cpp @@ -112,7 +112,7 @@ void SyncStatistics::processFile(const FilePair& file) break; case SO_MOVE_LEFT_FROM: //ignore; already counted - case SO_MOVE_RIGHT_FROM: //=> harmonize with FileView::applyFilterByAction() + case SO_MOVE_RIGHT_FROM: //=> harmonize with FileView::applyActionFilter() break; case SO_OVERWRITE_LEFT: diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp index 663eec57..179979f9 100644 --- a/FreeFileSync/Source/config.cpp +++ b/FreeFileSync/Source/config.cpp @@ -21,8 +21,8 @@ using namespace fff; //functionally needed for correct overload resolution!!! namespace { //------------------------------------------------------------------------------------------------------------------------------- -const int XML_FORMAT_GLOBAL_CFG = 18; //2020-06-13 -const int XML_FORMAT_SYNC_CFG = 16; //2020-04-24 +const int XML_FORMAT_GLOBAL_CFG = 19; //2020-10-13 +const int XML_FORMAT_SYNC_CFG = 17; //2020-10-14 //------------------------------------------------------------------------------------------------------------------------------- } @@ -452,8 +452,8 @@ void writeText(const GridViewType& value, std::string& output) { switch (value) { - case GridViewType::category: - output = "Category"; + case GridViewType::difference: + output = "Difference"; break; case GridViewType::action: output = "Action"; @@ -465,8 +465,8 @@ template <> inline bool readText(const std::string& input, GridViewType& value) { const std::string tmp = trimCpy(input); - if (tmp == "Category") - value = GridViewType::category; + if (tmp == "Difference") + value = GridViewType::difference; else if (tmp == "Action") value = GridViewType::action; else @@ -474,6 +474,18 @@ bool readText(const std::string& input, GridViewType& value) return true; } +//TODO: remove after migration! 2020-10-14 +bool readGridViewTypeV16(const std::string& input, GridViewType& value) +{ + const std::string tmp = trimCpy(input); + if (tmp == "Category") + value = GridViewType::difference; + else if (tmp == "Action") + value = GridViewType::action; + else + return false; + return true; +} template <> inline @@ -792,6 +804,16 @@ bool readText(const std::string& input, SyncVariant& value) template <> inline +void writeStruc(const ColAttributesRim& value, XmlElement& output) +{ + XmlOut out(output); + out.attribute("Type", value.type); + out.attribute("Visible", value.visible); + out.attribute("Width", value.offset); + out.attribute("Stretch", value.stretch); +} + +template <> inline bool readStruc(const XmlElement& input, ColAttributesRim& value) { XmlIn in(input); @@ -802,8 +824,9 @@ bool readStruc(const XmlElement& input, ColAttributesRim& value) return rv1 && rv2 && rv3 && rv4; } + template <> inline -void writeStruc(const ColAttributesRim& value, XmlElement& output) +void writeStruc(const ColAttributesCfg& value, XmlElement& output) { XmlOut out(output); out.attribute("Type", value.type); @@ -812,7 +835,6 @@ void writeStruc(const ColAttributesRim& value, XmlElement& output) out.attribute("Stretch", value.stretch); } - template <> inline bool readStruc(const XmlElement& input, ColAttributesCfg& value) { @@ -824,8 +846,9 @@ bool readStruc(const XmlElement& input, ColAttributesCfg& value) return rv1 && rv2 && rv3 && rv4; } + template <> inline -void writeStruc(const ColAttributesCfg& value, XmlElement& output) +void writeStruc(const ColAttributesTree& value, XmlElement& output) { XmlOut out(output); out.attribute("Type", value.type); @@ -834,7 +857,6 @@ void writeStruc(const ColAttributesCfg& value, XmlElement& output) out.attribute("Stretch", value.stretch); } - template <> inline bool readStruc(const XmlElement& input, ColAttributesTree& value) { @@ -846,16 +868,32 @@ bool readStruc(const XmlElement& input, ColAttributesTree& value) return rv1 && rv2 && rv3 && rv4; } + template <> inline -void writeStruc(const ColAttributesTree& value, XmlElement& output) +void writeStruc(const ViewFilterDefault& value, XmlElement& output) { XmlOut out(output); - out.attribute("Type", value.type); - out.attribute("Visible", value.visible); - out.attribute("Width", value.offset); - out.attribute("Stretch", value.stretch); -} + out.attribute("Equal", value.equal); + out.attribute("Conflict", value.conflict); + out.attribute("Excluded", value.excluded); + + XmlOut catView = out["Difference"]; + catView.attribute("LeftOnly", value.leftOnly); + catView.attribute("RightOnly", value.rightOnly); + catView.attribute("LeftNewer", value.leftNewer); + catView.attribute("RightNewer", value.rightNewer); + catView.attribute("Different", value.different); + + XmlOut actView = out["Action"]; + actView.attribute("CreateLeft", value.createLeft); + actView.attribute("CreateRight", value.createRight); + actView.attribute("UpdateLeft", value.updateLeft); + actView.attribute("UpdateRight", value.updateRight); + actView.attribute("DeleteLeft", value.deleteLeft); + actView.attribute("DeleteRight", value.deleteRight); + actView.attribute("DoNothing", value.doNothing); +} template <> inline bool readStruc(const XmlElement& input, ViewFilterDefault& value) @@ -873,14 +911,14 @@ bool readStruc(const XmlElement& input, ViewFilterDefault& value) readAttr(in, "Conflict", value.conflict); readAttr(in, "Excluded", value.excluded); - XmlIn catView = in["CategoryView"]; - readAttr(catView, "LeftOnly", value.leftOnly); - readAttr(catView, "RightOnly", value.rightOnly); - readAttr(catView, "LeftNewer", value.leftNewer); - readAttr(catView, "RightNewer", value.rightNewer); - readAttr(catView, "Different", value.different); + XmlIn diffView = in["Difference"]; + readAttr(diffView, "LeftOnly", value.leftOnly); + readAttr(diffView, "RightOnly", value.rightOnly); + readAttr(diffView, "LeftNewer", value.leftNewer); + readAttr(diffView, "RightNewer", value.rightNewer); + readAttr(diffView, "Different", value.different); - XmlIn actView = in["ActionView"]; + XmlIn actView = in["Action"]; readAttr(actView, "CreateLeft", value.createLeft); readAttr(actView, "CreateRight", value.createRight); readAttr(actView, "UpdateLeft", value.updateLeft); @@ -892,42 +930,42 @@ bool readStruc(const XmlElement& input, ViewFilterDefault& value) return success; //[!] avoid short-circuit evaluation above } -template <> inline -void writeStruc(const ViewFilterDefault& value, XmlElement& output) +//TODO: remove after migration! 2020-10-13 +bool readViewFilterDefaultV19(const XmlElement& input, ViewFilterDefault& value) { - XmlOut out(output); + XmlIn in(input); - out.attribute("Equal", value.equal); - out.attribute("Conflict", value.conflict); - out.attribute("Excluded", value.excluded); + bool success = true; + auto readAttr = [&](XmlIn& elemIn, const char name[], bool& v) + { + if (!elemIn.attribute(name, v)) + success = false; + }; - XmlOut catView = out["CategoryView"]; - catView.attribute("LeftOnly", value.leftOnly); - catView.attribute("RightOnly", value.rightOnly); - catView.attribute("LeftNewer", value.leftNewer); - catView.attribute("RightNewer", value.rightNewer); - catView.attribute("Different", value.different); + readAttr(in, "Equal", value.equal); + readAttr(in, "Conflict", value.conflict); + readAttr(in, "Excluded", value.excluded); - XmlOut actView = out["ActionView"]; - actView.attribute("CreateLeft", value.createLeft); - actView.attribute("CreateRight", value.createRight); - actView.attribute("UpdateLeft", value.updateLeft); - actView.attribute("UpdateRight", value.updateRight); - actView.attribute("DeleteLeft", value.deleteLeft); - actView.attribute("DeleteRight", value.deleteRight); - actView.attribute("DoNothing", value.doNothing); -} + XmlIn diffView = in["CategoryView"]; + readAttr(diffView, "LeftOnly", value.leftOnly); + readAttr(diffView, "RightOnly", value.rightOnly); + readAttr(diffView, "LeftNewer", value.leftNewer); + readAttr(diffView, "RightNewer", value.rightNewer); + readAttr(diffView, "Different", value.different); + XmlIn actView = in["ActionView"]; + readAttr(actView, "CreateLeft", value.createLeft); + readAttr(actView, "CreateRight", value.createRight); + readAttr(actView, "UpdateLeft", value.updateLeft); + readAttr(actView, "UpdateRight", value.updateRight); + readAttr(actView, "DeleteLeft", value.deleteLeft); + readAttr(actView, "DeleteRight", value.deleteRight); + readAttr(actView, "DoNothing", value.doNothing); -template <> inline -bool readStruc(const XmlElement& input, ExternalApp& value) -{ - XmlIn in(input); - const bool rv1 = in(value.cmdLine); - const bool rv2 = in.attribute("Label", value.description); - return rv1 && rv2; + return success; //[!] avoid short-circuit evaluation above } + template <> inline void writeStruc(const ExternalApp& value, XmlElement& output) { @@ -936,6 +974,15 @@ void writeStruc(const ExternalApp& value, XmlElement& output) out.attribute("Label", value.description); } +template <> inline +bool readStruc(const XmlElement& input, ExternalApp& value) +{ + XmlIn in(input); + const bool rv1 = in(value.cmdLine); + const bool rv2 = in.attribute("Label", value.description); + return rv1 && rv2; +} + template <> inline void writeText(const SyncResult& value, std::string& output) @@ -1406,8 +1453,15 @@ void readConfig(const XmlIn& in, XmlGuiConfig& cfg, int formatVer) //read GUI specific config data XmlIn inGuiCfg = in[formatVer < 10 ? "GuiConfig" : "Gui"]; //TODO: remove if parameter migration after some time! 2018-02-25 - inGuiCfg["MiddleGridView"](cfg.gridViewType); - + //TODO: remove after migration! 2020-10-14 + if (formatVer < 17) + { + if (const XmlElement* elem = inGuiCfg["MiddleGridView"].get()) + if (std::string val; elem->getValue<std::string>(val)) + readGridViewTypeV16(val, cfg.gridViewType); + } + else + inGuiCfg["GridViewType"](cfg.gridViewType); //TODO: remove if clause after migration! 2017-10-24 if (formatVer < 8) @@ -1720,8 +1774,14 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) if (formatVer < 6) inFileGrid = inWnd["CenterPanel"]; - inFileGrid.attribute("ShowIcons", cfg.gui.mainDlg.showIcons); - inFileGrid.attribute("IconSize", cfg.gui.mainDlg.iconSize); + //TODO: remove after migration! 2020-10-13 + if (formatVer < 19) + ; //new icon layout => let user re-evaluate settings + else + { + inFileGrid.attribute("ShowIcons", cfg.gui.mainDlg.showIcons); + inFileGrid.attribute("IconSize", cfg.gui.mainDlg.iconSize); + } inFileGrid.attribute("SashOffset", cfg.gui.mainDlg.sashOffset); //TODO: remove if parameter migration after some time! 2018-09-09 @@ -1763,7 +1823,17 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer) inCopyToHistory.attribute("LastSelected", cfg.gui.mainDlg.copyToCfg.targetFolderLastSelected); //########################################################### - inWnd["DefaultViewFilter"](cfg.gui.mainDlg.viewFilterDefault); + //TODO: remove after migration! 2020-10-13 + if (formatVer < 19) + { + if (const XmlElement* elem = inWnd["DefaultViewFilter"].get()) + readViewFilterDefaultV19(*elem, cfg.gui.mainDlg.viewFilterDefault); + } + else + { + inWnd["DefaultViewFilter"](cfg.gui.mainDlg.viewFilterDefault); + } + //TODO: remove old parameter after migration! 2018-02-04 if (formatVer < 8) @@ -2203,7 +2273,7 @@ void writeConfig(const XmlGuiConfig& cfg, XmlOut& out) //write GUI specific config data XmlOut outGuiCfg = out["Gui"]; - outGuiCfg["MiddleGridView"](cfg.gridViewType); + outGuiCfg["GridViewType"](cfg.gridViewType); } diff --git a/FreeFileSync/Source/config.h b/FreeFileSync/Source/config.h index f65c286b..df7e0e3b 100644 --- a/FreeFileSync/Source/config.h +++ b/FreeFileSync/Source/config.h @@ -99,7 +99,7 @@ struct ViewFilterDefault bool equal = false; bool conflict = true; bool excluded = false; - //category view + //difference view bool leftOnly = true; bool rightOnly = true; bool leftNewer = true; diff --git a/FreeFileSync/Source/ffs_paths.cpp b/FreeFileSync/Source/ffs_paths.cpp index d62299b2..e728c54b 100644 --- a/FreeFileSync/Source/ffs_paths.cpp +++ b/FreeFileSync/Source/ffs_paths.cpp @@ -12,6 +12,8 @@ #include <wx/stdpaths.h> #include <wx/app.h> + #include <iostream> //std::cerr + using namespace zen; @@ -88,7 +90,11 @@ Zstring fff::getConfigDirPathPf() { createDirectoryIfMissingRecursion(appendSeparator(cfgFolderPath) + Zstr("Logs")); //throw FileError } - catch (FileError&) { assert(false); } + catch (const FileError& e) + { + assert(false); + std::cerr << utfTo<std::string>(e.toString()) << '\n'; + } return appendSeparator(cfgFolderPath); }(); diff --git a/FreeFileSync/Source/help_provider.h b/FreeFileSync/Source/help_provider.h deleted file mode 100644 index d9a1b3fc..00000000 --- a/FreeFileSync/Source/help_provider.h +++ /dev/null @@ -1,22 +0,0 @@ -// ***************************************************************************** -// * 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 HELP_PROVIDER_H_85930427583421563126 -#define HELP_PROVIDER_H_85930427583421563126 - -#include <wx/utils.h> - - -namespace fff -{ -inline -void displayHelpEntry(const wxString& topic, wxWindow* parent) -{ - wxLaunchDefaultBrowser(L"https://freefilesync.org/manual.php?topic=" + topic); -} -} - -#endif //HELP_PROVIDER_H_85930427583421563126 diff --git a/FreeFileSync/Source/icon_buffer.cpp b/FreeFileSync/Source/icon_buffer.cpp index 2a57d369..145178c9 100644 --- a/FreeFileSync/Source/icon_buffer.cpp +++ b/FreeFileSync/Source/icon_buffer.cpp @@ -74,10 +74,7 @@ public: assert(runningOnMainThread()); { std::lock_guard dummy(lockFiles_); - - workLoad_.clear(); - for (const AbstractPath& filePath : newLoad) - workLoad_.emplace_back(filePath); + workLoad_ = newLoad; } conditionNewWork_.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 //condition handling, see: https://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref @@ -426,10 +423,40 @@ wxImage IconBuffer::linkOverlayIcon(IconSize sz) { const int iconSize = IconBuffer::getSize(sz); - if (iconSize >= fastFromDIP(128)) return "link_128"; - if (iconSize >= fastFromDIP( 48)) return "link_48"; - if (iconSize >= fastFromDIP( 24)) return "link_24"; - return "link_16"; + if (iconSize >= fastFromDIP(128)) return "file_link_128"; + if (iconSize >= fastFromDIP( 48)) return "file_link_48"; + if (iconSize >= fastFromDIP( 24)) return "file_link_24"; + return "file_link_16"; + }()); +} + + +wxImage IconBuffer::plusOverlayIcon(IconSize sz) +{ + //coordinate with IconBuffer::getSize()! + return loadImage([sz] + { + const int iconSize = IconBuffer::getSize(sz); + + if (iconSize >= fastFromDIP(128)) return "file_plus_128"; + if (iconSize >= fastFromDIP( 48)) return "file_plus_48"; + if (iconSize >= fastFromDIP( 24)) return "file_plus_24"; + return "file_plus_16"; + }()); +} + + +wxImage IconBuffer::minusOverlayIcon(IconSize sz) +{ + //coordinate with IconBuffer::getSize()! + return loadImage([sz] + { + const int iconSize = IconBuffer::getSize(sz); + + if (iconSize >= fastFromDIP(128)) return "file_minus_128"; + if (iconSize >= fastFromDIP( 48)) return "file_minus_48"; + if (iconSize >= fastFromDIP( 24)) return "file_minus_24"; + return "file_minus_16"; }()); } diff --git a/FreeFileSync/Source/icon_buffer.h b/FreeFileSync/Source/icon_buffer.h index 4220facf..3693281a 100644 --- a/FreeFileSync/Source/icon_buffer.h +++ b/FreeFileSync/Source/icon_buffer.h @@ -41,6 +41,8 @@ public: static wxImage genericFileIcon(IconSize sz); static wxImage genericDirIcon (IconSize sz); static wxImage linkOverlayIcon(IconSize sz); + static wxImage plusOverlayIcon(IconSize sz); + static wxImage minusOverlayIcon(IconSize sz); private: struct Impl; diff --git a/FreeFileSync/Source/log_file.cpp b/FreeFileSync/Source/log_file.cpp index 4b06bb45..30ecd80b 100644 --- a/FreeFileSync/Source/log_file.cpp +++ b/FreeFileSync/Source/log_file.cpp @@ -11,6 +11,7 @@ #include <wx/datetime.h> #include "ffs_paths.h" #include "afs/concrete.h" + using namespace zen; using namespace fff; using AFS = AbstractFileSystem; diff --git a/FreeFileSync/Source/ui/batch_config.cpp b/FreeFileSync/Source/ui/batch_config.cpp index a7256d47..77ce2f9e 100644 --- a/FreeFileSync/Source/ui/batch_config.cpp +++ b/FreeFileSync/Source/ui/batch_config.cpp @@ -14,7 +14,6 @@ #include <wx+/popup_dlg.h> #include "gui_generated.h" #include "folder_selector.h" -#include "../help_provider.h" using namespace zen; @@ -47,8 +46,6 @@ private: updateGui(); } - void onHelpScheduleBatch(wxHyperlinkEvent& event) override { displayHelpEntry(L"schedule-a-batch-job", this); } - void onLocalKeyEvent(wxKeyEvent& event); void updateGui(); //re-evaluate gui after config changes @@ -73,7 +70,7 @@ BatchDialog::BatchDialog(wxWindow* parent, BatchDialogConfig& dlgCfg) : m_staticTextHeader->SetLabel(replaceCpy(m_staticTextHeader->GetLabel(), L"%x", L"FreeFileSync.exe <" + _("configuration file") + L">.ffs_batch")); m_staticTextHeader->Wrap(fastFromDIP(520)); - m_bitmapBatchJob->SetBitmap(loadImage("file_batch")); + m_bitmapBatchJob->SetBitmap(loadImage("cfg_batch")); enumPostSyncAction_. add(PostSyncAction::none, L""). diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp index 3142d08b..e3d4f4eb 100644 --- a/FreeFileSync/Source/ui/cfg_grid.cpp +++ b/FreeFileSync/Source/ui/cfg_grid.cpp @@ -334,12 +334,11 @@ private: return std::wstring(); } - void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override + void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, HoverArea rowHover) override { if (selected) clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)); - else - clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + //else: clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -> already the default } enum class HoverAreaLog @@ -397,9 +396,9 @@ private: case ConfigView::Details::CFG_TYPE_NONE: return wxNullImage; case ConfigView::Details::CFG_TYPE_GUI: - return loadImage("file_sync_sicon"); + return loadImage("start_sync_sicon"); case ConfigView::Details::CFG_TYPE_BATCH: - return loadImage("file_batch_sicon"); + return loadImage("cfg_batch_sicon"); } assert(false); return wxNullImage; @@ -447,7 +446,7 @@ private: drawBitmapRtlNoMirror(dc, enabled ? statusIcon : statusIcon.ConvertToDisabled(), rectTmp, wxALIGN_CENTER); } if (static_cast<HoverAreaLog>(rowHover) == HoverAreaLog::link) - drawBitmapRtlNoMirror(dc, loadImage("link_16"), rectTmp, wxALIGN_CENTER); + drawBitmapRtlNoMirror(dc, loadImage("file_link_16"), rectTmp, wxALIGN_CENTER); break; } } @@ -471,7 +470,7 @@ private: return 0; } - HoverArea getRowMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override + HoverArea getMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override { if (const ConfigView::Details* item = cfgView_.getItem(row)) switch (static_cast<ColumnTypeCfg>(colType)) @@ -563,7 +562,7 @@ private: return std::wstring(); } - std::wstring getToolTip(size_t row, ColumnType colType) const override + std::wstring getToolTip(size_t row, ColumnType colType, HoverArea rowHover) override { if (const ConfigView::Details* item = cfgView_.getItem(row)) switch (static_cast<ColumnTypeCfg>(colType)) @@ -593,7 +592,7 @@ private: openWithDefaultApp(*nativePath); //throw FileError else assert(false); - assert(!AFS::isNullPath(item->cfgItem.logFilePath)); //see getRowMouseHover() + assert(!AFS::isNullPath(item->cfgItem.logFilePath)); //see getMouseHover() } catch (const FileError& e) { showNotificationDialog(&grid_, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString())); } return; diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp index 8bfa23d9..98f5b215 100644 --- a/FreeFileSync/Source/ui/file_grid.cpp +++ b/FreeFileSync/Source/ui/file_grid.cpp @@ -41,7 +41,7 @@ inline wxColor getColorConflictBackground (bool faint) { if (faint) return { 0xf inline wxColor getColorDifferentBackground(bool faint) { if (faint) return { 0xff, 0xed, 0xee }; return { 255, 185, 187 }; } //red inline wxColor getColorSymlinkBackground() { return { 238, 201, 0 }; } //orange -inline wxColor getColorFolderBackground () { return { 212, 208, 200 }; } //grey +//inline wxColor getColorItemMissing () { return { 212, 208, 200 }; } //medium grey inline wxColor getColorInactiveBack(bool faint) { if (faint) return { 0xf6, 0xf6, 0xf6}; return { 0xe4, 0xe4, 0xe4 }; } //light grey inline wxColor getColorInactiveText() { return { 0x40, 0x40, 0x40 }; } //dark grey @@ -49,6 +49,7 @@ inline wxColor getColorInactiveText() { return { 0x40, 0x40, 0x40 }; } //dark gr inline wxColor getColorGridLine() { return { 192, 192, 192 }; } //light grey const int FILE_GRID_GAP_SIZE_DIP = 2; +const int FILE_GRID_GAP_SIZE_WIDE_DIP = 6; /* class hierarchy: GridDataBase /|\ @@ -117,22 +118,57 @@ wxColor getDefaultBackgroundColorAlternating(bool wantStandardColor) } -wxColor getBackGroundColorSyncAction(SyncOperation so, bool faint) +enum class CudAction +{ + doNothing, + create, + update, + destroy, +}; +std::pair<CudAction, SelectedSide> getCudAction(SyncOperation so) { switch (so) { + //*INDENT-OFF* + case SO_CREATE_NEW_LEFT: + case SO_MOVE_LEFT_TO: return {CudAction::create, LEFT_SIDE}; + + case SO_CREATE_NEW_RIGHT: + case SO_MOVE_RIGHT_TO: return {CudAction::create, RIGHT_SIDE}; + + case SO_DELETE_LEFT: + case SO_MOVE_LEFT_FROM: return {CudAction::destroy, LEFT_SIDE}; + + case SO_DELETE_RIGHT: + case SO_MOVE_RIGHT_FROM: return {CudAction::destroy, RIGHT_SIDE}; + + case SO_OVERWRITE_LEFT: + case SO_COPY_METADATA_TO_LEFT: return {CudAction::update, LEFT_SIDE}; + + case SO_OVERWRITE_RIGHT: + case SO_COPY_METADATA_TO_RIGHT: return {CudAction::update, RIGHT_SIDE}; + case SO_DO_NOTHING: - return getColorInactiveBack(faint); case SO_EQUAL: - break; //usually white + case SO_UNRESOLVED_CONFLICT: return {CudAction::doNothing, LEFT_SIDE}; + //*INDENT-ON* + } + assert(false); + return {CudAction::doNothing, LEFT_SIDE}; +} + +wxColor getBackGroundColorSyncAction(SyncOperation so) +{ + switch (so) + { case SO_CREATE_NEW_LEFT: case SO_OVERWRITE_LEFT: case SO_DELETE_LEFT: case SO_MOVE_LEFT_FROM: case SO_MOVE_LEFT_TO: case SO_COPY_METADATA_TO_LEFT: - return getColorSyncBlue(faint); + return getColorSyncBlue(false /*faint*/); case SO_CREATE_NEW_RIGHT: case SO_OVERWRITE_RIGHT: @@ -140,36 +176,39 @@ wxColor getBackGroundColorSyncAction(SyncOperation so, bool faint) case SO_MOVE_RIGHT_FROM: case SO_MOVE_RIGHT_TO: case SO_COPY_METADATA_TO_RIGHT: - return getColorSyncGreen(faint); + return getColorSyncGreen(false /*faint*/); + case SO_DO_NOTHING: + return getColorInactiveBack(false /*faint*/); + case SO_EQUAL: + break; //usually white case SO_UNRESOLVED_CONFLICT: - return getColorConflictBackground(faint); + return getColorConflictBackground(false /*faint*/); } return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } -wxColor getBackGroundColorCmpCategory(CompareFileResult cmpResult, bool faint) +wxColor getBackGroundColorCmpDifference(CompareFileResult cmpResult) { switch (cmpResult) { - case FILE_LEFT_SIDE_ONLY: - case FILE_LEFT_NEWER: - return getColorSyncBlue(faint); + //*INDENT-OFF* + case FILE_LEFT_SIDE_ONLY: return getColorSyncBlue(false /*faint*/); + case FILE_LEFT_NEWER: return getColorSyncBlue(true /*faint*/); - case FILE_RIGHT_SIDE_ONLY: - case FILE_RIGHT_NEWER: - return getColorSyncGreen(faint); + case FILE_RIGHT_SIDE_ONLY: return getColorSyncGreen(false /*faint*/); + case FILE_RIGHT_NEWER: return getColorSyncGreen(true /*faint*/); case FILE_DIFFERENT_CONTENT: - return getColorDifferentBackground(faint); - + return getColorDifferentBackground(false /*faint*/); case FILE_EQUAL: break; //usually white case FILE_CONFLICT: case FILE_DIFFERENT_METADATA: //= sub-category of equal, but hint via background that sync direction follows conflict-setting - return getColorConflictBackground(faint); + return getColorConflictBackground(false /*faint*/); + //*INDENT-ON* } return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } @@ -185,8 +224,11 @@ struct IconManager IconManager() {} IconManager(GridDataLeft& provLeft, GridDataRight& provRight, IconBuffer::IconSize sz, bool showFileIcons) : - dirIcon_ (IconBuffer::genericDirIcon (showFileIcons ? sz : IconBuffer::SIZE_SMALL)), - linkOverlayIcon_(IconBuffer::linkOverlayIcon(showFileIcons ? sz : IconBuffer::SIZE_SMALL)) + fileIcon_ (IconBuffer::genericFileIcon (showFileIcons ? sz : IconBuffer::SIZE_SMALL)), + dirIcon_ (IconBuffer::genericDirIcon (showFileIcons ? sz : IconBuffer::SIZE_SMALL)), + linkOverlayIcon_ (IconBuffer::linkOverlayIcon (showFileIcons ? sz : IconBuffer::SIZE_SMALL)), + plusOverlayIcon_ (IconBuffer::plusOverlayIcon (showFileIcons ? sz : IconBuffer::SIZE_SMALL)), + minusOverlayIcon_(IconBuffer::minusOverlayIcon(showFileIcons ? sz : IconBuffer::SIZE_SMALL)) { if (showFileIcons) { @@ -200,12 +242,18 @@ struct IconManager IconBuffer* getIconBuffer() { return iconBuffer_.get(); } void startIconUpdater(); - const wxImage& getGenericDirIcon () const { return dirIcon_; } - const wxImage& getLinkOverlayIcon() const { return linkOverlayIcon_; } + const wxImage& getGenericFileIcon () const { return fileIcon_; } + const wxImage& getGenericDirIcon () const { return dirIcon_; } + const wxImage& getLinkOverlayIcon () const { return linkOverlayIcon_; } + const wxImage& getPlusOverlayIcon () const { return plusOverlayIcon_; } + const wxImage& getMinusOverlayIcon() const { return minusOverlayIcon_; } private: - const wxImage dirIcon_ = IconBuffer::genericDirIcon (IconBuffer::SIZE_SMALL); - const wxImage linkOverlayIcon_ = IconBuffer::linkOverlayIcon(IconBuffer::SIZE_SMALL); + const wxImage fileIcon_; + const wxImage dirIcon_; + const wxImage linkOverlayIcon_; + const wxImage plusOverlayIcon_; + const wxImage minusOverlayIcon_; std::unique_ptr<IconBuffer> iconBuffer_; std::unique_ptr<IconUpdater> iconUpdater_; //bind ownership to GridDataRim<>! @@ -333,7 +381,7 @@ class GridDataRim : public GridDataBase public: GridDataRim(Grid& grid, const SharedRef<SharedComponents>& sharedComp) : GridDataBase(grid, sharedComp) {} - void setItemPathForm(ItemPathFormat fmt) { itemPathFormat_ = fmt; } + void setItemPathForm(ItemPathFormat fmt) { itemPathFormat_ = fmt; groupItemNamesWidthBuf_.clear(); } void getUnbufferedIconsForPreload(std::vector<std::pair<ptrdiff_t, AbstractPath>>& newLoad) //return (priority, filepath) list { @@ -350,10 +398,10 @@ public: { const ptrdiff_t currentRow = rowsOnScreen.first - (preloadSize + 1) / 2 + getAlternatingPos(i, visibleRowCount + preloadSize); //for odd preloadSize start one row earlier - const IconInfo ii = getIconInfo(currentRow); - if (ii.type == IconType::standard) - if (!iconBuf->readyForRetrieval(ii.fsObj->template getAbstractPath<side>())) - newLoad.emplace_back(i, ii.fsObj->template getAbstractPath<side>()); //insert least-important items on outer rim first + if (const FileSystemObject* fsObj = getFsObject(currentRow)) + if (getIconInfo(*fsObj).type == IconType::standard) + if (!iconBuf->readyForRetrieval(fsObj->template getAbstractPath<side>())) + newLoad.emplace_back(i, fsObj->template getAbstractPath<side>()); //insert least-important items on outer rim first } } else assert(false); @@ -373,19 +421,19 @@ public: const ptrdiff_t currentRow = rowsOnScreen.first + getAlternatingPos(i, visibleRowCount); if (isFailedLoad(currentRow)) //find failed attempts to load icon - if (const IconInfo ii = getIconInfo(currentRow); - ii.type == IconType::standard) - { - //test if they are already loaded in buffer: - if (iconBuf->readyForRetrieval(ii.fsObj->template getAbstractPath<side>())) + if (const FileSystemObject* fsObj = getFsObject(currentRow)) + if (getIconInfo(*fsObj).type == IconType::standard) { - //do a *full* refresh for *every* failed load to update partial DC updates while scrolling - refGrid().refreshCell(currentRow, static_cast<ColumnType>(ColumnTypeRim::path)); - setFailedLoad(currentRow, false); + //test if they are already loaded in buffer: + if (iconBuf->readyForRetrieval(fsObj->template getAbstractPath<side>())) + { + //do a *full* refresh for *every* failed load to update partial DC updates while scrolling + refGrid().refreshCell(currentRow, static_cast<ColumnType>(ColumnTypeRim::path)); + setFailedLoad(currentRow, false); + } + else //not yet in buffer: mark for async. loading + newLoad.push_back(fsObj->template getAbstractPath<side>()); } - else //not yet in buffer: mark for async. loading - newLoad.push_back(ii.fsObj->template getAbstractPath<side>()); - } } } else assert(false); @@ -415,17 +463,16 @@ private: { inactive, normal, - folder, symlink, }; - DisplayType getObjectDisplayType(const FileSystemObject* fsObj) const + static DisplayType getObjectDisplayType(const FileSystemObject& fsObj) { - if (!fsObj || !fsObj->isActive()) + if (!fsObj.isActive()) return DisplayType::inactive; DisplayType output = DisplayType::normal; - visitFSObject(*fsObj, [&](const FolderPair& folder) { output = DisplayType::folder; }, + visitFSObject(fsObj, [](const FolderPair& folder) {}, [](const FilePair& file) {}, [&](const SymlinkPair& symlink) { output = DisplayType::symlink; }); @@ -454,10 +501,10 @@ private: break; case ColumnTypeRim::size: - visitFSObject(*fsObj, [&](const FolderPair& folder) { value = L"<" + _("Folder") + L">"; }, + 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">"; }); + [&](const SymlinkPair& symlink) { value = L'<' + _("Symlink") + L'>'; }); break; case ColumnTypeRim::date: @@ -475,7 +522,7 @@ private: return value; } - void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override + void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, HoverArea rowHover) override { const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); @@ -483,53 +530,27 @@ private: { const wxColor backCol = [&] { - const DisplayType dispTp = getObjectDisplayType(pdi.fsObj); - - //highlight empty status by repeating middle grid colors - if (pdi.fsObj && pdi.fsObj->isEmpty<side>()) - { - if (dispTp == DisplayType::inactive) - return getColorInactiveBack(true /*faint*/); - - switch (getViewType()) + if (pdi.fsObj && !pdi.fsObj->isEmpty<side>()) //do we need color indication for *inactive* empty rows? probably not... + switch (getObjectDisplayType(*pdi.fsObj)) { - case GridViewType::category: - return getBackGroundColorCmpCategory(pdi.fsObj->getCategory(), true /*faint*/); - case GridViewType::action: - return getBackGroundColorSyncAction(pdi.fsObj->getSyncOperation(), true /*faint*/); + //*INDENT-OFF* + case DisplayType::normal: break; + case DisplayType::symlink: return getColorSymlinkBackground(); + case DisplayType::inactive: return getColorInactiveBack(false /*faint*/); + //*INDENT-ON* } - } - - 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* - case DisplayType::normal: break; - case DisplayType::folder: return getColorFolderBackground(); - case DisplayType::symlink: return getColorSymlinkBackground(); - case DisplayType::inactive: return getColorInactiveBack(false /*faint*/); - //*INDENT-ON* - } - assert(false); - return wxNullColour; + return getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0); }(); - if (backCol.IsOk()) + if (backCol != wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW) /*already the default!*/) clearArea(dc, rect, backCol); } else - GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/ ); + GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/, rowHover); //---------------------------------------------------------------------------------- - wxDCPenChanger dummy(dc, wxPen(row == pdi.groupLastRow - 1 /*last group item*/ ? - getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0), fastFromDIP(1))); - dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); + const wxRect rectLine(rect.x, rect.y + rect.height - fastFromDIP(1), rect.width, fastFromDIP(1)); + clearArea(dc, rectLine, row == pdi.groupLastRow - 1 /*last group item*/ ? + getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0)); } @@ -544,18 +565,24 @@ private: auto& widthBuf = groupItemNamesWidthBuf_; if (pdi.groupIdx >= widthBuf.size()) - widthBuf.resize(pdi.groupIdx + 1); + widthBuf.resize(pdi.groupIdx + 1, -1 /*sentinel value*/); int& itemNamesWidth = widthBuf[pdi.groupIdx]; - if (itemNamesWidth == 0) + if (itemNamesWidth < 0) { - itemNamesWidth = getTextExtentBuffered(dc, ELLIPSIS).x; + itemNamesWidth = 0; + const int ellipsisWidth = getTextExtentBuffered(dc, ELLIPSIS).x; std::vector<int> itemWidths; for (size_t row2 = pdi.groupFirstRow; row2 < pdi.groupLastRow; ++row2) if (const FileSystemObject* fsObj = getDataView().getFsObject(row2)) - if (!fsObj->isEmpty<side>() && fsObj != pdi.folderGroupObj) - itemWidths.push_back(getTextExtentBuffered(dc, utfTo<std::wstring>(fsObj->getItemName<side>())).x); + if (itemPathFormat_ == ItemPathFormat::name || fsObj != pdi.folderGroupObj) + { + if (fsObj->isEmpty<side>()) + itemNamesWidth = ellipsisWidth; + else + itemWidths.push_back(getTextExtentBuffered(dc, utfTo<std::wstring>(fsObj->getItemName<side>())).x); + } if (!itemWidths.empty()) { @@ -564,7 +591,7 @@ private: std::nth_element(itemWidths.begin(), itPercentile, itemWidths.end()); //complexity: O(n) itemNamesWidth = std::max(itemNamesWidth, *itPercentile); } - assert(itemNamesWidth > 0); + assert(itemNamesWidth >= 0); } return itemNamesWidth; } @@ -577,8 +604,8 @@ private: std::wstring groupParentFolder; size_t groupFirstRow; bool stackedGroupRender; - int widthGroupParent; - int widthGroupName; + int groupParentWidth; + int groupNameWidth; }; GroupRenderLayout getGroupRenderLayout(wxDC& dc, size_t row, const FileView::PathDrawInfo& pdi, int maxWidth) { @@ -598,11 +625,10 @@ private: const bool multiItemGroup = pdi.groupLastRow - groupFirstRow > 1; std::wstring itemName; - if (!pdi.fsObj->isEmpty<side>() && - (itemPathFormat_ == ItemPathFormat::name || //hack: show folder name in item colum since groupName/groupParentFolder are unused! - // => inconsistent with groupItemNamesWidth! (but without consequence) - pdi.fsObj != pdi.folderGroupObj)) + if (itemPathFormat_ == ItemPathFormat::name || //hack: show folder name in item colum since groupName/groupParentFolder are unused! + pdi.fsObj != pdi.folderGroupObj) //=> consider groupItemNamesWidth! itemName = utfTo<std::wstring>(pdi.fsObj->getItemName<side>()); + //=> doesn't matter if isEmpty()! => only indicates if component should be drawn std::wstring groupName; std::wstring groupParentFolder; @@ -622,7 +648,7 @@ private: case ItemPathFormat::full: if (pdi.folderGroupObj) { - groupName = utfTo<std::wstring>(pdi.folderGroupObj ->template getItemName <side>()); + groupName = utfTo<std::wstring>(pdi.folderGroupObj ->template getItemName <side>()); groupParentFolder = AFS::getDisplayPath(pdi.folderGroupObj->parent().template getAbstractPath<side>()); } else //=> BaseFolderPair @@ -636,27 +662,30 @@ private: replace(groupParentFolder, L'/', slashBidi_); replace(groupParentFolder, L'\\', bslashBidi_); - /* group details: single row - _______ ________________________ ______________________________________ ____________________________________________ - | gap | | (group parent | gap) | | (icon | gap | group name | 2x gap) | | (vline | gap) | (icon | gap) | item name | - ------- ------------------------ -------------------------------------- -------------------------------------------- + ________________________ ___________________________________ _____________________________________________________ + | (gap | group parent) | | (gap | icon | gap | group name) | | (2x gap | vline) | (gap | icon) | gap | item name | + ------------------------ ----------------------------------- ----------------------------------------------------- group details: stacked - _______ ________________________________________________________ ____________________________________________ - | gap | | <right-aligned> (icon | gap | group name | 2x gap) | | | (icon | gap) | item name | <- group name on first row - |-----| |------------------------------------------------------| | (vline | gap) |--------------------------| - | gap | | (group parent/... | gap) | | | (icon | gap) | item name | <- group parent on second - ------- -------------------------------------------------------- -------------------------------------------- */ - const int widthGroupSep = !groupParentFolder.empty() || !groupName.empty() ? fastFromDIP(1) + gridGap_ : 0; - + _____________________________________________________ _____________________________________________________ + | <right-aligned> (gap | icon | gap | group name) | | | (gap | icon) | gap | item name | <- group name on first row + |---------------------------------------------------| | (2x gap | vline) |--------------------------------| + | (gap | group parent/... | wide gap) | | | (gap | icon) | gap | item name | <- group parent on second + ----------------------------------------------------- ----------------------------------------------------- */ bool stackedGroupRender = false; - int widthGroupParent = groupParentFolder.empty() ? 0 : (getTextExtentBuffered(dc, groupParentFolder).x + gridGap_); - int widthGroupName = groupName .empty() ? 0 : (iconSize + gridGap_ + getTextExtentBuffered(dc, groupName).x + 2 * gridGap_); - int widthGroupItems = widthGroupSep + (drawFileIcons ? iconSize + gridGap_ : 0) + groupItemNamesWidth; + int groupParentWidth = groupParentFolder.empty() ? 0 : (gapSize_ + getTextExtentBuffered(dc, groupParentFolder).x); + + int groupNameWidth = groupName.empty() ? 0 : (gapSize_ + iconSize + gapSize_ + getTextExtentBuffered(dc, groupName).x); + const int groupNameMinWidth = groupName.empty() ? 0 : (gapSize_ + iconSize + gapSize_ + ellipsisWidth); + + const int groupSepWidth = (groupParentFolder.empty() && groupName.empty()) ? 0 : (2 * gapSize_ + fastFromDIP(1)); + + int groupItemsWidth = groupSepWidth + (drawFileIcons ? gapSize_ + iconSize : 0) + gapSize_ + groupItemNamesWidth; + const int groupItemsMinWidth = groupSepWidth + (drawFileIcons ? gapSize_ + iconSize : 0) + gapSize_ + ellipsisWidth; //not enough space? => collapse - if (int excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth; + if (int excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth; excessWidth > 0) { if (multiItemGroup && !groupParentFolder.empty() && !groupName.empty()) @@ -664,7 +693,7 @@ private: //1. render group components on two rows stackedGroupRender = true; - //add slashes for better readability + //add slashes for better readability + a wide gap for disambiguation assert(!contains(groupParentFolder, L'/') || !contains(groupParentFolder, L'\\')); const wchar_t groupParentSep = contains(groupParentFolder, L'/') ? L'/' : (contains(groupParentFolder, L'\\') ? L'\\' : FILE_NAME_SEPARATOR); @@ -673,32 +702,33 @@ private: groupParentFolder += groupParentSep; groupParentFolder += ELLIPSIS; - widthGroupParent = getTextExtentBuffered(dc, groupParentFolder).x + gridGap_; + const int groupParentMinWidth = gapSize_ + ellipsisWidth + gapSizeWide_; + groupParentWidth = gapSize_ + getTextExtentBuffered(dc, groupParentFolder).x + gapSizeWide_; - int widthGroupStack = std::max(widthGroupParent, widthGroupName); - excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth; + int groupStackWidth = std::max(groupParentWidth, groupNameWidth); + excessWidth = groupStackWidth + groupItemsWidth - maxWidth; if (excessWidth > 0) { //2. shrink group stack (group parent only) - if (widthGroupParent > widthGroupName) + if (groupParentWidth > groupNameWidth) { - widthGroupStack = widthGroupParent = std::max(widthGroupParent - excessWidth, widthGroupName); - excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth; + groupStackWidth = groupParentWidth = std::max({groupParentWidth - excessWidth, groupNameWidth, groupParentMinWidth}); + excessWidth = groupStackWidth + groupItemsWidth - maxWidth; } if (excessWidth > 0) { //3. shrink item rendering - widthGroupItems = std::max(widthGroupItems - excessWidth, widthGroupSep + (drawFileIcons ? iconSize + gridGap_ : 0) + ellipsisWidth); - excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth; + groupItemsWidth = std::max(groupItemsWidth - excessWidth, groupItemsMinWidth); + excessWidth = groupStackWidth + groupItemsWidth - maxWidth; if (excessWidth > 0) { //4. shrink group stack - widthGroupStack = std::max(widthGroupStack - excessWidth, iconSize + gridGap_ + ellipsisWidth + 2 * gridGap_); + groupStackWidth = std::max({groupStackWidth - excessWidth, groupNameMinWidth, groupParentMinWidth}); - widthGroupParent = std::min(widthGroupParent, widthGroupStack); - widthGroupName = std::min(widthGroupName, widthGroupStack); + groupParentWidth = std::min(groupParentWidth, groupStackWidth); + groupNameWidth = std::min(groupNameWidth, groupStackWidth); } } } @@ -708,19 +738,20 @@ private: //1. shrink group parent if (!groupParentFolder.empty()) { - widthGroupParent = std::max(widthGroupParent - excessWidth, ellipsisWidth + gridGap_); - excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth; + const int groupParentMinWidth = gapSize_ + ellipsisWidth; + groupParentWidth = std::max(groupParentWidth - excessWidth, groupParentMinWidth); + excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth; } if (excessWidth > 0) { //2. shrink item rendering - widthGroupItems = std::max(widthGroupItems - excessWidth, widthGroupSep + (drawFileIcons ? iconSize + gridGap_ : 0) + ellipsisWidth); - excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth; + groupItemsWidth = std::max(groupItemsWidth - excessWidth, groupItemsMinWidth); + excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth; if (excessWidth > 0) //3. shrink group name if (!groupName.empty()) - widthGroupName = std::max(widthGroupName - excessWidth, iconSize + gridGap_ + ellipsisWidth + 2 * gridGap_); + groupNameWidth = std::max(groupNameWidth - excessWidth, groupNameMinWidth); } } } @@ -732,8 +763,8 @@ private: groupParentFolder, groupFirstRow, stackedGroupRender, - widthGroupParent, - widthGroupName, + groupParentWidth, + groupNameWidth, }; } @@ -746,19 +777,19 @@ private: if (const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); pdi.fsObj) { - const DisplayType dispTp = getObjectDisplayType(pdi.fsObj); - //accessibility: always set both foreground AND background colors! wxDCTextColourChanger textColor(dc); - if (!enabled || !selected) //=> coordinate with renderRowBackgound() - { - if (dispTp == DisplayType::inactive) - textColor.Set(getColorInactiveText()); - else if (dispTp != DisplayType::normal) - textColor.Set(*wxBLACK); - } - else + if (enabled && selected) //=> coordinate with renderRowBackgound() textColor.Set(*wxBLACK); + else if (!pdi.fsObj->isEmpty<side>()) + switch (getObjectDisplayType(*pdi.fsObj)) + { + //*INDENT-OFF* + case DisplayType::normal: break; + case DisplayType::symlink: textColor.Set(*wxBLACK); break; + case DisplayType::inactive: textColor.Set(getColorInactiveText()); break; + //*INDENT-ON* + } wxRect rectTmp = rect; @@ -766,13 +797,25 @@ private: { case ColumnTypeRim::path: { - const auto& [itemName, - groupName, - groupParentFolder, - groupFirstRow, - stackedGroupRender, - widthGroupParent, - widthGroupName] = getGroupRenderLayout(dc, row, pdi, rectTmp.width); + auto drawCudHighlight = [&](wxRect rectCud, SyncOperation syncOp) + { + if (getViewType() == GridViewType::action) + if (!enabled || !selected) + if (const auto& [cudAction, cudSide] = getCudAction(syncOp); + cudAction != CudAction::doNothing && side == cudSide) + { + wxColor backCol = *wxWHITE; + dc.GetPixel(rectCud.GetTopRight(), &backCol); + + rectCud.width = gapSize_ + IconBuffer::getSize(IconBuffer::SIZE_SMALL); + //fixed-size looks fine for all icon sizes! use same width even if file icons are disabled! + clearArea(dc, rectCud, getBackGroundColorSyncAction(syncOp)); + + rectCud.x += rectCud.width; + rectCud.width = gapSize_ + fastFromDIP(2); + dc.GradientFillLinear(rectCud, getBackGroundColorSyncAction(syncOp), backCol, wxEAST); + } + }; auto drawIcon = [&](wxImage icon, wxRect rectIcon, bool drawActive) { @@ -782,118 +825,206 @@ private: if (!enabled) icon = icon.ConvertToDisabled(); + rectIcon.x += gapSize_; rectIcon.width = getIconManager().getIconSize(); //center smaller-than-default icons drawBitmapRtlNoMirror(dc, icon, rectIcon, wxALIGN_CENTER); }; + + auto drawFileIcon = [this, &drawIcon](const wxImage& fileIcon, bool drawAsLink, const wxRect& rectIcon, const FileSystemObject& fsObj) + { + if (fileIcon.IsOk()) + drawIcon(fileIcon, rectIcon, fsObj.isActive()); + + if (drawAsLink) + drawIcon(getIconManager().getLinkOverlayIcon(), rectIcon, fsObj.isActive()); + + if (getViewType() == GridViewType::action) + if (const auto& [cudAction, cudSide] = getCudAction(fsObj.getSyncOperation()); + side == cudSide) + switch (cudAction) + { + case CudAction::create: + assert(!fileIcon.IsOk() && !drawAsLink); + if (const bool isFolder = dynamic_cast<const FolderPair*>(&fsObj) != nullptr) + drawIcon(getIconManager().getGenericDirIcon().ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3). //treat all channels equally! + ConvertToDisabled(), rectIcon, true /*drawActive: [!]*/); //visual hint to distinguish file/folder creation + + drawIcon(getIconManager().getPlusOverlayIcon(), rectIcon, true /*drawActive: [!] e.g. disabled folder, exists left only, where child item is copied*/); + break; + case CudAction::destroy: + drawIcon(getIconManager().getMinusOverlayIcon(), rectIcon, true /*drawActive: [!]*/); + break; + case CudAction::doNothing: + case CudAction::update: + break; + }; + }; //------------------------------------------------------------------------- - rectTmp.x += gridGap_; - rectTmp.width -= gridGap_; + + const auto& [itemName, + groupName, + groupParentFolder, + groupFirstRow, + stackedGroupRender, + groupParentWidth, + groupNameWidth] = getGroupRenderLayout(dc, row, pdi, rectTmp.width); wxRect rectGroup, rectGroupParent, rectGroupName; rectGroup = rectGroupParent = rectGroupName = rectTmp; - rectGroupParent.width = widthGroupParent; - rectGroupName .width = widthGroupName; + rectGroupParent.width = groupParentWidth; + rectGroupName .width = groupNameWidth; if (stackedGroupRender) { - rectGroup.width = std::max(widthGroupParent, widthGroupName); - rectGroupName.x += rectGroup.width - widthGroupName; //right-align + rectGroup.width = std::max(groupParentWidth, groupNameWidth); + rectGroupName.x += rectGroup.width - groupNameWidth; //right-align } else //group details on single row { - rectGroup.width = widthGroupParent + widthGroupName; - rectGroupName.x += widthGroupParent; + rectGroup.width = groupParentWidth + groupNameWidth; + rectGroupName.x += groupParentWidth; } rectTmp.x += rectGroup.width; rectTmp.width -= rectGroup.width; wxRect rectGroupItems = rectTmp; - //------------------------------------------------------------------------- + + if (itemName.empty()) //expand group name to include (empty) item area { - const bool groupFolderActive = groupName.empty() || pdi.folderGroupObj->isActive(); + rectGroupName.width += rectGroupItems.width; + rectGroupItems.width = 0; + } + //------------------------------------------------------------------------- + { //clear background below parent path => harmonize with renderRowBackgound() wxDCTextColourChanger textColorGroup(dc); - if ((!enabled || !selected) && - (!groupParentFolder.empty() || !groupName.empty())) + if ((!groupParentFolder.empty() || !groupName.empty()) && + (!enabled || !selected)) { - rectGroup.x -= gridGap_; //include lead gap - rectGroup.width += gridGap_; // + wxRect rectGroupBack = rectGroup; + rectGroupBack.width += 2 * gapSize_; //include gap before vline + + if (row == pdi.groupLastRow - 1 /*last group item*/) //preserve the group separation line! + rectGroupBack.height -= fastFromDIP(1); - clearArea(dc, rectGroup, getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0)); + clearArea(dc, rectGroupBack, getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0)); //clearArea() is surprisingly expensive => call just once! - textColorGroup.Set(wxSystemSettings::GetColour(groupFolderActive ? wxSYS_COLOUR_WINDOWTEXT : wxSYS_COLOUR_GRAYTEXT)); + textColorGroup.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //accessibility: always set *both* foreground AND background colors! - - if (row == pdi.groupLastRow - 1 /*last group item*/) //restore the group separation line we just cleared - { - wxDCPenChanger dummy(dc, wxPen(getColorGridLine(), fastFromDIP(1))); - dc.DrawLine(rectGroup.GetBottomLeft(), rectGroup.GetBottomRight() + wxPoint(1, 0)); - } } if (isNavMarked(*pdi.fsObj)) //draw *after* clearing area for parent components - { - wxRect rectNav = rect; - rectNav.width = fastFromDIP(20); + if (!enabled || !selected) + { + wxRect rectNav = rect; + rectNav.width = fastFromDIP(20); - wxColor backCol = *wxWHITE; - dc.GetPixel(rectNav.GetTopRight(), &backCol); //e.g. selected row! + if (row == pdi.groupLastRow - 1 /*last group item*/) //preserve the group separation line! + rectNav.height -= fastFromDIP(1); - dc.GradientFillLinear(rectNav, getColorSelectionGradientFrom(), backCol, wxEAST); - } + wxColor backCol = *wxWHITE; + dc.GetPixel(rectNav.GetTopRight(), &backCol); //e.g. selected row! + + dc.GradientFillLinear(rectNav, getColorSelectionGradientFrom(), backCol, wxEAST); + } if (!groupName.empty() && row == groupFirstRow) { + wxRect rectGroupNameBack = rectGroupName; + rectGroupNameBack.width += 2 * gapSize_; //include gap left of vline + rectGroupNameBack.height -= fastFromDIP(1); //harmonize with item separation lines + + //mouse highlight: group name wxDCTextColourChanger textColorGroupName(dc); - if (static_cast<HoverAreaGroup>(rowHover) == HoverAreaGroup::groupName) + if (static_cast<HoverAreaGroup>(rowHover) == HoverAreaGroup::groupName || + (static_cast<HoverAreaGroup>(rowHover) == HoverAreaGroup::item && pdi.fsObj == pdi.folderGroupObj /*exception: extend highlight*/)) { - dc.GradientFillLinear(rectGroupName, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST); + clearArea(dc, rectGroupNameBack, getColorSelectionGradientTo()); textColorGroupName.Set(*wxBLACK); } + else //folder background: coordinate with renderRowBackgound() + { + if (!enabled || !selected) + if (!pdi.folderGroupObj->isEmpty<side>() && + !pdi.folderGroupObj->isActive()) + { + clearArea(dc, rectGroupNameBack, getColorInactiveBack(false /*faint*/)); + textColorGroupName.Set(getColorInactiveText()); + } - drawIcon(getIconManager().getGenericDirIcon(), rectGroupName, groupFolderActive); - - if (!pdi.folderGroupObj->isEmpty<side>() && - pdi.folderGroupObj->isFollowedSymlink<side>()) - drawIcon(getIconManager().getLinkOverlayIcon(), rectGroupName, groupFolderActive); + drawCudHighlight(rectGroupNameBack, pdi.folderGroupObj->getSyncOperation()); + } - rectGroupName.x += getIconManager().getIconSize() + gridGap_; - rectGroupName.width -= getIconManager().getIconSize() + gridGap_; + wxImage folderIcon; + bool drawAsLink = false; + if (!pdi.folderGroupObj->isEmpty<side>()) + { + folderIcon = getIconManager().getGenericDirIcon(); + drawAsLink = pdi.folderGroupObj->isFollowedSymlink<side>(); + } + drawFileIcon(folderIcon, drawAsLink, rectGroupName, *pdi.folderGroupObj); + rectGroupName.x += gapSize_ + getIconManager().getIconSize() + gapSize_; + rectGroupName.width -= gapSize_ + getIconManager().getIconSize() + gapSize_; - drawCellText(dc, rectGroupName, groupName, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupName)); + if (!pdi.folderGroupObj->isEmpty<side>()) + drawCellText(dc, rectGroupName, groupName, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupName)); } if (!groupParentFolder.empty() && (( stackedGroupRender && row == groupFirstRow + 1) || - (!stackedGroupRender && row == groupFirstRow))) + (!stackedGroupRender && row == groupFirstRow)) && + (groupName.empty() || !pdi.folderGroupObj->isEmpty<side>())) { - drawCellText(dc, rectGroupParent, groupParentFolder, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupParentFolder)); - } - } + wxRect rectGroupParentText = rectGroupParent; + rectGroupParentText.x += gapSize_; + rectGroupParentText.width -= stackedGroupRender ? gapSize_ + gapSizeWide_ : gapSize_; - if (!groupParentFolder.empty() || !groupName.empty()) - { - wxDCPenChanger dummy(dc, wxPen(getColorGridLine(), fastFromDIP(1))); - dc.DrawLine(rectGroupItems.GetTopLeft(), rectGroupItems.GetBottomLeft() + wxPoint(0, 1)); //draws half-open range! - rectGroupItems.x += fastFromDIP(1) + gridGap_; - rectGroupItems.width -= fastFromDIP(1) + gridGap_; + drawCellText(dc, rectGroupParentText, groupParentFolder, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupParentFolder)); + } } + //------------------------------------------------------------------------- if (!itemName.empty()) { + //draw group/items separation line + if (!groupParentFolder.empty() || !groupName.empty()) + { + rectGroupItems.x += 2 * gapSize_; + rectGroupItems.width -= 2 * gapSize_; + + wxDCPenChanger dummy(dc, wxPen(getColorGridLine(), fastFromDIP(1))); + dc.DrawLine(rectGroupItems.GetTopLeft(), rectGroupItems.GetBottomLeft() + wxPoint(0, 1)); //draws half-open range! + + rectGroupItems.x += fastFromDIP(1); + rectGroupItems.width -= fastFromDIP(1); + } + //------------------------------------------------------------------------- + + wxRect rectItemsBack = rectGroupItems; + rectItemsBack.height -= fastFromDIP(1); //preserve item separation lines! + + //mouse highlight: item name + wxDCTextColourChanger textColorGroupItems(dc); + if (static_cast<HoverAreaGroup>(rowHover) == HoverAreaGroup::item) + { + clearArea(dc, rectItemsBack, getColorSelectionGradientTo()); + textColorGroupItems.Set(*wxBLACK); + } + else + drawCudHighlight(rectItemsBack, pdi.fsObj->getSyncOperation()); + if (IconBuffer* iconBuf = getIconManager().getIconBuffer()) //=> draw file icons { - //whenever there's something new to render on screen, start up watching for failed icon drawing: - //=> ideally it would suffice to start watching only when scrolling grid or showing new grid content, but this solution is more robust - //and the icon updater will stop automatically when finished anyway - //Note: it's not sufficient to start up on failed icon loads only, since we support prefetching of not yet visible rows!!! + /* whenever there's something new to render on screen, start up watching for failed icon drawing: + => ideally it would suffice to start watching only when scrolling grid or showing new grid content, but this solution is more robust + and the icon updater will stop automatically when finished anyway + Note: it's not sufficient to start up on failed icon loads only, since we support prefetching of not yet visible rows!!! */ getIconManager().startIconUpdater(); wxImage fileIcon; - - const IconInfo ii = getIconInfo(row); + const IconInfo ii = getIconInfo(*pdi.fsObj); switch (ii.type) { case IconType::folder: @@ -901,33 +1032,30 @@ private: break; case IconType::standard: - if (std::optional<wxImage> tmpIco = iconBuf->retrieveFileIcon(ii.fsObj->template getAbstractPath<side>())) + if (std::optional<wxImage> tmpIco = iconBuf->retrieveFileIcon(pdi.fsObj->template getAbstractPath<side>())) fileIcon = *tmpIco; else { setFailedLoad(row); //save status of failed icon load -> used for async. icon loading - //falsify only! we want to avoid writing incorrect success values when only partially updating the DC, e.g. when scrolling, + //falsify only! avoid writing incorrect success status when only partially updating the DC, e.g. during scrolling, //see repaint behavior of ::ScrollWindow() function! - fileIcon = iconBuf->getIconByExtension(ii.fsObj->template getItemName<side>()); //better than nothing + fileIcon = iconBuf->getIconByExtension(pdi.fsObj->template getItemName<side>()); //better than nothing } break; case IconType::none: break; } - - if (fileIcon.IsOk()) - { - drawIcon(fileIcon, rectGroupItems, pdi.fsObj->isActive()); - - if (ii.drawAsLink) - drawIcon(getIconManager().getLinkOverlayIcon(), rectGroupItems, pdi.fsObj->isActive()); - } - rectGroupItems.x += getIconManager().getIconSize() + gridGap_; - rectGroupItems.width -= getIconManager().getIconSize() + gridGap_; + drawFileIcon(fileIcon, ii.drawAsLink, rectGroupItems, *pdi.fsObj); + rectGroupItems.x += gapSize_ + getIconManager().getIconSize(); + rectGroupItems.width -= gapSize_ + getIconManager().getIconSize(); } - drawCellText(dc, rectGroupItems, itemName, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, itemName)); + rectGroupItems.x += gapSize_; + rectGroupItems.width -= gapSize_; + + if (!pdi.fsObj->isEmpty<side>()) + drawCellText(dc, rectGroupItems, itemName, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, itemName)); } } break; @@ -935,21 +1063,21 @@ private: case ColumnTypeRim::size: if (refGrid().GetLayoutDirection() != wxLayout_RightToLeft) { - rectTmp.width -= gridGap_; //have file size right-justified (but don't change for RTL languages) + rectTmp.width -= gapSize_; //have file size right-justified (but don't change for RTL languages) drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL); } else { - rectTmp.x += gridGap_; - rectTmp.width -= gridGap_; + rectTmp.x += gapSize_; + rectTmp.width -= gapSize_; drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); } break; case ColumnTypeRim::date: case ColumnTypeRim::extension: - rectTmp.x += gridGap_; - rectTmp.width -= gridGap_; + rectTmp.x += gapSize_; + rectTmp.width -= gapSize_; drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); break; } @@ -957,7 +1085,7 @@ private: } - HoverArea getRowMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override + HoverArea getMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override { if (static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::path) if (const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); @@ -968,20 +1096,19 @@ private: groupParentFolder, groupFirstRow, stackedGroupRender, - widthGroupParent, - widthGroupName] = getGroupRenderLayout(dc, row, pdi, cellWidth); + groupParentWidth, + groupNameWidth] = getGroupRenderLayout(dc, row, pdi, cellWidth); - if (!groupName.empty() && row == groupFirstRow) + if (!groupName.empty() && row == groupFirstRow && pdi.fsObj != pdi.folderGroupObj) { - const int groupNameCellBeginX = gridGap_ + - (stackedGroupRender ? std::max(widthGroupParent, widthGroupName) - widthGroupName : //right-align - widthGroupParent); //group details on single row + const int groupNameCellBeginX = (stackedGroupRender ? std::max(groupParentWidth, groupNameWidth) - groupNameWidth : //right-aligned + groupParentWidth); //group details on single row - if (groupNameCellBeginX <= cellRelativePosX && cellRelativePosX < groupNameCellBeginX + widthGroupName) + if (groupNameCellBeginX <= cellRelativePosX && cellRelativePosX < groupNameCellBeginX + groupNameWidth + 2 * gapSize_ /*include gap before vline*/) return static_cast<HoverArea>(HoverAreaGroup::groupName); } } - return HoverArea::none; + return static_cast<HoverArea>(HoverAreaGroup::item); } @@ -994,35 +1121,34 @@ private: if (const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); pdi.fsObj) { - /* _______ ________________________ ______________________________________ ____________________________________________ - | gap | | (group parent | gap) | | (icon | gap | group name | 2x gap) | | (vline | gap) | (icon | gap) | item name | - ------- ------------------------ -------------------------------------- -------------------------------------------- */ - const int insanelyHugeWidth = 1000'000'000; //(hopefully) still small enough to avoid integer overflows - + /* ________________________ ___________________________________ _____________________________________________________ + | (gap | group parent) | | (gap | icon | gap | group name) | | (2x gap | vline) | (gap | icon) | gap | item name | + ------------------------ ----------------------------------- ----------------------------------------------------- */ const auto& [itemName, groupName, groupParentFolder, groupFirstRow, stackedGroupRender, - widthGroupParent, - widthGroupName] = getGroupRenderLayout(dc, row, pdi, insanelyHugeWidth); + groupParentWidth, + groupNameWidth] = getGroupRenderLayout(dc, row, pdi, insanelyHugeWidth); assert(!stackedGroupRender); + const int groupSepWidth = (groupParentFolder.empty() && groupName.empty()) ? 0 : (2 * gapSize_ + fastFromDIP(1)); + const int fileIconWidth = getIconManager().getIconBuffer() ? gapSize_ + getIconManager().getIconSize() : 0; const int ellipsisWidth = getTextExtentBuffered(dc, ELLIPSIS).x; - const int fileIconSize = getIconManager().getIconBuffer() ? getIconManager().getIconSize() + gridGap_ : 0; - const int widthGroupSep = !groupParentFolder.empty() || !groupName.empty() ? fastFromDIP(1) + gridGap_ : 0; + const int itemWidth = itemName.empty() ? 0 : + (groupSepWidth + fileIconWidth + gapSize_ + + (pdi.fsObj->isEmpty<side>() ? ellipsisWidth : getTextExtentBuffered(dc, itemName).x)); - const int widthGroupItems = widthGroupSep + fileIconSize + std::max(getTextExtentBuffered(dc, itemName).x, ellipsisWidth); - - bestSize += gridGap_ + widthGroupParent + widthGroupName + widthGroupItems + gridGap_ /*[!]*/; + bestSize += groupParentWidth + groupNameWidth + itemWidth + gapSize_ /*[!]*/; } return bestSize; } else { const std::wstring cellValue = getValue(row, colType); - return gridGap_ + dc.GetTextExtent(cellValue).GetWidth() + gridGap_; + return gapSize_ + dc.GetTextExtent(cellValue).GetWidth() + gapSize_; } } @@ -1073,38 +1199,42 @@ private: } } - - std::wstring getToolTip(size_t row, ColumnType colType) const override + std::wstring getToolTip(size_t row, ColumnType colType, HoverArea rowHover) override { + const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); + std::wstring toolTip; - if (const FileSystemObject* fsObj = getFsObject(row)) - if (!fsObj->isEmpty<side>()) + if (const FileSystemObject* tipObj = static_cast<HoverAreaGroup>(rowHover) == HoverAreaGroup::groupName ? pdi.folderGroupObj : pdi.fsObj) + { + toolTip = getDataView().getEffectiveFolderPairCount() > 1 ? + AFS::getDisplayPath(tipObj->getAbstractPath<side>()) : + utfTo<std::wstring>(tipObj->getRelativePath<side>()); + + //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(toolTip, slashBidi_) && !contains(toolTip, bslashBidi_)); + replace(toolTip, L'/', slashBidi_); + replace(toolTip, L'\\', bslashBidi_); + + if (tipObj->isEmpty<side>()) + toolTip += L"\n<" + _("Item not existing") + L'>'; + else + visitFSObject(*tipObj, [&](const FolderPair& folder) { - toolTip = getDataView().getEffectiveFolderPairCount() > 1 ? - AFS::getDisplayPath(fsObj->getAbstractPath<side>()) : - utfTo<std::wstring>(fsObj->getRelativePath<side>()); - - //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(toolTip, slashBidi_) && !contains(toolTip, bslashBidi_)); - replace(toolTip, L'/', slashBidi_); - replace(toolTip, L'\\', bslashBidi_); - - visitFSObject(*fsObj, [](const FolderPair& folder) {}, - [&](const FilePair& file) - { - toolTip += L'\n' + - _("Size:") + L' ' + formatFilesizeShort(file.getFileSize<side>()) + L'\n' + - _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime<side>()); - }, - - [&](const SymlinkPair& symlink) - { - toolTip += L'\n' + - _("Date:") + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime<side>()); - }); - } + //toolTip += L"\n<" + _("Folder") + L'>'; -> redundant!? + }, + [&](const FilePair& file) + { + toolTip += L'\n' + _("Size:") + L' ' + formatFilesizeShort (file.getFileSize <side>()) + + L'\n' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime<side>()); + }, + [&](const SymlinkPair& symlink) + { + toolTip += L"\n<" + _("Symlink") + L'>' + + L'\n' + _("Date:") + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime<side>()); + }); + } return toolTip; } @@ -1118,41 +1248,35 @@ private: struct IconInfo { IconType type = IconType::none; - const FileSystemObject* fsObj = nullptr; //only set if type != IconType::none bool drawAsLink = false; }; - - IconInfo getIconInfo(size_t row) const //return ICON_FILE_FOLDER if row points to a folder + static IconInfo getIconInfo(const FileSystemObject& fsObj) { IconInfo out; - if (const FileSystemObject* fsObj = getFsObject(row); - fsObj && !fsObj->isEmpty<side>()) + if (!fsObj.isEmpty<side>()) + visitFSObject(fsObj, [&](const FolderPair& folder) { - out.fsObj = fsObj; - - visitFSObject(*fsObj, [&](const FolderPair& folder) - { - out.type = IconType::folder; - out.drawAsLink = folder.isFollowedSymlink<side>(); - }, + out.type = IconType::folder; + out.drawAsLink = folder.isFollowedSymlink<side>(); + }, - [&](const FilePair& file) - { - out.type = IconType::standard; - out.drawAsLink = file.isFollowedSymlink<side>() || hasLinkExtension(file.getItemName<side>()); - }, + [&](const FilePair& file) + { + out.type = IconType::standard; + out.drawAsLink = file.isFollowedSymlink<side>() || hasLinkExtension(file.getItemName<side>()); + }, - [&](const SymlinkPair& symlink) - { - out.type = IconType::standard; - out.drawAsLink = true; - }); - } + [&](const SymlinkPair& symlink) + { + out.type = IconType::standard; + out.drawAsLink = true; + }); return out; } - const int gridGap_ = fastFromDIP(FILE_GRID_GAP_SIZE_DIP); + const int gapSize_ = fastFromDIP(FILE_GRID_GAP_SIZE_DIP); + const int gapSizeWide_ = fastFromDIP(FILE_GRID_GAP_SIZE_WIDE_DIP); ItemPathFormat itemPathFormat_ = ItemPathFormat::full; @@ -1268,7 +1392,7 @@ private: { case ColumnTypeCenter::checkbox: break; - case ColumnTypeCenter::category: + case ColumnTypeCenter::difference: return getSymbol(fsObj->getCategory()); case ColumnTypeCenter::action: return getSymbol(fsObj->getSyncOperation()); @@ -1276,29 +1400,32 @@ private: return std::wstring(); } - void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override + void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, HoverArea rowHover) override { const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row); if (!enabled || !selected) { - if (pdi.fsObj) + const wxColor backCol = [&] { - if (pdi.fsObj->isActive()) - clearArea(dc, rect, getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0)); - else - clearArea(dc, rect, getColorInactiveBack(false /*faint*/)); - } - else - clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + if (!pdi.fsObj) + return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + + if (!pdi.fsObj->isActive()) + return getColorInactiveBack(false /*faint*/); + + return getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0); + }(); + if (backCol != wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW) /*already the default!*/) + clearArea(dc, rect, backCol); } else - GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/ ); + GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/, rowHover); //---------------------------------------------------------------------------------- - wxDCPenChanger dummy(dc, wxPen(row == pdi.groupLastRow - 1 /*last group item*/ ? - getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0), fastFromDIP(1))); - dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); + const wxRect rectLine(rect.x, rect.y + rect.height - fastFromDIP(1), rect.width, fastFromDIP(1)); + clearArea(dc, rectLine, row == pdi.groupLastRow - 1 /*last group item*/ ? + getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0)); } enum class HoverAreaCenter //each cell can be divided into four blocks concerning mouse selections @@ -1318,13 +1445,11 @@ private: { if ((!enabled || !selected) && pdi.fsObj->isActive()) //coordinate with renderRowBackgound()! { - clearArea(dc, rect, col); + wxRect rectBack = rect; + if (row == pdi.groupLastRow - 1 /*last group item*/) //preserve the group separation line! + rectBack.height -= fastFromDIP(1); - if (row == pdi.groupLastRow - 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)); - } + clearArea(dc, rectBack, col); } }; @@ -1344,10 +1469,10 @@ private: } break; - case ColumnTypeCenter::category: + case ColumnTypeCenter::difference: { - if (getViewType() == GridViewType::category) - drawHighlightBackground(getBackGroundColorCmpCategory(pdi.fsObj->getCategory(), false /*faint*/)); + if (getViewType() == GridViewType::difference) + drawHighlightBackground(getBackGroundColorCmpDifference(pdi.fsObj->getCategory())); wxRect rectTmp = rect; { @@ -1369,7 +1494,7 @@ private: drawBitmapRtlMirror(dc, icon, rectTmp, alignment, renderBufCmp_); }; - if (getViewType() == GridViewType::category) + if (getViewType() == GridViewType::difference) drawIcon(getCmpResultImage(pdi.fsObj->getCategory()), wxALIGN_CENTER); else if (pdi.fsObj->getCategory() != FILE_EQUAL) //don't show = in both middle columns drawIcon(greyScale(getCmpResultImage(pdi.fsObj->getCategory())), wxALIGN_CENTER); @@ -1379,7 +1504,7 @@ private: case ColumnTypeCenter::action: { if (getViewType() == GridViewType::action) - drawHighlightBackground(getBackGroundColorSyncAction(pdi.fsObj->getSyncOperation(), false /*faint*/)); + drawHighlightBackground(getBackGroundColorSyncAction(pdi.fsObj->getSyncOperation())); auto drawIcon = [&](wxImage icon, int alignment) { @@ -1415,13 +1540,13 @@ private: } } - HoverArea getRowMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override + HoverArea getMouseHover(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: + case ColumnTypeCenter::difference: return static_cast<HoverArea>(HoverAreaCenter::checkbox); case ColumnTypeCenter::action: @@ -1450,8 +1575,8 @@ private: { case ColumnTypeCenter::checkbox: break; - case ColumnTypeCenter::category: - return _("Category") + L" (F11)"; + case ColumnTypeCenter::difference: + return _("Difference") + L" (F11)"; case ColumnTypeCenter::action: return _("Action") + L" (F11)"; } @@ -1472,12 +1597,12 @@ private: case ColumnTypeCenter::checkbox: break; - case ColumnTypeCenter::category: - colIcon = greyScaleIfDisabled(loadImage("compare_sicon"), getViewType() == GridViewType::category); + case ColumnTypeCenter::difference: + colIcon = greyScaleIfDisabled(loadImage("compare_sicon"), getViewType() == GridViewType::difference); break; case ColumnTypeCenter::action: - colIcon = greyScaleIfDisabled(loadImage("file_sync_sicon"), getViewType() == GridViewType::action); + colIcon = greyScaleIfDisabled(loadImage("start_sync_sicon"), getViewType() == GridViewType::action); break; } @@ -1507,7 +1632,7 @@ private: switch (colType) { case ColumnTypeCenter::checkbox: - case ColumnTypeCenter::category: + case ColumnTypeCenter::difference: { const char* imageName = [&] { @@ -1589,8 +1714,8 @@ public: gridL_(gridL), gridC_(gridC), gridR_(gridR), provCenter_(provCenter) { - gridL_.Bind(EVENT_GRID_COL_RESIZE, [this](GridColumnResizeEvent& event) { onResizeColumnL(event); }); - gridR_.Bind(EVENT_GRID_COL_RESIZE, [this](GridColumnResizeEvent& event) { onResizeColumnR(event); }); + gridL_.Bind(EVENT_GRID_COL_RESIZE, [this](GridColumnResizeEvent& event) { onResizeColumn(event, gridL_, gridR_); }); + gridR_.Bind(EVENT_GRID_COL_RESIZE, [this](GridColumnResizeEvent& event) { onResizeColumn(event, gridR_, gridL_); }); gridL_.getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event, gridL_); }); gridC_.getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event, gridC_); }); @@ -1602,15 +1727,21 @@ public: gridC_.Bind(EVENT_GRID_MOUSE_LEFT_DOWN, [this](GridClickEvent& event) { onCenterSelectBegin(event); }); gridC_.Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onCenterSelectEnd (event); }); + gridL_.Bind(EVENT_GRID_MOUSE_LEFT_DOWN, [this](GridClickEvent& event) { onGridClickRim(event, gridL_); }); + gridR_.Bind(EVENT_GRID_MOUSE_LEFT_DOWN, [this](GridClickEvent& event) { onGridClickRim(event, gridR_); }); + //clear selection of other grid when selecting on - gridL_.Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onGridSelectionL(event); }); - gridR_.Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onGridSelectionR(event); }); + gridL_.Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onGridSelection(event, gridR_); }); + gridR_.Bind(EVENT_GRID_SELECT_RANGE, [this](GridSelectEvent& event) { onGridSelection(event, gridL_); }); //parallel grid scrolling: do NOT use DoPrepareDC() to align grids! GDI resource leak! Use regular paint event instead: 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(); }); + //----------------------------------------------------------------------------------------------------- + //scroll master event handling: connect LAST, so that scrollMaster_ is set BEFORE other event handling! + //----------------------------------------------------------------------------------------------------- auto connectGridAccess = [&](Grid& grid, std::function<void(wxEvent& event)> handler) { grid.Bind(wxEVT_SCROLLWIN_TOP, handler); @@ -1634,9 +1765,9 @@ public: grid.getMainWin().Bind(wxEVT_RIGHT_DOWN, handler); grid.getMainWin().Bind(wxEVT_MOUSEWHEEL, handler); }; - 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); }); // + connectGridAccess(gridL_, [this](wxEvent& event) { scrollMaster_ = &gridL_; event.Skip(); }); // + connectGridAccess(gridC_, [this](wxEvent& event) { scrollMaster_ = &gridC_; event.Skip(); }); //connect *after* onKeyDown() in order to receive callback *before*!!! + connectGridAccess(gridR_, [this](wxEvent& event) { scrollMaster_ = &gridR_; event.Skip(); }); // Bind(EVENT_ALIGN_SCROLLBARS, [this](wxCommandEvent& event) { onAlignScrollBars(event); }); } @@ -1679,13 +1810,23 @@ private: event.Skip(); } - void onGridSelectionL(GridSelectEvent& event) { onGridSelection(gridL_, gridR_); event.Skip(); } - void onGridSelectionR(GridSelectEvent& event) { onGridSelection(gridR_, gridL_); event.Skip(); } + void onGridClickRim(GridClickEvent& event, Grid& grid) + { + if (static_cast<HoverAreaGroup>(event.hoverArea_) == HoverAreaGroup::groupName) + if (const FileView::PathDrawInfo pdi = provCenter_.getDataView().getDrawInfo(event.row_); + pdi.fsObj) + { + grid.setGridCursor(pdi.groupFirstRow, GridEventPolicy::allow); + return; + } + event.Skip(); + } - void onGridSelection(const Grid& grid, Grid& other) + void onGridSelection(GridSelectEvent& event, Grid& gridOther) { if (!wxGetKeyState(WXK_CONTROL)) //clear other grid unless user is holding CTRL - other.clearSelection(GridEventPolicy::deny); //don't emit event, prevent recursion! + gridOther.clearSelection(GridEventPolicy::deny); //don't emit event, prevent recursion! + event.Skip(); } void onKeyDown(wxKeyEvent& event, const Grid& grid) @@ -1728,14 +1869,11 @@ private: event.Skip(); } - void onResizeColumnL(GridColumnResizeEvent& event) { resizeOtherSide(gridL_, gridR_, event.colType_, event.offset_); } - void onResizeColumnR(GridColumnResizeEvent& event) { resizeOtherSide(gridR_, gridL_, event.colType_, event.offset_); } - - void resizeOtherSide(const Grid& src, Grid& trg, ColumnType type, int offset) + void onResizeColumn(GridColumnResizeEvent& event, const Grid& grid, Grid& gridOther) { //find stretch factor of resized column: type is unique due to makeConsistent()! - std::vector<Grid::ColAttributes> cfgSrc = src.getColumnConfig(); - auto it = std::find_if(cfgSrc.begin(), cfgSrc.end(), [&](Grid::ColAttributes& ca) { return ca.type == type; }); + std::vector<Grid::ColAttributes> cfgSrc = grid.getColumnConfig(); + auto it = std::find_if(cfgSrc.begin(), cfgSrc.end(), [&](Grid::ColAttributes& ca) { return ca.type == event.colType_; }); if (it == cfgSrc.end()) return; const int stretchSrc = it->stretch; @@ -1745,17 +1883,13 @@ private: return; //apply resized offset to other side, but only if stretch factors match! - std::vector<Grid::ColAttributes> cfgTrg = trg.getColumnConfig(); + std::vector<Grid::ColAttributes> cfgTrg = gridOther.getColumnConfig(); for (Grid::ColAttributes& ca : cfgTrg) - if (ca.type == type && ca.stretch == stretchSrc) - ca.offset = offset; - trg.setColumnConfig(cfgTrg); + if (ca.type == event.colType_ && ca.stretch == stretchSrc) + ca.offset = event.offset_; + gridOther.setColumnConfig(cfgTrg); } - void onGridAccessL(wxEvent& event) { scrollMaster_ = &gridL_; event.Skip(); } - void onGridAccessC(wxEvent& event) { scrollMaster_ = &gridC_; event.Skip(); } - void onGridAccessR(wxEvent& event) { scrollMaster_ = &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) @@ -1865,15 +1999,15 @@ 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 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); + const int widthCheckbox = loadImage("checkbox_true").GetWidth() + fastFromDIP(3); + const int widthDifference = 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(widthDifference + widthCheckbox + widthAction, -1); gridCenter.setColumnConfig( { { static_cast<ColumnType>(ColumnTypeCenter::checkbox), widthCheckbox, 0, true }, - { static_cast<ColumnType>(ColumnTypeCenter::category), widthCategory, 0, true }, + { static_cast<ColumnType>(ColumnTypeCenter::difference), widthDifference, 0, true }, { static_cast<ColumnType>(ColumnTypeCenter::action), widthAction, 0, true }, }); } diff --git a/FreeFileSync/Source/ui/file_grid.h b/FreeFileSync/Source/ui/file_grid.h index d6406fad..c71f8288 100644 --- a/FreeFileSync/Source/ui/file_grid.h +++ b/FreeFileSync/Source/ui/file_grid.h @@ -46,7 +46,8 @@ wxImage getCmpResultImage(CompareFileResult cmpResult); //grid hover area for file group rendering enum class HoverAreaGroup { - groupName + groupName, + item }; //---------- custom events for middle grid ---------- diff --git a/FreeFileSync/Source/ui/file_grid_attr.h b/FreeFileSync/Source/ui/file_grid_attr.h index 324619c1..13c4dab9 100644 --- a/FreeFileSync/Source/ui/file_grid_attr.h +++ b/FreeFileSync/Source/ui/file_grid_attr.h @@ -16,7 +16,7 @@ namespace fff { enum class GridViewType { - category, + difference, action, }; @@ -88,7 +88,7 @@ const ItemPathFormat defaultItemPathFormatRightGrid = ItemPathFormat::relative; enum class ColumnTypeCenter { checkbox, - category, + difference, action, }; diff --git a/FreeFileSync/Source/ui/file_view.cpp b/FreeFileSync/Source/ui/file_view.cpp index 57a5a500..8d06b266 100644 --- a/FreeFileSync/Source/ui/file_view.cpp +++ b/FreeFileSync/Source/ui/file_view.cpp @@ -182,16 +182,16 @@ void addNumbers(const FileSystemObject& fsObj, ViewStats& stats) } -FileView::CategoryViewStats FileView::applyFilterByCategory(bool showExcluded, //maps sortedRef to viewRef - bool showLeftOnly, - bool showRightOnly, - bool showLeftNewer, - bool showRightNewer, - bool showDifferent, - bool showEqual, - bool showConflict) +FileView::DifferenceViewStats FileView::applyDifferenceFilter(bool showExcluded, //maps sortedRef to viewRef + bool showLeftOnly, + bool showRightOnly, + bool showLeftNewer, + bool showRightNewer, + bool showDifferent, + bool showEqual, + bool showConflict) { - CategoryViewStats stats; + DifferenceViewStats stats; updateView([&](const FileSystemObject& fsObj) { @@ -237,16 +237,16 @@ FileView::CategoryViewStats FileView::applyFilterByCategory(bool showExcluded, / } -FileView::ActionViewStats FileView::applyFilterByAction(bool showExcluded, //maps sortedRef to viewRef - bool showCreateLeft, - bool showCreateRight, - bool showDeleteLeft, - bool showDeleteRight, - bool showUpdateLeft, - bool showUpdateRight, - bool showDoNothing, - bool showEqual, - bool showConflict) +FileView::ActionViewStats FileView::applyActionFilter(bool showExcluded, //maps sortedRef to viewRef + bool showCreateLeft, + bool showCreateRight, + bool showDeleteLeft, + bool showDeleteRight, + bool showUpdateLeft, + bool showUpdateRight, + bool showDoNothing, + bool showEqual, + bool showConflict) { ActionViewStats stats; @@ -846,7 +846,7 @@ void FileView::sortView(ColumnTypeCenter type, bool ascending) case ColumnTypeCenter::checkbox: assert(false); break; - case ColumnTypeCenter::category: + case ColumnTypeCenter::difference: if ( ascending) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessCmpResult<true >()); else if (!ascending) std::stable_sort(sortedRef_.begin(), sortedRef_.end(), LessCmpResult<false>()); break; diff --git a/FreeFileSync/Source/ui/file_view.h b/FreeFileSync/Source/ui/file_view.h index 416a4edf..47be4875 100644 --- a/FreeFileSync/Source/ui/file_view.h +++ b/FreeFileSync/Source/ui/file_view.h @@ -52,7 +52,7 @@ public: uint64_t bytes = 0; }; - struct CategoryViewStats + struct DifferenceViewStats { int excluded = 0; int equal = 0; @@ -67,14 +67,14 @@ public: FileStats fileStatsLeft; FileStats fileStatsRight; }; - CategoryViewStats applyFilterByCategory(bool showExcluded, - bool showLeftOnly, - bool showRightOnly, - bool showLeftNewer, - bool showRightNewer, - bool showDifferent, - bool showEqual, - bool showConflict); + DifferenceViewStats applyDifferenceFilter(bool showExcluded, + bool showLeftOnly, + bool showRightOnly, + bool showLeftNewer, + bool showRightNewer, + bool showDifferent, + bool showEqual, + bool showConflict); struct ActionViewStats { @@ -93,16 +93,16 @@ public: FileStats fileStatsLeft; FileStats fileStatsRight; }; - ActionViewStats applyFilterByAction(bool showExcluded, - bool showCreateLeft, - bool showCreateRight, - bool showDeleteLeft, - bool showDeleteRight, - bool showUpdateLeft, - bool showUpdateRight, - bool showDoNothing, - bool showEqual, - bool showConflict); + ActionViewStats applyActionFilter(bool showExcluded, + bool showCreateLeft, + bool showCreateRight, + bool showDeleteLeft, + bool showDeleteRight, + bool showUpdateLeft, + bool showUpdateRight, + bool showDoNothing, + bool showEqual, + bool showConflict); void removeInvalidRows(); //remove references to rows that have been deleted meanwhile: call after manual deletion and synchronization! diff --git a/FreeFileSync/Source/ui/folder_pair.h b/FreeFileSync/Source/ui/folder_pair.h index fac14b8d..5940e25e 100644 --- a/FreeFileSync/Source/ui/folder_pair.h +++ b/FreeFileSync/Source/ui/folder_pair.h @@ -84,7 +84,7 @@ private: zen::ContextMenu menu; menu.addItem(_("Remove local settings"), removeLocalCompCfg, wxNullImage, static_cast<bool>(localCmpCfg_)); - menu.popup(basicPanel_); + menu.popup(*basicPanel_.m_bpButtonLocalCompCfg, { basicPanel_.m_bpButtonLocalCompCfg->GetSize().x, 0 }); } void onLocalSyncCfgContext(wxEvent& event) @@ -98,7 +98,7 @@ private: zen::ContextMenu menu; menu.addItem(_("Remove local settings"), removeLocalSyncCfg, wxNullImage, static_cast<bool>(localSyncCfg_)); - menu.popup(basicPanel_); + menu.popup(*basicPanel_.m_bpButtonLocalSyncCfg, { basicPanel_.m_bpButtonLocalSyncCfg->GetSize().x, 0 }); } void onLocalFilterCfgContext(wxEvent& event) @@ -128,7 +128,7 @@ private: menu.addSeparator(); menu.addItem( _("Copy"), copyFilter, wxNullImage, !isNullFilter(localFilter_)); menu.addItem( _("Paste"), pasteFilter, wxNullImage, filterCfgOnClipboard.get() != nullptr); - menu.popup(basicPanel_); + menu.popup(*basicPanel_.m_bpButtonLocalFilter, { basicPanel_.m_bpButtonLocalFilter->GetSize().x, 0 }); } @@ -147,9 +147,9 @@ private: std::optional<SyncConfig> localSyncCfg_; FilterConfig localFilter_; - const wxImage imgCmp_ = zen::loadImage("cfg_compare", zen::fastFromDIP(20)); - const wxImage imgSync_ = zen::loadImage("cfg_sync", zen::fastFromDIP(20)); - const wxImage imgFilter_ = zen::loadImage("cfg_filter", zen::fastFromDIP(20)); + const wxImage imgCmp_ = zen::loadImage("options_compare", zen::fastFromDIP(20)); + const wxImage imgSync_ = zen::loadImage("options_sync", zen::fastFromDIP(20)); + const wxImage imgFilter_ = zen::loadImage("options_filter", zen::fastFromDIP(20)); }; } diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp index c5c1c80a..dbb2a688 100644 --- a/FreeFileSync/Source/ui/gui_generated.cpp +++ b/FreeFileSync/Source/ui/gui_generated.cpp @@ -844,62 +844,62 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizerViewFilter->Add( 0, 0, 1, wxEXPAND, 5 ); - wxBoxSizer* bSizer287; - bSizer287 = new wxBoxSizer( wxHORIZONTAL ); - - m_bpButtonViewTypeSyncAction = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizer287->Add( m_bpButtonViewTypeSyncAction, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - m_bpButtonViewContext = new wxBitmapButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizer287->Add( m_bpButtonViewContext, 0, wxRIGHT|wxEXPAND, 5 ); - + m_bpButtonViewType = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); + bSizerViewFilter->Add( m_bpButtonViewType, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - bSizerViewFilter->Add( bSizer287, 0, wxALIGN_CENTER_VERTICAL, 5 ); + wxBoxSizer* bSizer300; + bSizer300 = new wxBoxSizer( wxHORIZONTAL ); m_bpButtonShowExcluded = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowExcluded, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxRIGHT, 5 ); + bSizer300->Add( m_bpButtonShowExcluded, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxRIGHT, 5 ); m_bpButtonShowDeleteLeft = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowDeleteLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowDeleteLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowUpdateLeft = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowUpdateLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowUpdateLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowCreateLeft = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowCreateLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowCreateLeft, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowLeftOnly = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowLeftOnly, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowLeftOnly, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowLeftNewer = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowLeftNewer, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowLeftNewer, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowEqual = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowEqual, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowEqual, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowDoNothing = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowDoNothing, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowDoNothing, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowDifferent = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowDifferent, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowDifferent, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowRightNewer = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowRightNewer, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowRightNewer, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowRightOnly = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowRightOnly, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowRightOnly, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowCreateRight = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowCreateRight, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowCreateRight, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowUpdateRight = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowUpdateRight, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowUpdateRight, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowDeleteRight = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowDeleteRight, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowDeleteRight, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); m_bpButtonShowConflict = new zen::ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); - bSizerViewFilter->Add( m_bpButtonShowConflict, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer300->Add( m_bpButtonShowConflict, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + m_bpButtonViewFilterContext = new wxBitmapButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 ); + bSizer300->Add( m_bpButtonViewFilterContext, 0, wxEXPAND, 5 ); + + + bSizerViewFilter->Add( bSizer300, 0, wxALIGN_CENTER_VERTICAL, 5 ); bSizerViewFilter->Add( 0, 0, 1, wxEXPAND, 5 ); @@ -1136,6 +1136,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const 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_bpButtonCmpConfig->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onCompSettingsContextMouse ), 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 ); @@ -1145,6 +1146,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const 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_bpButtonSyncConfig->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onSyncSettingsContextMouse ), 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 ); @@ -1163,25 +1165,40 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const 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_bpButtonViewType->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewType ), NULL, this ); + m_bpButtonViewType->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewTypeContextMouse ), NULL, this ); m_bpButtonShowExcluded->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowExcluded->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowDeleteLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowDeleteLeft->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowUpdateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowUpdateLeft->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowCreateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowCreateLeft->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowLeftOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowLeftOnly->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowLeftNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowLeftNewer->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowEqual->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowEqual->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowDoNothing->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowDoNothing->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowDifferent->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowDifferent->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowRightNewer->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowRightNewer->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowRightOnly->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowRightOnly->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowCreateRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowCreateRight->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowUpdateRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowUpdateRight->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowDeleteRight->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowDeleteRight->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); m_bpButtonShowConflict->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onToggleViewButton ), NULL, this ); + m_bpButtonShowConflict->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); + m_bpButtonViewFilterContext->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::onViewFilterContext ), NULL, this ); + m_bpButtonViewFilterContext->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::onViewFilterContextMouse ), NULL, this ); } MainDialogGenerated::~MainDialogGenerated() @@ -1434,7 +1451,9 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer1721->Add( 0, 0, 1, wxEXPAND, 5 ); - m_hyperlink24 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("More information"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink24 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("More information"), wxT("https://freefilesync.org/manual.php?topic=comparison-settings"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink24->SetToolTip( _("https://freefilesync.org/manual.php?topic=comparison-settings") ); + bSizer1721->Add( m_hyperlink24, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); @@ -1476,7 +1495,9 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer1733->Add( 0, 0, 1, wxEXPAND, 5 ); - m_hyperlink241 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("Handle daylight saving time"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink241 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("Handle daylight saving time"), wxT("https://freefilesync.org/manual.php?topic=daylight-saving-time"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink241->SetToolTip( _("https://freefilesync.org/manual.php?topic=daylight-saving-time") ); + bSizer1733->Add( m_hyperlink241, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); @@ -1604,7 +1625,9 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w fgSizerPerf->Fit( m_scrolledWindowPerf ); bSizer260->Add( m_scrolledWindowPerf, 1, wxALL|wxEXPAND, 5 ); - m_hyperlink1711 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("How to get best performance?"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink1711 = new wxHyperlinkCtrl( m_panelComparisonSettings, wxID_ANY, _("How to get best performance?"), wxT("https://freefilesync.org/manual.php?topic=performance"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink1711->SetToolTip( _("https://freefilesync.org/manual.php?topic=performance") ); + bSizer260->Add( m_hyperlink1711, 0, wxALL, 5 ); @@ -1702,7 +1725,9 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer189->Add( 0, 0, 1, wxEXPAND, 5 ); - m_hyperlink171 = new wxHyperlinkCtrl( m_panelFilterSettings, wxID_ANY, _("Show examples"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink171 = new wxHyperlinkCtrl( m_panelFilterSettings, wxID_ANY, _("Show examples"), wxT("https://freefilesync.org/manual.php?topic=exclude-items"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink171->SetToolTip( _("https://freefilesync.org/manual.php?topic=exclude-items") ); + bSizer189->Add( m_hyperlink171, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); @@ -1928,9 +1953,9 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizerSyncDirections = new wxBoxSizer( wxVERTICAL ); - m_staticTextCategory = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Category"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextCategory->Wrap( -1 ); - bSizerSyncDirections->Add( m_staticTextCategory, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + m_staticText184 = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Difference"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText184->Wrap( -1 ); + bSizerSyncDirections->Add( m_staticText184, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); ffgSizer11 = new wxFlexGridSizer( 2, 0, 5, 5 ); ffgSizer11->SetFlexibleDirection( wxBOTH ); @@ -2041,7 +2066,9 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer249->Add( m_checkBoxDetectMove, 0, wxALL|wxEXPAND, 5 ); - m_hyperlink242 = new wxHyperlinkCtrl( m_panelSyncSettings, wxID_ANY, _("More information"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink242 = new wxHyperlinkCtrl( m_panelSyncSettings, wxID_ANY, _("More information"), wxT("https://freefilesync.org/manual.php?topic=synchronization-settings"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink242->SetToolTip( _("https://freefilesync.org/manual.php?topic=synchronization-settings") ); + bSizer249->Add( m_hyperlink242, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); @@ -2148,7 +2175,9 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer254->Add( 0, 0, 1, wxEXPAND, 5 ); - m_hyperlink243 = new wxHyperlinkCtrl( m_panelVersioning, wxID_ANY, _("Show examples"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink243 = new wxHyperlinkCtrl( m_panelVersioning, wxID_ANY, _("Show examples"), wxT("https://freefilesync.org/manual.php?topic=versioning"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink243->SetToolTip( _("https://freefilesync.org/manual.php?topic=versioning") ); + bSizer254->Add( m_hyperlink243, 0, wxLEFT|wxALIGN_BOTTOM, 5 ); @@ -2469,13 +2498,9 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w 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 ); @@ -2497,11 +2522,9 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w 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 ); @@ -2889,7 +2912,9 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, bSizer219->Add( 0, 0, 1, wxEXPAND, 5 ); - m_hyperlink171 = new wxHyperlinkCtrl( this, wxID_ANY, _("How to get best performance?"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink171 = new wxHyperlinkCtrl( this, wxID_ANY, _("How to get best performance?"), wxT("https://freefilesync.org/manual.php?topic=ftp-setup"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink171->SetToolTip( _("https://freefilesync.org/manual.php?topic=ftp-setup") ); + bSizer219->Add( m_hyperlink171, 0, wxALL|wxALIGN_CENTER_VERTICAL, 10 ); @@ -3020,7 +3045,6 @@ CloudSetupDlgGenerated::CloudSetupDlgGenerated( wxWindow* parent, wxWindowID id, 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 ); @@ -4026,7 +4050,9 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_staticline25 = new wxStaticLine( m_panel35, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer172->Add( m_staticline25, 0, wxEXPAND, 5 ); - m_hyperlink17 = new wxHyperlinkCtrl( m_panel35, wxID_ANY, _("How can I schedule a batch job?"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink17 = new wxHyperlinkCtrl( m_panel35, wxID_ANY, _("How can I schedule a batch job?"), wxT("https://freefilesync.org/manual.php?topic=schedule-a-batch-job"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink17->SetToolTip( _("https://freefilesync.org/manual.php?topic=schedule-a-batch-job") ); + bSizer172->Add( m_hyperlink17, 0, wxALL, 10 ); @@ -4064,7 +4090,6 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS 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 ); } @@ -4466,7 +4491,7 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const m_staticText163->Wrap( -1 ); bSizer258->Add( m_staticText163, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); - m_hyperlinkLogFolder = new wxHyperlinkCtrl( m_panel39, wxID_ANY, _("dummy"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlinkLogFolder = new wxHyperlinkCtrl( m_panel39, wxID_ANY, _("dummy"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_ALIGN_CENTRE|wxBORDER_NONE ); bSizer258->Add( m_hyperlinkLogFolder, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); @@ -4730,7 +4755,9 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const bSizer298->Add( 0, 0, 1, 0, 5 ); - m_hyperlink17 = new wxHyperlinkCtrl( m_panel39, wxID_ANY, _("Show examples"), wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink17 = new wxHyperlinkCtrl( m_panel39, wxID_ANY, _("Show examples"), wxT("https://freefilesync.org/manual.php?topic=external-applications"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink17->SetToolTip( _("https://freefilesync.org/manual.php?topic=external-applications") ); + bSizer298->Add( m_hyperlink17, 0, wxLEFT|wxALIGN_BOTTOM, 5 ); @@ -4829,7 +4856,6 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const 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 ); diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h index 399cb0e0..9c538ac1 100644 --- a/FreeFileSync/Source/ui/gui_generated.h +++ b/FreeFileSync/Source/ui/gui_generated.h @@ -172,8 +172,7 @@ protected: wxPanel* m_panelViewFilter; wxBoxSizer* bSizerViewFilter; wxBitmapButton* m_bpButtonShowLog; - zen::ToggleButton* m_bpButtonViewTypeSyncAction; - wxBitmapButton* m_bpButtonViewContext; + zen::ToggleButton* m_bpButtonViewType; zen::ToggleButton* m_bpButtonShowExcluded; zen::ToggleButton* m_bpButtonShowDeleteLeft; zen::ToggleButton* m_bpButtonShowUpdateLeft; @@ -189,6 +188,7 @@ protected: zen::ToggleButton* m_bpButtonShowUpdateRight; zen::ToggleButton* m_bpButtonShowDeleteRight; zen::ToggleButton* m_bpButtonShowConflict; + wxBitmapButton* m_bpButtonViewFilterContext; wxStaticText* m_staticText96; wxPanel* m_panelStatistics; wxBoxSizer* bSizer1801; @@ -245,8 +245,9 @@ protected: 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 onViewFilterContextMouse( wxMouseEvent& event ) { event.Skip(); } + virtual void onViewFilterContext( wxCommandEvent& event ) { event.Skip(); } public: @@ -412,7 +413,7 @@ protected: zen::ToggleButton* m_buttonCustom; wxBoxSizer* bSizerSyncDirHolder; wxBoxSizer* bSizerSyncDirections; - wxStaticText* m_staticTextCategory; + wxStaticText* m_staticText184; wxFlexGridSizer* ffgSizer11; wxStaticBitmap* m_bitmapLeftOnly; wxStaticBitmap* m_bitmapLeftNewer; @@ -497,13 +498,9 @@ protected: 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(); } @@ -521,11 +518,9 @@ protected: 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(); } @@ -651,7 +646,6 @@ protected: 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(); } @@ -912,7 +906,6 @@ protected: 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(); } @@ -1086,7 +1079,6 @@ protected: 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(); } diff --git a/FreeFileSync/Source/ui/log_panel.cpp b/FreeFileSync/Source/ui/log_panel.cpp index 73412a0c..a357f657 100644 --- a/FreeFileSync/Source/ui/log_panel.cpp +++ b/FreeFileSync/Source/ui/log_panel.cpp @@ -44,7 +44,7 @@ wxImage getImageButtonReleased(const char* imageName) enum class ColumnTypeLog { time, - category, + severity, text, }; } @@ -166,7 +166,7 @@ public: return utfTo<std::wstring>(formatTime(formatTimeTag, getLocalTime(entry->time))); break; - case ColumnTypeLog::category: + case ColumnTypeLog::severity: if (entry->firstLine) switch (entry->type) { @@ -185,12 +185,12 @@ public: return std::wstring(); } - void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override + void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, HoverArea rowHover) override { if (!enabled || !selected) - clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + ; //clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -> already the default else - GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/ ); + GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/, rowHover); //-------------- draw item separation line ----------------- wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), fastFromDIP(1))); @@ -221,7 +221,7 @@ public: drawCellText(dc, rectTmp, getValue(row, colType), wxALIGN_CENTER); break; - case ColumnTypeLog::category: + case ColumnTypeLog::severity: if (entry->firstLine) { wxImage msgTypeIcon = [&] @@ -260,7 +260,7 @@ public: case ColumnTypeLog::time: return 2 * getColumnGapLeft() + dc.GetTextExtent(getValue(row, colType)).GetWidth(); - case ColumnTypeLog::category: + case ColumnTypeLog::severity: return getDefaultMenuIconSize(); case ColumnTypeLog::text: @@ -276,7 +276,7 @@ public: return 2 * getColumnGapLeft() + dc.GetTextExtent(utfTo<wxString>(formatTime(formatTimeTag))).GetWidth(); } - static int getColumnCategoryDefaultWidth() + static int getColumnSeverityDefaultWidth() { return getDefaultMenuIconSize(); } @@ -286,7 +286,7 @@ public: return std::max(getDefaultMenuIconSize(), grid.getMainWin().GetCharHeight() + fastFromDIP(2)) + 1; //+ some space + bottom border } - std::wstring getToolTip(size_t row, ColumnType colType) const override + std::wstring getToolTip(size_t row, ColumnType colType, HoverArea rowHover) override { switch (static_cast<ColumnTypeLog>(colType)) { @@ -294,7 +294,7 @@ public: case ColumnTypeLog::text: break; - case ColumnTypeLog::category: + case ColumnTypeLog::severity: return getValue(row, colType); } return std::wstring(); @@ -313,7 +313,7 @@ LogPanel::LogPanel(wxWindow* parent) : LogPanelGenerated(parent) { const int rowHeight = GridDataMessages::getRowDefaultHeight(*m_gridMessages); const int colMsgTimeWidth = GridDataMessages::getColumnTimeDefaultWidth(*m_gridMessages); - const int colMsgCategoryWidth = GridDataMessages::getColumnCategoryDefaultWidth(); + const int colMsgSeverityWidth = GridDataMessages::getColumnSeverityDefaultWidth(); m_gridMessages->setColumnLabelHeight(0); m_gridMessages->showRowLabel(false); @@ -321,8 +321,8 @@ LogPanel::LogPanel(wxWindow* parent) : LogPanelGenerated(parent) m_gridMessages->setColumnConfig( { { static_cast<ColumnType>(ColumnTypeLog::time ), colMsgTimeWidth, 0, true }, - { static_cast<ColumnType>(ColumnTypeLog::category), colMsgCategoryWidth, 0, true }, - { static_cast<ColumnType>(ColumnTypeLog::text ), -colMsgTimeWidth - colMsgCategoryWidth, 1, true }, + { static_cast<ColumnType>(ColumnTypeLog::severity), colMsgSeverityWidth, 0, true }, + { static_cast<ColumnType>(ColumnTypeLog::text ), -colMsgTimeWidth - colMsgSeverityWidth, 1, true }, }); //support for CTRL + C @@ -462,7 +462,7 @@ void LogPanel::onMsgGridContext(GridContextMenuEvent& event) menu.addSeparator(); menu.addItem(_("Select all") + L"\tCtrl+A", [this] { m_gridMessages->selectAllRows(GridEventPolicy::allow); }, wxNullImage, rowCount > 0); - menu.popup(*m_gridMessages, event.mousePos_); + menu.popup(*m_gridMessages); } diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp index ad0c898d..0fe2d02e 100644 --- a/FreeFileSync/Source/ui/main_dlg.cpp +++ b/FreeFileSync/Source/ui/main_dlg.cpp @@ -46,7 +46,6 @@ #include "../base/resolve_path.h" #include "../base/lock_holder.h" #include "../ffs_paths.h" -#include "../help_provider.h" #include "../localization.h" #include "../version/version.h" @@ -426,36 +425,36 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, auto generateSaveAsImage = [](const char* layoverName) { - const wxSize oldSize = loadImage("file_save").GetSize(); + const wxSize oldSize = loadImage("cfg_save").GetSize(); - wxImage backImg = loadImage("file_save", oldSize.GetWidth() * 9 / 10); + wxImage backImg = loadImage("cfg_save", oldSize.GetWidth() * 9 / 10); backImg = resizeCanvas(backImg, oldSize, wxALIGN_BOTTOM | wxALIGN_LEFT); return layOver(backImg, loadImage(layoverName, backImg.GetWidth() * 7 / 10), wxALIGN_TOP | wxALIGN_RIGHT); }; - m_bpButtonCmpConfig ->SetBitmapLabel(loadImage("cfg_compare")); - m_bpButtonSyncConfig->SetBitmapLabel(loadImage("cfg_sync")); + m_bpButtonCmpConfig ->SetBitmapLabel(loadImage("options_compare")); + m_bpButtonSyncConfig->SetBitmapLabel(loadImage("options_sync")); m_bpButtonCmpContext ->SetBitmapLabel(mirrorIfRtl(loadImage("button_arrow_right"))); m_bpButtonFilterContext->SetBitmapLabel(mirrorIfRtl(loadImage("button_arrow_right"))); m_bpButtonSyncContext ->SetBitmapLabel(mirrorIfRtl(loadImage("button_arrow_right"))); - m_bpButtonViewContext ->SetBitmapLabel(mirrorIfRtl(loadImage("button_arrow_right"))); + m_bpButtonViewFilterContext->SetBitmapLabel(mirrorIfRtl(loadImage("button_arrow_right"))); - m_bpButtonNew ->SetBitmapLabel(loadImage("file_new")); - m_bpButtonOpen ->SetBitmapLabel(loadImage("file_load")); - m_bpButtonSaveAs ->SetBitmapLabel(generateSaveAsImage("file_sync")); - m_bpButtonSaveAsBatch->SetBitmapLabel(generateSaveAsImage("file_batch")); + m_bpButtonNew ->SetBitmapLabel(loadImage("cfg_new")); + m_bpButtonOpen ->SetBitmapLabel(loadImage("cfg_load")); + m_bpButtonSaveAs ->SetBitmapLabel(generateSaveAsImage("start_sync")); + m_bpButtonSaveAsBatch->SetBitmapLabel(generateSaveAsImage("cfg_batch")); m_bpButtonAddPair ->SetBitmapLabel(loadImage("item_add")); m_bpButtonHideSearch ->SetBitmapLabel(loadImage("close_panel")); m_bpButtonShowLog ->SetBitmapLabel(loadImage("log_file")); - m_bpButtonFilter ->SetMinSize({loadImage("cfg_filter").GetWidth() + fastFromDIP(27), -1}); //make the filter button wider + m_bpButtonFilter ->SetMinSize({loadImage("options_filter").GetWidth() + fastFromDIP(27), -1}); //make the filter button wider m_textCtrlSearchTxt->SetMinSize({fastFromDIP(220), -1}); //---------------------------------------------------------------------------------------- - wxImage labelImage = createImageFromText(_("Select view:"), m_bpButtonViewTypeSyncAction->GetFont(), wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT)); + wxImage labelImage = createImageFromText(_("Select view:"), m_bpButtonViewType->GetFont(), wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT)); labelImage = resizeCanvas(labelImage, labelImage.GetSize() + wxSize(fastFromDIP(10), 0), wxALIGN_CENTER); //add border space @@ -463,8 +462,8 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, { return stackImages(labelImage, mirrorIfRtl(loadImage(imgName)), ImageStackLayout::vertical, ImageStackAlignment::center); }; - m_bpButtonViewTypeSyncAction->init(generateViewTypeImage("viewtype_sync_action"), - generateViewTypeImage("viewtype_cmp_result")); + m_bpButtonViewType->init(generateViewTypeImage("viewtype_sync_action"), + generateViewTypeImage("viewtype_cmp_result")); //tooltip is updated dynamically in setViewTypeSyncAction() //---------------------------------------------------------------------------------------- m_bpButtonShowExcluded ->SetToolTip(_("Show filtered or temporarily excluded files")); @@ -602,33 +601,30 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, defaultPerspective_ = auiMgr_.SavePerspective(); //---------------------------------------------------------------------------------- //register view layout context menu - 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); }); + m_panelTopButtons->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onSetLayoutContext(event); }); + m_panelConfig ->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onSetLayoutContext(event); }); + m_panelViewFilter->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onSetLayoutContext(event); }); + m_panelStatusBar ->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& event) { onSetLayoutContext(event); }); //---------------------------------------------------------------------------------- //file grid: sorting - m_gridMainL->Bind(EVENT_GRID_COL_LABEL_MOUSE_LEFT, [this](GridLabelClickEvent& event) { onGridLabelLeftClickL(event); }); + m_gridMainL->Bind(EVENT_GRID_COL_LABEL_MOUSE_LEFT, [this](GridLabelClickEvent& event) { onGridLabelLeftClickRim(event, true /*leftSide*/); }); + m_gridMainR->Bind(EVENT_GRID_COL_LABEL_MOUSE_LEFT, [this](GridLabelClickEvent& event) { onGridLabelLeftClickRim(event, false /*leftSide*/); }); 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->Bind(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, [this](GridLabelClickEvent& event) { onGridLabelContextL(event); }); + m_gridMainL->Bind(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, [this](GridLabelClickEvent& event) { onGridLabelContextRim(event, true /*leftSide*/); }); + m_gridMainR->Bind(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, [this](GridLabelClickEvent& event) { onGridLabelContextRim(event, false /*leftSide*/); }); 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->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_CONTEXT_MENU, [this](GridContextMenuEvent& event) { onGridContextRim(event, true /*leftSide*/); }); + m_gridMainR->Bind(EVENT_GRID_CONTEXT_MENU, [this](GridContextMenuEvent& event) { onGridContextRim(event, false /*leftSide*/); }); - 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->Bind(EVENT_GRID_MOUSE_RIGHT_DOWN, [this](GridClickEvent& event) { onGridGroupContextRim(event, true /*leftSide*/); }); + m_gridMainR->Bind(EVENT_GRID_MOUSE_RIGHT_DOWN, [this](GridClickEvent& event) { onGridGroupContextRim(event, false /*leftSide*/); }); - 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); }); + m_gridMainL->Bind(EVENT_GRID_MOUSE_LEFT_DOUBLE, [this](GridClickEvent& event) { onGridDoubleClickRim(event, true /*leftSide*/); }); + m_gridMainR->Bind(EVENT_GRID_MOUSE_LEFT_DOUBLE, [this](GridClickEvent& event) { onGridDoubleClickRim(event, false /*leftSide*/); }); //tree grid: m_gridOverview->Bind(EVENT_GRID_CONTEXT_MENU, [this](GridContextMenuEvent& event) { onTreeGridContext (event); }); @@ -670,17 +666,17 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, m_bitmapSmallFileRight ->SetBitmap(imgFile); - m_menuItemNew ->SetBitmap(loadImage("file_new_sicon")); - m_menuItemLoad ->SetBitmap(loadImage("file_load_sicon")); - m_menuItemSave ->SetBitmap(loadImage("file_save_sicon")); - m_menuItemSaveAsBatch->SetBitmap(loadImage("file_batch_sicon")); + m_menuItemNew ->SetBitmap(loadImage("cfg_new_sicon")); + m_menuItemLoad ->SetBitmap(loadImage("cfg_load_sicon")); + m_menuItemSave ->SetBitmap(loadImage("cfg_save_sicon")); + m_menuItemSaveAsBatch->SetBitmap(loadImage("cfg_batch_sicon")); m_menuItemShowLog ->SetBitmap(loadImage("log_file_sicon")); m_menuItemCompare ->SetBitmap(loadImage("compare_sicon")); - m_menuItemCompSettings->SetBitmap(loadImage("cfg_compare_sicon")); - m_menuItemFilter ->SetBitmap(loadImage("cfg_filter_sicon")); - m_menuItemSyncSettings->SetBitmap(loadImage("cfg_sync_sicon")); - m_menuItemSynchronize ->SetBitmap(loadImage("file_sync_sicon")); + m_menuItemCompSettings->SetBitmap(loadImage("options_compare_sicon")); + m_menuItemFilter ->SetBitmap(loadImage("options_filter_sicon")); + m_menuItemSyncSettings->SetBitmap(loadImage("options_sync_sicon")); + m_menuItemSynchronize ->SetBitmap(loadImage("start_sync_sicon")); m_menuItemOptions ->SetBitmap(loadImage("settings_sicon")); m_menuItemFind ->SetBitmap(loadImage("find_sicon")); @@ -746,11 +742,11 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath, setConfig(guiCfg, referenceFiles); //support for CTRL + C and DEL on grids - 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_gridMainL->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridKeyEvent(event, *m_gridMainL, true /*leftSide*/); }); + m_gridMainC->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridKeyEvent(event, *m_gridMainC, true /*leftSide*/); }); + m_gridMainR->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onGridKeyEvent(event, *m_gridMainR, false /*leftSide*/); }); - m_gridOverview->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onTreeButtonEvent(event); }); + m_gridOverview->getMainWin().Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onTreeKeyEvent(event); }); Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); }); //enable dialog-specific key events @@ -1875,7 +1871,7 @@ void MainDialog::onResizeLeftFolderWidth(wxEvent& event) } -void MainDialog::onTreeButtonEvent(wxKeyEvent& event) +void MainDialog::onTreeKeyEvent(wxKeyEvent& event) { const std::vector<FileSystemObject*> selection = getTreeSelection(); @@ -1943,7 +1939,7 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event) } -void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) +void MainDialog::onGridKeyEvent(wxKeyEvent& event, Grid& grid, bool leftSide) { const std::vector<FileSystemObject*> selection = getGridSelection(); const std::vector<FileSystemObject*> selectionLeft = getGridSelection(true, false); @@ -2091,7 +2087,7 @@ void MainDialog::onLocalKeyEvent(wxKeyEvent& event) //process key events without //return; //-> swallow event! case WXK_F11: - setGridViewType(m_bpButtonViewTypeSyncAction->isActive() ? GridViewType::category : GridViewType::action); + setGridViewType(m_bpButtonViewType->isActive() ? GridViewType::difference : GridViewType::action); return; //-> swallow event! //redirect certain (unhandled) keys directly to grid! @@ -2179,7 +2175,7 @@ void MainDialog::onTreeGridSelection(GridSelectEvent& event) { leadRow = std::max<ptrdiff_t>(0, leadRow - 1); //scroll one more row - m_gridMainL->scrollTo(leadRow); //scroll all of them (includes the "scroll master") + m_gridMainL->scrollTo(leadRow); //scroll all of them (including "scroll master") m_gridMainC->scrollTo(leadRow); // m_gridMainR->scrollTo(leadRow); // @@ -2300,7 +2296,7 @@ void MainDialog::onTreeGridContext(GridContextMenuEvent& event) return false; }(); menu.addSeparator(); - menu.addItem(_("&Synchronize selection") + L"\tEnter", [&] { startSyncForSelecction(selection); }, loadImage("file_sync_selection_sicon"), selectionContainsItemsToSync); + menu.addItem(_("&Synchronize selection") + L"\tEnter", [&] { startSyncForSelecction(selection); }, loadImage("start_sync_selection_sicon"), selectionContainsItemsToSync); //---------------------------------------------------------------------------------------------------- const bool haveNonEmptyItems = std::any_of(selection.begin(), selection.end(), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<LEFT_SIDE>() || !fsObj->isEmpty<RIGHT_SIDE>(); }); //menu.addSeparator(); @@ -2309,7 +2305,7 @@ void MainDialog::onTreeGridContext(GridContextMenuEvent& event) menu.addSeparator(); menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selection, selection, true /*moveToRecycler*/); }, wxNullImage, haveNonEmptyItems); - menu.popup(*m_gridOverview, event.mousePos_); + menu.popup(*m_gridOverview); } @@ -2321,7 +2317,7 @@ void MainDialog::onGridContextRim(GridContextMenuEvent& event, bool leftSide) onGridContextRim(getGridSelection(), getGridSelection(true, false), - getGridSelection(false, true), event.mousePos_, leftSide); + getGridSelection(false, true), leftSide); } @@ -2341,7 +2337,7 @@ void MainDialog::onGridGroupContextRim(GridClickEvent& event, bool leftSide) onGridContextRim({pdi.folderGroupObj}, selectionLeft, - selectionRight, event.mousePos_, leftSide); + selectionRight, leftSide); return; //"swallow" event => suppress default context menu handling } @@ -2352,7 +2348,7 @@ void MainDialog::onGridGroupContextRim(GridClickEvent& event, bool leftSide) 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*>& selectionRight, bool leftSide) { ContextMenu menu; @@ -2451,7 +2447,7 @@ void MainDialog::onGridContextRim(const std::vector<FileSystemObject*>& selectio return false; }(); menu.addSeparator(); - menu.addItem(_("&Synchronize selection") + L"\tEnter", [&] { startSyncForSelecction(selection); }, loadImage("file_sync_selection_sicon"), selectionContainsItemsToSync); + menu.addItem(_("&Synchronize selection") + L"\tEnter", [&] { startSyncForSelecction(selection); }, loadImage("start_sync_selection_sicon"), selectionContainsItemsToSync); //---------------------------------------------------------------------------------------------------- if (!globalCfg_.gui.externalApps.empty()) { @@ -2488,69 +2484,7 @@ void MainDialog::onGridContextRim(const std::vector<FileSystemObject*>& selectio menu.addSeparator(); menu.addItem(_("&Delete") + L"\t(Shift+)Del", [&] { deleteSelectedFiles(selectionLeft, selectionRight, true /*moveToRecycler*/); }, wxNullImage, haveNonEmptyItemsL || haveNonEmptyItemsR); - 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) - { - 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 == pdi.folderGroupObj) - return true; - - for (const FileSystemObject* fsObj2 = fsObj;;) - { - const ContainerObject& parent = fsObj2->parent(); - if (&parent == pdi.folderGroupObj) - 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(); + menu.popup(leftSide ? *m_gridMainL : *m_gridMainR); } @@ -2647,15 +2581,14 @@ 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(_("Action") + (!actionView ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::action ); }, actionView); - - menu.popup(*this); + const GridViewType viewType = m_bpButtonViewType->isActive() ? GridViewType::action : GridViewType::difference; + menu.addItem(_("Difference") + (viewType != GridViewType::difference ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::difference); }, greyScaleIfDisabled(loadImage("compare_sicon" ), viewType == GridViewType::difference)); + menu.addItem(_("Action") + (viewType != GridViewType::action ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::action ); }, greyScaleIfDisabled(loadImage("start_sync_sicon"), viewType == GridViewType::action)); + menu.popup(*m_gridMainC, { m_gridMainC->GetSize().x, 0 }); } -void MainDialog::onGridLabelContextRim(bool leftSide) +void MainDialog::onGridLabelContextRim(GridLabelClickEvent& event, bool leftSide) { ContextMenu menu; //-------------------------------------------------------------------------------------------------------- @@ -2760,7 +2693,7 @@ void MainDialog::onGridLabelContextRim(bool leftSide) menu.addItem(_("Select time span..."), selectTimeSpan); } //-------------------------------------------------------------------------------------------------------- - menu.popup(*this); + menu.popup(grid, { event.mousePos_.x, grid.getColumnLabelHeight() }); //event.Skip(); } @@ -2799,7 +2732,7 @@ void MainDialog::resetLayout() } -void MainDialog::onContextSetLayout(wxMouseEvent& event) +void MainDialog::onSetLayoutContext(wxMouseEvent& event) { ContextMenu menu; @@ -2964,7 +2897,7 @@ void MainDialog::updateUnsavedCfgStatus() return img; }; - setImage(*m_bpButtonSave, allowSave ? loadImage("file_save") : makeBrightGrey(loadImage("file_save"))); + setImage(*m_bpButtonSave, allowSave ? loadImage("cfg_save") : makeBrightGrey(loadImage("cfg_save"))); m_bpButtonSave->Enable(allowSave); m_menuItemSave->Enable(allowSave); //bitmap is automatically greyscaled on Win7 (introducing a crappy looking shift), but not on XP @@ -3048,7 +2981,7 @@ bool MainDialog::trySaveConfig(const Zstring* guiCfgPath) //return true if saved //attention: activeConfigFiles_ may be an imported ffs_batch file! We don't want to overwrite it with a GUI config! defaultFileName = beforeLast(defaultFileName, Zstr('.'), IfNotFoundReturn::all) + Zstr(".ffs_gui"); - wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), utfTo<wxString>(defaultFileName), + wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), utfTo<wxString>(defaultFileName), wxString(L"FreeFileSync (*.ffs_gui)|*.ffs_gui") + L"|" +_("All files") + L" (*.*)|*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (fileSelector.ShowModal() != wxID_OK) @@ -3076,7 +3009,7 @@ bool MainDialog::trySaveConfig(const Zstring* guiCfgPath) //return true if saved bool MainDialog::trySaveBatchConfig(const Zstring* batchCfgPath) { - //essentially behave like trySaveConfig(): the collateral damage of not saving GUI-only settings "m_bpButtonViewTypeSyncAction" is negligible + //essentially behave like trySaveConfig(): the collateral damage of not saving GUI-only settings "m_bpButtonViewType" is negligible const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalNativePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); @@ -3134,7 +3067,7 @@ bool MainDialog::trySaveBatchConfig(const Zstring* batchCfgPath) //attention: activeConfigFiles_ may be an ffs_gui file! We don't want to overwrite it with a BATCH config! defaultFileName = beforeLast(defaultFileName, Zstr('.'), IfNotFoundReturn::all) + Zstr(".ffs_batch"); - wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), utfTo<wxString>(defaultFileName), + wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), utfTo<wxString>(defaultFileName), _("FreeFileSync batch") + L" (*.ffs_batch)|*.ffs_batch" + L"|" +_("All files") + L" (*.*)|*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (fileSelector.ShowModal() != wxID_OK) @@ -3225,13 +3158,9 @@ bool MainDialog::saveOldConfig() //return false on user abort void MainDialog::onConfigLoad(wxCommandEvent& event) { - const Zstring activeCfgFilePath = activeConfigFiles_.size() == 1 && !equalNativePath(activeConfigFiles_[0], lastRunConfigPath_) ? activeConfigFiles_[0] : Zstring(); - - std::optional<Zstring> defaultFolderPath = getParentFolderPath(activeCfgFilePath); - if (!defaultFolderPath) - defaultFolderPath = getParentFolderPath(globalCfg_.gui.mainDlg.cfgFileLastSelected); + std::optional<Zstring> defaultFolderPath = getParentFolderPath(globalCfg_.gui.mainDlg.cfgFileLastSelected); - wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), wxString() /*default file name*/, + wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), wxString() /*default file name*/, wxString(L"FreeFileSync (*.ffs_gui; *.ffs_batch)|*.ffs_gui;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", wxFD_OPEN | wxFD_MULTIPLE); if (fileSelector.ShowModal() != wxID_OK) @@ -3470,7 +3399,14 @@ void MainDialog::onCfgGridContext(GridContextMenuEvent& event) const std::vector<size_t> selectedRows = m_gridCfgHistory->getSelectedRows(); //-------------------------------------------------------------------------------------------------------- - menu.addItem(_("&Rename...") + L"\tF2", [this] { renameSelectedCfgHistoryItem (); }, wxNullImage, !selectedRows.empty()); + const bool renameEnabled = [&] + { + if (!selectedRows.empty()) + if (const ConfigView::Details* cfg = cfggrid::getDataView(*m_gridCfgHistory).getItem(selectedRows[0])) + return !cfg->isLastRunCfg; + return false; + }(); + menu.addItem(_("&Rename...") + L"\tF2", [this] { renameSelectedCfgHistoryItem (); }, wxNullImage, renameEnabled); //-------------------------------------------------------------------------------------------------------- ContextMenu submenu; @@ -3514,7 +3450,7 @@ void MainDialog::onCfgGridContext(GridContextMenuEvent& event) //-------------------------------------------------------------------------------------------------------- menu.addItem(_("Hide configuration") + L"\tDel", [this] { deleteSelectedCfgHistoryItems(); }, wxNullImage, !selectedRows.empty()); //-------------------------------------------------------------------------------------------------------- - menu.popup(*m_gridCfgHistory, event.mousePos_); + menu.popup(*m_gridCfgHistory); //event.Skip(); } @@ -3577,7 +3513,7 @@ void MainDialog::onCfgGridLabelContext(GridLabelClickEvent& event) menu.addItem(_("Highlight..."), setCfgHighlight); //-------------------------------------------------------------------------------------------------------- - menu.popup(*m_gridCfgHistory); + menu.popup(*m_gridCfgHistory, { event.mousePos_.x, m_gridCfgHistory->getColumnLabelHeight() }); //event.Skip(); } @@ -3685,7 +3621,7 @@ XmlGuiConfig MainDialog::getConfig() const guiCfg.mainCfg.additionalPairs.push_back(panel->getValues()); //sync preview - guiCfg.gridViewType = m_bpButtonViewTypeSyncAction->isActive() ? GridViewType::action : GridViewType::category; + guiCfg.gridViewType = m_bpButtonViewType->isActive() ? GridViewType::action : GridViewType::difference; return guiCfg; } @@ -3864,7 +3800,7 @@ void MainDialog::onGlobalFilterContext(wxEvent& event) void MainDialog::onToggleViewType(wxCommandEvent& event) { - setGridViewType(m_bpButtonViewTypeSyncAction->isActive() ? GridViewType::category : GridViewType::action); + setGridViewType(m_bpButtonViewType->isActive() ? GridViewType::difference : GridViewType::action); } @@ -3905,8 +3841,22 @@ void MainDialog::setViewFilterDefault() } -void MainDialog::onViewTypeContext(wxEvent& event) +void MainDialog::onViewTypeContextMouse(wxMouseEvent& event) +{ + ContextMenu menu; + + const GridViewType viewType = m_bpButtonViewType->isActive() ? GridViewType::action : GridViewType::difference; + menu.addItem(_("Difference") + (viewType != GridViewType::difference ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::difference); }, greyScaleIfDisabled(loadImage("compare_sicon" ), viewType == GridViewType::difference)); + menu.addItem(_("Action") + (viewType != GridViewType::action ? L"\tF11" : L""), [&] { setGridViewType(GridViewType::action ); }, greyScaleIfDisabled(loadImage("start_sync_sicon"), viewType == GridViewType::action)); + + menu.popup(*m_bpButtonViewType, { m_bpButtonViewType->GetSize().x, 0 }); +} + + +void MainDialog::onViewFilterContext(wxEvent& event) { + ContextMenu menu; + auto saveButtonDefault = [](const ToggleButton& tb, bool& defaultValue) { if (tb.IsShown()) @@ -3937,16 +3887,15 @@ void MainDialog::onViewTypeContext(wxEvent& event) flashStatusInformation(_("View settings saved")); }; - ContextMenu menu; - menu.addItem( _("Save as default"), saveDefault, loadImage("file_save_sicon")); - menu.popup(*m_bpButtonViewContext, { m_bpButtonViewContext->GetSize().x, 0 }); + menu.addItem( _("Save as default"), saveDefault, loadImage("cfg_save_sicon")); + menu.popup(*m_bpButtonViewFilterContext, { m_bpButtonViewFilterContext->GetSize().x, 0 }); } void MainDialog::updateGlobalFilterButton() { //global filter: test for Null-filter - setImage(*m_bpButtonFilter, greyScaleIfDisabled(loadImage("cfg_filter"), !isNullFilter(currentCfg_.mainCfg.globalFilter))); + setImage(*m_bpButtonFilter, greyScaleIfDisabled(loadImage("options_filter"), !isNullFilter(currentCfg_.mainCfg.globalFilter))); const std::wstring status = !isNullFilter(currentCfg_.mainCfg.globalFilter) ? _("Active") : _("None"); m_bpButtonFilter->SetToolTip(_("Filter") + L" (F7) (" + status + L')'); @@ -4094,7 +4043,7 @@ void MainDialog::updateGui() } updateTopButton(*m_buttonCompare, loadImage("compare"), getVariantName(cmpVar), cmpVarIconName, false /*makeGrey*/); - updateTopButton(*m_buttonSync, loadImage("file_sync"), getVariantName(syncVar), syncVarIconName, folderCmp_.empty()); + updateTopButton(*m_buttonSync, loadImage("start_sync"), getVariantName(syncVar), syncVarIconName, folderCmp_.empty()); m_panelTopButtons->Layout(); m_menuItemExportList->Enable(!folderCmp_.empty()); //a CSV without even folder names confuses users: https://freefilesync.org/forum/viewtopic.php?t=4787 @@ -4170,7 +4119,7 @@ void MainDialog::applyCompareConfig(bool setDefaultViewType) break; case CompareVariant::content: - setGridViewType(GridViewType::category); + setGridViewType(GridViewType::difference); break; } } @@ -4477,7 +4426,7 @@ void MainDialog::updateConfigLastRunStats(time_t lastRunTime, SyncResult result, } -void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::shared_ptr<const zen::ErrorLog>& errorLog) +void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::shared_ptr<const ErrorLog>& errorLog) { const wxImage syncResultImage = [&] { @@ -4614,13 +4563,13 @@ void MainDialog::showLogPanel(bool show) } -void MainDialog::onGridDoubleClickRim(size_t row, bool leftSide) +void MainDialog::onGridDoubleClickRim(GridClickEvent& event, bool leftSide) { if (!globalCfg_.gui.externalApps.empty()) { std::vector<FileSystemObject*> selectionLeft; std::vector<FileSystemObject*> selectionRight; - if (FileSystemObject* fsObj = filegrid::getDataView(*m_gridMainC).getFsObject(row)) //selection must be a list of BOUND pointers! + if (FileSystemObject* fsObj = filegrid::getDataView(*m_gridMainC).getFsObject(event.row_)) //selection must be a list of BOUND pointers! (leftSide ? selectionLeft : selectionRight) = { fsObj }; openExternalApplication(globalCfg_.gui.externalApps[0].cmdLine, leftSide, selectionLeft, selectionRight); @@ -4628,41 +4577,20 @@ void MainDialog::onGridDoubleClickRim(size_t row, bool leftSide) } -void MainDialog::onGridLabelLeftClickC(GridLabelClickEvent& event) +void MainDialog::onGridLabelLeftClickRim(GridLabelClickEvent& event, bool leftSide) { - const ColumnTypeCenter colType = static_cast<ColumnTypeCenter>(event.colType_); - if (colType != ColumnTypeCenter::checkbox) - { - bool sortAscending = getDefaultSortDirection(colType); - - 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); + const ColumnTypeRim colType = static_cast<ColumnTypeRim>(event.colType_); - updateGui(); //refresh gridDataView - } -} - - -void MainDialog::onGridLabelLeftClick(bool onLeft, ColumnTypeRim colType) -{ bool sortAscending = getDefaultSortDirection(colType); if (auto sortInfo = filegrid::getDataView(*m_gridMainC).getSortConfig()) if (const ColumnTypeRim* sortType = std::get_if<ColumnTypeRim>(&sortInfo->sortCol)) - if (*sortType == colType && sortInfo->onLeft == onLeft) + if (*sortType == colType && sortInfo->onLeft == leftSide) sortAscending = !sortInfo->ascending; - const ItemPathFormat itemPathFormat = onLeft ? globalCfg_.gui.mainDlg.itemPathFormatLeftGrid : globalCfg_.gui.mainDlg.itemPathFormatRightGrid; + const ItemPathFormat itemPathFormat = leftSide ? globalCfg_.gui.mainDlg.itemPathFormatLeftGrid : globalCfg_.gui.mainDlg.itemPathFormatRightGrid; - filegrid::getDataView(*m_gridMainC).sortView(colType, itemPathFormat, onLeft, sortAscending); + filegrid::getDataView(*m_gridMainC).sortView(colType, itemPathFormat, leftSide, sortAscending); m_gridMainL->clearSelection(GridEventPolicy::allow); m_gridMainC->clearSelection(GridEventPolicy::allow); @@ -4672,17 +4600,27 @@ void MainDialog::onGridLabelLeftClick(bool onLeft, ColumnTypeRim colType) } -void MainDialog::onGridLabelLeftClickL(GridLabelClickEvent& event) +void MainDialog::onGridLabelLeftClickC(GridLabelClickEvent& event) { - onGridLabelLeftClick(true, static_cast<ColumnTypeRim>(event.colType_)); -} + const ColumnTypeCenter colType = static_cast<ColumnTypeCenter>(event.colType_); + if (colType != ColumnTypeCenter::checkbox) + { + bool sortAscending = getDefaultSortDirection(colType); + if (auto sortInfo = filegrid::getDataView(*m_gridMainC).getSortConfig()) + if (const ColumnTypeCenter* sortType = std::get_if<ColumnTypeCenter>(&sortInfo->sortCol)) + if (*sortType == colType) + sortAscending = !sortInfo->ascending; -void MainDialog::onGridLabelLeftClickR(GridLabelClickEvent& event) -{ - onGridLabelLeftClick(false, static_cast<ColumnTypeRim>(event.colType_)); -} + filegrid::getDataView(*m_gridMainC).sortView(colType, sortAscending); + m_gridMainL->clearSelection(GridEventPolicy::allow); + m_gridMainC->clearSelection(GridEventPolicy::allow); + m_gridMainR->clearSelection(GridEventPolicy::allow); + + updateGui(); //refresh gridDataView + } +} void MainDialog::onSwapSides(wxCommandEvent& event) { @@ -4816,9 +4754,9 @@ void MainDialog::updateGridViewData() FileView::FileStats fileStatsLeft; FileView::FileStats fileStatsRight; - if (m_bpButtonViewTypeSyncAction->isActive()) + if (m_bpButtonViewType->isActive()) { - const FileView::ActionViewStats viewStats = filegrid::getDataView(*m_gridMainC).applyFilterByAction(m_bpButtonShowExcluded->isActive(), + const FileView::ActionViewStats viewStats = filegrid::getDataView(*m_gridMainC).applyActionFilter(m_bpButtonShowExcluded->isActive(), m_bpButtonShowCreateLeft ->isActive(), m_bpButtonShowCreateRight->isActive(), m_bpButtonShowDeleteLeft ->isActive(), @@ -4852,14 +4790,14 @@ void MainDialog::updateGridViewData() } else { - const FileView::CategoryViewStats viewStats = filegrid::getDataView(*m_gridMainC).applyFilterByCategory(m_bpButtonShowExcluded->isActive(), - m_bpButtonShowLeftOnly ->isActive(), - m_bpButtonShowRightOnly ->isActive(), - m_bpButtonShowLeftNewer ->isActive(), - m_bpButtonShowRightNewer->isActive(), - m_bpButtonShowDifferent ->isActive(), - m_bpButtonShowEqual ->isActive(), - m_bpButtonShowConflict ->isActive()); + const FileView::DifferenceViewStats viewStats = filegrid::getDataView(*m_gridMainC).applyDifferenceFilter(m_bpButtonShowExcluded->isActive(), + m_bpButtonShowLeftOnly ->isActive(), + m_bpButtonShowRightOnly ->isActive(), + m_bpButtonShowLeftNewer ->isActive(), + m_bpButtonShowRightNewer->isActive(), + m_bpButtonShowDifferent ->isActive(), + m_bpButtonShowEqual ->isActive(), + m_bpButtonShowConflict ->isActive()); fileStatsLeft = viewStats.fileStatsLeft; fileStatsRight = viewStats.fileStatsRight; @@ -4901,8 +4839,8 @@ void MainDialog::updateGridViewData() m_bpButtonShowRightNewer->IsShown() || m_bpButtonShowDifferent ->IsShown(); - m_bpButtonViewTypeSyncAction->Show(anyViewButtonShown); - m_bpButtonViewContext ->Show(anyViewButtonShown); + m_bpButtonViewType ->Show(anyViewButtonShown); + m_bpButtonViewFilterContext->Show(anyViewButtonShown); m_panelViewFilter->Layout(); @@ -4910,19 +4848,19 @@ void MainDialog::updateGridViewData() filegrid::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); //overview panel - if (m_bpButtonViewTypeSyncAction->isActive()) - treegrid::getDataView(*m_gridOverview).applyFilterByAction(m_bpButtonShowExcluded ->isActive(), - m_bpButtonShowCreateLeft ->isActive(), - m_bpButtonShowCreateRight->isActive(), - m_bpButtonShowDeleteLeft ->isActive(), - m_bpButtonShowDeleteRight->isActive(), - m_bpButtonShowUpdateLeft ->isActive(), - m_bpButtonShowUpdateRight->isActive(), - m_bpButtonShowDoNothing ->isActive(), - m_bpButtonShowEqual ->isActive(), - m_bpButtonShowConflict ->isActive()); + if (m_bpButtonViewType->isActive()) + treegrid::getDataView(*m_gridOverview).applyActionFilter(m_bpButtonShowExcluded ->isActive(), + m_bpButtonShowCreateLeft ->isActive(), + m_bpButtonShowCreateRight->isActive(), + m_bpButtonShowDeleteLeft ->isActive(), + m_bpButtonShowDeleteRight->isActive(), + m_bpButtonShowUpdateLeft ->isActive(), + m_bpButtonShowUpdateRight->isActive(), + m_bpButtonShowDoNothing ->isActive(), + m_bpButtonShowEqual ->isActive(), + m_bpButtonShowConflict ->isActive()); else - treegrid::getDataView(*m_gridOverview).applyFilterByCategory(m_bpButtonShowExcluded ->isActive(), + treegrid::getDataView(*m_gridOverview).applyDifferenceFilter(m_bpButtonShowExcluded ->isActive(), m_bpButtonShowLeftOnly ->isActive(), m_bpButtonShowRightOnly ->isActive(), m_bpButtonShowLeftNewer ->isActive(), @@ -5483,7 +5421,7 @@ void MainDialog::onMenuExportFileList(wxCommandEvent& event) if (defaultFileName.empty()) defaultFileName = Zstr("FileList.csv"); - wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), utfTo<wxString>(defaultFileName), + wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), utfTo<wxString>(defaultFileName), _("Comma-separated values") + L" (*.csv)|*.csv" + L"|" +_("All files") + L" (*.*)|*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (fileSelector.ShowModal() != wxID_OK) @@ -5694,12 +5632,6 @@ void MainDialog::onMenuAbout(wxCommandEvent& event) } -void MainDialog::onShowHelp(wxCommandEvent& event) -{ - displayHelpEntry(L"freefilesync", this); -} - - void MainDialog::switchProgramLanguage(wxLanguage langId) { //create new dialog with respect to new language @@ -5718,10 +5650,10 @@ void MainDialog::switchProgramLanguage(wxLanguage langId) void MainDialog::setGridViewType(GridViewType vt) { - //if (m_bpButtonViewTypeSyncAction->isActive() == value) return; support polling -> what about initialization? + //if (m_bpButtonViewType->isActive() == value) return; support polling -> what about initialization? - m_bpButtonViewTypeSyncAction->setActive(vt == GridViewType::action); - m_bpButtonViewTypeSyncAction->SetToolTip((vt == GridViewType::action ? _("Action") : _("Category")) + L" (F11)"); + m_bpButtonViewType->setActive(vt == GridViewType::action); + m_bpButtonViewType->SetToolTip((vt == GridViewType::action ? _("Action") : _("Difference")) + L" (F11)"); //toggle display of sync preview in middle grid filegrid::setViewType(*m_gridMainC, vt); diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h index 9e9ead04..26cb167f 100644 --- a/FreeFileSync/Source/ui/main_dlg.h +++ b/FreeFileSync/Source/ui/main_dlg.h @@ -129,47 +129,22 @@ private: void flashStatusInformation(const wxString& msg); //temporarily show different status (only valid for setStatusBarFileStats) //events - 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 onGridKeyEvent(wxKeyEvent& event, zen::Grid& grid, bool leftSide); - void onTreeButtonEvent (wxKeyEvent& event); - void onContextSetLayout(wxMouseEvent& event); + void onTreeKeyEvent (wxKeyEvent& event); + void onSetLayoutContext(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(wxEvent& event); - void onSyncSettingsContext(wxEvent& event); - void onGlobalFilterContext(wxEvent& event); - void onViewTypeContext (wxEvent& event); - void applyCompareConfig(bool setDefaultViewType); //context menu handler methods - 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 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); + const std::vector<FileSystemObject*>& selectionRight, bool leftSide); void onTreeGridContext(zen::GridContextMenuEvent& event); @@ -183,28 +158,27 @@ private: void onCheckRows (CheckRowsEvent& event); void onSetSyncDirection(SyncDirectionEvent& 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 onGridDoubleClickRim(zen::GridClickEvent& event, bool leftSide); - void onGridLabelLeftClickL(zen::GridLabelClickEvent& event); - void onGridLabelLeftClickC(zen::GridLabelClickEvent& event); - void onGridLabelLeftClickR(zen::GridLabelClickEvent& event); - void onGridLabelLeftClick(bool onLeft, ColumnTypeRim colType); + void onGridLabelLeftClickRim(zen::GridLabelClickEvent& event, bool onLeft); + void onGridLabelLeftClickC (zen::GridLabelClickEvent& event); - void onGridLabelContextL(zen::GridLabelClickEvent& event) { onGridLabelContextRim(true /*leftSide*/); } - void onGridLabelContextC(zen::GridLabelClickEvent& event); - void onGridLabelContextR(zen::GridLabelClickEvent& event) { onGridLabelContextRim(false /*leftSide*/); } - void onGridLabelContextRim(bool leftSide); + void onGridLabelContextRim(zen::GridLabelClickEvent& event, bool leftSide); + void onGridLabelContextC (zen::GridLabelClickEvent& event); 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 { trySaveConfig(nullptr); } - void onSaveAsBatchJob (wxCommandEvent& event) override { trySaveBatchConfig(nullptr); } - void onConfigLoad (wxCommandEvent& event) override; + void onViewTypeContextMouse (wxMouseEvent& event) override; + void onViewFilterContext (wxCommandEvent& event) override { onViewFilterContext(static_cast<wxEvent&>(event)); } + void onViewFilterContextMouse(wxMouseEvent& event) override { onViewFilterContext(static_cast<wxEvent&>(event)); } + void onViewFilterContext(wxEvent& event); + + 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); @@ -232,8 +206,19 @@ private: void startSyncForSelecction(const std::vector<FileSystemObject*>& selection); void onCmpSettings (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::compare, -1); } - void onConfigureFilter(wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::filter, -1); } - void onSyncSettings (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::sync, -1); } + void onSyncSettings (wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::sync, -1); } + void onConfigureFilter(wxCommandEvent& event) override { showConfigDialog(SyncConfigPanel::filter, -1); } + + 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 onCompSettingsContext(wxEvent& event); + void onSyncSettingsContext(wxEvent& event); + void onGlobalFilterContext(wxEvent& event); void showConfigDialog(SyncConfigPanel panelToShow, int localPairIndexToShow); @@ -285,7 +270,7 @@ private: void onMenuCheckVersion (wxCommandEvent& event) override; void onMenuCheckVersionAutomatically(wxCommandEvent& event) override; void onMenuAbout (wxCommandEvent& event) override; - void onShowHelp (wxCommandEvent& event) override; + void onShowHelp (wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/manual.php?topic=freefilesync"); } void onMenuQuit (wxCommandEvent& event) override { Close(); } void switchProgramLanguage(wxLanguage langId); @@ -341,7 +326,7 @@ private: LogPanel* logPanel_ = nullptr; //toggle to display configuration preview instead of comparison result: - //for read access use: m_bpButtonViewTypeSyncAction->isActive() + //for read access use: m_bpButtonViewType->isActive() //when changing value use: void setGridViewType(GridViewType vt); diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp index 3010c344..bf41479f 100644 --- a/FreeFileSync/Source/ui/small_dlgs.cpp +++ b/FreeFileSync/Source/ui/small_dlgs.cpp @@ -42,7 +42,6 @@ #include "../version/version.h" #include "../log_file.h" #include "../ffs_paths.h" -#include "../help_provider.h" #include "../icon_buffer.h" @@ -197,7 +196,6 @@ private: 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(); } @@ -565,9 +563,7 @@ void CloudSetupDlg::onSelectKeyfile(wxCommandEvent& event) { assert (type_ == CloudType::sftp && sftpAuthType_ == SftpAuthType::keyFile); - std::optional<Zstring> defaultFolderPath = getParentFolderPath(utfTo<Zstring>(m_textCtrlKeyfilePath->GetValue())); - if (!defaultFolderPath) - defaultFolderPath = getParentFolderPath(sftpKeyFileLastSelected_); + std::optional<Zstring> defaultFolderPath = getParentFolderPath(sftpKeyFileLastSelected_); wxFileDialog fileSelector(this, wxString() /*message*/, utfTo<wxString>(defaultFolderPath ? *defaultFolderPath : Zstr("")), wxString() /*default file name*/, _("All files") + L" (*.*)|*" + @@ -1049,7 +1045,7 @@ SyncConfirmationDlg::SyncConfirmationDlg(wxWindow* parent, setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonStartSync).setCancel(m_buttonCancel)); setMainInstructionFont(*m_staticTextCaption); - m_bitmapSync->SetBitmap(loadImage(syncSelection ? "file_sync_selection" : "file_sync")); + m_bitmapSync->SetBitmap(loadImage(syncSelection ? "start_sync_selection" : "start_sync")); m_staticTextCaption->SetLabel(syncSelection ?_("Start to synchronize the selection?") : _("Start synchronization now?")); m_staticTextSyncVar->SetLabel(getVariantName(syncVar)); @@ -1149,7 +1145,6 @@ private: void onClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ConfirmationButton::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(); } @@ -1208,7 +1203,7 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) : m_bitmapNotificationSounds->SetBitmap (loadImage("notification_sounds")); m_bitmapConsole ->SetBitmap (loadImage("command_line", fastFromDIP(20))); m_bitmapCompareDone ->SetBitmap (loadImage("compare_sicon")); - m_bitmapSyncDone ->SetBitmap (loadImage("file_sync_sicon")); + m_bitmapSyncDone ->SetBitmap (loadImage("start_sync_sicon")); m_bpButtonPlayCompareDone ->SetBitmapLabel(loadImage("play_sound")); m_bpButtonPlaySyncDone ->SetBitmapLabel(loadImage("play_sound")); m_bpButtonAddRow ->SetBitmapLabel(loadImage("item_add")); diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp index dfad6a6f..f2200e2e 100644 --- a/FreeFileSync/Source/ui/sync_cfg.cpp +++ b/FreeFileSync/Source/ui/sync_cfg.cpp @@ -23,7 +23,6 @@ #include "folder_selector.h" #include "../base/norm_filter.h" #include "../base/file_hierarchy.h" -#include "../help_provider.h" #include "../log_file.h" #include "../afs/concrete.h" #include "../base_tools.h" @@ -118,10 +117,6 @@ private: }; //------------- 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(); } @@ -145,7 +140,6 @@ 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()); } @@ -182,9 +176,6 @@ private: 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(); } @@ -344,7 +335,7 @@ showMultipleCfgs_(showMultipleCfgs) m_notebook->SetPadding(wxSize(fastFromDIP(2), 0)); //height cannot be changed //fill image list to cope with wxNotebook image setting design desaster... - const int imgListSize = loadImage("cfg_compare_sicon").GetHeight(); + const int imgListSize = loadImage("options_compare_sicon").GetHeight(); auto imgList = std::make_unique<wxImageList>(imgListSize, imgListSize); auto addToImageList = [&](const wxImage& img) @@ -355,9 +346,9 @@ showMultipleCfgs_(showMultipleCfgs) imgList->Add(greyScale(img)); }; //add images in same sequence like ConfigTypeImage enum!!! - addToImageList(loadImage("cfg_compare_sicon")); - addToImageList(loadImage("cfg_filter_sicon")); - addToImageList(loadImage("cfg_sync_sicon")); + addToImageList(loadImage("options_compare_sicon")); + addToImageList(loadImage("options_filter_sicon")); + addToImageList(loadImage("options_sync_sicon")); assert(imgList->GetImageCount() == static_cast<int>(ConfigTypeImage::syncGrey) + 1); m_notebook->AssignImageList(imgList.release()); //pass ownership diff --git a/FreeFileSync/Source/ui/tree_grid.cpp b/FreeFileSync/Source/ui/tree_grid.cpp index e101a094..c0a1d993 100644 --- a/FreeFileSync/Source/ui/tree_grid.cpp +++ b/FreeFileSync/Source/ui/tree_grid.cpp @@ -501,7 +501,7 @@ ptrdiff_t TreeView::getParent(size_t row) const } -void TreeView::applyFilterByCategory(bool showExcluded, +void TreeView::applyDifferenceFilter(bool showExcluded, bool leftOnlyFilesActive, bool rightOnlyFilesActive, bool leftNewerFilesActive, @@ -546,16 +546,16 @@ void TreeView::applyFilterByCategory(bool showExcluded, } -void TreeView::applyFilterByAction(bool showExcluded, - bool syncCreateLeftActive, - bool syncCreateRightActive, - bool syncDeleteLeftActive, - bool syncDeleteRightActive, - bool syncDirOverwLeftActive, - bool syncDirOverwRightActive, - bool syncDirNoneActive, - bool syncEqualActive, - bool conflictFilesActive) +void TreeView::applyActionFilter(bool showExcluded, + bool syncCreateLeftActive, + bool syncCreateRightActive, + bool syncDeleteLeftActive, + bool syncDeleteRightActive, + bool syncDirOverwLeftActive, + bool syncDirOverwRightActive, + bool syncDirNoneActive, + bool syncEqualActive, + bool conflictFilesActive) { updateView([showExcluded, //make sure the predicate can be stored safely! syncCreateLeftActive, @@ -716,7 +716,7 @@ public: private: size_t getRowCount() const override { return getDataView().rowsTotal(); } - std::wstring getToolTip(size_t row, ColumnType colType) const override + std::wstring getToolTip(size_t row, ColumnType colType, HoverArea rowHover) override { switch (static_cast<ColumnTypeTree>(colType)) { @@ -785,12 +785,12 @@ private: } - void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) override + void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, HoverArea rowHover) override { if (!enabled || !selected) - clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + ; //clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -> already the default else - GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/ ); + GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/, rowHover); } @@ -812,7 +812,7 @@ private: // ________________________________________________________________________________ // | space | gap | percentage bar | 2 x gap | node status | gap |icon | gap | rest | // -------------------------------------------------------------------------------- - // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() + // -> synchronize renderCell() <-> getBestSize() <-> getMouseHover() if (static_cast<ColumnTypeTree>(colType) == ColumnTypeTree::folder) { @@ -820,9 +820,9 @@ private: { ////clear first section: //clearArea(dc, wxRect(rect.GetTopLeft(), wxSize( - // node->level_ * widthLevelStep_ + gridGap_ + //width - // (showPercentBar ? percentageBarWidth_ + 2 * gridGap_ : 0) + // - // widthNodeStatus_ + gridGap_ + widthNodeIcon + gridGap_, // + // node->level_ * widthLevelStep_ + gapSize_ + //width + // (showPercentBar ? percentageBarWidth_ + 2 * gapSize_ : 0) + // + // widthNodeStatus_ + gapSize_ + widthNodeIcon + gapSize_, // // rect.height)), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); auto drawIcon = [&](wxImage icon, const wxRect& rectIcon, bool drawActive) @@ -840,8 +840,8 @@ private: rectTmp.x += static_cast<int>(node->level_) * widthLevelStep_; rectTmp.width -= static_cast<int>(node->level_) * widthLevelStep_; - rectTmp.x += gridGap_; - rectTmp.width -= gridGap_; + rectTmp.x += gapSize_; + rectTmp.width -= gapSize_; if (rectTmp.width > 0) { @@ -861,8 +861,8 @@ private: 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_; - rectTmp.width -= percentageBarWidth_ + 2 * gridGap_; + rectTmp.x += percentageBarWidth_ + 2 * gapSize_; + rectTmp.width -= percentageBarWidth_ + 2 * gapSize_; } if (rectTmp.width > 0) { @@ -880,8 +880,8 @@ private: break; } - rectTmp.x += widthNodeStatus_ + gridGap_; - rectTmp.width -= widthNodeStatus_ + gridGap_; + rectTmp.x += widthNodeStatus_ + gapSize_; + rectTmp.width -= widthNodeStatus_ + gapSize_; if (rectTmp.width > 0) { wxImage nodeIcon; @@ -899,8 +899,8 @@ private: drawIcon(nodeIcon, rectTmp, isActive); - rectTmp.x += widthNodeIcon_ + gridGap_; - rectTmp.width -= widthNodeIcon_ + gridGap_; + rectTmp.x += widthNodeIcon_ + gapSize_; + rectTmp.width -= widthNodeIcon_ + gapSize_; if (rectTmp.width > 0) { @@ -922,13 +922,13 @@ private: if ((static_cast<ColumnTypeTree>(colType) == ColumnTypeTree::bytes || static_cast<ColumnTypeTree>(colType) == ColumnTypeTree::itemCount) && grid_.GetLayoutDirection() != wxLayout_RightToLeft) { - rectTmp.width -= 2 * gridGap_; + rectTmp.width -= 2 * gapSize_; alignment = wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL; } else //left-justified { - rectTmp.x += 2 * gridGap_; - rectTmp.width -= 2 * gridGap_; + rectTmp.x += 2 * gapSize_; + rectTmp.width -= 2 * gapSize_; } drawCellText(dc, rectTmp, getValue(row, colType), alignment); @@ -937,23 +937,23 @@ private: int getBestSize(wxDC& dc, size_t row, ColumnType colType) override { - // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() + // -> synchronize renderCell() <-> getBestSize() <-> getMouseHover() if (static_cast<ColumnTypeTree>(colType) == ColumnTypeTree::folder) { 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 + return node->level_ * widthLevelStep_ + gapSize_ + (showPercentBar_ ? percentageBarWidth_ + 2 * gapSize_ : 0) + widthNodeStatus_ + gapSize_ + + widthNodeIcon_ + gapSize_ + dc.GetTextExtent(getValue(row, colType)).GetWidth() + + gapSize_; //additional gap from right else return 0; } else - return 2 * gridGap_ + dc.GetTextExtent(getValue(row, colType)).GetWidth() + - 2 * gridGap_; //include gap from right! + return 2 * gapSize_ + dc.GetTextExtent(getValue(row, colType)).GetWidth() + + 2 * gapSize_; //include gap from right! } - HoverArea getRowMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override + HoverArea getMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override { switch (static_cast<ColumnTypeTree>(colType)) { @@ -961,9 +961,9 @@ private: 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); + const int nodeStatusXFirst = -tolerance + static_cast<int>(node->level_) * widthLevelStep_ + gapSize_ + (showPercentBar_ ? percentageBarWidth_ + 2 * gapSize_ : 0); const int nodeStatusXLast = (nodeStatusXFirst + tolerance) + widthNodeStatus_ + tolerance; - // -> synchronize renderCell() <-> getBestSize() <-> getRowMouseHover() + // -> synchronize renderCell() <-> getBestSize() <-> getMouseHover() if (nodeStatusXFirst <= cellRelativePosX && cellRelativePosX < nodeStatusXLast) return static_cast<HoverArea>(HoverAreaTree::node); @@ -1131,7 +1131,7 @@ private: menu.addItem(_("&Default"), setDefaultColumns); //'&' -> reuse text from "default" buttons elsewhere //-------------------------------------------------------------------------------------------------------- - menu.popup(grid_); + menu.popup(grid_, { event.mousePos_.x, grid_.getColumnLabelHeight() }); //event.Skip(); } @@ -1166,7 +1166,7 @@ private: SharedRef<TreeView> treeDataView_ = makeSharedRef<TreeView>(); - const int gridGap_ = fastFromDIP(TREE_GRID_GAP_SIZE_DIP); + const int gapSize_ = fastFromDIP(TREE_GRID_GAP_SIZE_DIP); const int percentageBarWidth_ = fastFromDIP(PERCENTAGE_BAR_WIDTH_DIP); const wxImage fileIcon_ = IconBuffer::genericFileIcon(IconBuffer::SIZE_SMALL); diff --git a/FreeFileSync/Source/ui/tree_grid.h b/FreeFileSync/Source/ui/tree_grid.h index 9d735804..28a30d8d 100644 --- a/FreeFileSync/Source/ui/tree_grid.h +++ b/FreeFileSync/Source/ui/tree_grid.h @@ -29,7 +29,7 @@ public: TreeView(FolderComparison& folderCmp, const SortInfo& si); //takes (shared) ownership //apply view filter: comparison results - void applyFilterByCategory(bool showExcluded, + void applyDifferenceFilter(bool showExcluded, bool leftOnlyFilesActive, bool rightOnlyFilesActive, bool leftNewerFilesActive, @@ -39,16 +39,16 @@ public: bool conflictFilesActive); //apply view filter: synchronization preview - void applyFilterByAction(bool showExcluded, - bool syncCreateLeftActive, - bool syncCreateRightActive, - bool syncDeleteLeftActive, - bool syncDeleteRightActive, - bool syncDirOverwLeftActive, - bool syncDirOverwRightActive, - bool syncDirNoneActive, - bool syncEqualActive, - bool conflictFilesActive); + void applyActionFilter(bool showExcluded, + bool syncCreateLeftActive, + bool syncCreateRightActive, + bool syncDeleteLeftActive, + bool syncDeleteRightActive, + bool syncDirOverwLeftActive, + bool syncDirOverwRightActive, + bool syncDirNoneActive, + bool syncEqualActive, + bool conflictFilesActive); enum NodeStatus { diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp index e45ea5bb..d19d05ef 100644 --- a/FreeFileSync/Source/ui/version_check.cpp +++ b/FreeFileSync/Source/ui/version_check.cpp @@ -17,6 +17,7 @@ #include <zen/file_error.h> #include <zen/http.h> #include <zen/sys_version.h> +#include <zen/sys_info.h> #include <zen/thread.h> #include <wx+/popup_dlg.h> #include <wx+/image_resources.h> @@ -112,7 +113,7 @@ std::wstring getIso3166Country() //coordinate with get_latest_version_number.php -std::vector<std::pair<std::string, std::string>> geHttpPostParameters(wxWindow& parent) +std::vector<std::pair<std::string, std::string>> geHttpPostParameters(wxWindow& parent) //throw SysError { assert(runningOnMainThread()); //this function is not thread-safe, e.g. consider wxWidgets usage in isPortableVersion() std::vector<std::pair<std::string, std::string>> params; @@ -142,6 +143,7 @@ std::vector<std::pair<std::string, std::string>> geHttpPostParameters(wxWindow& params.emplace_back("language", utfTo<std::string>(getIso639Language())); params.emplace_back("country", utfTo<std::string>(getIso3166Country())); + return params; } @@ -274,38 +276,48 @@ void fff::checkForUpdateNow(wxWindow& parent, std::string& lastOnlineVersion) struct fff::UpdateCheckResultPrep { std::vector<std::pair<std::string, std::string>> postParameters; + std::optional<SysError> error; }; - std::shared_ptr<const UpdateCheckResultPrep> fff::automaticUpdateCheckPrepare(wxWindow& parent) { assert(runningOnMainThread()); auto prep = std::make_shared<UpdateCheckResultPrep>(); - prep->postParameters = geHttpPostParameters(parent); + try + { + prep->postParameters = geHttpPostParameters(parent); //throw SysError + } + catch (const SysError& e) + { + prep->error = e; + } return prep; } struct fff::UpdateCheckResult { - UpdateCheckResult(const std::string& ver, const std::optional<SysError>& err, bool alive) : onlineVersion(ver), error(err), internetIsAlive(alive) {} - std::string onlineVersion; - std::optional<SysError> error; bool internetIsAlive = false; + std::optional<SysError> error; }; - std::shared_ptr<const UpdateCheckResult> fff::automaticUpdateCheckRunAsync(const UpdateCheckResultPrep* resultPrep) { //assert(!runningOnMainThread()); -> allow synchronous call, too + auto result = std::make_shared<UpdateCheckResult>(); try { - const std::string onlineVersion = getOnlineVersion(resultPrep->postParameters); //throw SysError - return std::make_shared<UpdateCheckResult>(onlineVersion, std::nullopt, true); + if (resultPrep->error) + throw* resultPrep->error; //throw SysError + + result->onlineVersion = getOnlineVersion(resultPrep->postParameters); //throw SysError + result->internetIsAlive = true; } catch (const SysError& e) { - return std::make_shared<UpdateCheckResult>("", e, internetIsAlive()); + result->error = e; + result->internetIsAlive = internetIsAlive(); } + return result; } diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h index 990af5ad..d2124cae 100644 --- a/FreeFileSync/Source/version/version.h +++ b/FreeFileSync/Source/version/version.h @@ -3,7 +3,7 @@ namespace fff { -const char ffsVersion[] = "11.2"; //internal linkage! +const char ffsVersion[] = "11.3"; //internal linkage! const char FFS_VERSION_SEPARATOR = '.'; } diff --git a/libcurl/curl_wrap.h b/libcurl/curl_wrap.h index bc924713..2c445771 100644 --- a/libcurl/curl_wrap.h +++ b/libcurl/curl_wrap.h @@ -138,8 +138,9 @@ std::wstring formatCurlStatusCode(CURLcode sc) ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_HTTP3); ZEN_CHECK_CASE_FOR_CONSTANT(CURL_LAST); ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_QUIC_CONNECT_ERROR); + ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_PROXY); } - static_assert(CURL_LAST == CURLE_QUIC_CONNECT_ERROR + 1); + static_assert(CURL_LAST == CURLE_PROXY + 1); return replaceCpy<std::wstring>(L"Curl status %x", L"%x", numberTo<std::wstring>(static_cast<int>(sc))); } diff --git a/libssh2/libssh2_wrap.h b/libssh2/libssh2_wrap.h index 98b73af2..1b16bad2 100644 --- a/libssh2/libssh2_wrap.h +++ b/libssh2/libssh2_wrap.h @@ -175,7 +175,7 @@ std::wstring formatSshStatusCode(int sc) ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_CHANNEL_WINDOW_FULL); ZEN_CHECK_CASE_FOR_CONSTANT(LIBSSH2_ERROR_KEYFILE_AUTH_FAILED); - default: + default: return replaceCpy<std::wstring>(L"SSH status %x", L"%x", numberTo<std::wstring>(sc)); } } @@ -39,8 +39,8 @@ void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) { //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); + wxDCPenChanger areaPen (dc, *wxTRANSPARENT_PEN); + wxDCBrushChanger areaBrush(dc, col); dc.DrawRectangle(rect); } } @@ -51,8 +51,8 @@ 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); + wxDCPenChanger rectPen (dc, *wxTRANSPARENT_PEN); + wxDCBrushChanger rectBrush(dc, borderCol); dc.DrawRectangle(rect); rect.Deflate(borderWidth); //attention, more wxWidgets design mistakes: behavior of wxRect::Deflate depends on object being const/non-const!!! @@ -93,8 +93,11 @@ public: oldRect_ = it->second; wxRect tmp = r; - tmp.Intersect(*oldRect_); //better safe than sorry - dc_.SetClippingRegion(tmp); // + tmp.Intersect(*oldRect_); //better safe than sorry + + assert(!tmp.IsEmpty()); //"setting an empty clipping region is equivalent to DestroyClippingRegion()" + + dc_.SetClippingRegion(tmp); //new clipping region is intersection of given and previously set regions it->second = tmp; } else diff --git a/wx+/file_drop.cpp b/wx+/file_drop.cpp index 42cfbd3d..86db57c1 100644 --- a/wx+/file_drop.cpp +++ b/wx+/file_drop.cpp @@ -49,8 +49,7 @@ private: => switching to wxTextDropTarget won't help (much): we'd get the format mtp://[usb:001,002]/Telefonspeicher/Folder/file.txt instead of - /run/user/1000/gvfs/mtp:host=%5Busb%3A001%2C002%5D/Telefonspeicher/Folder/file.txt - */ + /run/user/1000/gvfs/mtp:host=%5Busb%3A001%2C002%5D/Telefonspeicher/Folder/file.txt */ if (!dropWindow_.IsEnabled()) return false; diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 80c9aaf1..6eb25ac9 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -99,14 +99,13 @@ wxDEFINE_EVENT(EVENT_GRID_CONTEXT_MENU, GridContextMenuEvent); } //---------------------------------------------------------------------------------------------------------------- -void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) +void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, HoverArea rowHover) { 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_WINDOW)); -> already the default } else clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); @@ -384,6 +383,9 @@ private: else parent_.HandleOnMouseWheel(event); + onMouseMovement(event); + event.Skip(false); + //if (!sendEventToParent(event)) // event.Skip(); } @@ -462,7 +464,7 @@ public: int bestWidth = 0; for (ptrdiff_t i = rowFrom; i <= rowTo; ++i) - bestWidth = std::max(bestWidth, dc.GetTextExtent(formatRow(i)).GetWidth() + fastFromDIP(2 * ROW_LABEL_BORDER_DIP)); + bestWidth = std::max(bestWidth, dc.GetTextExtent(formatRowNum(i)).GetWidth() + fastFromDIP(2 * ROW_LABEL_BORDER_DIP)); return bestWidth; } @@ -500,7 +502,7 @@ public: } private: - static std::wstring formatRow(size_t row) { return formatNumber(row + 1); } //convert number to std::wstring including thousands separator + static std::wstring formatRowNum(size_t row) { return formatNumber(row + 1); } //convert number to std::wstring including thousands separator bool AcceptsFocus() const override { return false; } @@ -533,7 +535,7 @@ private: wxRect textRect = rect; textRect.Deflate(fastFromDIP(1)); - GridData::drawCellText(dc, textRect, formatRow(row), wxALIGN_CENTRE); + GridData::drawCellText(dc, textRect, formatRowNum(row), wxALIGN_CENTRE); //border lines { @@ -733,8 +735,9 @@ private: } else //notify single label click { + const wxPoint mousePos = GetPosition() + event.GetPosition(); if (const std::optional<ColumnType> colType = refParent().colToType(activeClickOrMove_->getColumnFrom())) - sendEventToParent(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, *colType)); + sendEventToParent(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, *colType, mousePos)); } activeClickOrMove_.reset(); } @@ -840,16 +843,18 @@ private: void onMouseRightDown(wxMouseEvent& event) override { + const wxPoint mousePos = GetPosition() + event.GetPosition(); + if (const std::optional<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) { if (const std::optional<ColumnType> colType = refParent().colToType(action->col)) - sendEventToParent(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, *colType)); //notify right click + sendEventToParent(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, *colType, mousePos)); //notify right click else assert(false); } 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, mousePos)); event.Skip(); } @@ -894,14 +899,16 @@ private: void render(wxDC& dc, const wxRect& rect) override { const bool enabled = renderAsEnabled(*this); - clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); //CONTRACT! expected by GridData::renderRowBackgound()! - dc.SetFont(GetFont()); //harmonize with Grid::getBestColumnSize() + if (auto prov = refParent().getDataProvider()) + { + dc.SetFont(GetFont()); //harmonize with Grid::getBestColumnSize() - wxDCTextColourChanger textColor(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels + wxDCTextColourChanger textColor(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels + + std::vector<ColumnWidth> absWidths = refParent().getColWidths(); //resolve stretched widths - std::vector<ColumnWidth> absWidths = refParent().getColWidths(); //resolve stretched widths - { int totalRowWidth = 0; for (const ColumnWidth& cw : absWidths) totalRowWidth += cw.width; @@ -910,36 +917,37 @@ private: if (fillGapAfterColumns) totalRowWidth = std::max(totalRowWidth, GetClientSize().GetWidth()); - if (auto prov = refParent().getDataProvider()) - { - RecursiveDcClipper dummy2(dc, rect); //do NOT draw background on cells outside of invalidated rect invalidating foreground text! + RecursiveDcClipper dummy(dc, rect); //do NOT draw background on cells outside of invalidated rect invalidating foreground text! - wxPoint cellAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates - const int rowHeight = rowLabelWin_.getRowHeight(); - const auto rowRange = rowLabelWin_.getRowsOnClient(rect); //returns range [begin, end) + const wxPoint gridAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates + const int rowHeight = rowLabelWin_.getRowHeight(); + const auto rowRange = rowLabelWin_.getRowsOnClient(rect); //returns range [begin, end) + for (auto row = rowRange.first; row < rowRange.second; ++row) + { //draw background lines - for (auto row = rowRange.first; row < rowRange.second; ++row) - { - const wxRect rowRect(cellAreaTL + wxPoint(0, row * rowHeight), wxSize(totalRowWidth, rowHeight)); - RecursiveDcClipper dummy3(dc, rowRect); - prov->renderRowBackgound(dc, rowRect, row, enabled, drawAsSelected(row)); - } + const wxRect rowRect(gridAreaTL + wxPoint(0, row * rowHeight), wxSize(totalRowWidth, rowHeight)); + const bool drawSelected = drawAsSelected(row); + const HoverArea rowHover = getRowHoverToDraw(row); - //draw single cells, column by column + RecursiveDcClipper dummy2(dc, rowRect); + prov->renderRowBackgound(dc, rowRect, row, enabled, drawSelected, rowHover); + + //draw cells column by column + wxRect cellRect = rowRect; for (const ColumnWidth& cw : absWidths) { - if (cellAreaTL.x > rect.GetRight()) - return; //done - - if (cellAreaTL.x + cw.width > rect.x) - for (auto row = rowRange.first; row < rowRange.second; ++row) - { - const wxRect cellRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, cw.width, rowHeight); - RecursiveDcClipper dummy3(dc, cellRect); - prov->renderCell(dc, cellRect, row, cw.type, enabled, drawAsSelected(row), getRowHoverToDraw(row)); - } - cellAreaTL.x += cw.width; + cellRect.width = cw.width; + + if (cellRect.x > rect.GetRight()) + break; //done + + if (cellRect.x + cw.width > rect.x) + { + RecursiveDcClipper dummy3(dc, cellRect); + prov->renderCell(dc, cellRect, row, cw.type, enabled, drawSelected, rowHover); + } + cellRect.x += cw.width; } } } @@ -979,15 +987,21 @@ private: { if (auto prov = refParent().getDataProvider()) { - wxClientDC dc(this); - dc.SetFont(GetFont()); - + const wxPoint mousePos = GetPosition() + event.GetPosition(); 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(); + const HoverArea rowHover = [&] + { + if (0 <= row && row < rowCount && cpi.colType != ColumnType::none) + { + wxClientDC dc(this); + dc.SetFont(GetFont()); + return prov->getMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + } + return HoverArea::none; + }(); //client is interested in all double-clicks, even those outside of the grid! sendEventToParent(GridClickEvent(EVENT_GRID_MOUSE_LEFT_DOUBLE, row, rowHover, mousePos)); @@ -999,17 +1013,23 @@ 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()); + onMouseMovement(event); //update highlight in obscure cases (e.g. right-click while context menu is open) - 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(); + 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 = [&] + { + if (0 <= row && row < rowCount && cpi.colType != ColumnType::none) + { + wxClientDC dc(this); + dc.SetFont(GetFont()); + return prov->getMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + } + return HoverArea::none; + }(); assert(row >= 0); //row < 0 was possible in older wxWidgets: https://github.com/wxWidgets/wxWidgets/commit/2c69d27c0d225d3a331c773da466686153185320#diff-9f11c8f2cb1f734f7c0c1071aba491a5 @@ -1050,7 +1070,7 @@ private: //update mouse highlight (in case it was frozen above) event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)! - onMouseMovement(event); + onMouseMovement(event); } event.Skip(); //allow changing focus } @@ -1077,9 +1097,9 @@ private: } //slight deviation from Explorer: change cursor while dragging mouse! -> unify behavior with shift + direction keys - const ptrdiff_t rowFrom = activeSelection_->getStartRow(); - const ptrdiff_t rowTo = activeSelection_->getCurrentRow(); - const bool positive = activeSelection_->isPositiveSelect(); + const ptrdiff_t rowFrom = activeSelection_->getStartRow(); + 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()); @@ -1090,29 +1110,34 @@ private: if (mouseClick.GetEventType() == EVENT_GRID_MOUSE_RIGHT_DOWN) sendEventToParent(GridContextMenuEvent(mouseClick.mousePos_)); } - #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(); + 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 = [&] + { + if (0 <= row && row < rowCount && cpi.colType != ColumnType::none) + { + wxClientDC dc(this); + dc.SetFont(GetFont()); + return prov->getMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + } + return HoverArea::none; + }(); //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); + onMouseMovement(event); event.Skip(); //allow changing focus } @@ -1135,18 +1160,25 @@ 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 = [&] + { + if (0 <= row && row < rowCount && cpi.colType != ColumnType::none) + { + wxClientDC dc(this); + dc.SetFont(GetFont()); + return prov->getMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + } + return HoverArea::none; + }(); const std::wstring toolTip = [&] { - if (cpi.colType != ColumnType::none && 0 <= row && row < rowCount) - return prov->getToolTip(row, cpi.colType); + if (0 <= row && row < rowCount && cpi.colType != ColumnType::none) + return prov->getToolTip(row, cpi.colType, rowHover); return std::wstring(); }(); setToolTip(toolTip); //show even during mouse selection! @@ -1154,10 +1186,7 @@ private: if (activeSelection_) activeSelection_->evalMousePos(); //call on both mouse movement + timer event! else - { - const HoverArea rowHover = 0 <= row && row < rowCount ? prov->getRowMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth) : HoverArea::none; updateMouseHover({row, rowHover}); - } } event.Skip(); } @@ -66,17 +66,18 @@ struct GridSelectEvent : public wxEvent GridSelectEvent* Clone() const override { return new GridSelectEvent(*this); } const size_t rowFirst_; //selected range: [rowFirst_, rowLast_) - const size_t rowLast_; - const bool positive_; //"false" when clearing selection! + const size_t rowLast_; // + const bool positive_; //"false" when clearing selection! const std::optional<GridClickEvent> mouseClick_; //filled unless selection was performed via keyboard shortcuts }; struct GridLabelClickEvent : public wxEvent { - GridLabelClickEvent(wxEventType et, ColumnType colType) : wxEvent(0 /*winid*/, et), colType_(colType) {} + GridLabelClickEvent(wxEventType et, ColumnType colType, const wxPoint& mousePos) : wxEvent(0 /*winid*/, et), colType_(colType), mousePos_(mousePos) {} GridLabelClickEvent* Clone() const override { return new GridLabelClickEvent(*this); } const ColumnType colType_; //may be ColumnType::none + const wxPoint mousePos_; //client coordinates }; struct GridColumnResizeEvent : public wxEvent @@ -85,7 +86,7 @@ struct GridColumnResizeEvent : public wxEvent GridColumnResizeEvent* Clone() const override { return new GridColumnResizeEvent(*this); } const ColumnType colType_; - const int offset_; + const int offset_; }; struct GridContextMenuEvent : public wxEvent @@ -109,11 +110,11 @@ public: //cell area: virtual std::wstring getValue(size_t row, ColumnType colType) const = 0; - virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected); //default implementation + virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, HoverArea rowHover); //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 getMouseHover (wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) { return HoverArea::none; } + virtual std::wstring getToolTip (size_t row, ColumnType colType, HoverArea rowHover) { return std::wstring(); } //label area: virtual std::wstring getColumnLabel(ColumnType colType) const = 0; @@ -174,6 +175,7 @@ public: //----------------------------------------------------------------------------- void setColumnLabelHeight(int height); + int getColumnLabelHeight() const { return colLabelHeight_; } void showRowLabel(bool visible); enum ScrollBarStatus diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index b89abe6d..6931e82e 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -317,7 +317,7 @@ void zen::imageResourcesInit(const Zstring& zipPath) //throw FileError } -void zen::ImageResourcesCleanup() +void zen::imageResourcesCleanup() { assert(runningOnMainThread()); //wxWidgets is not thread-safe! assert(globalImageBuffer); diff --git a/wx+/image_resources.h b/wx+/image_resources.h index 2ac6f308..6993e6f3 100644 --- a/wx+/image_resources.h +++ b/wx+/image_resources.h @@ -15,7 +15,7 @@ namespace zen { //pass resources .zip file at application startup void imageResourcesInit(const Zstring& zipPath); //throw FileError -void ImageResourcesCleanup(); +void imageResourcesCleanup(); const wxImage& loadImage(const std::string& name, int maxWidth /*optional*/, int maxHeight /*optional*/); const wxImage& loadImage(const std::string& name, int maxSize = -1); diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp index ee682e19..541a787c 100644 --- a/wx+/popup_dlg.cpp +++ b/wx+/popup_dlg.cpp @@ -233,6 +233,8 @@ public: GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() Center(); //needs to be re-applied after a dialog size change! + Raise(); //[!] popup may be triggered by ffs_batch job running in the background! + if (m_buttonAccept->IsEnabled()) m_buttonAccept->SetFocus(); else if (m_buttonAccept2->IsEnabled()) diff --git a/zen/basic_math.h b/zen/basic_math.h index 26fb9e58..0f5191e3 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -10,7 +10,7 @@ #include <cassert> #include <algorithm> #include <cmath> - #include <numbers> +#include <numbers> #include "type_traits.h" diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h index ad5442fe..0cbb830e 100644 --- a/zen/legacy_compiler.h +++ b/zen/legacy_compiler.h @@ -7,7 +7,7 @@ #ifndef LEGACY_COMPILER_H_839567308565656789 #define LEGACY_COMPILER_H_839567308565656789 -#include <version> +#include <version> //contains all __cpp_lib_<feature> macros /* C++ standard conformance: https://en.cppreference.com/w/cpp/feature_test diff --git a/zen/string_tools.h b/zen/string_tools.h index fc715961..83d87244 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -83,7 +83,7 @@ template <class Num, class S> Num stringTo(const S& str); std::pair<char, char> hexify (unsigned char c, bool upperCase = true); char unhexify(char high, char low); -std::string formatAsHexString(const std::string& blob); //bytes -> (human-readable) hex string +std::string formatAsHexString(const std::string_view& blob); //bytes -> (human-readable) hex string template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using std::snprintf() @@ -859,7 +859,7 @@ char unhexify(char high, char low) inline -std::string formatAsHexString(const std::string& blob) +std::string formatAsHexString(const std::string_view& blob) { std::string output; for (const char c : blob) diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp index da5cc61f..475e1c6c 100644 --- a/zen/sys_info.cpp +++ b/zen/sys_info.cpp @@ -63,9 +63,12 @@ ComputerModel zen::getComputerModel() //throw FileError cm.vendor = tryGetInfo("/sys/devices/virtual/dmi/id/sys_vendor"); // //clean up: + cm.model = beforeFirst(cm.model , L'\u00ff', IfNotFoundReturn::all); //fix broken BIOS entries: + cm.vendor = beforeFirst(cm.vendor, L'\u00ff', IfNotFoundReturn::all); //0xff can be considered 0 + for (const char* dummyModel : { - "To Be Filled By O.E.M.", "Default string", "empty", "O.E.M", "OEM", "NA", + "To Be Filled By O.E.M.", "Default string", "$(DEFAULT_STRING)", "Undefined", "empty", "O.E.M", "OEM", "NA", "System Product Name", "Please change product name", "INVALID", }) if (equalAsciiNoCase(cm.model, dummyModel)) @@ -76,7 +79,7 @@ ComputerModel zen::getComputerModel() //throw FileError for (const char* dummyVendor : { - "To Be Filled By O.E.M.", "Default string", "empty", "O.E.M", "OEM", "NA", + "To Be Filled By O.E.M.", "Default string", "$(DEFAULT_STRING)", "Undefined", "empty", "O.E.M", "OEM", "NA", "System manufacturer", "OEM Manufacturer", }) if (equalAsciiNoCase(cm.vendor, dummyVendor)) |