diff --git a/.github/workflows/clang-tidy-review.yml b/.github/workflows/clang-tidy-review.yml index d8b0b989cbc18..e522ce518c9c1 100644 --- a/.github/workflows/clang-tidy-review.yml +++ b/.github/workflows/clang-tidy-review.yml @@ -1,8 +1,7 @@ name: clang-tidy-review on: - pull_request: - types: [opened, synchronize, reopened] + workflow_dispatch: jobs: clang-tidy: diff --git a/.github/workflows/linux-clang-compile-tests.yml b/.github/workflows/linux-clang-compile-tests.yml index 9345c21e6f6b1..f7c4afdb0aeb8 100644 --- a/.github/workflows/linux-clang-compile-tests.yml +++ b/.github/workflows/linux-clang-compile-tests.yml @@ -1,7 +1,6 @@ name: Linux Clang compilation and tests on: - pull_request: - types: [opened, synchronize, reopened] + workflow_dispatch: jobs: build: name: Linux Clang compilation and tests diff --git a/.github/workflows/linux-gcc-compile-tests.yml b/.github/workflows/linux-gcc-compile-tests.yml index 2d556eea2ae99..a435d6baaff08 100644 --- a/.github/workflows/linux-gcc-compile-tests.yml +++ b/.github/workflows/linux-gcc-compile-tests.yml @@ -1,7 +1,6 @@ name: Linux GCC compilation and tests on: - pull_request: - types: [opened, synchronize, reopened] + workflow_dispatch: jobs: build: name: Linux GCC compilation and tests diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 59ec83b32ce68..a064ee4bd8deb 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -1,7 +1,6 @@ name: SonarCloud analysis on: - pull_request: - types: [opened, synchronize, reopened] + workflow_dispatch: jobs: build: name: SonarCloud analysis diff --git a/.github/workflows/windows-build-and-test.yml b/.github/workflows/windows-build-and-test.yml index 03b9688497320..184f46d2fd5de 100644 --- a/.github/workflows/windows-build-and-test.yml +++ b/.github/workflows/windows-build-and-test.yml @@ -1,5 +1,10 @@ name: Windows Build and Test on: + workflow_dispatch: + # push: + # branches: + # - master + # - develop pull_request: types: [opened, synchronize, reopened] jobs: diff --git a/.gitignore b/.gitignore index 85a87ed91464e..8b46ffc793a25 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ t1.cfg ## Ignore Visual Studio Code config & environment files .vs/ -.vscode/ +# .vscode/ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. @@ -190,3 +190,7 @@ convert.exe *-w10startmenu.png *state-*.png theme.qrc + +.idea/ + +shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000000000..3f0c64ea209b8 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,14 @@ +{ + "configurations": [ + { + "name": "Linux", + "compileCommands": [ + "${workspaceFolder}/../build/win32-MSVC-x64/RelWithDebInfo/compile_commands.json" + ], + "intelliSenseMode": "linux-gcc-x64", + "cStandard": "c17", + "cppStandard": "c++17" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000000..f123a092fbd16 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,54 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "(RelWithDebInfo) Launch HiDriveNext", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/../build/win32-MSVC-x64/RelWithDebInfo/bin/ionos_hidrive_next.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + "name": "PATH", + "value": "C:/Craft64/bin;%PATH%" + }, + ], + }, + { + "name": "(RelWithDebInfo) Launch NextCloud", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/../build/win32-MSVC-x64/RelWithDebInfo/bin/nextcloud.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + "name": "PATH", + "value": "C:/Craft64/bin;%PATH%" + }, + ], + }, + { + "name": "(Release) Launch NextCloud", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/../build/win32-MSVC-x64/Release/bin/nextcloud.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + "name": "PATH", + "value": "C:/Craft64/bin;%PATH%" + }, + ], + }, + ] +} \ No newline at end of file diff --git a/.vscode/nc-desktop-snippets.code-snippets b/.vscode/nc-desktop-snippets.code-snippets new file mode 100644 index 0000000000000..920dea7cb70f6 --- /dev/null +++ b/.vscode/nc-desktop-snippets.code-snippets @@ -0,0 +1,30 @@ +{ + // Place your nc-desktop workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + // "Print to console": { + // "scope": "javascript,typescript", + // "prefix": "log", + // "body": [ + // "console.log('$1');", + // "$2" + // ], + // "description": "Log output to console" + // } + + "DebugLog": { + "scope": "cpp", + "prefix": "dlog", + "body": [ + "char buffer$1[256];", + "sprintf(buffer$1, \"$2\\n\", $3);", + "OutputDebugStringA(buffer$1);" + ], + "description": "Debug log output" + } + +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000..957d14f2294df --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "cmake.generator": "Ninja", + "cmake.configureSettings": { + "CMAKE_PREFIX_PATH": "C:/Craft64", + "BUILD_TESTING":"OFF", + }, + "cmake.buildDirectory": "${workspaceFolder}/../build/${buildKitTargetOs}-${buildKitVendor}-${buildKitTargetArch}/${buildType}", +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000000..153627d3103a2 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,37 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "clean RelWithDebInfo", + "type": "shell", + "command": "powershell", + "args": [ + "-NoProfile", + "-Command", + "Remove-Item -Recurse -Force ..\\build\\win32-MSVC-x64\\RelWithDebInfo" + ], + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "A task to clean the build directory using PowerShell" + }, + { + "label": "clean Release", + "type": "shell", + "command": "powershell", + "args": [ + "-NoProfile", + "-Command", + "Remove-Item -Recurse -Force ..\\build\\win32-MSVC-x64\\Release" + ], + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "A task to clean the build directory using PowerShell" + }, + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 17738c161729e..d90aec29e8846 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ cmake_policy(SET CMP0071 NEW) # Enable use of QtQuick compiler/generated code project(client) +add_compile_definitions(IONOS_BUILD) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(APPLE) set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING "Minimum OSX deployment version") endif() @@ -21,7 +23,7 @@ set(CMAKE_XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES) set(BIN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") -include(${CMAKE_SOURCE_DIR}/NEXTCLOUD.cmake) +include(${CMAKE_SOURCE_DIR}/IONOS.cmake) # CfAPI Shell Extensions set( CFAPI_SHELL_EXTENSIONS_LIB_NAME CfApiShellExtensions ) @@ -57,10 +59,6 @@ string(REPLACE "&" "&" APPLICATION_NAME_XML_ESCAPED "${APPLICATION_NAME}") string(REPLACE "<" "<" APPLICATION_NAME_XML_ESCAPED "${APPLICATION_NAME_XML_ESCAPED}") string(REPLACE ">" ">" APPLICATION_NAME_XML_ESCAPED "${APPLICATION_NAME_XML_ESCAPED}") -string(REPLACE "&" "&" APPLICATION_VENDOR_XML_ESCAPED "${APPLICATION_VENDOR}") -string(REPLACE "<" "<" APPLICATION_VENDOR_XML_ESCAPED "${APPLICATION_VENDOR_XML_ESCAPED}") -string(REPLACE ">" ">" APPLICATION_VENDOR_XML_ESCAPED "${APPLICATION_VENDOR_XML_ESCAPED}") - if (NOT DEFINED LINUX_PACKAGE_SHORTNAME) set(LINUX_PACKAGE_SHORTNAME "${APPLICATION_SHORTNAME}") endif() diff --git a/CPackOptions.cmake.in b/CPackOptions.cmake.in index 109af43d21514..ac7e214aec20b 100644 --- a/CPackOptions.cmake.in +++ b/CPackOptions.cmake.in @@ -10,7 +10,7 @@ endif(CPACK_GENERATOR MATCHES "NSIS") set( CMAKE_SOURCE_DIR @CMAKE_SOURCE_DIR@ ) set( CMAKE_BINARY_DIR @CMAKE_BINARY_DIR@ ) -include("${CMAKE_SOURCE_DIR}/NEXTCLOUD.cmake") +include("${CMAKE_SOURCE_DIR}/IONOS.cmake") set( CRASHREPORTER_EXECUTABLE @CRASHREPORTER_EXECUTABLE@) diff --git a/IONOS.cmake b/IONOS.cmake new file mode 100644 index 0000000000000..6914d2fe84b69 --- /dev/null +++ b/IONOS.cmake @@ -0,0 +1,83 @@ +set( APPLICATION_NAME "IONOS HiDrive Next" ) +set( APPLICATION_SHORTNAME "IONOSHiDriveNext" ) +set( APPLICATION_EXECUTABLE "IONOS_HiDrive_Next" ) +# set( APPLICATION_CONFIG_NAME "${APPLICATION_EXECUTABLE}" ) +set( APPLICATION_CONFIG_NAME "IONOS-HiDrive-Next" ) +set( APPLICATION_DOMAIN "ionos.com" ) +set( APPLICATION_VENDOR "IONOS SE" ) +set( APPLICATION_UPDATE_URL "https://customerupdates.nextcloud.com/client/" CACHE STRING "URL for updater" ) +set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" ) + +if(APPLE AND APPLICATION_NAME STREQUAL "IONOS HiDrive Next") + set( APPLICATION_ICON_NAME "ionos_hidrive_next-macOS" ) + message("Using macOS-specific application icon: ${APPLICATION_ICON_NAME}") +else() + set( APPLICATION_ICON_NAME "ionos_hidrive_next" ) +endif() + +set( APPLICATION_ICON_SET "SVG" ) +set( APPLICATION_SERVER_URL "https://storage.ionos.fr" CACHE STRING "URL for the server to use. If entered, the UI field will be pre-filled with it" ) +set( APPLICATION_SERVER_URL_ENFORCE ON ) # If set and APPLICATION_SERVER_URL is defined, the server can only connect to the pre-defined URL +set( APPLICATION_REV_DOMAIN "com.ionos.hidrivenext.desktopclient" ) +# set( APPLICATION_VIRTUALFILE_SUFFIX "nextcloud" CACHE STRING "Virtual file suffix (not including the .)") +set( APPLICATION_OCSP_STAPLING_ENABLED OFF ) +set( APPLICATION_FORBID_BAD_SSL OFF ) + +set( LINUX_PACKAGE_SHORTNAME "hidrivenext" ) +set( LINUX_APPLICATION_ID "${APPLICATION_REV_DOMAIN}.${LINUX_PACKAGE_SHORTNAME}") + +set( THEME_CLASS "NextcloudTheme" ) +set( WIN_SETUP_BITMAP_PATH "${CMAKE_SOURCE_DIR}/admin/win/nsi" ) + +set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-background.png" CACHE STRING "The MacOSX installer background image") + +# set( THEME_INCLUDE "${OEM_THEME_DIR}/mytheme.h" ) +# set( APPLICATION_LICENSE "${OEM_THEME_DIR}/license.txt ) + +option( WITH_CRASHREPORTER "Build crashreporter" OFF ) +#set( CRASHREPORTER_SUBMIT_URL "https://crash-reports.owncloud.com/submit" CACHE STRING "URL for crash reporter" ) +#set( CRASHREPORTER_ICON ":/owncloud-icon.png" ) + +## Updater options +option( BUILD_UPDATER "Build updater" ON ) + +option( WITH_PROVIDERS "Build with providers list" ON ) + +option( ENFORCE_VIRTUAL_FILES_SYNC_FOLDER "Enforce use of virtual files sync folder when available" OFF ) + +option(ENFORCE_SINGLE_ACCOUNT "Enforce use of a single account in desktop client" OFF) + +option( DO_NOT_USE_PROXY "Do not use system wide proxy, instead always do a direct connection to server" OFF ) + +## Theming options +set(NEXTCLOUD_BACKGROUND_COLOR "#0082c9" CACHE STRING "Default Nextcloud background color") +set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR ${NEXTCLOUD_BACKGROUND_COLOR} CACHE STRING "Hex color of the wizard header background") +set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#000000" CACHE STRING "Hex color of the text in the wizard header") +option( APPLICATION_WIZARD_USE_CUSTOM_LOGO "Use the logo from ':/client/theme/colored/wizard_logo.(png|svg)' else the default application icon is used" ON ) + + +# +## Windows Shell Extensions & MSI - IMPORTANT: Generate new GUIDs for custom builds with "guidgen" or "uuidgen" +# +if(WIN32) + # Context Menu + set( WIN_SHELLEXT_CONTEXT_MENU_GUID "{6B16FF7B-F242-4CE3-8FB9-F06EF127E0DC}" ) + + # Overlays + set( WIN_SHELLEXT_OVERLAY_GUID_ERROR "{243D887B-9F74-41DD-BACA-BC5501AF10AC}" ) + set( WIN_SHELLEXT_OVERLAY_GUID_OK "{2D88D499-3272-4A76-84BF-D252254B40D6}" ) + set( WIN_SHELLEXT_OVERLAY_GUID_OK_SHARED "{7BEF6B56-5B5B-4284-A70C-56D62254C97A}" ) + set( WIN_SHELLEXT_OVERLAY_GUID_SYNC "{5F2F493D-A683-426F-925E-4CA25F17C4A9}" ) + set( WIN_SHELLEXT_OVERLAY_GUID_WARNING "{7F256BB6-29D2-4E40-A6C4-E5E756E64C82}" ) + + # MSI Upgrade Code (without brackets) + set( WIN_MSI_UPGRADE_CODE "6C9E5670-E8A9-4BBD-9BDF-D003794AC177" ) + + # Windows build options + option( BUILD_WIN_MSI "Build MSI scripts and helper DLL" OFF ) + option( BUILD_WIN_TOOLS "Build Win32 migration tools" OFF ) +endif() + +if (APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 11.0) + option( BUILD_FILE_PROVIDER_MODULE "Build the macOS virtual files File Provider module" OFF ) +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000..eda7bae4c9726 --- /dev/null +++ b/LICENSE @@ -0,0 +1,73 @@ +Apache License, Version 2.0 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright 2022-2024 chinchilla + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/NEXTCLOUD~~.cmake b/NEXTCLOUD~~.cmake new file mode 100644 index 0000000000000..ab93325d011ba --- /dev/null +++ b/NEXTCLOUD~~.cmake @@ -0,0 +1,82 @@ +set( APPLICATION_NAME "HiDrvie Next" ) +set( APPLICATION_SHORTNAME "Easystorage" ) +set( APPLICATION_EXECUTABLE "hidrivenext" ) +set( APPLICATION_CONFIG_NAME "${APPLICATION_EXECUTABLE}" ) +set( APPLICATION_DOMAIN "nextcloud.com" ) +set( APPLICATION_VENDOR "IONOS Group SE" ) +set( APPLICATION_UPDATE_URL "https://updates.nextcloud.org/client/" CACHE STRING "URL for updater" ) +set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" ) + +if(APPLE AND APPLICATION_NAME STREQUAL "Nextcloud" AND EXISTS "${CMAKE_SOURCE_DIR}/theme/colored/Nextcloud-macOS-icon.svg") + set( APPLICATION_ICON_NAME "Nextcloud-macOS" ) + message("Using macOS-specific application icon: ${APPLICATION_ICON_NAME}") +else() + set( APPLICATION_ICON_NAME "${APPLICATION_SHORTNAME}" ) +endif() + +set( APPLICATION_ICON_SET "SVG" ) +set( APPLICATION_SERVER_URL "https://easy-qa-1.nextcloud-ionos.com" CACHE STRING "URL for the server to use. If entered, the UI field will be pre-filled with it" ) +set( APPLICATION_SERVER_URL_ENFORCE ON ) # If set and APPLICATION_SERVER_URL is defined, the server can only connect to the pre-defined URL +set( APPLICATION_REV_DOMAIN "com.nextcloud.desktopclient" ) +# set( APPLICATION_VIRTUALFILE_SUFFIX "nextcloud" CACHE STRING "Virtual file suffix (not including the .)") +set( APPLICATION_OCSP_STAPLING_ENABLED OFF ) +set( APPLICATION_FORBID_BAD_SSL OFF ) + +set( LINUX_PACKAGE_SHORTNAME "easystorage" ) +set( LINUX_APPLICATION_ID "${APPLICATION_REV_DOMAIN}.${LINUX_PACKAGE_SHORTNAME}") + +set( THEME_CLASS "NextcloudTheme" ) +set( WIN_SETUP_BITMAP_PATH "${CMAKE_SOURCE_DIR}/admin/win/nsi" ) + +set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-background.png" CACHE STRING "The MacOSX installer background image") + +# set( THEME_INCLUDE "${OEM_THEME_DIR}/mytheme.h" ) +# set( APPLICATION_LICENSE "${OEM_THEME_DIR}/license.txt ) + +option( WITH_CRASHREPORTER "Build crashreporter" OFF ) +#set( CRASHREPORTER_SUBMIT_URL "https://crash-reports.owncloud.com/submit" CACHE STRING "URL for crash reporter" ) +#set( CRASHREPORTER_ICON ":/owncloud-icon.png" ) + +## Updater options +option( BUILD_UPDATER "Build updater" ON ) + +option( WITH_PROVIDERS "Build with providers list" ON ) + +option( ENFORCE_VIRTUAL_FILES_SYNC_FOLDER "Enforce use of virtual files sync folder when available" OFF ) + +option(ENFORCE_SINGLE_ACCOUNT "Enforce use of a single account in desktop client" OFF) + +option( DO_NOT_USE_PROXY "Do not use system wide proxy, instead always do a direct connection to server" OFF ) + +## Theming options +set(NEXTCLOUD_BACKGROUND_COLOR "#0082c9" CACHE STRING "Default Nextcloud background color") +set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR ${NEXTCLOUD_BACKGROUND_COLOR} CACHE STRING "Hex color of the wizard header background") +set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#000000" CACHE STRING "Hex color of the text in the wizard header") +option( APPLICATION_WIZARD_USE_CUSTOM_LOGO "Use the logo from ':/client/theme/colored/wizard_logo.(png|svg)' else the default application icon is used" ON ) + + +# +## Windows Shell Extensions & MSI - IMPORTANT: Generate new GUIDs for custom builds with "guidgen" or "uuidgen" +# +if(WIN32) + # Context Menu + set( WIN_SHELLEXT_CONTEXT_MENU_GUID "{BC6988AB-ACE2-4B81-84DC-DC34F9B24401}" ) + + # Overlays + set( WIN_SHELLEXT_OVERLAY_GUID_ERROR "{E0342B74-7593-4C70-9D61-22F294AAFE05}" ) + set( WIN_SHELLEXT_OVERLAY_GUID_OK "{E1094E94-BE93-4EA2-9639-8475C68F3886}" ) + set( WIN_SHELLEXT_OVERLAY_GUID_OK_SHARED "{E243AD85-F71B-496B-B17E-B8091CBE93D2}" ) + set( WIN_SHELLEXT_OVERLAY_GUID_SYNC "{E3D6DB20-1D83-4829-B5C9-941B31C0C35A}" ) + set( WIN_SHELLEXT_OVERLAY_GUID_WARNING "{E4977F33-F93A-4A0A-9D3C-83DEA0EE8483}" ) + + # MSI Upgrade Code (without brackets) + set( WIN_MSI_UPGRADE_CODE "FD2FCCA9-BB8F-4485-8F70-A0621B84A7F4" ) + + # Windows build options + option( BUILD_WIN_MSI "Build MSI scripts and helper DLL" OFF ) + option( BUILD_WIN_TOOLS "Build Win32 migration tools" OFF ) +endif() + +if (APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 11.0) + option( BUILD_FILE_PROVIDER_MODULE "Build the macOS virtual files File Provider module" OFF ) +endif() diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000000000..59ff260650176 --- /dev/null +++ b/NOTICE @@ -0,0 +1,18 @@ +Nextcloud Desktop - Ionos HiDrive Next + +This project includes software developed by chinchilla, available at: +[https://github.com/564398053/Qt-GoogleAnalytics4]. + +Bundled Components: + Qt-GoogleAnalytics4 + ga4/ganalytics.cpp + ga4/ganalytics_worker.cpp + Copyright 2022 chinchilla + Licensed under the Apache License, Version 2.0. + You may obtain a copy of the license at: + [http://www.apache.org/licenses/LICENSE-2.0]. + + +Important Note: +The rest of this project is not licensed under the Apache License, Version 2.0. It remains under the original license as specified in the original repository: +[https://github.com/IONOS-Productivity/nc-desktop]. \ No newline at end of file diff --git a/NextcloudCPack.cmake b/NextcloudCPack.cmake index 0682090c06d4c..96bc665a274f3 100644 --- a/NextcloudCPack.cmake +++ b/NextcloudCPack.cmake @@ -1,8 +1,8 @@ include( InstallRequiredSystemLibraries ) -set( CPACK_PACKAGE_CONTACT "Dominik Schmidt " ) +# set( CPACK_PACKAGE_CONTACT "Dominik Schmidt " ) -include("${CMAKE_SOURCE_DIR}/NEXTCLOUD.cmake") +include("${CMAKE_SOURCE_DIR}/IONOS.cmake") include( VERSION.cmake ) set( CPACK_PACKAGE_VERSION_MAJOR ${MIRALL_VERSION_MAJOR} ) diff --git a/VERSION.cmake b/VERSION.cmake index b36d622d7191e..e0aa2870d5f33 100644 --- a/VERSION.cmake +++ b/VERSION.cmake @@ -1,7 +1,7 @@ set( MIRALL_VERSION_MAJOR 3 ) set( MIRALL_VERSION_MINOR 13 ) set( MIRALL_VERSION_PATCH 4 ) -set( MIRALL_VERSION_YEAR 2024 ) +set( MIRALL_VERSION_YEAR 2025 ) set( MIRALL_SOVERSION 0 ) # Minimum supported server version according to https://docs.nextcloud.com/server/latest/admin_manual/release_schedule.html diff --git a/admin/osx/installer-background.png b/admin/osx/installer-background.png index ca273880b37aa..7833f187dcaa8 100644 Binary files a/admin/osx/installer-background.png and b/admin/osx/installer-background.png differ diff --git a/admin/osx/installer-background.svg b/admin/osx/installer-background.svg index 73af25b6d05d6..6a25f5f66f099 100644 --- a/admin/osx/installer-background.svg +++ b/admin/osx/installer-background.svg @@ -1,2 +1,35 @@ - + diff --git a/admin/osx/installer-background_2x.png b/admin/osx/installer-background_2x.png index 0117c4974989a..210f1b91845e5 100644 Binary files a/admin/osx/installer-background_2x.png and b/admin/osx/installer-background_2x.png differ diff --git a/admin/osx/ionos_macmaker/start.sh b/admin/osx/ionos_macmaker/start.sh new file mode 100755 index 0000000000000..09a6fb896f469 --- /dev/null +++ b/admin/osx/ionos_macmaker/start.sh @@ -0,0 +1,181 @@ +#!/bin/bash + +recursive_sign(){ + local path="$1" + local extension="${path##*.}" + if [[ "$extension" == "dylib" || "$extension" == "framework" || "$extension" == "appex" ]]; then + echo "Signing directory: $path" + codesign -s "$2" --force --preserve-metadata=entitlements --verbose=4 --deep --options=runtime --timestamp "${path}" + fi +} + +export -f recursive_sign + +sign_folder_content(){ + local folder="$1" + local identity="$2" + local entitlements="$3" + codesign -s "$identity" --force $entitlements --verbose=4 --deep --options=runtime --timestamp "${folder}" +} + +export -f sign_folder_content + +# This script is used to build the Mac OS X version of the IONOS client. +set -xe + +# Parse the command line arguments +while getopts "b:s:ci" opt; do + case ${opt} in + b )BUILD_DIR=$OPTARG;; + s )CODE_SIGN_IDENTITY=$OPTARG ;; + c )CLEAN_REBUILD=true ;; + i )PACKAGE_INSTALLER=true ;; + \? ) + echo "Usage: start.sh [-b ] [-s ] [-c] [-i]" + exit 1 + ;; + esac +done + +# Set the deployment target +export MACOSX_DEPLOYMENT_TARGET=10.15 + +# Some variables +PRODUCT_NAME="IONOS HiDrive Next" +REPO_ROOT_DIR="../../.." +CRAFT_DIR=~/Craft64 +PRODUCT_DIR=$BUILD_DIR/product +BUILD_UPDATER=true +TEAM_IDENTIFIER="5TDLCVD243" + +# Check if the client is running and kill it +# This is necessary to avoid issues with replacement of the bundle file +if pgrep -x "$PRODUCT_NAME" >/dev/null; then + killall "$PRODUCT_NAME" +fi + +# Check if BUILD_DIR is set, so we don't accidentally delete the whole filesystem +if [ -z "$BUILD_DIR" ]; then + echo "Build dir not set. Add -b to the command." + exit 0 +fi + +# Check if BUILD_DIR exists. If not, create it. If so, clear it. +if [ ! -d $BUILD_DIR ]; then + mkdir -p $BUILD_DIR +else + if [ $CLEAN_REBUILD = true ]; then + rm -rf $BUILD_DIR/* + fi +fi + +# Check if Craft dir exists, if not exit +if [ ! -d $CRAFT_DIR ]; then + echo "Craft dir not found. Exiting." + exit 1 +fi + +# Load Sparkle +SPARKLE_DIR=$BUILD_DIR/sparkle +SPARKLE_DOWNLOAD_URI="https://github.com/sparkle-project/Sparkle/releases/download/1.27.3/Sparkle-1.27.3.tar.xz" + +if [ "$CLEAN_REBUILD" == "true" ] && [ "$BUILD_UPDATER" == "true" ]; then + mkdir -p $SPARKLE_DIR + wget $SPARKLE_DOWNLOAD_URI -O $SPARKLE_DIR/Sparkle.tar.xz + tar -xvf $SPARKLE_DIR/Sparkle.tar.xz -C $SPARKLE_DIR + + # Sign Sparkle + if [ -n "$CODE_SIGN_IDENTITY" ]; then + SPARKLE_FRAMEWORK_DIR=$SPARKLE_DIR/Sparkle.framework + find "$SPARKLE_FRAMEWORK_DIR/Resources/Autoupdate.app/Contents/MacOS" -mindepth 1 -print0 | xargs -0 -I {} bash -c 'sign_folder_content "$@" "$CODE_SIGN_IDENTITY"' _ {} "$CODE_SIGN_IDENTITY" + codesign -s "$CODE_SIGN_IDENTITY" --force --preserve-metadata=entitlements --verbose=4 --deep --options=runtime --timestamp "$SPARKLE_FRAMEWORK_DIR/Sparkle" + fi +fi + +# Build the client +cmake -S $REPO_ROOT_DIR/ -B $BUILD_DIR \ + -DQT_TRANSLATIONS_DIR=$REPO_ROOT_DIR/translations \ + -DCMAKE_INSTALL_PREFIX=$PRODUCT_DIR \ + -DBUILD_TESTING=OFF \ + -DBUILD_UPDATER=$(if [ $BUILD_UPDATER == true ]; then echo "ON"; else echo "OFF"; fi) \ + -DMIRALL_VERSION_BUILD=`date +%Y%m%d` \ + -DMIRALL_VERSION_SUFFIX="stable" \ + -DBUILD_OWNCLOUD_OSX_BUNDLE=ON \ + -DCMAKE_OSX_ARCHITECTURES=x86_64 \ + -DBUILD_FILE_PROVIDER_MODULE=ON \ + -DCMAKE_PREFIX_PATH=$CRAFT_DIR \ + -DSPARKLE_LIBRARY=$SPARKLE_DIR/Sparkle.framework \ + -DSOCKETAPI_TEAM_IDENTIFIER_PREFIX="$TEAM_IDENTIFIER." \ + -DARG_SIDEBAR_ICONS=ON \ + +make install -C $BUILD_DIR -j4 + +# --------------------------------------------------- +# Sign the client +# CODE_SIGN_IDENTITY="Developer ID Application: IONOS SE (5TDLCVD243)" + +# Check if CODE_SIGN_IDENTITY is set, if not exit +if [ -z "$CODE_SIGN_IDENTITY" ]; then + echo "Code sign identity not set. Exiting." + open $PRODUCT_DIR + exit 0 +fi + +PRODUCT_PATH=$PRODUCT_DIR/$PRODUCT_NAME.app + +CLIENT_CONTENTS_DIR=$PRODUCT_PATH/Contents +CLIENT_FRAMEWORKS_DIR=$CLIENT_CONTENTS_DIR/Frameworks +CLIENT_PLUGINS_DIR=$CLIENT_CONTENTS_DIR/PlugIns +CLIENT_RESOURCES_DIR=$CLIENT_CONTENTS_DIR/Resources + +find "$CLIENT_FRAMEWORKS_DIR" -print0 | xargs -0 -I {} bash -c 'recursive_sign "$@" "$CODE_SIGN_IDENTITY"' _ {} "$CODE_SIGN_IDENTITY" +find "$CLIENT_PLUGINS_DIR" -print0 | xargs -0 -I {} bash -c 'recursive_sign "$@" "$CODE_SIGN_IDENTITY"' _ {} "$CODE_SIGN_IDENTITY" +find "$CLIENT_RESOURCES_DIR" -print0 | xargs -0 -I {} bash -c 'recursive_sign "$@" "$CODE_SIGN_IDENTITY"' _ {} "$CODE_SIGN_IDENTITY" + +codesign -s "$CODE_SIGN_IDENTITY" --force --preserve-metadata=entitlements --verbose=4 --deep --options=runtime --timestamp "$PRODUCT_PATH" + + +# Sign the client +find "$CLIENT_CONTENTS_DIR/MacOS" -mindepth 1 -print0 | xargs -0 -I {} bash -c 'sign_folder_content "$@" "$CODE_SIGN_IDENTITY" "$entitlements" ' _ {} "$CODE_SIGN_IDENTITY" "--preserve-metadata=entitlements" + +# Validate that the key used for signing the binary matches the expected TeamIdentifier +# needed to pass the SocketApi through the sandbox for communication with virtual file system +if ! codesign -dv "$PRODUCT_PATH" 2>&1 | grep -q "TeamIdentifier=$TEAM_IDENTIFIER"; then + echo "TeamIdentifier does not match. Exiting." + exit 0 +fi + +# --------------------------------------------------- +# Installer + +# Build the installer, if enabled +if [ -z "$PACKAGE_INSTALLER" ]; then + echo "Installer packaging not enabled. Exiting." + open $PRODUCT_DIR + exit 0 +fi + +# package +$BUILD_DIR/admin/osx/create_mac.sh "$PRODUCT_DIR" "$BUILD_DIR" 'Developer ID Installer: IONOS SE (5TDLCVD243)' + +# notariaze +# Extract package filename from filesystem per .pkg extension +PACKAGE_FILENAME=$(ls $PRODUCT_DIR/*.pkg) + +# catch the output of the notarytool command +OUTPUT=$(xcrun notarytool submit --wait $PACKAGE_FILENAME\ + --keychain-profile "IONOS SE HiDrive Next") + +SUBMISSION_STATUS=$(echo $OUTPUT | grep -o 'status: [^ ]*' | cut -d ' ' -f 2) + +# Check if the notarization was successful +if [ $SUBMISSION_STATUS != "Accepted" ]; then + echo "Notarization failed. Exiting." + exit 1 +fi + +# staple +xcrun stapler staple $PACKAGE_FILENAME +xcrun stapler validate $PACKAGE_FILENAME + +open $PRODUCT_DIR diff --git a/admin/win/msi/CMakeLists.txt b/admin/win/msi/CMakeLists.txt index 933037dac1151..297336ae8d324 100644 --- a/admin/win/msi/CMakeLists.txt +++ b/admin/win/msi/CMakeLists.txt @@ -26,7 +26,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/collect-transform.xsl ${CMAKE_CURRENT_BINARY_DIR}/make-msi.bat Platform.wxi - Nextcloud.wxs + Ionos.wxs ${CMAKE_CURRENT_BINARY_DIR}/RegistryCleanup.vbs RegistryCleanupCustomAction.wxs gui/banner.bmp diff --git a/admin/win/msi/Ionos.wxs b/admin/win/msi/Ionos.wxs new file mode 100644 index 0000000000000..aed4d0b74820e --- /dev/null +++ b/admin/win/msi/Ionos.wxs @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + + (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + + (SCHEDULE_REBOOT=1) OR NOT (UILevel=2) + + + + $(var.AppName) + $(var.AppIcon) + $(var.AppHelpLink) + $(var.AppInfoLink) + + + + + + + + + + + + + + + + + + + + + + + + 1 + + 1 + + + WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed + + Removing previous installation + Trying to terminate application process of previous installation + Removing sync folders from Explorer's Navigation Pane + + + + + + + NOT (LAUNCH=0) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (NO_SHELL_EXTENSIONS=1) + + + + + (NO_START_MENU_SHORTCUTS=1) + + + + + (NO_DESKTOP_SHORTCUT=1) + + + + diff --git a/admin/win/msi/Nextcloud~~.wxs b/admin/win/msi/Nextcloud~~.wxs new file mode 100644 index 0000000000000..c14cbe030699b --- /dev/null +++ b/admin/win/msi/Nextcloud~~.wxs @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSIS_UNINSTALLEXE AND NOT Installed + + + (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + + (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + + (SCHEDULE_REBOOT=1) OR NOT (UILevel=2) + + + + $(var.AppName) + $(var.AppIcon) + $(var.AppHelpLink) + $(var.AppInfoLink) + + + + + + + + + + + + + + + + + + + + + + + + 1 + + 1 + + + WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed + + Removing previous installation + Trying to terminate application process of previous installation + Removing sync folders from Explorer's Navigation Pane + + + + + + + NOT (LAUNCH=0) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (NO_SHELL_EXTENSIONS=1) + + + + + (NO_START_MENU_SHORTCUTS=1) + + + + + (NO_DESKTOP_SHORTCUT=1) + + + + diff --git a/admin/win/msi/gui/banner.bmp b/admin/win/msi/gui/banner.bmp index 7eba2ce1a7700..5270a99dea084 100644 Binary files a/admin/win/msi/gui/banner.bmp and b/admin/win/msi/gui/banner.bmp differ diff --git a/admin/win/msi/gui/dialog.bmp b/admin/win/msi/gui/dialog.bmp index f548941594d77..b870b472556df 100644 Binary files a/admin/win/msi/gui/dialog.bmp and b/admin/win/msi/gui/dialog.bmp differ diff --git a/admin/win/msi/make-msi.bat.in b/admin/win/msi/make-msi.bat.in index eb14735327fdf..1b19cea7cc855 100644 --- a/admin/win/msi/make-msi.bat.in +++ b/admin/win/msi/make-msi.bat.in @@ -17,10 +17,10 @@ Rem Generate collect.wxs if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% Rem Compile en-US (https://www.firegiant.com/wix/tutorial/transforms/morphing-installers/) -"%WIX%\bin\candle.exe" -dcodepage=1252 -dPlatform=%BuildArch% -arch %BuildArch% -dHarvestAppDir="%HarvestAppDir%" -ext WixUtilExtension NCMsiHelper.wxs WinShellExt.wxs collect.wxs Nextcloud.wxs RegistryCleanupCustomAction.wxs +"%WIX%\bin\candle.exe" -dcodepage=1252 -dPlatform=%BuildArch% -arch %BuildArch% -dHarvestAppDir="%HarvestAppDir%" -ext WixUtilExtension NCMsiHelper.wxs WinShellExt.wxs collect.wxs Ionos.wxs RegistryCleanupCustomAction.wxs if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% Rem Link MSI package -"%WIX%\bin\light.exe" -sw1076 -ext WixUIExtension -ext WixUtilExtension -cultures:en-us NCMsiHelper.wixobj WinShellExt.wixobj collect.wixobj Nextcloud.wixobj RegistryCleanupCustomAction.wixobj -out "@MSI_INSTALLER_FILENAME@" +"%WIX%\bin\light.exe" -sw1076 -ext WixUIExtension -ext WixUtilExtension -cultures:en-us NCMsiHelper.wixobj WinShellExt.wixobj collect.wixobj Ionos.wixobj RegistryCleanupCustomAction.wixobj -out "@MSI_INSTALLER_FILENAME@" exit %ERRORLEVEL% diff --git a/cmake/modules/MacOSXBundleInfo.plist.in b/cmake/modules/MacOSXBundleInfo.plist.in index 0dffc284ad2b8..ba9916d942c22 100644 --- a/cmake/modules/MacOSXBundleInfo.plist.in +++ b/cmake/modules/MacOSXBundleInfo.plist.in @@ -29,17 +29,15 @@ CFBundleShortVersionString @MIRALL_VERSION_STRING@ NSHumanReadableCopyright - (C) 2014-2022 @APPLICATION_VENDOR_XML_ESCAPED@ + (C) 2014-2022 @APPLICATION_VENDOR@ NSSupportsAutomaticGraphicsSwitching NSRequiresAquaSystemAppearance SUShowReleaseNotes - SUPublicDSAKeyFile - dsa_pub.pem SUPublicEDKey - c3RcfDWDayvsYSZW8FhZN1UOJhvPVN30zleb4zOqbtU= + FQ8Dq6AiSDDv4XpnyJ3b6mQBFYLPKgj9ziEg/+VNGHg= UTExportedTypeDeclarations diff --git a/doc/ADR/20241007_TrackingWithGA4.md b/doc/ADR/20241007_TrackingWithGA4.md new file mode 100644 index 0000000000000..8422656e3ddc1 --- /dev/null +++ b/doc/ADR/20241007_TrackingWithGA4.md @@ -0,0 +1,32 @@ +# Use Tracking with Google Analytics 4 + +## Status + +proposed + +## Context + +The web front end already tracks user interactions with GA4. To track interactions in the client software as well, an implementation with GA should also be done here. + +## Decision + +Since the use of GA4 in desktop software and the use of the pure API are not natively supported, a reverse-engineered solution had to be used (see https://ga4mp.dev/#/), similar to the use of the GA4 Measurement Protocol, which, however, does not provide a complete replacement. + +This approach implements GA4 tracking based on HTTP POST requests. + +## Implementation Details + +The implementation consists of three classes: + +* GAnalyticsWorker +* GAnalytics +* DataCollectionWrapper + +The DataCollectionWrapper is the outermost layer and is used by the application to track actions and events. The DataCollectionWrapper provides enums for various tracking pages (areas of the application, such as GeneralSettings or UserSettings) and tracking elements (specific buttons, checkboxes, or similar items). +GAnalytics acts as an intermediary between the outer layer (DataCollectionWrapper) and the communication logic. Various variables are also set here. When a tracking call is made, it is forwarded to the GAnalyticsWorker, where it is queued. + +The GAnalyticsWorker contains a queue, a message loop, and a QNetworkAccessManager. At fixed intervals, the queue is checked for tracking calls, which are then sent to the GA4 interface. If multiple calls are present, the connection is kept alive until all tracking calls have been sent. + +## Consequences + +This approach allows client-side tracking using GA4, bridging the gap between web and desktop tracking. The modular design simplifies maintenance by separating tracking logic from communication handling. Usage is straightforward; the application simply calls the DataCollectionWrapper at points where tracking should occur, ensuring that actions and events are recorded seamlessly. However, relying on a reverse-engineered solution introduces risks of future incompatibility with GA4 updates. Managing a queue and network connection adds complexity, and there might be latency when batching tracking calls. Overall, the solution balances functionality with maintainability but carries some technical risks. diff --git a/doc/ADR/20250212_UseCostomizationService.md b/doc/ADR/20250212_UseCostomizationService.md new file mode 100644 index 0000000000000..2131d2188ea8f --- /dev/null +++ b/doc/ADR/20250212_UseCostomizationService.md @@ -0,0 +1,69 @@ +# Use Customization Service + +## Status + +accepted + +## Context + +Nextcloud (NC) offers a customization service. The service is a Nextcloud white-labeling offering. It allows customers and their forks to be redesigned according to their own specifications. There are two levels. + +### Brander + +Here, the icons, names, and technical aspects are being adjusted to release a simply redesigned, color-enhanced Nextcloud. + +### Actual Customization Service + +This will integrate deeper changes into the branded client. This could be anything, in principle. + +For this to work we need to create Pull-Requests against the branches of the nextcloud repo. These pull requests can not depend on the same code line. + +More information can be read [here](https://portal.nextcloud.com/article/Branding/Customization-Service) + +### Workflow on the Repo + +A PR always has a source branch and a target branch. The target branch must be `master` in our case. Source branches can follow our established workflow. However, there are some conditions: + +- Concurrent work on different features must be possible. +- Local builds must be possible for testing purposes. +- A nightly/onPush build might be desirable. +- It must be ensured that everyone has an up-to-date status with all PRs. + +There are two possible Solutions: + +1. We use one Pull-Request containing all changes. +2. We use multiple Pull requests containing changes grouped by files. + +## Decision + +We will use only one PR containing all our changes. + +At this point in time, it would be too much work to split all Changes into separate branches/PRs. + +## Implementation Details + +### Branching + +- We use the master branch only for pulling the updated nc/master branch. +- We have a separate develop branch to develop changes. +- We create a PR to nc/masetr from develop. + +## Consequences + +### Positive + +- We can use our existing git-workflow +- We have a simple repo-structure with only 2 really needed branches +- It is easy to build locally, because all changes are on one branch +- Less Branches to backport +- We do not need to sort every change to a separate PR + +### Negative + +- Merge conflicts can be complex +- The PR will contain all the changes, this could be overwhelming to check + +### Further Thoughts + +- We need to see how this works in practice +- This is not final \ No newline at end of file diff --git a/doc/ADR/_template.md b/doc/ADR/_template.md new file mode 100644 index 0000000000000..0f2aef85df5b5 --- /dev/null +++ b/doc/ADR/_template.md @@ -0,0 +1,30 @@ +# ADR template by Michael Nygard + +This is the template in [Documenting architecture decisions - Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). +You can use [adr-tools](https://github.com/npryce/adr-tools) for managing the ADR files. + +We extended this Template with ```Implementation Details``` (optional) + +In each ADR file, write these sections: + +# Title + +## Status + +What is the status, such as proposed, accepted, rejected, deprecated, superseded, etc.? + +## Context + +What is the issue that we're seeing that is motivating this decision or change? + +## Decision + +What is the change that we're proposing and/or doing? + +## Implementation Details + +(Optional) How was this Decision implemented. I.e. Uml Diagrams, Code etc. + +## Consequences + +What becomes easier or more difficult to do because of this change? diff --git a/fonts/OpenSans-Bold.ttf b/fonts/OpenSans-Bold.ttf new file mode 100644 index 0000000000000..efdd5e84a0397 Binary files /dev/null and b/fonts/OpenSans-Bold.ttf differ diff --git a/fonts/OpenSans-BoldItalic.ttf b/fonts/OpenSans-BoldItalic.ttf new file mode 100644 index 0000000000000..9bf9b4e97b657 Binary files /dev/null and b/fonts/OpenSans-BoldItalic.ttf differ diff --git a/fonts/OpenSans-ExtraBold.ttf b/fonts/OpenSans-ExtraBold.ttf new file mode 100644 index 0000000000000..67fcf0fb2af0c Binary files /dev/null and b/fonts/OpenSans-ExtraBold.ttf differ diff --git a/fonts/OpenSans-ExtraBoldItalic.ttf b/fonts/OpenSans-ExtraBoldItalic.ttf new file mode 100644 index 0000000000000..086722809c74d Binary files /dev/null and b/fonts/OpenSans-ExtraBoldItalic.ttf differ diff --git a/fonts/OpenSans-Italic.ttf b/fonts/OpenSans-Italic.ttf new file mode 100644 index 0000000000000..117856707b9b8 Binary files /dev/null and b/fonts/OpenSans-Italic.ttf differ diff --git a/fonts/OpenSans-Light.ttf b/fonts/OpenSans-Light.ttf new file mode 100644 index 0000000000000..6580d3a169e89 Binary files /dev/null and b/fonts/OpenSans-Light.ttf differ diff --git a/fonts/OpenSans-LightItalic.ttf b/fonts/OpenSans-LightItalic.ttf new file mode 100644 index 0000000000000..1e0c3319810ad Binary files /dev/null and b/fonts/OpenSans-LightItalic.ttf differ diff --git a/fonts/OpenSans-Regular.ttf b/fonts/OpenSans-Regular.ttf new file mode 100644 index 0000000000000..29bfd35a2bfdd Binary files /dev/null and b/fonts/OpenSans-Regular.ttf differ diff --git a/fonts/OpenSans-SemiBold.ttf b/fonts/OpenSans-SemiBold.ttf new file mode 100644 index 0000000000000..54e7059cf3635 Binary files /dev/null and b/fonts/OpenSans-SemiBold.ttf differ diff --git a/fonts/OpenSans-SemiBoldItalic.ttf b/fonts/OpenSans-SemiBoldItalic.ttf new file mode 100644 index 0000000000000..aebcf1421226d Binary files /dev/null and b/fonts/OpenSans-SemiBoldItalic.ttf differ diff --git a/resources.qrc b/resources.qrc index 7222e8c9ff0e7..2e1abe7196291 100644 --- a/resources.qrc +++ b/resources.qrc @@ -39,7 +39,9 @@ src/gui/tray/UnifiedSearchResultNothingFound.qml src/gui/tray/UnifiedSearchResultSectionItem.qml src/gui/tray/CustomButton.qml + src/gui/tray/SesCustomButton.qml src/gui/tray/NCButtonContents.qml + src/gui/tray/SesButtonContents.qml src/gui/tray/NCButtonBackground.qml src/gui/tray/TextButtonContents.qml src/gui/tray/ActivityItemContextMenu.qml @@ -53,6 +55,10 @@ src/gui/tray/NCToolTip.qml src/gui/tray/NCProgressBar.qml src/gui/tray/EnforcedPlainTextLabel.qml + src/gui/tray/TrayWindowHeaderBar.qml + src/gui/tray/TrayWindowAccountMenu.qml + src/gui/tray/HeaderLogo.qml + src/gui/tray/AccountMenuItem.qml theme/Style/Style.qml theme/Style/qmldir src/gui/filedetails/NCRadioButton.qml @@ -68,5 +74,7 @@ src/gui/macOS/ui/FileProviderSyncStatus.qml src/gui/macOS/ui/FileProviderStorageInfo.qml src/gui/macOS/ui/FileProviderFastEnumerationSettings.qml + src/gui/SesComponents/SesErrorBox.qml + src/gui/SesComponents/SesTrayHeader.qml diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift index bc66d57f4bc1d..d32464f017cc4 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift @@ -99,7 +99,7 @@ extension FileProviderExtension: NSFileProviderServicing { userId: newNcAccount.username, password: newNcAccount.password, urlBase: newNcAccount.serverUrl, - userAgent: "Nextcloud-macOS/FileProviderExt", + userAgent: "IONOS HiDrive Next/FileProviderExt", nextcloudVersion: 25, delegate: nil) // TODO: add delegate methods for self diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift index 1223982037416..2394866c71c72 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift @@ -37,7 +37,7 @@ import OSLog return LocalSocketClient(socketPath: socketPath.path, lineProcessor: lineProcessor) }() - let urlSessionIdentifier = "com.nextcloud.session.upload.fileproviderext" + let urlSessionIdentifier = "com.ionos.hidrivenext.session.upload.fileproviderext" let urlSessionMaximumConnectionsPerHost = 5 lazy var urlSession: URLSession = { let configuration = URLSessionConfiguration.background(withIdentifier: urlSessionIdentifier) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift index 76a0f00ce267a..1624d68907b11 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift @@ -18,7 +18,7 @@ import OSLog class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCListenerDelegate, ClientCommunicationProtocol { let listener = NSXPCListener.anonymous() - let serviceName = NSFileProviderServiceName("com.nextcloud.desktopclient.ClientCommunicationService") + let serviceName = NSFileProviderServiceName("com.ionos.hidrivenext.desktopclient.ClientCommunicationService") let fpExtension: FileProviderExtension init(fpExtension: FileProviderExtension) { diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/FPUIExtensionService.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/FPUIExtensionService.swift index 0c3e444a9abbc..446b5e9a64f7d 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/FPUIExtensionService.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/FPUIExtensionService.swift @@ -9,7 +9,7 @@ import FileProvider import NextcloudKit let fpUiExtensionServiceName = NSFileProviderServiceName( - "com.nextcloud.desktopclient.FPUIExtensionService" + "com.ionos.hidrivenext.desktopclient.FPUIExtensionService" ) @objc protocol FPUIExtensionService { diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/DocumentActionViewController.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/DocumentActionViewController.swift index 96363e962b124..64948f13bf1ff 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/DocumentActionViewController.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/DocumentActionViewController.swift @@ -36,7 +36,7 @@ class DocumentActionViewController: FPUIActionExtensionViewController { ) { Logger.actionViewController.info("Preparing action: \(actionIdentifier, privacy: .public)") - if actionIdentifier == "com.nextcloud.desktopclient.FileProviderUIExt.ShareAction" { + if actionIdentifier == "com.ionos.hidrivenext.desktopclient.FileProviderUIExt.ShareAction" { prepare(childViewController: ShareViewController(itemIdentifiers)) } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Info.plist b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Info.plist index 85f108b15fc96..e2a7f25d39a3b 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Info.plist +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Info.plist @@ -16,7 +16,7 @@ NSExtensionFileProviderActionActivationRule TRUEPREDICATE NSExtensionFileProviderActionIdentifier - com.nextcloud.desktopclient.FileProviderUIExt.ShareAction + com.ionos.hidrivenext.desktopclient.FileProviderUIExt.ShareAction NSExtensionFileProviderActionName Share options diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareTableViewDataSource.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareTableViewDataSource.swift index 3cca1d1a9df51..b72c1d77614e4 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareTableViewDataSource.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareTableViewDataSource.swift @@ -44,7 +44,10 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele user: account.username, userId: account.username, password: account.password, - urlBase: account.serverUrl + urlBase: account.serverUrl, + userAgent: "IONOS HiDrive Next/FileProviderUIExt", + nextcloudVersion: 25, + delegate: nil ) } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareViewController.xib b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareViewController.xib index 7a61de7033656..f64580297a7d3 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareViewController.xib +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareViewController.xib @@ -242,6 +242,8 @@ + diff --git a/shell_integration/windows/WinShellExt.wxs.in b/shell_integration/windows/WinShellExt.wxs.in index 6b770dfe7f727..1d2154200437a 100644 --- a/shell_integration/windows/WinShellExt.wxs.in +++ b/shell_integration/windows/WinShellExt.wxs.in @@ -42,11 +42,11 @@ There is a limit in Windows (oh wonder^^) so that only the first 15 extensions get invoked, this is why to use that dirty little trick to get ahead ;) See: https://docs.microsoft.com/en-us/windows/win32/shell/context-menu-handlers?redirectedfrom=MSDN#employing-the-verb-selection-model --> - - - - - + + + + + diff --git a/src/common/checksumcalculator.cpp b/src/common/checksumcalculator.cpp index ec9faa2de8c5a..410097d7ea095 100644 --- a/src/common/checksumcalculator.cpp +++ b/src/common/checksumcalculator.cpp @@ -26,7 +26,7 @@ constexpr qint64 bufSize = 500 * 1024; namespace OCC { -Q_LOGGING_CATEGORY(lcChecksumCalculator, "nextcloud.common.checksumcalculator", QtInfoMsg) +Q_LOGGING_CATEGORY(lcChecksumCalculator, "hidrivenext.common.checksumcalculator", QtInfoMsg) static QCryptographicHash::Algorithm algorithmTypeToQCryptoHashAlgorithm(ChecksumCalculator::AlgorithmType algorithmType) { diff --git a/src/common/checksums.cpp b/src/common/checksums.cpp index 5787f2d26dd09..37f54738325ba 100644 --- a/src/common/checksums.cpp +++ b/src/common/checksums.cpp @@ -87,7 +87,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcChecksums, "nextcloud.sync.checksums", QtInfoMsg) +Q_LOGGING_CATEGORY(lcChecksums, "hidrivenext.sync.checksums", QtInfoMsg) #define BUFSIZE qint64(500 * 1024) // 500 KiB diff --git a/src/common/filesystembase.cpp b/src/common/filesystembase.cpp index ef9ba1efd6778..c7c87fafb88ce 100644 --- a/src/common/filesystembase.cpp +++ b/src/common/filesystembase.cpp @@ -39,7 +39,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcFileSystem, "nextcloud.sync.filesystem", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFileSystem, "hidrivenext.sync.filesystem", QtInfoMsg) QString FileSystem::longWinPath(const QString &inpath) { diff --git a/src/common/ownsql.cpp b/src/common/ownsql.cpp index 7b4fbd225dcfc..f0e937b4ac58d 100644 --- a/src/common/ownsql.cpp +++ b/src/common/ownsql.cpp @@ -41,7 +41,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcSql, "nextcloud.sync.database.sql", QtInfoMsg) +Q_LOGGING_CATEGORY(lcSql, "hidrivenext.sync.database.sql", QtInfoMsg) SqlDatabase::SqlDatabase() = default; diff --git a/src/common/remotepermissions.cpp b/src/common/remotepermissions.cpp index c1e6ebcd7d691..57570ca56dfbc 100644 --- a/src/common/remotepermissions.cpp +++ b/src/common/remotepermissions.cpp @@ -24,7 +24,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcRemotePermissions, "nextcloud.sync.remotepermissions", QtInfoMsg) +Q_LOGGING_CATEGORY(lcRemotePermissions, "hidrivenext.sync.remotepermissions", QtInfoMsg) static const char letters[] = " WDNVCKRSMm"; diff --git a/src/common/shellextensionutils.cpp b/src/common/shellextensionutils.cpp index b2b59be80f663..ce25508c697df 100644 --- a/src/common/shellextensionutils.cpp +++ b/src/common/shellextensionutils.cpp @@ -4,7 +4,7 @@ namespace VfsShellExtensions { -Q_LOGGING_CATEGORY(lcShellExtensionUtils, "nextcloud.gui.shellextensionutils", QtInfoMsg) +Q_LOGGING_CATEGORY(lcShellExtensionUtils, "hidrivenext.gui.shellextensionutils", QtInfoMsg) QString VfsShellExtensions::serverNameForApplicationName(const QString &applicationName) { diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index bbdf63346bab7..f534438a8c3cd 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -44,7 +44,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcDb, "nextcloud.sync.database", QtInfoMsg) +Q_LOGGING_CATEGORY(lcDb, "hidrivenext.sync.database", QtInfoMsg) #define GET_FILE_RECORD_QUERY \ "SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," \ diff --git a/src/common/utility.cpp b/src/common/utility.cpp index b95d40db24ed3..f93a1a4efa055 100644 --- a/src/common/utility.cpp +++ b/src/common/utility.cpp @@ -59,7 +59,7 @@ constexpr qint64 terabytes = bytes * gigabytes; namespace OCC { -Q_LOGGING_CATEGORY(lcUtility, "nextcloud.sync.utility", QtInfoMsg) +Q_LOGGING_CATEGORY(lcUtility, "hidrivenext.sync.utility", QtInfoMsg) bool Utility::writeRandomFile(const QString &fname, int size) { @@ -179,8 +179,8 @@ QByteArray Utility::userAgentString() QByteArray Utility::friendlyUserAgentString() { - const auto pattern = QStringLiteral("%1 (Desktop Client - %2)"); - const auto userAgent = pattern.arg(QSysInfo::machineHostName(), platform()); + const auto pattern = QStringLiteral("%1 (%2 Desktop Client - %3)"); + const auto userAgent = pattern.arg(QSysInfo::machineHostName(), qApp->applicationName(), platform()); return userAgent.toUtf8(); } diff --git a/src/csync/csync.h b/src/csync/csync.h index 0a22571687f0c..3baf87587f46c 100644 --- a/src/csync/csync.h +++ b/src/csync/csync.h @@ -34,21 +34,18 @@ #include "std/c_private.h" #include "ocsynclib.h" -#include "config_csync.h" -#include "common/remotepermissions.h" - -#include -#include #include + #include #include +#include #include #include +#include +#include "common/remotepermissions.h" namespace OCC { -Q_DECLARE_LOGGING_CATEGORY(lcPermanentLog) - class SyncJournalFileRecord; namespace EncryptionStatusEnums { diff --git a/src/csync/csync_exclude.cpp b/src/csync/csync_exclude.cpp index 5c3ab723cedac..5206def1a8d64 100644 --- a/src/csync/csync_exclude.cpp +++ b/src/csync/csync_exclude.cpp @@ -151,7 +151,7 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QString &path, bool exclu if (bname.startsWith(QLatin1String(".owncloudsync.log"), Qt::CaseInsensitive)) { // ".owncloudsync.log*" return CSYNC_FILE_SILENTLY_EXCLUDED; } - if (bname.startsWith(QLatin1String(".nextcloudsync.log"), Qt::CaseInsensitive)) { // ".nextcloudsync.log*" + if (bname.startsWith(QLatin1String(".hidrivenextsync.log"), Qt::CaseInsensitive)) { // ".hidrivenextsync.log*" return CSYNC_FILE_SILENTLY_EXCLUDED; } } diff --git a/src/csync/vio/csync_vio_local_unix.cpp b/src/csync/vio/csync_vio_local_unix.cpp index c5e22abb3c8b0..afd2e52dd781d 100644 --- a/src/csync/vio/csync_vio_local_unix.cpp +++ b/src/csync/vio/csync_vio_local_unix.cpp @@ -38,7 +38,7 @@ #include #include -Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "nextcloud.sync.csync.vio_local", QtInfoMsg) +Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "hidrivenext.sync.csync.vio_local", QtInfoMsg) /* * directory functions diff --git a/src/csync/vio/csync_vio_local_win.cpp b/src/csync/vio/csync_vio_local_win.cpp index d0dec638a4791..df7a6532355ed 100644 --- a/src/csync/vio/csync_vio_local_win.cpp +++ b/src/csync/vio/csync_vio_local_win.cpp @@ -40,7 +40,7 @@ #include "common/vfs.h" -Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "nextcloud.sync.csync.vio_local", QtInfoMsg) +Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "hidrivenext.sync.csync.vio_local", QtInfoMsg) /* * directory functions diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 6e4acf203d199..74ad64430721b 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -18,6 +18,9 @@ endif() if(CMAKE_BUILD_TYPE MATCHES Debug) add_definitions(-DQT_QML_DEBUG) endif() +if(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo") + add_definitions(-DBUILDTYPE_RELWITHDEBINFO) +endif() IF(BUILD_UPDATER) add_subdirectory(updater) @@ -156,6 +159,16 @@ set(client_SRCS syncrunfilelog.cpp systray.h systray.cpp + sesstyle.h + sesstyle.cpp + buttonstylestrategy.h + sesFileIconProvider.h + sesFileIconProvider.cpp + pushbuttonstylehelper.h + pushbuttonstylehelper.cpp + moreoptionsbuttonstylehelper.h + moreoptionsbuttonstylehelper.cpp + buttonstyle.h thumbnailjob.h thumbnailjob.cpp userinfo.h @@ -190,8 +203,18 @@ set(client_SRCS emojimodel.cpp syncconflictsmodel.h syncconflictsmodel.cpp + linkbutton.h + linkbutton.cpp + sessnackbar.h + sessnackbar.cpp fileactivitylistmodel.h fileactivitylistmodel.cpp + ga4/ganalytics.h + ga4/ganalytics.cpp + ga4/ganalytics_worker.h + ga4/ganalytics_worker.cpp + ga4/datacollectionwrapper.cpp + ga4/datacollectionwrapper.h filedetails/datefieldbackend.h filedetails/datefieldbackend.cpp filedetails/filedetails.h @@ -202,6 +225,8 @@ set(client_SRCS filedetails/shareemodel.cpp filedetails/sortedsharemodel.h filedetails/sortedsharemodel.cpp + SesComponents/syncdirvalidation.h + SesComponents/syncdirvalidation.cpp tray/svgimageprovider.h tray/svgimageprovider.cpp tray/syncstatussummary.h @@ -763,7 +788,7 @@ if(BUILD_OWNCLOUD_OSX_BUNDLE AND NOT BUILD_LIBRARIES_ONLY) "$/../.." -qmldir=${CMAKE_SOURCE_DIR}/src/gui -always-overwrite - -executable="$/${cmd_NAME}" + -executable=$/${cmd_NAME} ${NO_STRIP} COMMAND "${CMAKE_COMMAND}" -E rm -rf "${BIN_OUTPUT_DIRECTORY}/${OWNCLOUD_OSX_BUNDLE}/Contents/PlugIns/bearer" diff --git a/src/gui/ConflictDelegate.qml b/src/gui/ConflictDelegate.qml index 915173507233a..716c55da835e2 100644 --- a/src/gui/ConflictDelegate.qml +++ b/src/gui/ConflictDelegate.qml @@ -17,7 +17,7 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import Style 1.0 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import "./tray" Item { diff --git a/src/gui/EmojiPicker.qml b/src/gui/EmojiPicker.qml index 9c1882ed433ff..0225ac0a5a5a5 100644 --- a/src/gui/EmojiPicker.qml +++ b/src/gui/EmojiPicker.qml @@ -17,7 +17,7 @@ import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import Style 1.0 -import com.nextcloud.desktopclient 1.0 as NC +import com.ionos.hidrivenext.desktopclient 1.0 as NC import "./tray" ColumnLayout { diff --git a/src/gui/ResolveConflictsDialog.qml b/src/gui/ResolveConflictsDialog.qml index 285bd8c038a27..2e77d1189b109 100644 --- a/src/gui/ResolveConflictsDialog.qml +++ b/src/gui/ResolveConflictsDialog.qml @@ -19,7 +19,7 @@ import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import QtQml.Models 2.15 import Style 1.0 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import "./tray" ApplicationWindow { @@ -200,15 +200,39 @@ ApplicationWindow { } DialogButtonBox { + id: buttonBox Layout.fillWidth: true - Button { + readonly property int pixelSize: Style.sesFontPixelSize + readonly property int fontWeight: Style.sesFontNormalWeight + + SesCustomButton { + font.pixelSize: pixelSize + font.weight: fontWeight text: qsTr("Resolve conflicts") - DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + textColor: palette.brightText + + bgColor: Style.sesActionPressed + bgNormalOpacity: 1.0 + bgHoverOpacity: Style.hoverOpacity + + onClicked: buttonBox.onAccepted() } - Button { + + SesCustomButton { + font.pixelSize: pixelSize + font.weight: fontWeight text: qsTr("Cancel") - DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + textColor: Style.sesActionPressed + + bgColor: palette.highlight + bgNormalOpacity: 1.0 + + bgBorderWidth: 2 + bgBorderColor: Style.sesActionPressed + bgHoverOpacity: Style.hoverOpacity + + onClicked: buttonBox.onRejected() } onAccepted: function() { diff --git a/src/gui/SesComponents/SesErrorBox.qml b/src/gui/SesComponents/SesErrorBox.qml new file mode 100644 index 0000000000000..6a0d6ea2ba8a4 --- /dev/null +++ b/src/gui/SesComponents/SesErrorBox.qml @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 by Felix Weilbach + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 + +import Style 1.0 +import "../tray/" + +Item { + id: errorBox + + property string text: "" + + implicitHeight: errorMessageLayout.implicitHeight + (2 * Style.standardSpacing) + + Rectangle { + anchors.fill: parent + border.color: Style.sesErrorBoxBorder + border.width: Style.thickBorderWidth + radius: Style.sesCornerRadius + } + + GridLayout { + id: errorMessageLayout + + anchors.fill: parent + anchors.margins: Style.standardSpacing + anchors.leftMargin: Style.standardSpacing + solidStripe.width + + columns: 2 + + Image { + source: Style.sesErrorBoxIcon + width: 24 + height: 24 + Layout.rightMargin: Style.standardSpacing + } + + EnforcedPlainTextLabel { + Layout.fillWidth: true + + font.pixelSize: Style.sesFontPixelSize + font.weight: Style.sesFontBoldWeight + + text: qsTr("Error") + color: Style.sesErrorBoxText + } + + EnforcedPlainTextLabel { + id: errorMessage + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.columnSpan: 2 + + wrapMode: Text.WordWrap + text: errorBox.text + + font.pixelSize: Style.sesFontErrortextPixelSize + font.weight: Style.sesFontNormalWeight + } + } +} diff --git a/src/gui/SesComponents/SesTrayHeader.qml b/src/gui/SesComponents/SesTrayHeader.qml new file mode 100644 index 0000000000000..3f4dd42c70894 --- /dev/null +++ b/src/gui/SesComponents/SesTrayHeader.qml @@ -0,0 +1,67 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtGraphicalEffects 1.15 +import Qt.labs.platform 1.1 as NativeDialogs + +import "../" +import "../filedetails/" +import "../tray/" + +// Custom qml modules are in /theme (and included by resources.qrc) +import Style 1.0 + +import com.ionos.hidrivenext.desktopclient 1.0 + + +Rectangle { + + height: Style.trayWindowHeaderHeight + color: Style.sesWhite + radius: 0.0 + + RowLayout { + id: trayWindowHeaderLayout + + anchors.fill: parent + anchors.leftMargin: Style.sesTrayHeaderMargin + anchors.rightMargin: Style.sesTrayHeaderMargin + + TrayWindowAccountMenu{ + Layout.preferredWidth: Style.sesAccountButtonWidth + Layout.preferredHeight: Style.sesAccountButtonHeight + } + + HeaderButton { + id: trayWindowWebsiteButton + + icon.source: Style.sesWebsiteIcon + icon.color: Style.sesIconColor + onClicked: UserModel.openCurrentAccountServer() + + text: qsTr("Website") + + Layout.rightMargin: 2 + + Accessible.role: Accessible.Button + Accessible.name: qsTr("Open Nextcloud in browser") + Accessible.onPressAction: trayWindowWebsiteButton.clicked() + } + + TrayFoldersMenuButton { + id: openLocalFolderButton + + visible: currentUser.hasLocalFolder + currentUser: UserModel.currentUser + + onClicked: openLocalFolderButton.userHasGroupFolders ? openLocalFolderButton.toggleMenuOpen() : UserModel.openCurrentAccountLocalFolder() + + onFolderEntryTriggered: isGroupFolder ? UserModel.openCurrentAccountFolderFromTrayInfo(fullFolderPath) : UserModel.openCurrentAccountLocalFolder() + + Accessible.role: Accessible.Graphic + Accessible.name: qsTr("Open local or group folders") + Accessible.onPressAction: openLocalFolderButton.userHasGroupFolders ? openLocalFolderButton.toggleMenuOpen() : UserModel.openCurrentAccountLocalFolder() + } + } +} // Rectangle trayWindowHeaderBackground diff --git a/src/gui/SesComponents/syncdirvalidation.cpp b/src/gui/SesComponents/syncdirvalidation.cpp new file mode 100644 index 0000000000000..df358e2f54ffe --- /dev/null +++ b/src/gui/SesComponents/syncdirvalidation.cpp @@ -0,0 +1,53 @@ +#include "syncdirvalidation.h" +#include +#include +#include "logger.h" + +#ifdef Q_OS_WIN +bool SyncDirValidator::isValidDir() { + QString appDataPath = SyncDirValidator::appDataPath().replace("/", QDir::separator()); + QStringList pathComponents = _path.replace("/", QDir::separator()).split(QDir::separator(), QString::SkipEmptyParts); + QStringList appDataPathComponents = appDataPath.split(QDir::separator(), QString::SkipEmptyParts); + /* + If path is shorter than appDataPath and one path component is different, then path cannot be a real subset and is sowith valid + If appDataPath is shorter than path, we need to check, if the last appDataPath component is different from the related path component, then path is valid. + Otherwise path is a subpath from appDataPath and invalid + */ + for(int i = 0; i < qMin(pathComponents.size(), appDataPathComponents.size()); i++) { + if(pathComponents[i] != appDataPathComponents[i]) { + return true; + } + } + return false; + +} + +QString SyncDirValidator::message() { + return QObject::tr("The directory %1 cannot be part of your sync directory. Please choose another folder.").arg(_path.replace("/", QDir::separator())); +} + +QString SyncDirValidator::appDataPath() { + //Path: AppData/Roaming/ + QString appDataRoamingApplicationNamePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir appDataRoamingApplicationNameDir(appDataRoamingApplicationNamePath); + appDataRoamingApplicationNameDir.cdUp(); + appDataRoamingApplicationNameDir.cdUp(); + QString appDataPath = appDataRoamingApplicationNameDir.absolutePath(); + return appDataPath; +} + +#else + +bool SyncDirValidator::isValidDir() { + return true; +} + +QString SyncDirValidator::message() { + return ""; +} + +QString SyncDirValidator::appDataPath() { + return ""; +} + +#endif \ No newline at end of file diff --git a/src/gui/SesComponents/syncdirvalidation.h b/src/gui/SesComponents/syncdirvalidation.h new file mode 100644 index 0000000000000..a91812da85775 --- /dev/null +++ b/src/gui/SesComponents/syncdirvalidation.h @@ -0,0 +1,19 @@ +#ifndef SYNCDIRVALIDATION_H +#define SYNCDIRVALIDATION_H + +#include + +class SyncDirValidator { +public: + SyncDirValidator(const QString &path) : _path(path) {} + + bool isValidDir(); + QString message(); + +private: + QString appDataPath(); + QString _path; +}; + + +#endif // SYNCDIRVALIDATION_H \ No newline at end of file diff --git a/src/gui/UserStatusSelector.qml b/src/gui/UserStatusSelector.qml index 35c4793ce2d31..5816b0dd47f8c 100644 --- a/src/gui/UserStatusSelector.qml +++ b/src/gui/UserStatusSelector.qml @@ -17,7 +17,7 @@ import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import QtQuick.Window 2.15 -import com.nextcloud.desktopclient 1.0 as NC +import com.ionos.hidrivenext.desktopclient 1.0 as NC import Style 1.0 import "./tray" diff --git a/src/gui/UserStatusSelectorPage.qml b/src/gui/UserStatusSelectorPage.qml index 42b92249f35ae..19c21104d0bdb 100644 --- a/src/gui/UserStatusSelectorPage.qml +++ b/src/gui/UserStatusSelectorPage.qml @@ -16,7 +16,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import Style 1.0 -import com.nextcloud.desktopclient 1.0 as NC +import com.ionos.hidrivenext.desktopclient 1.0 as NC Page { id: page diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index 8a2f3bb38f696..7ee0e7362e4d2 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -66,7 +66,7 @@ constexpr auto maxAccountVersion = 1; namespace OCC { -Q_LOGGING_CATEGORY(lcAccountManager, "nextcloud.gui.account.manager", QtInfoMsg) +Q_LOGGING_CATEGORY(lcAccountManager, "hidrivenext.gui.account.manager", QtInfoMsg) AccountManager *AccountManager::instance() { diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 8f5621704d47b..5465895bda40e 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -12,53 +12,55 @@ * for more details. */ - #include "accountsettings.h" #include "common/syncjournaldb.h" #include "common/syncjournalfilerecord.h" #include "qmessagebox.h" #include "ui_accountsettings.h" -#include "theme.h" -#include "foldercreationdialog.h" -#include "folderman.h" -#include "folderwizard.h" -#include "folderstatusmodel.h" -#include "folderstatusdelegate.h" -#include "common/utility.h" -#include "guiutility.h" -#include "application.h" -#include "configfile.h" +#include "ionostheme.h" +#include "buttonstyle.h" #include "account.h" -#include "accountstate.h" -#include "userinfo.h" #include "accountmanager.h" -#include "owncloudsetupwizard.h" +#include "accountstate.h" +#include "application.h" +#include "common/utility.h" +#include "configfile.h" #include "creds/abstractcredentials.h" #include "creds/httpcredentialsgui.h" -#include "tooltipupdater.h" -#include "filesystem.h" #include "encryptfolderjob.h" -#include "syncresult.h" +#include "filesystem.h" +#include "foldercreationdialog.h" +#include "folderman.h" +#include "folderstatusdelegate.h" +#include "folderstatusmodel.h" +#include "folderwizard.h" +#include "guiutility.h" #include "ignorelisttablewidget.h" -#include "wizard/owncloudwizard.h" +#include "owncloudsetupwizard.h" +#include "syncresult.h" +#include "theme.h" +#include "tooltipupdater.h" #include "ui_mnemonicdialog.h" +#include "userinfo.h" +#include "wizard/owncloudwizard.h" #include +#include #include #include #include +#include +#include +#include #include #include -#include -#include +#include #include -#include -#include +#include #include -#include -#include +#include #ifdef BUILD_FILE_PROVIDER_MODULE #include "macOS/fileprovider.h" @@ -66,7 +68,8 @@ #include "account.h" -namespace { +namespace +{ constexpr auto propertyFolder = "folder"; constexpr auto propertyPath = "path"; constexpr auto e2eUiActionIdKey = "id"; @@ -75,32 +78,32 @@ constexpr auto e2EeUiActionDisableEncryptionId = "disable_encryption"; constexpr auto e2EeUiActionDisplayMnemonicId = "display_mnemonic"; } -namespace OCC { +namespace OCC +{ class AccountSettings; -Q_LOGGING_CATEGORY(lcAccountSettings, "nextcloud.gui.account.settings", QtInfoMsg) +Q_LOGGING_CATEGORY(lcAccountSettings, "hidrivenext.gui.account.settings", QtInfoMsg) -static const char progressBarStyleC[] = - "QProgressBar {" - "border: 1px solid grey;" - "border-radius: 5px;" - "text-align: center;" - "}" - "QProgressBar::chunk {" - "background-color: %1; width: 1px;" - "}"; +const QString progressBarStyle() +{ + return QStringLiteral( + "QProgressBar { border: 1px solid grey; border-radius: 5px; text-align: center; }" + "QProgressBar::chunk { background-color: %1; width: 1px; }" + ); +} void showEnableE2eeWithVirtualFilesWarningDialog(std::function onAccept) { const auto messageBox = new QMessageBox; messageBox->setAttribute(Qt::WA_DeleteOnClose); messageBox->setText(AccountSettings::tr("End-to-end Encryption with Virtual Files")); - messageBox->setInformativeText(AccountSettings::tr("You seem to have the Virtual Files feature enabled on this folder. " - "At the moment, it is not possible to implicitly download virtual files that are " - "end-to-end encrypted. To get the best experience with virtual files and " - "end-to-end encryption, make sure the encrypted folder is marked with " - "\"Make always available locally\".")); + messageBox->setInformativeText( + AccountSettings::tr("You seem to have the Virtual Files feature enabled on this folder. " + "At the moment, it is not possible to implicitly download virtual files that are " + "end-to-end encrypted. To get the best experience with virtual files and " + "end-to-end encryption, make sure the encrypted folder is marked with " + "\"Make always available locally\".")); messageBox->setIcon(QMessageBox::Warning); const auto dontEncryptButton = messageBox->addButton(QMessageBox::StandardButton::Cancel); Q_ASSERT(dontEncryptButton); @@ -159,9 +162,9 @@ class MouseCursorChanger : public QObject Qt::CursorShape shape = Qt::ArrowCursor; const auto pos = folderList->mapFromGlobal(QCursor::pos()); const auto index = folderList->indexAt(pos); - if (model->classify(index) == FolderStatusModel::RootFolder && - (FolderStatusDelegate::errorsListRect(folderList->visualRect(index)).contains(pos) || - FolderStatusDelegate::optionsButtonRect(folderList->visualRect(index),folderList->layoutDirection()).contains(pos))) { + if (model->classify(index) == FolderStatusModel::RootFolder + && (FolderStatusDelegate::errorsListRect(folderList->visualRect(index)).contains(pos) + || FolderStatusDelegate::optionsButtonRect(folderList->visualRect(index), folderList->layoutDirection()).contains(pos))) { shape = Qt::PointingHandCursor; } folderList->setCursor(shape); @@ -187,6 +190,8 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) connect(this, &AccountSettings::styleChanged, delegate, &FolderStatusDelegate::slotStyleChanged); + _ui->_folderList->setFont(IonosTheme::settingsFontDefault()); + _ui->_folderList->header()->hide(); _ui->_folderList->setItemDelegate(delegate); _ui->_folderList->setModel(_model); @@ -207,6 +212,13 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) fpSettingsLayout->setMargin(0); fpSettingsLayout->addWidget(fpSettingsWidget); fileProviderTab->setLayout(fpSettingsLayout); + + _ui->tabWidget->tabBar()->setStyleSheet("QTabBar::tab {\ + color: #000000;\ + }\ + QTabBar::tab:selected {\ + color: #ffffff;\ + }"); } else { disguiseTabWidget(); } @@ -222,21 +234,21 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) _ui->_folderList->setAttribute(Qt::WA_Hover, true); _ui->_folderList->installEventFilter(mouseCursorChanger); - connect(this, &AccountSettings::removeAccountFolders, - AccountManager::instance(), &AccountManager::removeAccountFolders); - connect(_ui->_folderList, &QWidget::customContextMenuRequested, - this, &AccountSettings::slotCustomContextMenuRequested); - connect(_ui->_folderList, &QAbstractItemView::clicked, - this, &AccountSettings::slotFolderListClicked); +#ifdef Q_OS_MAC + _ui->expandMemoryButton->setAutoDefault(false); + _ui->expandMemoryButton->setFocusPolicy(Qt::NoFocus); +#endif + + connect(this, &AccountSettings::removeAccountFolders, AccountManager::instance(), &AccountManager::removeAccountFolders); + connect(_ui->_folderList, &QWidget::customContextMenuRequested, this, &AccountSettings::slotCustomContextMenuRequested); + connect(_ui->_folderList, &QAbstractItemView::clicked, this, &AccountSettings::slotFolderListClicked); connect(_ui->_folderList, &QTreeView::expanded, this, &AccountSettings::refreshSelectiveSyncStatus); connect(_ui->_folderList, &QTreeView::collapsed, this, &AccountSettings::refreshSelectiveSyncStatus); - connect(_ui->selectiveSyncNotification, &QLabel::linkActivated, - this, &AccountSettings::slotLinkActivated); + connect(_ui->selectiveSyncNotification, &QLabel::linkActivated, this, &AccountSettings::slotLinkActivated); connect(_model, &FolderStatusModel::suggestExpand, _ui->_folderList, &QTreeView::expand); connect(_model, &FolderStatusModel::dirtyChanged, this, &AccountSettings::refreshSelectiveSyncStatus); refreshSelectiveSyncStatus(); - connect(_model, &QAbstractItemModel::rowsInserted, - this, &AccountSettings::refreshSelectiveSyncStatus); + connect(_model, &QAbstractItemModel::rowsInserted, this, &AccountSettings::refreshSelectiveSyncStatus); auto *syncNowAction = new QAction(this); syncNowAction->setShortcut(QKeySequence(Qt::Key_F6)); @@ -248,7 +260,6 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) connect(syncNowWithRemoteDiscovery, &QAction::triggered, this, &AccountSettings::slotScheduleCurrentFolderForceRemoteDiscovery); addAction(syncNowWithRemoteDiscovery); - slotHideSelectiveSyncWidget(); _ui->bigFolderUi->setVisible(false); connect(_model, &QAbstractItemModel::dataChanged, this, &AccountSettings::slotSelectiveSyncChanged); @@ -264,11 +275,6 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) connect(FolderMan::instance(), &FolderMan::folderListChanged, _model, &FolderStatusModel::resetFolders); connect(this, &AccountSettings::folderChanged, _model, &FolderStatusModel::resetFolders); - - // quotaProgressBar style now set in customizeStyle() - /*QColor color = palette().highlight().color(); - _ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name()));*/ - // Connect E2E stuff initializeE2eEncryption(); _ui->encryptionMessage->setCloseButtonVisible(false); @@ -278,8 +284,9 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged); slotAccountStateChanged(); - connect(&_userInfo, &UserInfo::quotaUpdated, - this, &AccountSettings::slotUpdateQuota); + connect(&_userInfo, &UserInfo::quotaUpdated, this, &AccountSettings::slotUpdateQuota); + + connect(_ui->expandMemoryButton, &QAbstractButton::clicked, this, &AccountSettings::slotExpandMemoryClicked); customizeStyle(); } @@ -326,7 +333,7 @@ void AccountSettings::slotE2eEncryptionInitializationFinished(bool isNewMnemonic void AccountSettings::slotEncryptFolderFinished(int status) { qCInfo(lcAccountSettings) << "Current folder encryption status code:" << status; - auto job = qobject_cast(sender()); + auto job = qobject_cast(sender()); Q_ASSERT(job); if (!job->errorString().isEmpty()) { QMessageBox::warning(nullptr, tr("Warning"), job->errorString()); @@ -390,9 +397,10 @@ bool AccountSettings::canEncryptOrDecrypt(const FolderStatusModel::SubFolderInfo if (!_accountState->account()->e2e() || _accountState->account()->e2e()->_mnemonic.isEmpty()) { QMessageBox msgBox; - msgBox.setText(tr("End-to-end encryption is not configured on this device. " - "Once it is configured, you will be able to encrypt this folder.\n" - "Would you like to set up end-to-end encryption?")); + msgBox.setText( + tr("End-to-end encryption is not configured on this device. " + "Once it is configured, you will be able to encrypt this folder.\n" + "Would you like to set up end-to-end encryption?")); msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Ok); const auto ret = msgBox.exec(); @@ -411,19 +419,20 @@ bool AccountSettings::canEncryptOrDecrypt(const FolderStatusModel::SubFolderInfo // for some reason the actual folder in disk is info->_folder->path + info->_path. QDir folderPath(info->_folder->path() + info->_path); - folderPath.setFilter( QDir::AllEntries | QDir::NoDotAndDotDot ); + folderPath.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); if (folderPath.count() != 0) { QMessageBox msgBox; - msgBox.setText(tr("You cannot encrypt a folder with contents, please remove the files.\n" - "Wait for the new sync, then encrypt it.")); + msgBox.setText( + tr("You cannot encrypt a folder with contents, please remove the files.\n" + "Wait for the new sync, then encrypt it.")); msgBox.exec(); return false; } return true; } -void AccountSettings::slotMarkSubfolderEncrypted(FolderStatusModel::SubFolderInfo* folderInfo) +void AccountSettings::slotMarkSubfolderEncrypted(FolderStatusModel::SubFolderInfo *folderInfo) { if (!canEncryptOrDecrypt(folderInfo)) { return; @@ -529,23 +538,26 @@ void AccountSettings::slotEditCurrentLocalIgnoredFiles() openIgnoredFilesDialog(fileName); } -void AccountSettings::openIgnoredFilesDialog(const QString & absFolderPath) +void AccountSettings::openIgnoredFilesDialog(const QString &absFolderPath) { Q_ASSERT(QFileInfo(absFolderPath).isAbsolute()); const QString ignoreFile{absFolderPath + ".sync-exclude.lst"}; const auto layout = new QVBoxLayout(); const auto ignoreListWidget = new IgnoreListTableWidget(this); + ignoreListWidget->setFont(IonosTheme::settingsFont()); ignoreListWidget->readIgnoreFile(ignoreFile); layout->addWidget(ignoreListWidget); const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + customizeButtonBox(buttonBox); layout->addWidget(buttonBox); const auto dialog = new QDialog(); dialog->setLayout(layout); + dialog->setStyleSheet(QStringLiteral("QDialog { background-color: %1; }").arg(IonosTheme::dialogBackgroundColor())); - connect(buttonBox, &QDialogButtonBox::clicked, [=](QAbstractButton * button) { + connect(buttonBox, &QDialogButtonBox::clicked, [=](QAbstractButton *button) { if (buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { ignoreListWidget->slotWriteIgnoreFile(ignoreFile); } @@ -553,10 +565,29 @@ void AccountSettings::openIgnoredFilesDialog(const QString & absFolderPath) }); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::close); + dialog->setPalette(QPalette(QPalette::Window, IonosTheme::white())); + dialog->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + dialog->open(); } -void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index, const QPoint& pos) +void AccountSettings::customizeButtonBox(QDialogButtonBox *buttonBox){ + buttonBox->layout()->setSpacing(16); + buttonBox->setContentsMargins(0,0,11,10); + + const auto okButton = buttonBox->button(QDialogButtonBox::Ok); + + okButton->setProperty("buttonStyle", QVariant::fromValue(ButtonStyleName::Primary)); + okButton->setMinimumSize(80, 40); + + buttonBox->button(QDialogButtonBox::Cancel)->setMinimumSize(80, 40); + +#if defined(Q_OS_MAC) + buttonBox->layout()->setSpacing(32); +#endif +} + +void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex &index, const QPoint &pos) { Q_UNUSED(pos); @@ -580,7 +611,9 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index if (!isEncrypted && !isParentEncrypted && isTopFolder) { ac = menu.addAction(tr("Encrypt")); - connect(ac, &QAction::triggered, [this, info] { slotMarkSubfolderEncrypted(info); }); + connect(ac, &QAction::triggered, [this, info] { + slotMarkSubfolderEncrypted(info); + }); } else { // Ignore decrypting for now since it only works with an empty folder // connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderDecrypted(info); }); @@ -613,15 +646,64 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index const auto path = rec.isValid() ? rec._path : remotePath; ac = availabilityMenu->addAction(Utility::vfsPinActionText()); - connect(ac, &QAction::triggered, this, [this, folder, path] { slotSetSubFolderAvailability(folder, path, PinState::AlwaysLocal); }); + connect(ac, &QAction::triggered, this, [this, folder, path] { + slotSetSubFolderAvailability(folder, path, PinState::AlwaysLocal); + }); ac = availabilityMenu->addAction(Utility::vfsFreeSpaceActionText()); - connect(ac, &QAction::triggered, this, [this, folder, path] { slotSetSubFolderAvailability(folder, path, PinState::OnlineOnly); }); + connect(ac, &QAction::triggered, this, [this, folder, path] { + slotSetSubFolderAvailability(folder, path, PinState::OnlineOnly); + }); + + styleCustomContextMenu(availabilityMenu); } + styleCustomContextMenu(&menu); + menu.exec(QCursor::pos()); } +void AccountSettings::styleCustomContextMenu(QMenu *menu) const +{ + menu->setWindowFlags(menu->windowFlags() | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint); + + menu->setAttribute(Qt::WA_TranslucentBackground); + + menu->setStyleSheet( + QStringLiteral( + "QMenu {" + "background-color: %1; " + "border: 1px solid %2; " + "padding: 15px; " + "border-radius: %5; " + "font-family: %6; " + "font-size: %7; " + "font-weight: %8; " + "}" + "QMenu::item {" + "background-color: transparent;" + "padding: 16px 18px; " + "color: %3; " + "border-radius: 8px; " + "}" + "QMenu::item:selected {" + "background-color: %4; " + "color: %3; " + "border-radius: 8px; " + "}" + ).arg( + IonosTheme::white(), + IonosTheme::menuBorderColor(), + IonosTheme::menuTextColor(), + IonosTheme::menuSelectedItemColor(), + IonosTheme::menuBorderRadius(), + IonosTheme::contextMenuFont(), + IonosTheme::settingsTextSize(), + IonosTheme::settingsTextWeight() + ) + ); +} + void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) { const auto treeView = _ui->_folderList; @@ -630,6 +712,8 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) return; } + + if (_model->classify(index) == FolderStatusModel::SubFolder) { slotSubfolderContextMenuRequested(index, pos); return; @@ -649,9 +733,15 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) if (!folder) { return; } - const auto menu = new QMenu(treeView); + connect(menu, &QMenu::aboutToHide, [treeView, index]() { + auto* delegate = qobject_cast(treeView->itemDelegate(index)); + delegate->MousePos = QPoint(-1, -1); + treeView->update(); + }); + + menu->setAttribute(Qt::WA_DeleteOnClose); auto ac = menu->addAction(tr("Open folder")); @@ -689,15 +779,21 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) auto availabilityMenu = menu->addMenu(tr("Availability")); ac = availabilityMenu->addAction(Utility::vfsPinActionText()); - connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::AlwaysLocal); }); + connect(ac, &QAction::triggered, this, [this]() { + slotSetCurrentFolderAvailability(PinState::AlwaysLocal); + }); ac->setDisabled(Theme::instance()->enforceVirtualFilesSyncFolder()); ac = availabilityMenu->addAction(Utility::vfsFreeSpaceActionText()); - connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::OnlineOnly); }); + connect(ac, &QAction::triggered, this, [this]() { + slotSetCurrentFolderAvailability(PinState::OnlineOnly); + }); ac = menu->addAction(tr("Disable virtual file support …")); connect(ac, &QAction::triggered, this, &AccountSettings::slotDisableVfsCurrentFolder); ac->setDisabled(Theme::instance()->enforceVirtualFilesSyncFolder()); + + styleCustomContextMenu(availabilityMenu); } if (Theme::instance()->showVirtualFilesOption() && !folder->virtualFilesEnabled() && Vfs::checkAvailability(folder->path())) { @@ -711,6 +807,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) } } + styleCustomContextMenu(menu); menu->popup(treeView->mapToGlobal(pos)); } @@ -719,24 +816,11 @@ void AccountSettings::slotFolderListClicked(const QModelIndex &indx) { if (indx.data(FolderStatusDelegate::AddButton).toBool()) { // "Add Folder Sync Connection" - const auto treeView = _ui->_folderList; - const auto pos = treeView->mapFromGlobal(QCursor::pos()); - QStyleOptionViewItem opt; - opt.initFrom(treeView); - const auto btnRect = treeView->visualRect(indx); - const auto btnSize = treeView->itemDelegate(indx)->sizeHint(opt, indx); - const auto actual = QStyle::visualRect(opt.direction, btnRect, QRect(btnRect.topLeft(), btnSize)); - if (!actual.contains(pos)) { - return; - } if (indx.flags() & Qt::ItemIsEnabled) { slotAddFolder(); } else { - QToolTip::showText( - QCursor::pos(), - _model->data(indx, Qt::ToolTipRole).toString(), - this); + QToolTip::showText(QCursor::pos(), _model->data(indx, Qt::ToolTipRole).toString(), this); } return; } @@ -774,7 +858,6 @@ void AccountSettings::slotAddFolder() folderWizard->open(); } - void AccountSettings::slotFolderWizardAccepted() { const auto folderWizard = qobject_cast(sender()); @@ -783,10 +866,8 @@ void AccountSettings::slotFolderWizardAccepted() qCInfo(lcAccountSettings) << "Folder wizard completed"; FolderDefinition definition; - definition.localPath = FolderDefinition::prepareLocalPath( - folderWizard->field(QLatin1String("sourceFolder")).toString()); - definition.targetPath = FolderDefinition::prepareTargetPath( - folderWizard->property("targetPath").toString()); + definition.localPath = FolderDefinition::prepareLocalPath(folderWizard->field(QLatin1String("sourceFolder")).toString()); + definition.targetPath = FolderDefinition::prepareTargetPath(folderWizard->property("targetPath").toString()); if (folderWizard->property("useVirtualFiles").toBool()) { definition.virtualFilesMode = bestAvailableVfsMode(); @@ -797,9 +878,9 @@ void AccountSettings::slotFolderWizardAccepted() if (!dir.exists()) { qCInfo(lcAccountSettings) << "Creating folder" << definition.localPath; if (!dir.mkpath(".")) { - QMessageBox::warning(this, tr("Folder creation failed"), - tr("

Could not create local folder %1.

") - .arg(QDir::toNativeSeparators(definition.localPath))); + QMessageBox::warning(this, + tr("Folder creation failed"), + tr("

Could not create local folder %1.

").arg(QDir::toNativeSeparators(definition.localPath))); return; } } @@ -830,8 +911,7 @@ void AccountSettings::slotFolderWizardAccepted() folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, selectiveSyncBlackList); // The user already accepted the selective sync dialog. everything is in the white list - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, - QStringList() << QLatin1String("/")); + folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList() << QLatin1String("/")); folderMan->scheduleAllFolders(); emit folderChanged(); } @@ -855,16 +935,29 @@ void AccountSettings::slotRemoveCurrentFolder() const auto shortGuiLocalPath = folder->shortGuiLocalPath(); auto messageBox = new QMessageBox(QMessageBox::Question, - tr("Confirm Folder Sync Connection Removal"), - tr("

Do you really want to stop syncing the folder %1?

" - "

Note: This will not delete any files.

") - .arg(shortGuiLocalPath), - QMessageBox::NoButton, - this); + tr("Confirm Folder Sync Connection Removal"), + tr("

Do you really want to stop syncing the folder %1?

" + "

Note: This will not delete any files.

") + .arg(shortGuiLocalPath), + QMessageBox::NoButton, + this); + + messageBox->setStyleSheet( + QStringLiteral("QMessageBox QLabel { %1; }").arg( + IonosTheme::fontConfigurationCss( + IonosTheme::settingsFont(), + IonosTheme::settingsTextSize(), + IonosTheme::settingsTextWeight(), + IonosTheme::titleColor() + ) + ) + ); + messageBox->setAttribute(Qt::WA_DeleteOnClose); const auto yesButton = messageBox->addButton(tr("Remove Folder Sync Connection"), QMessageBox::YesRole); + yesButton->setProperty("buttonStyle", QVariant::fromValue(ButtonStyleName::Primary)); messageBox->addButton(tr("Cancel"), QMessageBox::NoRole); - connect(messageBox, &QMessageBox::finished, this, [messageBox, yesButton, folder, row, this]{ + connect(messageBox, &QMessageBox::finished, this, [messageBox, yesButton, folder, row, this] { if (messageBox->clickedButton() == yesButton) { Utility::removeFavLink(folder->path()); FolderMan::instance()->removeFolder(folder); @@ -971,21 +1064,33 @@ void AccountSettings::slotDisableVfsCurrentFolder() return; } - const auto msgBox = new QMessageBox( - QMessageBox::Question, - tr("Disable virtual file support?"), - tr("This action will disable virtual file support. As a consequence contents of folders that " - "are currently marked as \"available online only\" will be downloaded." - "\n\n" - "The only advantage of disabling virtual file support is that the selective sync feature " - "will become available again." - "\n\n" - "This action will abort any currently running synchronization.")); + const auto msgBox = new QMessageBox(QMessageBox::Question, + tr("Disable virtual file support?"), + tr("This action will disable virtual file support. As a consequence contents of folders that " + "are currently marked as \"available online only\" will be downloaded." + "\n\n" + "The only advantage of disabling virtual file support is that the selective sync feature " + "will become available again." + "\n\n" + "This action will abort any currently running synchronization.")); + + msgBox->setStyleSheet(QString( + "QMessageBox {" + + IonosTheme::fontConfigurationCss( + IonosTheme::settingsFont(), + IonosTheme::settingsTextSize(), + IonosTheme::settingsTextWeight(), + IonosTheme::titleColor() + ) + "background-color: %1; }").arg(IonosTheme::dialogBackgroundColor()) + ); + const auto acceptButton = msgBox->addButton(tr("Disable support"), QMessageBox::AcceptRole); + acceptButton->setProperty("buttonStyle", QVariant::fromValue(ButtonStyleName::Primary)); + msgBox->addButton(tr("Cancel"), QMessageBox::RejectRole); connect(msgBox, &QMessageBox::finished, msgBox, [this, msgBox, folder, acceptButton] { msgBox->deleteLater(); - if (msgBox->clickedButton() != acceptButton|| !folder) { + if (msgBox->clickedButton() != acceptButton || !folder) { return; } @@ -1095,28 +1200,31 @@ void AccountSettings::disableEncryptionForAccount(const AccountPtr &account) con QMessageBox dialog; dialog.setWindowTitle(tr("Disable end-to-end encryption")); dialog.setText(tr("Disable end-to-end encryption for %1?").arg(account->davUser())); - dialog.setInformativeText(tr("Removing end-to-end encryption will remove locally-synced files that are encrypted." - "
" - "Encrypted files will remain on the server.")); + dialog.setInformativeText( + tr("Removing end-to-end encryption will remove locally-synced files that are encrypted." + "
" + "Encrypted files will remain on the server.")); dialog.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); dialog.setDefaultButton(QMessageBox::Ok); dialog.adjustSize(); const auto ret = dialog.exec(); - switch(ret) { + switch (ret) { case QMessageBox::Ok: - connect(account->e2e(), &ClientSideEncryption::sensitiveDataForgotten, - this, &AccountSettings::resetE2eEncryption); + connect(account->e2e(), &ClientSideEncryption::sensitiveDataForgotten, this, &AccountSettings::resetE2eEncryption); account->e2e()->forgetSensitiveData(account); break; case QMessageBox::Cancel: break; - Q_UNREACHABLE(); + Q_UNREACHABLE(); } } void AccountSettings::showConnectionLabel(const QString &message, QStringList errors) { + + #ifndef IONOS_BUILD + //SES-4 Removed const auto errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;" "border-width: 1px; border-style: solid; border-color: #aaaaaa;" "border-radius:5px;"); @@ -1135,7 +1243,8 @@ void AccountSettings::showConnectionLabel(const QString &message, QStringList er _ui->connectLabel->setToolTip({}); _ui->connectLabel->setStyleSheet(errStyle); } - _ui->accountStatus->setVisible(!message.isEmpty()); + #endif + _ui->accountStatus->setVisible(false); } void AccountSettings::slotEnableCurrentFolder(bool terminate) @@ -1157,14 +1266,37 @@ void AccountSettings::slotEnableCurrentFolder(bool terminate) if (!currentlyPaused && !terminate) { // check if a sync is still running and if so, ask if we should terminate. if (folder->isBusy()) { // its still running - const auto msgbox = new QMessageBox(QMessageBox::Question, tr("Sync Running"), - tr("The syncing operation is running.
Do you want to terminate it?"), - QMessageBox::Yes | QMessageBox::No, this); - msgbox->setAttribute(Qt::WA_DeleteOnClose); + const auto msgbox = new QMessageBox(QMessageBox::Question, + tr("Sync Running"), + tr("The syncing operation is running.
Do you want to terminate it?"), + QMessageBox::Yes | QMessageBox::No, + this); + msgbox->setAttribute(Qt::WA_DeleteOnClose); msgbox->setDefaultButton(QMessageBox::Yes); - connect(msgbox, &QMessageBox::accepted, this, [this]{ + msgbox->defaultButton()->setProperty("buttonStyle", QVariant::fromValue(ButtonStyleName::Primary)); + + QHBoxLayout *buttonLayout = msgbox->findChild(); + buttonLayout->setSpacing(8); + +#ifdef Q_OS_MAC + buttonLayout->setSpacing(24); +#endif + + connect(msgbox, &QMessageBox::accepted, this, [this] { slotEnableCurrentFolder(true); }); + + msgbox->setStyleSheet( + QStringLiteral("QMessageBox QLabel { %1; }").arg( + IonosTheme::fontConfigurationCss( + IonosTheme::settingsFont(), + IonosTheme::settingsTextSize(), + IonosTheme::settingsTextWeight(), + IonosTheme::titleColor() + ) + ) + ); + msgbox->open(); return; } @@ -1230,10 +1362,12 @@ void AccountSettings::slotUpdateQuota(qint64 total, qint64 used) const auto usedStr = Utility::octetsToString(used); const auto totalStr = Utility::octetsToString(total); const auto percentStr = Utility::compactFormatDouble(percent, 1); - const auto toolTip = tr("%1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits.").arg(usedStr, totalStr, percentStr); + const auto toolTip = tr("%1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits.") + .arg(usedStr, totalStr, percentStr); _ui->quotaInfoLabel->setText(tr("%1 of %2 in use").arg(usedStr, totalStr)); _ui->quotaInfoLabel->setToolTip(toolTip); _ui->quotaProgressBar->setToolTip(toolTip); + _ui->quotaInfo2Label->setText(tr("Storage space %1% occupied").arg(percentStr)); } else { _ui->quotaProgressBar->setVisible(false); _ui->quotaInfoLabel->setToolTip({}); @@ -1261,9 +1395,7 @@ void AccountSettings::slotAccountStateChanged() _model->slotUpdateFolderState(folder); } - const auto server = QString::fromLatin1("%2") - .arg(Utility::escape(account->url().toString()), - Utility::escape(safeUrl.toString())); + const auto server = QString::fromLatin1("%2").arg(Utility::escape(account->url().toString()), Utility::escape(safeUrl.toString())); auto serverWithUser = server; if (const auto cred = account->credentials()) { auto user = account->davDisplayName(); @@ -1299,14 +1431,11 @@ void AccountSettings::slotAccountStateChanged() break; } case AccountState::NetworkError: - showConnectionLabel(tr("Unable to connect to %1.") - .arg(Utility::escape(Theme::instance()->appNameGUI())), - _accountState->connectionErrors()); + showConnectionLabel(tr("Unable to connect to %1.").arg(Utility::escape(Theme::instance()->appNameGUI())), _accountState->connectionErrors()); break; case AccountState::ConfigurationError: - showConnectionLabel(tr("Server configuration error: %1 at %2.") - .arg(Utility::escape(Theme::instance()->appNameGUI()), server), - _accountState->connectionErrors()); + showConnectionLabel(tr("Server configuration error: %1 at %2.").arg(Utility::escape(Theme::instance()->appNameGUI()), server), + _accountState->connectionErrors()); break; case AccountState::Disconnected: // we can't end up here as the whole block is ifdeffed @@ -1315,8 +1444,7 @@ void AccountSettings::slotAccountStateChanged() } } else { // ownCloud is not yet configured. - showConnectionLabel(tr("No %1 connection configured.") - .arg(Utility::escape(Theme::instance()->appNameGUI()))); + showConnectionLabel(tr("No %1 connection configured.").arg(Utility::escape(Theme::instance()->appNameGUI()))); } /* Allow to expand the item if the account is connected. */ @@ -1349,8 +1477,8 @@ void AccountSettings::checkClientSideEncryptionState() * Verify if the user has a private key already uploaded to the server, * if it has, do not offer to create one. */ - qCInfo(lcAccountSettings) << "Account" << accountsState()->account()->displayName() - << "Client Side Encryption" << accountsState()->account()->capabilities().clientSideEncryptionAvailable(); + qCInfo(lcAccountSettings) << "Account" << accountsState()->account()->displayName() << "Client Side Encryption" + << accountsState()->account()->capabilities().clientSideEncryptionAvailable(); if (_accountState->account()->capabilities().clientSideEncryptionAvailable()) { _ui->encryptionMessage->show(); @@ -1406,9 +1534,12 @@ void AccountSettings::slotHideSelectiveSyncWidget() _ui->selectiveSyncLabel->hide(); } -void AccountSettings::slotSelectiveSyncChanged(const QModelIndex &topLeft, - const QModelIndex &bottomRight, - const QVector &roles) +void AccountSettings::slotExpandMemoryClicked() +{ + QDesktopServices::openUrl(QUrl(tr("https://wl.hidrive.com/easy/0057"))); +} + +void AccountSettings::slotSelectiveSyncChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { Q_UNUSED(bottomRight); if (!roles.contains(Qt::CheckStateRole)) { @@ -1463,7 +1594,10 @@ void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync() return; } - disconnect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync); + disconnect(_accountState->account()->e2e(), + &ClientSideEncryption::initializationFinished, + this, + &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync); for (const auto folder : FolderMan::instance()->map()) { if (folder->accountState() != _accountState) { @@ -1492,7 +1626,9 @@ void AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync() } } -void AccountSettings::updateBlackListAndScheduleFolderSync(const QStringList &blackList, OCC::Folder *folder, const QStringList &foldersToRemoveFromBlacklist) const +void AccountSettings::updateBlackListAndScheduleFolderSync(const QStringList &blackList, + OCC::Folder *folder, + const QStringList &foldersToRemoveFromBlacklist) const { folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList); folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncE2eFoldersToRemoveFromBlacklist, {}); @@ -1509,14 +1645,15 @@ void AccountSettings::folderTerminateSyncAndUpdateBlackList(const QStringList &b return; } // in case sync is already running - terminate it and start a new one - const QMetaObject::Connection syncTerminatedConnection = connect(folder, &Folder::syncFinished, this, [this, blackList, folder, foldersToRemoveFromBlacklist]() { - const auto foundConnectionIt = _folderConnections.find(folder->alias()); - if (foundConnectionIt != _folderConnections.end()) { - disconnect(*foundConnectionIt); - _folderConnections.erase(foundConnectionIt); - } - updateBlackListAndScheduleFolderSync(blackList, folder, foldersToRemoveFromBlacklist); - }); + const QMetaObject::Connection syncTerminatedConnection = + connect(folder, &Folder::syncFinished, this, [this, blackList, folder, foldersToRemoveFromBlacklist]() { + const auto foundConnectionIt = _folderConnections.find(folder->alias()); + if (foundConnectionIt != _folderConnections.end()) { + disconnect(*foundConnectionIt); + _folderConnections.erase(foundConnectionIt); + } + updateBlackListAndScheduleFolderSync(blackList, folder, foldersToRemoveFromBlacklist); + }); _folderConnections.insert(folder->alias(), syncTerminatedConnection); folder->slotTerminateSync(); } @@ -1630,12 +1767,39 @@ void AccountSettings::customizeStyle() _ui->connectLabel->setText(msg); const auto color = palette().highlight().color(); - _ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name())); + _ui->quotaProgressBar->setStyleSheet(progressBarStyle().arg(color.name())); + + _ui->quotaInfoLabel->setStyleSheet( + IonosTheme::fontConfigurationCss( + IonosTheme::settingsFont(), + IonosTheme::settingsTextSize(), + IonosTheme::settingsTitleWeight600(), + IonosTheme::titleColor() + ) + ); + + _ui->quotaInfo2Label->setStyleSheet( + IonosTheme::fontConfigurationCss( + IonosTheme::settingsFont(), + IonosTheme::settingsSmallTextSize(), + IonosTheme::settingsTextWeight(), + IonosTheme::titleColor() + ) + ); + +#if defined(Q_OS_MAC) + _ui->selectiveSyncLabel->setStyleSheet(QString("color: %1;").arg(IonosTheme::black())); + _ui->horizontalLayout->setSpacing(16); +#endif + } void AccountSettings::initializeE2eEncryption() { - connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync); + connect(_accountState->account()->e2e(), + &ClientSideEncryption::initializationFinished, + this, + &AccountSettings::slotPossiblyUnblacklistE2EeFoldersAndRestartSync); if (!_accountState->account()->e2e()->_mnemonic.isEmpty()) { slotE2eEncryptionMnemonicReady(); @@ -1644,11 +1808,12 @@ void AccountSettings::initializeE2eEncryption() connect(_accountState->account()->e2e(), &ClientSideEncryption::initializationFinished, this, [this] { if (!_accountState->account()->e2e()->_publicKey.isNull()) { - _ui->encryptionMessage->setText(tr("End-to-end encryption has been enabled on this account with another device." - "
" - "It can be enabled on this device by entering your mnemonic." - "
" - "This will enable synchronisation of existing encrypted folders.")); + _ui->encryptionMessage->setText( + tr("End-to-end encryption has been enabled on this account with another device." + "
" + "It can be enabled on this device by entering your mnemonic." + "
" + "This will enable synchronisation of existing encrypted folders.")); } }); _accountState->account()->setE2eEncryptionKeysGenerationAllowed(false); @@ -1674,9 +1839,10 @@ void AccountSettings::resetE2eEncryption() void AccountSettings::removeActionFromEncryptionMessage(const QString &actionId) { - const auto foundEnableEncryptionActionIt = std::find_if(std::cbegin(_ui->encryptionMessage->actions()), std::cend(_ui->encryptionMessage->actions()), [&actionId](const QAction *action) { - return action->property(e2eUiActionIdKey).toString() == actionId; - }); + const auto foundEnableEncryptionActionIt = + std::find_if(std::cbegin(_ui->encryptionMessage->actions()), std::cend(_ui->encryptionMessage->actions()), [&actionId](const QAction *action) { + return action->property(e2eUiActionIdKey).toString() == actionId; + }); if (foundEnableEncryptionActionIt != std::cend(_ui->encryptionMessage->actions())) { _ui->encryptionMessage->removeAction(*foundEnableEncryptionActionIt); (*foundEnableEncryptionActionIt)->deleteLater(); diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index 117a3aa536a0a..44982945be2d4 100644 --- a/src/gui/accountsettings.h +++ b/src/gui/accountsettings.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "folder.h" #include "userinfo.h" @@ -131,8 +132,10 @@ private slots: void resetE2eEncryption(); void checkClientSideEncryptionState(); void removeActionFromEncryptionMessage(const QString &actionId); + void slotExpandMemoryClicked(); private: + void styleCustomContextMenu(QMenu *menu) const; bool event(QEvent *) override; QAction *addActionToEncryptionMessage(const QString &actionTitle, const QString &actionId); @@ -143,6 +146,8 @@ private slots: void disguiseTabWidget() const; + void customizeButtonBox(QDialogButtonBox *buttonBox); + Ui::AccountSettings *_ui; FolderStatusModel *_model; diff --git a/src/gui/accountsettings.ui b/src/gui/accountsettings.ui index bc1594e8f3189..60f01052b444a 100644 --- a/src/gui/accountsettings.ui +++ b/src/gui/accountsettings.ui @@ -1,335 +1,387 @@ - OCC::AccountSettings - - - - 0 - 0 - 1028 - 871 - - - - Form - - - - - - - - - 0 - 0 - - - - - - - Storage space: … - - - Qt::PlainText - - - false - - - - - - - false - - - - 0 - 0 - - - - - 16777215 - 7 - - - - 100 - - - -1 - - - false - - - - - - - - - - - - - - - 0 - 0 - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - - - Qt::RichText - - - true - - - true - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Cancel - - - - - - - - 0 - 0 - - - - Apply - - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - Connected with <server> as <user> - - - Qt::RichText - - - true + OCC::AccountSettings + + + + 0 + 0 + 1028 + 871 + - - true + + Form - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - color: red - - - - - - true - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Synchronize all - - - - - - - Synchronize none - - - - - - - Apply manual changes - - - - - - - - - - - - 1 - - - - Standard file sync - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Qt::CustomContextMenu - - - false - - - QAbstractItemView::NoEditTriggers - - - true - - - - - - - - Virtual file sync - - + + 32 + + + 32 + + + 32 + + + 32 + + + + + + + + + 0 + 0 + + + + + + + Storage space: … + + + Qt::PlainText + + + false + + + + + + + + + false + + + + 0 + 0 + + + + + 16777215 + 8 + + + + 100 + + + -1 + + + false + + + + + + + + 0 + 0 + + + + Expand Memory + + + + + + + + + + 0 + 0 + + + + + + + Storage space: … + + + Qt::PlainText + + + false + + + + + + + + + + + + + + + 0 + 0 + + + + Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore + + + Qt::RichText + + + true + + + true + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Cancel + + + + + + + + 0 + 0 + + + + Apply + + + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + Connected with <server> as <user> + + + Qt::RichText + + + true + + + true + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + color: red + + + + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Synchronize all + + + + + + + Synchronize none + + + + + + + Apply manual changes + + + + + + + + + + + + 1 + + + + Standard file sync + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::CustomContextMenu + + + false + + + QAbstractItemView::NoEditTriggers + + + true + + + + + + + + Virtual file sync + + + + + - - - - - - SslButton - QToolButton -
sslbutton.h
-
- - OCC::FolderStatusView - QTreeView -
folderstatusview.h
-
- - KMessageWidget - QWidget -
kmessagewidget.h
- 1 -
-
- - -
+ + + SslButton + QToolButton +
sslbutton.h
+
+ + OCC::FolderStatusView + QTreeView +
folderstatusview.h
+
+ + KMessageWidget + QWidget +
kmessagewidget.h
+ 1 +
+
+ + + \ No newline at end of file diff --git a/src/gui/accountsetupcommandlinemanager.cpp b/src/gui/accountsetupcommandlinemanager.cpp index 94388b7ea5f17..3a702bf6a08db 100644 --- a/src/gui/accountsetupcommandlinemanager.cpp +++ b/src/gui/accountsetupcommandlinemanager.cpp @@ -17,7 +17,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcAccountSetupCommandLineManager, "nextcloud.gui.accountsetupcommandlinemanager", QtInfoMsg) +Q_LOGGING_CATEGORY(lcAccountSetupCommandLineManager, "hidrivenext.gui.accountsetupcommandlinemanager", QtInfoMsg) AccountSetupCommandLineManager *AccountSetupCommandLineManager::_instance = nullptr; diff --git a/src/gui/accountsetupfromcommandlinejob.cpp b/src/gui/accountsetupfromcommandlinejob.cpp index eeff0cfb00a31..6163b2b07254e 100644 --- a/src/gui/accountsetupfromcommandlinejob.cpp +++ b/src/gui/accountsetupfromcommandlinejob.cpp @@ -29,7 +29,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcAccountSetupCommandLineJob, "nextcloud.gui.accountsetupcommandlinejob", QtInfoMsg) +Q_LOGGING_CATEGORY(lcAccountSetupCommandLineJob, "hidrivenext.gui.accountsetupcommandlinejob", QtInfoMsg) AccountSetupFromCommandLineJob::AccountSetupFromCommandLineJob(QString appPassword, QString userId, diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index fc18efbd09230..a3de1a8d09b03 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -37,7 +37,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcAccountState, "nextcloud.gui.account.state", QtInfoMsg) +Q_LOGGING_CATEGORY(lcAccountState, "hidrivenext.gui.account.state", QtInfoMsg) AccountState::AccountState(const AccountPtr &account) : QObject() diff --git a/src/gui/addcertificatedialog.cpp b/src/gui/addcertificatedialog.cpp index fabaeec5b6d14..0d3e8f3209edb 100644 --- a/src/gui/addcertificatedialog.cpp +++ b/src/gui/addcertificatedialog.cpp @@ -26,6 +26,7 @@ AddCertificateDialog::AddCertificateDialog(QWidget *parent) { ui->setupUi(this); ui->labelErrorCertif->setText(""); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); } AddCertificateDialog::~AddCertificateDialog() diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 9092d05dcc53c..ab5c7fc9115a2 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -38,6 +38,8 @@ #include "pushnotifications.h" #include "shellextensionsserver.h" +#include "ga4/datacollectionwrapper.h" + #if defined(BUILD_UPDATER) #include "updater/ocupdater.h" #endif @@ -71,7 +73,7 @@ class QSocket; namespace OCC { -Q_LOGGING_CATEGORY(lcApplication, "nextcloud.gui.application", QtInfoMsg) +Q_LOGGING_CATEGORY(lcApplication, "hidrivenext.gui.application", QtInfoMsg) namespace { @@ -163,9 +165,6 @@ bool Application::configVersionMigration() configFile.setLaunchOnSystemStartup(configFile.launchOnSystemStartup()); Utility::setLaunchOnStartup(theme->appName(), theme->appNameGUI(), configFile.launchOnSystemStartup()); - // default is now off to displaying dialog warning user of too many files deletion - configFile.setPromptDeleteFiles(false); - // back up all old config files QStringList backupFilesList; QDir configDir(configFile.configPath()); @@ -374,6 +373,7 @@ Application::Application(int &argc, char **argv) this, &Application::slotAccountStateAdded); connect(AccountManager::instance(), &AccountManager::accountRemoved, this, &Application::slotAccountStateRemoved); + const auto accounts = AccountManager::instance()->accounts(); for (const auto &ai : accounts) { slotAccountStateAdded(ai.data()); @@ -439,6 +439,30 @@ Application::~Application() AccountManager::instance()->shutdown(); } +void Application::startTracking() +{ + DataCollectionWrapper dcw; + dcw.initDataCollection(); + AccountPtr account = AccountManager::instance()->accounts().first()->account(); + QByteArray byteArray = account->credentials()->user().toUtf8(); // Convert the input string to a byte array + QByteArray hash = QCryptographicHash::hash(byteArray, QCryptographicHash::Sha256); // Perform the hash + + ConfigFile cfg; + dcw.setSendData(cfg.sendData()); + dcw.setAccount(account); + + dcw.setClientID(hash.toHex()); + dcw.login(); +} + +void Application::stopTracking() +{ + DataCollectionWrapper dcw; + dcw.accountRemoved(); + dcw.setClientID(QString()); + dcw.setAccount(nullptr); +} + void Application::setupAccountsAndFolders() { _folderManager.reset(new FolderMan); @@ -577,6 +601,14 @@ void Application::slotAccountStateRemoved(AccountState *accountState) _folderManager.data(), &FolderMan::slotServerVersionChanged); } + if(AccountManager::instance()->accounts().isEmpty()) { + stopTracking(); + } + else + { + startTracking(); + } + // if there is no more account, show the wizard. if (_gui && AccountManager::instance()->accounts().isEmpty()) { // allow to add a new account if there is non any more. Always think @@ -596,6 +628,8 @@ void Application::slotAccountStateAdded(AccountState *accountState) connect(accountState->account().data(), &Account::serverVersionChanged, _folderManager.data(), &FolderMan::slotServerVersionChanged); + startTracking(); + _gui->slotTrayMessageIfServerUnsupported(accountState->account().data()); } @@ -674,8 +708,7 @@ void Application::setupLogging() logger->setLogDebug(true); #endif - logger->enterNextLogFile(QStringLiteral("nextcloud.log"), OCC::Logger::LogType::Log); - logger->enterNextLogFile(QStringLiteral("permanent_delete.log"), OCC::Logger::LogType::DeleteLog); + logger->enterNextLogFile(); qCInfo(lcApplication) << "##################" << _theme->appName() << "locale:" << QLocale::system().name() diff --git a/src/gui/application.h b/src/gui/application.h index fb9634b5ec974..62057186025b0 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -115,7 +115,8 @@ protected slots: private: void setHelp(); - + void startTracking(); + void stopTracking(); void handleEditLocallyFromOptions(); AccountManager::AccountsRestoreResult restoreLegacyAccount(); diff --git a/src/gui/authenticationdialog.cpp b/src/gui/authenticationdialog.cpp index 7b7de1abbebb0..aef5f8163c017 100644 --- a/src/gui/authenticationdialog.cpp +++ b/src/gui/authenticationdialog.cpp @@ -43,6 +43,7 @@ AuthenticationDialog::AuthenticationDialog(const QString &realm, const QString & connect(box, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject); lay->addWidget(box); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); } QString AuthenticationDialog::user() const diff --git a/src/gui/buttonstyle.h b/src/gui/buttonstyle.h new file mode 100644 index 0000000000000..2f199b1352c80 --- /dev/null +++ b/src/gui/buttonstyle.h @@ -0,0 +1,303 @@ + +#ifndef _BUTTONSTYLE_H +#define _BUTTONSTYLE_H + +#include "ionostheme.h" +#include +#include + +namespace OCC{ + +enum class ButtonStyleName { + Primary, + Secondary, + MoreOptions, +}; +OCSYNC_EXPORT Q_NAMESPACE; +Q_ENUM_NS(ButtonStyleName); +} +Q_DECLARE_METATYPE(OCC::ButtonStyleName); + +namespace OCC{ +class ButtonStyle +{ +protected: + ButtonStyle() + { + qRegisterMetaType("OCC::ButtonStyleName"); + } + ~ButtonStyle() {} + +public: + + // Default + virtual QString buttonDefaultColor() const = 0; + virtual QString buttonDefaultBorderColor() const = 0; + // Hover + virtual QString buttonHoverColor() const = 0; + virtual QString buttonHoverBorderColor() const = 0; + // Pressed + virtual QString buttonPressedColor() const = 0; + virtual QString buttonPressedBorderColor() const = 0; + // Disabled + virtual QString buttonDisabledColor() const = 0; + virtual QString buttonDisabledBorderColor() const = 0; + // Focused + virtual QString buttonFocusedColor() const = 0; + virtual QString buttonFocusedBorderColor() const = 0; + // Font + virtual QString buttonDisabledFontColor() const = 0; + virtual QString buttonFontColor() const = 0; +}; + +class PrimaryButtonStyle : public ButtonStyle { +private: + PrimaryButtonStyle() + { + } + ~PrimaryButtonStyle() {} +public: + + PrimaryButtonStyle(PrimaryButtonStyle &other) = delete; + void operator=(const PrimaryButtonStyle &) = delete; + + static PrimaryButtonStyle& GetInstance() { + static PrimaryButtonStyle instance; + return instance; + } + + // Default + QString buttonDefaultColor() const override + { + return OCC::IonosTheme::buttonPrimaryColor(); + } + + QString buttonDefaultBorderColor() const override + { + return OCC::IonosTheme::buttonPrimaryColor(); + } + + //Hover + QString buttonHoverColor() const override + { + return OCC::IonosTheme::buttonPrimaryHoverColor(); + } + + QString buttonHoverBorderColor() const override + { + return OCC::IonosTheme::buttonPrimaryHoverColor(); + } + + // Pressed + QString buttonPressedColor() const override + { + return OCC::IonosTheme::buttonPrimaryPressedColor(); + } + + QString buttonPressedBorderColor() const override + { + return OCC::IonosTheme::buttonPrimaryPressedColor(); + } + + // Disabled + QString buttonDisabledColor() const override + { + return OCC::IonosTheme::buttonDisabledColor(); + } + + QString buttonDisabledBorderColor() const override + { + return OCC::IonosTheme::buttonDisabledColor(); + } + + // Focused + QString buttonFocusedColor() const override + { + return OCC::IonosTheme::buttonPrimaryColor(); + } + + QString buttonFocusedBorderColor() const override + { + return OCC::IonosTheme::black(); + } + + // Font + QString buttonDisabledFontColor() const override + { + return OCC::IonosTheme::buttonDisabledFontColor(); + } + + QString buttonFontColor() const override + { + return OCC::IonosTheme::white(); + } +}; + +class SecondaryButtonStyle : public ButtonStyle { +protected: + SecondaryButtonStyle() + { + } + ~SecondaryButtonStyle() {} +public: + + SecondaryButtonStyle(SecondaryButtonStyle &other) = delete; + void operator=(const SecondaryButtonStyle &) = delete; + + static SecondaryButtonStyle& GetInstance() { + static SecondaryButtonStyle instance; + return instance; + } + + // Default + QString buttonDefaultColor() const override + { + return OCC::IonosTheme::white(); + } + + QString buttonDefaultBorderColor() const override + { + return OCC::IonosTheme::buttonSecondaryBorderColor(); + } + + // Hover + QString buttonHoverColor() const override + { + return OCC::IonosTheme::buttonSecondaryHoverColor(); + } + + QString buttonHoverBorderColor() const override + { + return OCC::IonosTheme::buttonSecondaryBorderColor(); + } + + // Pressed + QString buttonPressedColor() const override + { + return OCC::IonosTheme::buttonSecondaryPressedColor(); + } + + QString buttonPressedBorderColor() const override + { + return OCC::IonosTheme::buttonSecondaryBorderColor(); + } + + // Disabled + QString buttonDisabledColor() const override + { + return OCC::IonosTheme::buttonDisabledColor(); + } + + QString buttonDisabledBorderColor() const override + { + return OCC::IonosTheme::buttonDisabledColor(); + } + + // Focused + QString buttonFocusedColor() const override + { + return OCC::IonosTheme::white(); + } + + QString buttonFocusedBorderColor() const override + { + return OCC::IonosTheme::black(); + } + + // Font + QString buttonDisabledFontColor() const override + { + return OCC::IonosTheme::buttonDisabledFontColor(); + } + + QString buttonFontColor() const override + { + return OCC::IonosTheme::black(); + } +}; + +class MoreOptionsButtonStyle : public ButtonStyle { +protected: + MoreOptionsButtonStyle() + { + } + ~MoreOptionsButtonStyle() {} +public: + + MoreOptionsButtonStyle(MoreOptionsButtonStyle &other) = delete; + void operator=(const MoreOptionsButtonStyle &) = delete; + + static MoreOptionsButtonStyle& GetInstance() { + static MoreOptionsButtonStyle instance; + return instance; + } + + // Default + QString buttonDefaultColor() const override + { + return OCC::IonosTheme::white(); + } + + QString buttonDefaultBorderColor() const override + { + return OCC::IonosTheme::white(); + } + + // Hover + QString buttonHoverColor() const override + { + return OCC::IonosTheme::buttonHoveredColor(); + } + + QString buttonHoverBorderColor() const override + { + return OCC::IonosTheme::buttonHoveredColor(); + } + + // Pressed + QString buttonPressedColor() const override + { + return OCC::IonosTheme::buttonPressedColor(); + } + + QString buttonPressedBorderColor() const override + { + return OCC::IonosTheme::buttonPressedColor(); + } + + // Disabled + QString buttonDisabledColor() const override + { + return OCC::IonosTheme::buttonDisabledColor(); + } + + QString buttonDisabledBorderColor() const override + { + return OCC::IonosTheme::buttonDisabledColor(); + } + + // Focused + QString buttonFocusedColor() const override + { + return OCC::IonosTheme::white(); + } + + QString buttonFocusedBorderColor() const override + { + return OCC::IonosTheme::black(); + } + + // Font + QString buttonDisabledFontColor() const override + { + return OCC::IonosTheme::buttonDisabledFontColor(); + } + + QString buttonFontColor() const override + { + return OCC::IonosTheme::black(); + } +}; +} + +#endif // _BUTTONSTYLE_H diff --git a/src/gui/buttonstylestrategy.h b/src/gui/buttonstylestrategy.h new file mode 100644 index 0000000000000..c6a316d25d3de --- /dev/null +++ b/src/gui/buttonstylestrategy.h @@ -0,0 +1,60 @@ +#ifndef BUTTONSTYLESTRATEGY_H +#define BUTTONSTYLESTRATEGY_H + +#include "buttonstyle.h" +#include +#include + + +class ButtonStyleStrategy +{ +public: + virtual ~ButtonStyleStrategy() = default; + + static OCC::ButtonStyle& getButtonStyle(const QWidget *widget, const QStyleOptionButton *option) + { + OCC::ButtonStyleName buttonStyleName; + if(widget != nullptr) + { + buttonStyleName = determineButtonStyleName(widget, option); + } + else + { + buttonStyleName = OCC::ButtonStyleName::Secondary; + } + + switch (buttonStyleName) + { + case OCC::ButtonStyleName::MoreOptions: + return OCC::MoreOptionsButtonStyle::GetInstance(); + case OCC::ButtonStyleName::Primary: + return OCC::PrimaryButtonStyle::GetInstance(); + case OCC::ButtonStyleName::Secondary: + default: + return OCC::SecondaryButtonStyle::GetInstance(); + } + } + + static OCC::ButtonStyleName determineButtonStyleName(const QWidget *widget, const QStyleOptionButton *option) + { + QVariant propertyValue = widget->property("buttonStyle"); + if(propertyValue.isValid()){ + + return propertyValue.value(); + } + + return getButtonStyleNameByObjectName(widget); + } + + static OCC::ButtonStyleName getButtonStyleNameByObjectName(const QWidget *widget) + { + static const QMap buttonStyleMap = { + {"qt_wizard_finish", OCC::ButtonStyleName::Primary} + }; + + QString buttonName = widget->objectName(); + return buttonStyleMap.value(buttonName, OCC::ButtonStyleName::Secondary); + } +}; + +#endif // BUTTONSTYLESTRATEGY_H \ No newline at end of file diff --git a/src/gui/callstatechecker.cpp b/src/gui/callstatechecker.cpp index 242c5b0aba15b..875bf98033803 100644 --- a/src/gui/callstatechecker.cpp +++ b/src/gui/callstatechecker.cpp @@ -21,7 +21,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcCallStateChecker, "nextcloud.gui.callstatechecker", QtInfoMsg) +Q_LOGGING_CATEGORY(lcCallStateChecker, "hidrivenext.gui.callstatechecker", QtInfoMsg) constexpr int successStatusCode = 200; diff --git a/src/gui/caseclashfilenamedialog.cpp b/src/gui/caseclashfilenamedialog.cpp index 27ca251a69911..423902ee8366e 100644 --- a/src/gui/caseclashfilenamedialog.cpp +++ b/src/gui/caseclashfilenamedialog.cpp @@ -19,6 +19,7 @@ #include "account.h" #include "folder.h" +#include "buttonstyle.h" #include #include @@ -66,7 +67,7 @@ QString caseClashIllegalCharacterListToString(const QVector &illegalChara namespace OCC { -Q_LOGGING_CATEGORY(lcCaseClashConflictFialog, "nextcloud.sync.caseclash.dialog", QtInfoMsg) +Q_LOGGING_CATEGORY(lcCaseClashConflictFialog, "hidrivenext.sync.caseclash.dialog", QtInfoMsg) CaseClashFilenameDialog::CaseClashFilenameDialog(AccountPtr account, Folder *folder, @@ -83,6 +84,8 @@ CaseClashFilenameDialog::CaseClashFilenameDialog(AccountPtr account, Q_ASSERT(_account); Q_ASSERT(_folder); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + const auto filePathFileInfo = QFileInfo(_filePath); const auto conflictFileName = filePathFileInfo.fileName(); @@ -98,6 +101,7 @@ CaseClashFilenameDialog::CaseClashFilenameDialog(AccountPtr account, _ui->setupUi(this); _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); _ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Rename file")); + _ui->buttonBox->button(QDialogButtonBox::Ok)->setProperty("buttonStyle", QVariant::fromValue(ButtonStyleName::Primary)); _ui->descriptionLabel->setText(tr("The file \"%1\" could not be synced because of a case clash conflict with an existing file on this system.").arg(_originalFileName)); _ui->explanationLabel->setText(tr("%1 does not support equal file names with only letter casing differences.").arg(QSysInfo::prettyProductName())); @@ -149,10 +153,13 @@ CaseClashFilenameDialog::CaseClashFilenameDialog(AccountPtr account, _ui->buttonBox->setStandardButtons(_ui->buttonBox->standardButtons() &~ QDialogButtonBox::No); if (_conflictSolver.allowedToRename()) { _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + _ui->buttonBox->button(QDialogButtonBox::Ok)->setProperty("buttonStyle", QVariant::fromValue(ButtonStyleName::Primary)); + _ui->filenameLineEdit->setEnabled(true); _ui->filenameLineEdit->selectAll(); } else { _ui->buttonBox->setStandardButtons(_ui->buttonBox->standardButtons() | QDialogButtonBox::No); + _ui->buttonBox->button(QDialogButtonBox::Ok)->setProperty("buttonStyle", QVariant::fromValue(ButtonStyleName::Primary)); } }); @@ -166,6 +173,8 @@ CaseClashFilenameDialog::CaseClashFilenameDialog(AccountPtr account, }); checkIfAllowedToRename(); + + customizeStyle(); } CaseClashFilenameDialog::~CaseClashFilenameDialog() = default; @@ -283,5 +292,43 @@ void CaseClashFilenameDialog::onFilenameLineEditTextChanged(const QString &text) _ui->buttonBox->button(QDialogButtonBox::Ok) ->setEnabled(isTextValid); + _ui->buttonBox->button(QDialogButtonBox::Ok)->setProperty("buttonStyle", QVariant::fromValue(ButtonStyleName::Primary)); + } + +void CaseClashFilenameDialog::customizeStyle() +{ + this->setStyleSheet( + QStringLiteral("QDialog {background-color: %1; color: %2;} QLabel{ %3;}").arg( + IonosTheme::dialogBackgroundColor(), + IonosTheme::black(), + IonosTheme::fontConfigurationCss( + IonosTheme::settingsFont(), + IonosTheme::settingsTextSize(), + IonosTheme::settingsTextWeight(), + IonosTheme::titleColor() + ) + ) + ); + + _ui->filenameLineEdit->setStyleSheet( + QStringLiteral( + "color: %1; font-family: %2; font-size: %3; font-weight: %4; border-radius: %5; border: 1px " + "solid %6; padding: 0px 12px; text-align: left; vertical-align: middle; height: 40px; background: %7; ").arg( + IonosTheme::folderWizardPathColor(), + IonosTheme::settingsFont(), + IonosTheme::settingsTextSize(), + IonosTheme::settingsTextWeight(), + IonosTheme::buttonRadius(), + IonosTheme::menuBorderColor(), + IonosTheme::white() + ) + ); + + #ifdef Q_OS_MAC + _ui->buttonBox->layout()->setSpacing(24); + _ui->buttonBox->setLayoutDirection(Qt::LeftToRight); + #endif +} + } diff --git a/src/gui/caseclashfilenamedialog.h b/src/gui/caseclashfilenamedialog.h index 6608cb80102bc..feb88d43075ac 100644 --- a/src/gui/caseclashfilenamedialog.h +++ b/src/gui/caseclashfilenamedialog.h @@ -79,5 +79,7 @@ private slots: QString _relativeFilePath; QString _originalFileName; QString _newFilename; + + void customizeStyle(); }; } diff --git a/src/gui/caseclashfilenamedialog.ui b/src/gui/caseclashfilenamedialog.ui index a5944fc709886..59c22cf3ebb23 100644 --- a/src/gui/caseclashfilenamedialog.ui +++ b/src/gui/caseclashfilenamedialog.ui @@ -117,9 +117,12 @@ - - Open existing file - + + Open existing file + + + true + @@ -223,9 +226,12 @@ - - Open clashing file - + + Open clashing file + + + true + diff --git a/src/gui/conflictdialog.cpp b/src/gui/conflictdialog.cpp index bae7b190f957a..58170cf8b84f9 100644 --- a/src/gui/conflictdialog.cpp +++ b/src/gui/conflictdialog.cpp @@ -16,6 +16,7 @@ #include "ui_conflictdialog.h" #include "conflictsolver.h" +#include "buttonstyle.h" #include #include @@ -50,8 +51,10 @@ ConflictDialog::ConflictDialog(QWidget *parent) { _ui->setupUi(this); forceHeaderFont(_ui->conflictMessage); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); _ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); _ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Keep selected version")); + _ui->buttonBox->button(QDialogButtonBox::Ok)->setProperty("buttonStyle", QVariant::fromValue(ButtonStyleName::Primary)); _ui->conflictMessage->setTextFormat(Qt::PlainText); @@ -67,6 +70,8 @@ ConflictDialog::ConflictDialog(QWidget *parent) connect(_solver, &ConflictSolver::localVersionFilenameChanged, this, &ConflictDialog::updateWidgets); connect(_solver, &ConflictSolver::remoteVersionFilenameChanged, this, &ConflictDialog::updateWidgets); + + customizeStyle(); } QString ConflictDialog::baseFilename() const @@ -178,6 +183,37 @@ void ConflictDialog::updateButtonStates() : isRemotePicked ? tr("Keep server version") : tr("Keep selected version"); _ui->buttonBox->button(QDialogButtonBox::Ok)->setText(text); + _ui->buttonBox->button(QDialogButtonBox::Ok)->setProperty("buttonStyle", QVariant::fromValue(ButtonStyleName::Primary)); + +} + +void ConflictDialog::customizeStyle() +{ + this->setStyleSheet( + QStringLiteral("QDialog {background-color: %1; color: %2;} QLabel{ %3;}").arg( + IonosTheme::dialogBackgroundColor(), + IonosTheme::black(), + IonosTheme::fontConfigurationCss( + IonosTheme::settingsFont(), + IonosTheme::settingsTextSize(), + IonosTheme::settingsTextWeight(), + IonosTheme::titleColor() + ) + ) + ); + + #ifdef Q_OS_MAC + _ui->buttonBox->layout()->setSpacing(24); + _ui->buttonBox->setLayoutDirection(Qt::LeftToRight); + + _ui->localVersionRadio->setStyleSheet( + QStringLiteral("QCheckBox {color: %1;}").arg(IonosTheme::black()) + ); + + _ui->remoteVersionRadio->setStyleSheet( + QStringLiteral("QCheckBox {color: %1;}").arg(IonosTheme::black()) + ); + #endif } } // namespace OCC diff --git a/src/gui/conflictdialog.h b/src/gui/conflictdialog.h index b102b8de7797f..3ec4de41436ec 100644 --- a/src/gui/conflictdialog.h +++ b/src/gui/conflictdialog.h @@ -46,7 +46,8 @@ public slots: private: void updateWidgets(); void updateButtonStates(); - + void customizeStyle(); + QString _baseFilename; QScopedPointer _ui; ConflictSolver *_solver; diff --git a/src/gui/conflictsolver.cpp b/src/gui/conflictsolver.cpp index 6a1de573c1e73..ed0ffd714dee4 100644 --- a/src/gui/conflictsolver.cpp +++ b/src/gui/conflictsolver.cpp @@ -22,7 +22,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcConflict, "nextcloud.gui.conflictsolver", QtInfoMsg) +Q_LOGGING_CATEGORY(lcConflict, "hidrivenext.gui.conflictsolver", QtInfoMsg) ConflictSolver::ConflictSolver(QWidget *parent) : QObject(parent) diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index 9cc24de4a7521..1a9610730d097 100644 --- a/src/gui/connectionvalidator.cpp +++ b/src/gui/connectionvalidator.cpp @@ -31,7 +31,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcConnectionValidator, "nextcloud.sync.connectionvalidator", QtInfoMsg) +Q_LOGGING_CATEGORY(lcConnectionValidator, "hidrivenext.sync.connectionvalidator", QtInfoMsg) // Make sure the timeout for this job is less than how often we get called // This makes sure we get tried often enough without "ConnectionValidator already running" diff --git a/src/gui/creds/credentialsfactory.cpp b/src/gui/creds/credentialsfactory.cpp index 894eaeee6a92f..2925e5a3ade75 100644 --- a/src/gui/creds/credentialsfactory.cpp +++ b/src/gui/creds/credentialsfactory.cpp @@ -22,7 +22,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcGuiCredentials, "nextcloud.gui.credentials", QtInfoMsg) +Q_LOGGING_CATEGORY(lcGuiCredentials, "hidrivenext.gui.credentials", QtInfoMsg) namespace CredentialsFactory { diff --git a/src/gui/creds/flow2auth.cpp b/src/gui/creds/flow2auth.cpp index f90873dbc6793..80817da4039be 100644 --- a/src/gui/creds/flow2auth.cpp +++ b/src/gui/creds/flow2auth.cpp @@ -29,7 +29,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcFlow2auth, "nextcloud.sync.credentials.flow2auth", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFlow2auth, "hidrivenext.sync.credentials.flow2auth", QtInfoMsg) Flow2Auth::Flow2Auth(Account *account, QObject *parent) diff --git a/src/gui/creds/httpcredentialsgui.cpp b/src/gui/creds/httpcredentialsgui.cpp index 4e1d8ef1e630e..ee04f787dc23f 100644 --- a/src/gui/creds/httpcredentialsgui.cpp +++ b/src/gui/creds/httpcredentialsgui.cpp @@ -30,7 +30,7 @@ using namespace QKeychain; namespace OCC { -Q_LOGGING_CATEGORY(lcHttpCredentialsGui, "nextcloud.sync.credentials.http.gui", QtInfoMsg) +Q_LOGGING_CATEGORY(lcHttpCredentialsGui, "hidrivenext.sync.credentials.http.gui", QtInfoMsg) void HttpCredentialsGui::askFromUser() { diff --git a/src/gui/creds/webflowcredentials.cpp b/src/gui/creds/webflowcredentials.cpp index 037485913d266..6d168f3cfbd05 100644 --- a/src/gui/creds/webflowcredentials.cpp +++ b/src/gui/creds/webflowcredentials.cpp @@ -25,7 +25,7 @@ using namespace QKeychain; namespace OCC { -Q_LOGGING_CATEGORY(lcWebFlowCredentials, "nextcloud.sync.credentials.webflow", QtInfoMsg) +Q_LOGGING_CATEGORY(lcWebFlowCredentials, "hidrivenext.sync.credentials.webflow", QtInfoMsg) namespace { const char userC[] = "user"; @@ -163,8 +163,8 @@ void WebFlowCredentials::askFromUser() { _askDialog->setUrl(url); } - QString msg = tr("You have been logged out of your account %1 at %2. Please login again.") - .arg(_account->prettyName(), _account->url().toDisplayString()); + QString msg = tr("You have been logged out of your account %1 at %2. Please login again.") + .arg(_account->eliedName(200), _account->url().toDisplayString()); _askDialog->setInfo(msg); _askDialog->show(); diff --git a/src/gui/creds/webflowcredentialsdialog.cpp b/src/gui/creds/webflowcredentialsdialog.cpp index 4ec6747fc6ace..35284b294388f 100644 --- a/src/gui/creds/webflowcredentialsdialog.cpp +++ b/src/gui/creds/webflowcredentialsdialog.cpp @@ -7,6 +7,7 @@ #include "application.h" #include "owncloudgui.h" #include "wizard/owncloudwizardcommon.h" +#include "ionostheme.h" #ifdef WITH_WEBENGINE #include "wizard/webview.h" #endif // WITH_WEBENGINE @@ -20,6 +21,11 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlo { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setStyleSheet(QStringLiteral("QDialog { background-color: %1; }").arg(IonosTheme::dialogBackgroundColor())); + + setFixedWidth(646); + setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); + _layout = new QVBoxLayout(this); int spacing = _layout->spacing(); int margin = _layout->margin(); @@ -31,12 +37,24 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlo _containerLayout->setMargin(margin); _infoLabel = new QLabel(); - _infoLabel->setTextFormat(Qt::PlainText); + _infoLabel->setTextFormat(Qt::RichText); _infoLabel->setAlignment(Qt::AlignCenter); + _infoLabel->setWordWrap(true); + _infoLabel->setContentsMargins(0, 32, 0, 0); + _infoLabel->setStyleSheet(IonosTheme::fontConfigurationCss( + IonosTheme::settingsFont(), + IonosTheme::settingsTextSize(), + IonosTheme::settingsTitleWeight600(), + IonosTheme::titleColor() + )); _containerLayout->addWidget(_infoLabel); + layout()->setSizeConstraint(QLayout::SetFixedSize); + if (_useFlow2) { _flow2AuthWidget = new Flow2AuthWidget(); + _flow2AuthWidget->shrinkTopMarginForText(); + _containerLayout->addWidget(_flow2AuthWidget); connect(_flow2AuthWidget, &Flow2AuthWidget::authResult, this, &WebFlowCredentialsDialog::slotFlow2AuthResult); @@ -113,7 +131,7 @@ void WebFlowCredentialsDialog::setError(const QString &error) { slotShowSettingsDialog(); if (_useFlow2 && _flow2AuthWidget) { - _flow2AuthWidget->setError(error); + _flow2AuthWidget->setError("Error", error); return; } diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index f4109a970d8c4..5d14f0ec234ef 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -26,7 +26,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcEditLocallyJob, "nextcloud.gui.editlocallyjob", QtInfoMsg) +Q_LOGGING_CATEGORY(lcEditLocallyJob, "hidrivenext.gui.editlocallyjob", QtInfoMsg) EditLocallyJob::EditLocallyJob(const QString &userId, const QString &relPath, diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index cd6ade7d31874..54693d913f594 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -19,7 +19,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcEditLocallyManager, "nextcloud.gui.editlocallymanager", QtInfoMsg) +Q_LOGGING_CATEGORY(lcEditLocallyManager, "hidrivenext.gui.editlocallymanager", QtInfoMsg) EditLocallyManager *EditLocallyManager::_instance = nullptr; diff --git a/src/gui/fileactivitylistmodel.cpp b/src/gui/fileactivitylistmodel.cpp index 4e4c3c2203a00..5fcdec7267943 100644 --- a/src/gui/fileactivitylistmodel.cpp +++ b/src/gui/fileactivitylistmodel.cpp @@ -18,7 +18,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcFileActivityListModel, "nextcloud.gui.fileactivitylistmodel", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFileActivityListModel, "hidrivenext.gui.fileactivitylistmodel", QtInfoMsg) FileActivityListModel::FileActivityListModel(QObject *parent) : ActivityListModel(nullptr, parent) diff --git a/src/gui/filedetails/FileActivityView.qml b/src/gui/filedetails/FileActivityView.qml index 002654b7955bc..4bbedff9e7785 100644 --- a/src/gui/filedetails/FileActivityView.qml +++ b/src/gui/filedetails/FileActivityView.qml @@ -16,7 +16,7 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 import "../tray" diff --git a/src/gui/filedetails/FileDetailsPage.qml b/src/gui/filedetails/FileDetailsPage.qml index 5008017a8efe4..2e91913dff290 100644 --- a/src/gui/filedetails/FileDetailsPage.qml +++ b/src/gui/filedetails/FileDetailsPage.qml @@ -16,7 +16,7 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 import "../tray" @@ -45,6 +45,10 @@ Page { localPath: root.localPath } + font.family: Style.sesOpenSansRegular + font.pixelSize: Style.sesFontPixelSize + font.weight: Style.sesFontNormalWeight + Connections { target: Systray function onShowFileDetailsPage(fileLocalPath, page) { @@ -74,7 +78,7 @@ Page { } header: ColumnLayout { - spacing: root.intendedPadding + spacing: Style.sesMediumMargin GridLayout { id: headerGridLayout @@ -107,15 +111,15 @@ Page { id: fileIcon Layout.rowSpan: headerGridLayout.rows - Layout.preferredWidth: Style.trayListItemIconSize - Layout.leftMargin: root.intendedPadding + Layout.preferredWidth: Style.sesFileDetailsIconSize + Layout.leftMargin: Style.sesMediumMargin Layout.fillHeight: true verticalAlignment: Image.AlignVCenter horizontalAlignment: Image.AlignHCenter source: root.fileDetails.iconUrl - sourceSize.width: Style.trayListItemIconSize - sourceSize.height: Style.trayListItemIconSize + sourceSize.width: Style.sesFileDetailsIconSize + sourceSize.height: Style.sesFileDetailsIconSize fillMode: Image.PreserveAspectFit } @@ -123,10 +127,13 @@ Page { id: fileNameLabel Layout.fillWidth: true - Layout.rightMargin: headerGridLayout.textRightMargin + Layout.rightMargin: Style.sesFileDetailsHeaderModifier text: root.fileDetails.name - font.bold: true + + font.pixelSize: Style.sesFontPixelSizeTitle + font.weight: Style.sesFontBoldWeight + wrapMode: Text.Wrap } @@ -143,6 +150,9 @@ Page { bgNormalOpacity: 0 toolTipText: qsTr("Dismiss") + font.pixelSize: Style.sesFontPixelSize + font.weight: Style.sesFontNormalWeight + visible: root.showCloseButton onClicked: root.closeButtonClicked() @@ -152,10 +162,13 @@ Page { id: fileDetailsLabel Layout.fillWidth: true - Layout.rightMargin: headerGridLayout.textRightMargin + Layout.rightMargin: Style.sesFileDetailsHeaderModifier + + text: `${root.fileDetails.sizeString}, ${root.fileDetails.lastChangedString}` + + font.pixelSize: Style.sesFontHintPixelSize + font.weight: Style.sesFontNormalWeight - text: `${root.fileDetails.sizeString} · ${root.fileDetails.lastChangedString}` - color: palette.midlight wrapMode: Text.Wrap } @@ -169,6 +182,9 @@ Page { color: palette.midlight wrapMode: Text.Wrap visible: headerGridLayout.showFileLockedString + + font.pixelSize: Style.sesFontHintPixelSize + font.weight: Style.sesFontNormalWeight } Row { @@ -211,39 +227,13 @@ Page { NCToolTip { visible: hoverHandler.hovered text: tagRepeater.fileTagModel.overflowTagsString + + font.pixelSize: Style.sesFontPixelSize + font.weight: Style.sesFontNormalWeight } } } } - - TabBar { - id: viewBar - - Layout.leftMargin: root.intendedPadding - Layout.rightMargin: root.intendedPadding - - padding: 0 - background: null - - NCTabButton { - svgCustomColorSource: "image://svgimage-custom-color/activity.svg" - text: qsTr("Activity") - accentColor: root.accentColor - checked: swipeView.currentIndex === fileActivityView.swipeIndex - onClicked: swipeView.currentIndex = fileActivityView.swipeIndex - } - - NCTabButton { - width: visible ? implicitWidth : 0 - height: visible ? implicitHeight : 0 - svgCustomColorSource: "image://svgimage-custom-color/share.svg" - text: qsTr("Sharing") - accentColor: root.accentColor - checked: swipeView.currentIndex === shareViewLoader.swipeIndex - onClicked: swipeView.currentIndex = shareViewLoader.swipeIndex - visible: root.fileDetails.sharingAvailable - } - } } SwipeView { @@ -252,18 +242,6 @@ Page { anchors.fill: parent clip: true - FileActivityView { - id: fileActivityView - - readonly property int swipeIndex: SwipeView.index - - delegateHorizontalPadding: root.intendedPadding - - accountState: root.accountState - localPath: root.localPath - iconSize: root.iconSize - } - Loader { id: shareViewLoader diff --git a/src/gui/filedetails/FileDetailsView.qml b/src/gui/filedetails/FileDetailsView.qml index 5fae863d4f6d0..35981f15fb0f1 100644 --- a/src/gui/filedetails/FileDetailsView.qml +++ b/src/gui/filedetails/FileDetailsView.qml @@ -16,7 +16,7 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 StackView { diff --git a/src/gui/filedetails/FileDetailsWindow.qml b/src/gui/filedetails/FileDetailsWindow.qml index abcf3e850f90e..152b4663873f9 100644 --- a/src/gui/filedetails/FileDetailsWindow.qml +++ b/src/gui/filedetails/FileDetailsWindow.qml @@ -17,7 +17,7 @@ import QtQuick.Window 2.15 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 ApplicationWindow { diff --git a/src/gui/filedetails/NCInputDateField.qml b/src/gui/filedetails/NCInputDateField.qml index 4c0ea491c7fe4..bca7c701ff00f 100644 --- a/src/gui/filedetails/NCInputDateField.qml +++ b/src/gui/filedetails/NCInputDateField.qml @@ -14,7 +14,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 NCInputTextField { id: root diff --git a/src/gui/filedetails/NCInputTextEdit.qml b/src/gui/filedetails/NCInputTextEdit.qml index 3685b1f9deaa7..e65186b73815e 100644 --- a/src/gui/filedetails/NCInputTextEdit.qml +++ b/src/gui/filedetails/NCInputTextEdit.qml @@ -16,7 +16,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 TextEdit { diff --git a/src/gui/filedetails/NCInputTextField.qml b/src/gui/filedetails/NCInputTextField.qml index fa4bef0bfc8a2..ad92be0ac5edb 100644 --- a/src/gui/filedetails/NCInputTextField.qml +++ b/src/gui/filedetails/NCInputTextField.qml @@ -16,7 +16,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 TextField { diff --git a/src/gui/filedetails/NCTabButton.qml b/src/gui/filedetails/NCTabButton.qml index 8c6c7426ff5ee..b52e5a0d49b07 100644 --- a/src/gui/filedetails/NCTabButton.qml +++ b/src/gui/filedetails/NCTabButton.qml @@ -17,7 +17,7 @@ import QtQuick.Window 2.15 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 import "../tray" diff --git a/src/gui/filedetails/ShareDelegate.qml b/src/gui/filedetails/ShareDelegate.qml index baf378fe8ce6f..b4983329d1ba9 100644 --- a/src/gui/filedetails/ShareDelegate.qml +++ b/src/gui/filedetails/ShareDelegate.qml @@ -18,7 +18,7 @@ import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import QtGraphicalEffects 1.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 import "../tray" import "../" @@ -50,7 +50,7 @@ GridLayout { property FileDetails fileDetails: FileDetails {} property StackView rootStackView: StackView {} property bool backgroundsVisible: true - property color accentColor: Style.ncBlue + property color accentColor: Style.sesIconColor property bool canCreateLinkShares: true property bool serverAllowsResharing: true @@ -165,7 +165,7 @@ GridLayout { bgColor: palette.highlight bgNormalOpacity: 0 - icon.source: "image://svgimage-custom-color/add.svg/" + palette.buttonText + icon.source: Style.sesDarkPlus + palette.buttonText icon.width: Style.smallIconSize icon.height: Style.smallIconSize @@ -197,13 +197,14 @@ GridLayout { toolTipText: qsTr("Copy share link location") text: shareLinkCopied ? qsTr("Copied!") : "" - textColor: palette.brightText - contentsFont.bold: true - bgColor: shareLinkCopied ? Style.positiveColor : palette.highlight - bgNormalOpacity: shareLinkCopied ? 1 : 0 + textColor: Style.sesDarkGreen + + bgColor: palette.highlight + bgNormalOpacity: 0 + + icon.source: shareLinkCopied ? Style.sesSyncSuccessIcon + Style.positiveColor : + Style.sesClipboard + palette.brightText - icon.source: shareLinkCopied ? "image://svgimage-custom-color/copy.svg/" + palette.brightText : - "image://svgimage-custom-color/copy.svg/" + palette.buttonText icon.width: Style.smallIconSize icon.height: Style.smallIconSize @@ -212,14 +213,6 @@ GridLayout { onClicked: copyShareLink() - Behavior on bgColor { - ColorAnimation { duration: Style.shortAnimationDuration } - } - - Behavior on bgNormalOpacity { - NumberAnimation { duration: Style.shortAnimationDuration } - } - Behavior on Layout.preferredWidth { SmoothedAnimation { duration: Style.shortAnimationDuration } } @@ -239,6 +232,9 @@ GridLayout { CustomButton { id: moreButton + property bool isHovered: moreButton.hovered || moreButton.visualFocus + property bool isActive: moreButton.pressed + Layout.alignment: Qt.AlignCenter Layout.preferredWidth: Style.iconButtonWidth Layout.preferredHeight: width @@ -248,13 +244,20 @@ GridLayout { bgColor: palette.highlight bgNormalOpacity: 0 - icon.source: "image://svgimage-custom-color/more.svg/" + palette.buttonText + icon.source: "image://svgimage-custom-color/more.svg/" + (moreButton.isActive || moreButton.isHovered ? Style.sesWhite : Style.sesIconColor) icon.width: Style.smallIconSize icon.height: Style.smallIconSize visible: !root.isPlaceholderLinkShare && !root.isSecureFileDropPlaceholderLinkShare && !root.isInternalLinkShare enabled: visible + background: Rectangle { + anchors.fill: parent + anchors.margins: 1 + color: moreButton.isActive ? Style.sesActionPressed : moreButton.isHovered ? Style.sesActionHover : "transparent" + radius: width / 2 + } + onClicked: root.rootStackView.push(shareDetailsPageComponent, {}, StackView.PushTransition) Component { diff --git a/src/gui/filedetails/ShareDetailsPage.qml b/src/gui/filedetails/ShareDetailsPage.qml index 15ffbe61a14b4..b694045ba1eba 100644 --- a/src/gui/filedetails/ShareDetailsPage.qml +++ b/src/gui/filedetails/ShareDetailsPage.qml @@ -18,9 +18,10 @@ import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import QtGraphicalEffects 1.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 import "../tray" +import "../SesComponents/" import "../" Page { @@ -43,6 +44,11 @@ Page { signal setPassword(string password) signal setNote(string note) + + font.family: Style.sesOpenSansRegular + font.pixelSize: Style.sesFontPixelSize + font.weight: Style.sesFontNormalWeight + property bool backgroundsVisible: true property color accentColor: Style.ncBlue @@ -80,6 +86,7 @@ Page { readonly property bool isHideDownloadInProgress: shareModelData.isHideDownloadInProgress readonly property int currentPermissionMode: shareModelData.currentPermissionMode + readonly property bool isInternalShare: shareModelData.shareType === ShareModel.ShareTypeUser || shareModelData.shareType === ShareModel.ShareTypeGroup || shareModelData.shareType === ShareModel.ShareTypeCircle readonly property bool isLinkShare: shareModelData.shareType === ShareModel.ShareTypeLink readonly property bool isEmailShare: shareModelData.shareType === ShareModel.ShareTypeEmail readonly property bool shareSupportsPassword: isLinkShare || isEmailShare @@ -95,6 +102,16 @@ Page { property bool waitingForPasswordChange: false property bool waitingForNoteChange: false + readonly property int titlePixelSize: Style.sesFontPixelSizeTitle + readonly property int titleFontWeight: Style.sesFontNormalWeight + + readonly property int hintPixelSize: Style.sesFontHintPixelSize + readonly property int hintFontWeight: Style.sesFontNormalWeight + + + readonly property int pixelSize: Style.sesFontPixelSize + readonly property int fontWeight: Style.sesFontNormalWeight + function showPasswordSetError(message) { passwordErrorBoxLoader.message = message !== "" ? message : qsTr("An error occurred setting the share password."); @@ -106,8 +123,8 @@ Page { } function resetLinkShareLabelField() { - linkShareLabelTextField.text = linkShareLabel; - waitingForLinkShareLabelChange = false; + // linkShareLabelTextField.text = linkShareLabel; + // waitingForLinkShareLabelChange = false; } function resetPasswordField() { @@ -211,17 +228,21 @@ Page { } EnforcedPlainTextLabel { - id: headLabel + id: fileNameLabel Layout.fillWidth: true + Layout.rightMargin: headerGridLayout.textRightMargin + + text: root.fileDetails.name - text: qsTr("Edit share") - font.bold: true - elide: Text.ElideRight + font.pixelSize: titlePixelSize + font.weight: titleFontWeight + + wrapMode: Text.Wrap } CustomButton { - id: closeButton + id: placeholder Layout.rowSpan: headerGridLayout.rows Layout.preferredWidth: Style.iconButtonWidth @@ -233,18 +254,24 @@ Page { bgNormalOpacity: 0 toolTipText: qsTr("Dismiss") + font.pixelSize: pixelSize + font.weight: fontWeight + + onClicked: root.closeShareDetails() } EnforcedPlainTextLabel { - id: secondaryLabel + id: fileDetailsLabel Layout.fillWidth: true - Layout.rightMargin: root.padding + Layout.rightMargin: headerGridLayout.textRightMargin - text: root.fileDetails.name - color: palette.midlight + text: `${root.fileDetails.sizeString}, ${root.fileDetails.lastChangedString}` wrapMode: Text.Wrap + + font.pixelSize: hintPixelSize + font.weight: hintFontWeight } } } @@ -258,277 +285,13 @@ Page { readonly property int rowIconWidth: Style.smallIconSize readonly property int indicatorItemWidth: 20 - readonly property int indicatorSpacing: Style.standardSpacing - readonly property int itemPadding: Style.smallSpacing + readonly property int indicatorSpacing: Style.sesSmallMargin + readonly property int itemPadding: Style.sesSmallMargin width: parent.width - RowLayout { - Layout.fillWidth: true - height: visible ? implicitHeight : 0 - spacing: scrollContentsColumn.indicatorSpacing - - visible: root.isLinkShare - - Image { - Layout.preferredWidth: scrollContentsColumn.indicatorItemWidth - Layout.fillHeight: true - - verticalAlignment: Image.AlignVCenter - horizontalAlignment: Image.AlignHCenter - fillMode: Image.Pad - - source: "image://svgimage-custom-color/edit.svg/" + palette.dark - sourceSize.width: scrollContentsColumn.rowIconWidth - sourceSize.height: scrollContentsColumn.rowIconWidth - } - - NCInputTextField { - id: linkShareLabelTextField - - Layout.fillWidth: true - height: visible ? implicitHeight : 0 - - text: root.linkShareLabel - placeholderText: qsTr("Share label") - - enabled: root.isLinkShare && - !root.waitingForLinkShareLabelChange - - onAccepted: if(text !== root.linkShareLabel) { - root.setLinkShareLabel(text); - root.waitingForLinkShareLabelChange = true; - } - - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForLinkShareLabelChange - running: visible - z: 1 - } - } - } - - Loader { - Layout.fillWidth: true - active: !root.isFolderItem && !root.isEncryptedItem - visible: active - sourceComponent: CheckBox { - // TODO: Rather than setting all these palette colours manually, - // create a custom style and do it for all components globally. - // - // Additionally, we need to override the entire palette when we - // set one palette property, as otherwise we default back to the - // theme palette -- not the parent palette - palette { - text: Style.ncTextColor - windowText: Style.ncTextColor - buttonText: Style.ncTextColor - brightText: Style.ncTextBrightColor - highlight: Style.lightHover - highlightedText: Style.ncTextColor - light: Style.lightHover - midlight: Style.ncSecondaryTextColor - mid: Style.darkerHover - dark: Style.menuBorder - button: Style.buttonBackgroundColor - window: Style.menuBorder - base: Style.backgroundColor - toolTipBase: Style.backgroundColor - toolTipText: Style.ncTextColor - } - - spacing: scrollContentsColumn.indicatorSpacing - padding: scrollContentsColumn.itemPadding - indicator.width: scrollContentsColumn.indicatorItemWidth - indicator.height: scrollContentsColumn.indicatorItemWidth - - checkable: true - checked: root.editingAllowed - text: qsTr("Allow upload and editing") - enabled: !root.isSharePermissionChangeInProgress - - onClicked: root.toggleAllowEditing(checked) - - NCBusyIndicator { - anchors.fill: parent - visible: root.isSharePermissionChangeInProgress - running: visible - z: 1 - } - } - } - - Loader { - Layout.fillWidth: true - active: root.isFolderItem && !root.isEncryptedItem - visible: active - sourceComponent: ColumnLayout { - id: permissionRadioButtonsLayout - spacing: 0 - width: parent.width - - ButtonGroup { - id: permissionModeRadioButtonsGroup - } - - NCRadioButton { - readonly property int permissionMode: ShareModel.ModeViewOnly - Layout.fillWidth: true - ButtonGroup.group: permissionModeRadioButtonsGroup - enabled: !root.isSharePermissionChangeInProgress - checked: root.currentPermissionMode === permissionMode - text: qsTr("View only") - indicatorItemWidth: scrollContentsColumn.indicatorItemWidth - indicatorItemHeight: scrollContentsColumn.indicatorItemWidth - spacing: scrollContentsColumn.indicatorSpacing - padding: scrollContentsColumn.itemPadding - onClicked: root.permissionModeChanged(permissionMode) - } - - NCRadioButton { - readonly property int permissionMode: ShareModel.ModeUploadAndEditing - Layout.fillWidth: true - ButtonGroup.group: permissionModeRadioButtonsGroup - enabled: !root.isSharePermissionChangeInProgress - checked: root.currentPermissionMode === permissionMode - text: qsTr("Allow upload and editing") - indicatorItemWidth: scrollContentsColumn.indicatorItemWidth - indicatorItemHeight: scrollContentsColumn.indicatorItemWidth - spacing: scrollContentsColumn.indicatorSpacing - padding: scrollContentsColumn.itemPadding - onClicked: root.permissionModeChanged(permissionMode) - } - - NCRadioButton { - readonly property int permissionMode: ShareModel.ModeFileDropOnly - Layout.fillWidth: true - ButtonGroup.group: permissionModeRadioButtonsGroup - enabled: !root.isSharePermissionChangeInProgress - checked: root.currentPermissionMode === permissionMode - text: qsTr("File drop (upload only)") - indicatorItemWidth: scrollContentsColumn.indicatorItemWidth - indicatorItemHeight: scrollContentsColumn.indicatorItemWidth - spacing: scrollContentsColumn.indicatorSpacing - padding: scrollContentsColumn.itemPadding - onClicked: root.permissionModeChanged(permissionMode) - } - - CheckBox { - id: allowResharingCheckBox - - Layout.fillWidth: true - - // TODO: Rather than setting all these palette colours manually, - // create a custom style and do it for all components globally. - // - // Additionally, we need to override the entire palette when we - // set one palette property, as otherwise we default back to the - // theme palette -- not the parent palette - palette { - text: Style.ncTextColor - windowText: Style.ncTextColor - buttonText: Style.ncTextColor - brightText: Style.ncTextBrightColor - highlight: Style.lightHover - highlightedText: Style.ncTextColor - light: Style.lightHover - midlight: Style.ncSecondaryTextColor - mid: Style.darkerHover - dark: Style.menuBorder - button: Style.buttonBackgroundColor - window: palette.dark // NOTE: Fusion theme uses darker window colour for the border of the checkbox - base: Style.backgroundColor - toolTipBase: Style.backgroundColor - toolTipText: Style.ncTextColor - } - - spacing: scrollContentsColumn.indicatorSpacing - padding: scrollContentsColumn.itemPadding - indicator.width: scrollContentsColumn.indicatorItemWidth - indicator.height: scrollContentsColumn.indicatorItemWidth - - checkable: true - checked: root.resharingAllowed - text: qsTr("Allow resharing") - enabled: !root.isSharePermissionChangeInProgress && root.serverAllowsResharing - visible: root.serverAllowsResharing - onClicked: root.toggleAllowResharing(checked); - - Connections { - target: root - onResharingAllowedChanged: allowResharingCheckBox.checked = root.resharingAllowed - } - } - } - - NCBusyIndicator { - anchors.fill: parent - visible: root.isSharePermissionChangeInProgress - running: visible - z: 1 - } - } - - Loader { - Layout.fillWidth: true - - active: root.isLinkShare - visible: active - sourceComponent: ColumnLayout { - CheckBox { - id: hideDownloadEnabledMenuItem - - anchors.left: parent.left - anchors.right: parent.right - - // TODO: Rather than setting all these palette colours manually, - // create a custom style and do it for all components globally. - // - // Additionally, we need to override the entire palette when we - // set one palette property, as otherwise we default back to the - // theme palette -- not the parent palette - palette { - text: Style.ncTextColor - windowText: Style.ncTextColor - buttonText: Style.ncTextColor - brightText: Style.ncTextBrightColor - highlight: Style.lightHover - highlightedText: Style.ncTextColor - light: Style.lightHover - midlight: Style.ncSecondaryTextColor - mid: Style.darkerHover - dark: Style.menuBorder - button: Style.buttonBackgroundColor - window: palette.dark // NOTE: Fusion theme uses darker window colour for the border of the checkbox - base: Style.backgroundColor - toolTipBase: Style.backgroundColor - toolTipText: Style.ncTextColor - } - - spacing: scrollContentsColumn.indicatorSpacing - padding: scrollContentsColumn.itemPadding - indicator.width: scrollContentsColumn.indicatorItemWidth - indicator.height: scrollContentsColumn.indicatorItemWidth - - checked: root.hideDownload - text: qsTr("Hide download") - enabled: !root.isHideDownloadInProgress - onClicked: root.toggleHideDownload(checked); - - NCBusyIndicator { - anchors.fill: parent - visible: root.isHideDownloadInProgress - running: visible - z: 1 - } - } - } - } - CheckBox { id: passwordProtectEnabledMenuItem - Layout.fillWidth: true // TODO: Rather than setting all these palette colours manually, @@ -555,6 +318,9 @@ Page { toolTipText: Style.ncTextColor } + font.pixelSize: pixelSize + font.weight: fontWeight + spacing: scrollContentsColumn.indicatorSpacing padding: scrollContentsColumn.itemPadding indicator.width: scrollContentsColumn.indicatorItemWidth @@ -564,70 +330,14 @@ Page { checked: root.passwordProtectEnabled text: qsTr("Password protect") visible: root.shareSupportsPassword - enabled: visible && - !root.waitingForPasswordProtectEnabledChange && + enabled: visible && + !root.waitingForPasswordProtectEnabledChange && !root.passwordEnforced onClicked: { root.togglePasswordProtect(checked); root.waitingForPasswordProtectEnabledChange = true; } - - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForPasswordProtectEnabledChange - running: visible - z: 1 - } - } - - RowLayout { - Layout.fillWidth: true - - height: visible ? implicitHeight : 0 - spacing: scrollContentsColumn.indicatorSpacing - - visible: root.shareSupportsPassword && root.passwordProtectEnabled - - Image { - Layout.preferredWidth: scrollContentsColumn.indicatorItemWidth - Layout.fillHeight: true - - verticalAlignment: Image.AlignVCenter - horizontalAlignment: Image.AlignHCenter - fillMode: Image.Pad - - source: "image://svgimage-custom-color/lock-https.svg/" + palette.dark - sourceSize.width: scrollContentsColumn.rowIconWidth - sourceSize.height: scrollContentsColumn.rowIconWidth - } - - NCInputTextField { - id: passwordTextField - - Layout.fillWidth: true - height: visible ? implicitHeight : 0 - - text: root.password !== "" ? root.password : root.passwordPlaceholder - enabled: visible && - root.passwordProtectEnabled && - !root.waitingForPasswordChange && - !root.waitingForPasswordProtectEnabledChange - - onAccepted: if(text !== root.password && text !== root.passwordPlaceholder) { - passwordErrorBoxLoader.message = ""; - root.setPassword(text); - root.waitingForPasswordChange = true; - } - - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForPasswordChange || - root.waitingForPasswordProtectEnabledChange - running: visible - z: 1 - } - } } Loader { @@ -648,7 +358,7 @@ Page { // Artificially add vertical padding implicitHeight: passwordErrorBox.implicitHeight + (Style.smallSpacing * 2) - ErrorBox { + SesErrorBox { id: passwordErrorBox anchors.left: parent.left anchors.right: parent.right @@ -659,6 +369,44 @@ Page { } } + TextEdit { + id: passwordTextEdit + visible: root.passwordProtectEnabled + Layout.fillWidth: true + Layout.leftMargin: 3 + Layout.rightMargin: 3 + height: visible ? 64 : 0 + wrapMode: TextEdit.Wrap + selectByMouse: true + text: root.password !== "" ? root.password : root.passwordPlaceholder + + font.family: root.font.family + font.pixelSize: pixelSize + font.weight: fontWeight + + padding: scrollContentsColumn.itemPadding + enabled: visible && + root.passwordProtectEnabled && + !root.waitingForPasswordChange && + !root.waitingForPasswordProtectEnabledChange + + onEditingFinished: if(text !== root.password && text !== root.passwordPlaceholder) { + passwordErrorBoxLoader.message = ""; + root.setPassword(text); + root.waitingForPasswordChange = true; + } + + Rectangle { + id: passwordTextBorder + anchors.fill: parent + radius: Style.slightlyRoundedButtonRadius + border.width: Style.thickBorderWidth + border.color: Style.sesTrayInputField + color: palette.base + z: -1 + } + } + CheckBox { id: expireDateEnabledMenuItem @@ -688,6 +436,9 @@ Page { toolTipText: Style.ncTextColor } + font.pixelSize: pixelSize + font.weight: fontWeight + spacing: scrollContentsColumn.indicatorSpacing padding: scrollContentsColumn.itemPadding indicator.width: scrollContentsColumn.indicatorItemWidth @@ -702,73 +453,287 @@ Page { root.toggleExpirationDate(checked); root.waitingForExpireDateEnabledChange = true; } + } + + NCInputDateField { + id: expireDateField + + font.pixelSize: pixelSize + font.weight: fontWeight + + Layout.fillWidth: true + Layout.leftMargin: 3 + Layout.rightMargin: 3 + height: visible ? implicitHeight : 0 + leftPadding: 15 + + visible: root.expireDateEnabled + + selectByMouse: true + + dateInMs: root.expireDate + maximumDateMs: root.maximumExpireDate + minimumDateMs: { + const currentDate = new Date(); + const currentYear = currentDate.getFullYear(); + const currentMonth = currentDate.getMonth(); + const currentMonthDay = currentDate.getDate(); + // Start of day at 00:00:0000 + return Date.UTC(currentYear, currentMonth, currentMonthDay + 1); + } + + + enabled: root.expireDateEnabled && + !root.waitingForExpireDateChange && + !root.waitingForExpireDateEnabledChange + + onUserAcceptedDate: { + root.setExpireDate(dateInMs); + root.waitingForExpireDateChange = true; + } - NCBusyIndicator { + Rectangle { + id: dateTextBorder anchors.fill: parent - visible: root.waitingForExpireDateEnabledChange - running: visible - z: 1 + radius: Style.slightlyRoundedButtonRadius + border.width: Style.thickBorderWidth + border.color: Style.sesTrayInputField + color: palette.base + z: -1 } } - RowLayout { + ColumnLayout { Layout.fillWidth: true height: visible ? implicitHeight : 0 - spacing: scrollContentsColumn.indicatorSpacing + spacing: Style.extraSmallSpacing - visible: root.expireDateEnabled + CheckBox { + id: noteEnabledMenuItem + + Layout.fillWidth: true + + // TODO: Rather than setting all these palette colours manually, + // create a custom style and do it for all components globally. + // + // Additionally, we need to override the entire palette when we + // set one palette property, as otherwise we default back to the + // theme palette -- not the parent palette + palette { + text: Style.ncTextColor + windowText: Style.ncTextColor + buttonText: Style.ncTextColor + brightText: Style.ncTextBrightColor + highlight: Style.lightHover + highlightedText: Style.ncTextColor + light: Style.lightHover + midlight: Style.ncSecondaryTextColor + mid: Style.darkerHover + dark: Style.menuBorder + button: Style.buttonBackgroundColor + window: Style.menuBorder + base: Style.backgroundColor + toolTipBase: Style.backgroundColor + toolTipText: Style.ncTextColor + } + + font.pixelSize: pixelSize + font.weight: fontWeight - Image { - Layout.preferredWidth: scrollContentsColumn.indicatorItemWidth - Layout.fillHeight: true - verticalAlignment: Image.AlignVCenter - horizontalAlignment: Image.AlignHCenter - fillMode: Image.Pad + spacing: scrollContentsColumn.indicatorSpacing + padding: scrollContentsColumn.itemPadding + indicator.width: scrollContentsColumn.indicatorItemWidth + indicator.height: scrollContentsColumn.indicatorItemWidth + + checkable: true + checked: root.noteEnabled + text: qsTr("Note to recipient") + enabled: !root.waitingForNoteEnabledChange - source: "image://svgimage-custom-color/calendar.svg/" + palette.dark - sourceSize.width: scrollContentsColumn.rowIconWidth - sourceSize.height: scrollContentsColumn.rowIconWidth + onClicked: { + root.toggleNoteToRecipient(checked); + root.waitingForNoteEnabledChange = true; + } } - NCInputDateField { - id: expireDateField + Text{ + text: qsTr("Enter the note to recipient") + color: Style.sesGray + padding: scrollContentsColumn.itemPadding + visible: root.noteEnabled + font.family: root.font.family + font.pixelSize: pixelSize + font.weight: fontWeight + } + TextEdit { + id: noteTextEdit + visible: root.noteEnabled + font.family: root.font.family + font.pixelSize: pixelSize + font.weight: fontWeight Layout.fillWidth: true - height: visible ? implicitHeight : 0 - - dateInMs: root.expireDate - maximumDateMs: root.maximumExpireDate - minimumDateMs: { - const currentDate = new Date(); - const currentYear = currentDate.getFullYear(); - const currentMonth = currentDate.getMonth(); - const currentMonthDay = currentDate.getDate(); - // Start of day at 00:00:0000 UTC - return Date.UTC(currentYear, currentMonth, currentMonthDay + 1); + Layout.leftMargin: 3 + Layout.rightMargin: 3 + height: visible ? 64 : 0 + wrapMode: TextEdit.Wrap + selectByMouse: true + padding: scrollContentsColumn.itemPadding + enabled: root.noteEnabled && + !root.waitingForNoteChange && + !root.waitingForNoteEnabledChange + + onEditingFinished: if(text !== "") { + root.setNote(text); + root.waitingForNoteChange = true; } - enabled: root.expireDateEnabled && - !root.waitingForExpireDateChange && - !root.waitingForExpireDateEnabledChange + Rectangle { + id: noteTextBorder + anchors.fill: parent + radius: Style.slightlyRoundedButtonRadius + border.width: Style.thickBorderWidth + border.color: Style.sesTrayInputField + color: palette.base + z: -1 + } + } + } - onUserAcceptedDate: { - root.setExpireDate(dateInMs); - root.waitingForExpireDateChange = true; + Loader { + Layout.fillWidth: true + active: !root.isFolderItem && !root.isEncryptedItem + visible: active + sourceComponent: CheckBox { + // TODO: Rather than setting all these palette colours manually, + // create a custom style and do it for all components globally. + // + // Additionally, we need to override the entire palette when we + // set one palette property, as otherwise we default back to the + // theme palette -- not the parent palette + palette { + text: Style.ncTextColor + windowText: Style.ncTextColor + buttonText: Style.ncTextColor + brightText: Style.ncTextBrightColor + highlight: Style.lightHover + highlightedText: Style.ncTextColor + light: Style.lightHover + midlight: Style.ncSecondaryTextColor + mid: Style.darkerHover + dark: Style.menuBorder + button: Style.buttonBackgroundColor + window: Style.menuBorder + base: Style.backgroundColor + toolTipBase: Style.backgroundColor + toolTipText: Style.ncTextColor } - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForExpireDateEnabledChange || - root.waitingForExpireDateChange - running: visible - z: 1 + font.pixelSize: pixelSize + font.weight: fontWeight + + spacing: scrollContentsColumn.indicatorSpacing + padding: scrollContentsColumn.itemPadding + indicator.width: scrollContentsColumn.indicatorItemWidth + indicator.height: scrollContentsColumn.indicatorItemWidth + + checkable: true + checked: root.editingAllowed + text: qsTr("Allow upload and editing") + enabled: !root.isSharePermissionChangeInProgress + + onClicked: root.toggleAllowEditing(checked) + } + } + + Loader { + Layout.fillWidth: true + active: root.isFolderItem && !root.isEncryptedItem + visible: active + sourceComponent: ColumnLayout { + id: permissionRadioButtonsLayout + spacing: 0 + width: parent.width + + ButtonGroup { + id: permissionModeRadioButtonsGroup + } + + CheckBox { + id: customPermissionsCheckBox + Layout.fillWidth: true + enabled: !root.isSharePermissionChangeInProgress + checkable: false + checked: true + text: qsTr("Custom Permissions") + spacing: scrollContentsColumn.indicatorSpacing + padding: scrollContentsColumn.itemPadding + indicator.width: scrollContentsColumn.indicatorItemWidth + indicator.height: scrollContentsColumn.indicatorItemWidth + onClicked: root.permissionModeChanged(permissionMode) + font.pixelSize: pixelSize + font.weight: fontWeight + } + + CheckBox { + readonly property int permissionMode: ShareModel.ModeViewOnly + Layout.fillWidth: true + Layout.leftMargin: 30 + ButtonGroup.group: permissionModeRadioButtonsGroup + enabled: !root.isSharePermissionChangeInProgress + checked: root.currentPermissionMode === permissionMode + text: qsTr("View only") + indicator.width: scrollContentsColumn.indicatorItemWidth + indicator.height: scrollContentsColumn.indicatorItemWidth + spacing: scrollContentsColumn.indicatorSpacing + padding: scrollContentsColumn.itemPadding + onClicked: root.permissionModeChanged(permissionMode) + visible: customPermissionsCheckBox.checked + font.pixelSize: pixelSize + font.weight: fontWeight + } + + CheckBox { + readonly property int permissionMode: ShareModel.ModeUploadAndEditing + Layout.fillWidth: true + Layout.leftMargin: 30 + ButtonGroup.group: permissionModeRadioButtonsGroup + enabled: !root.isSharePermissionChangeInProgress + checked: root.currentPermissionMode === permissionMode + text: qsTr("Allow upload and editing") + indicator.width: scrollContentsColumn.indicatorItemWidth + indicator.height: scrollContentsColumn.indicatorItemWidth + spacing: scrollContentsColumn.indicatorSpacing + padding: scrollContentsColumn.itemPadding + onClicked: root.permissionModeChanged(permissionMode) + visible: customPermissionsCheckBox.checked + font.pixelSize: pixelSize + font.weight: fontWeight + } + + CheckBox { + readonly property int permissionMode: ShareModel.ModeFileDropOnly + Layout.fillWidth: true + Layout.leftMargin: 30 + ButtonGroup.group: permissionModeRadioButtonsGroup + enabled: !root.isSharePermissionChangeInProgress + checked: root.currentPermissionMode === permissionMode + text: qsTr("File drop (upload only)") + indicator.width: scrollContentsColumn.indicatorItemWidth + indicator.height: scrollContentsColumn.indicatorItemWidth + spacing: scrollContentsColumn.indicatorSpacing + padding: scrollContentsColumn.itemPadding + onClicked: root.permissionModeChanged(permissionMode) + visible: customPermissionsCheckBox.checked && !root.isInternalShare // Removed SES-307 + font.pixelSize: pixelSize + font.weight: fontWeight } } } CheckBox { - id: noteEnabledMenuItem + id: allowResharingCheckBox Layout.fillWidth: true @@ -790,131 +755,140 @@ Page { mid: Style.darkerHover dark: Style.menuBorder button: Style.buttonBackgroundColor - window: Style.menuBorder + window: palette.dark // NOTE: Fusion theme uses darker window colour for the border of the checkbox base: Style.backgroundColor toolTipBase: Style.backgroundColor toolTipText: Style.ncTextColor } + font.pixelSize: pixelSize + font.weight: fontWeight + spacing: scrollContentsColumn.indicatorSpacing padding: scrollContentsColumn.itemPadding indicator.width: scrollContentsColumn.indicatorItemWidth indicator.height: scrollContentsColumn.indicatorItemWidth checkable: true - checked: root.noteEnabled - text: qsTr("Note to recipient") - enabled: !root.waitingForNoteEnabledChange - - onClicked: { - root.toggleNoteToRecipient(checked); - root.waitingForNoteEnabledChange = true; - } - - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForNoteEnabledChange - running: visible - z: 1 + checked: root.resharingAllowed + text: qsTr("Allow resharing") + enabled: !root.isSharePermissionChangeInProgress && root.serverAllowsResharing + visible: root.serverAllowsResharing + onClicked: root.toggleAllowResharing(checked); + + Connections { + target: root + onResharingAllowedChanged: allowResharingCheckBox.checked = root.resharingAllowed } } - RowLayout { + Loader { Layout.fillWidth: true - height: visible ? implicitHeight : 0 - spacing: scrollContentsColumn.indicatorSpacing - visible: root.noteEnabled + active: root.isLinkShare + visible: active + sourceComponent: ColumnLayout { + CheckBox { + id: hideDownloadEnabledMenuItem - Image { - Layout.preferredWidth: scrollContentsColumn.indicatorItemWidth - Layout.fillHeight: true + anchors.left: parent.left + anchors.right: parent.right - verticalAlignment: Image.AlignVCenter - horizontalAlignment: Image.AlignHCenter - fillMode: Image.Pad + // TODO: Rather than setting all these palette colours manually, + // create a custom style and do it for all components globally. + // + // Additionally, we need to override the entire palette when we + // set one palette property, as otherwise we default back to the + // theme palette -- not the parent palette + palette { + text: Style.ncTextColor + windowText: Style.ncTextColor + buttonText: Style.ncTextColor + brightText: Style.ncTextBrightColor + highlight: Style.lightHover + highlightedText: Style.ncTextColor + light: Style.lightHover + midlight: Style.ncSecondaryTextColor + mid: Style.darkerHover + dark: Style.menuBorder + button: Style.buttonBackgroundColor + window: palette.dark // NOTE: Fusion theme uses darker window colour for the border of the checkbox + base: Style.backgroundColor + toolTipBase: Style.backgroundColor + toolTipText: Style.ncTextColor + } + + font.pixelSize: pixelSize + font.weight: fontWeight + + spacing: scrollContentsColumn.indicatorSpacing + padding: scrollContentsColumn.itemPadding + indicator.width: scrollContentsColumn.indicatorItemWidth + indicator.height: scrollContentsColumn.indicatorItemWidth - source: "image://svgimage-custom-color/edit.svg/" + palette.dark - sourceSize.width: scrollContentsColumn.rowIconWidth - sourceSize.height: scrollContentsColumn.rowIconWidth + checked: root.hideDownload + text: qsTr("Hide download") + enabled: !root.isHideDownloadInProgress + onClicked: root.toggleHideDownload(checked); + } } + } + } + } - NCInputTextEdit { - id: noteTextEdit + footer: GridLayout { + id: buttonGrid - Layout.fillWidth: true - height: visible ? Math.max(Style.talkReplyTextFieldPreferredHeight, contentHeight) : 0 - submitButton.height: Math.min(Style.talkReplyTextFieldPreferredHeight, height - 2) + columns: 1 + rows: 2 - text: root.note - enabled: root.noteEnabled && - !root.waitingForNoteChange && - !root.waitingForNoteEnabledChange + SesCustomButton { + Layout.columnSpan: buttonGrid.columns - onEditingFinished: if(text !== root.note) { - root.setNote(text); - root.waitingForNoteChange = true; - } + icon.source: Style.sesDarkPlus - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForNoteChange || - root.waitingForNoteEnabledChange - running: visible - z: 1 - } - } - } + font.pixelSize: pixelSize + font.weight: fontWeight + text: qsTr("Add another link") + textColor: palette.brightText - CustomButton { - height: Style.standardPrimaryButtonHeight - - icon.source: "image://svgimage-custom-color/close.svg/" + Style.errorBoxBackgroundColor - imageSourceHover: "image://svgimage-custom-color/close.svg/" + palette.brightText - text: qsTr("Unshare") - textColor: Style.errorBoxBackgroundColor - textColorHovered: "white" - contentsFont.bold: true - bgNormalColor: palette.button - bgHoverColor: Style.errorBoxBackgroundColor - bgNormalOpacity: 1.0 - bgHoverOpacity: 1.0 - - onClicked: root.deleteShare() - } + bgColor: Style.sesActionPressed + bgNormalOpacity: 1.0 + bgHoverOpacity: Style.hoverOpacity - CustomButton { - height: Style.standardPrimaryButtonHeight - - icon.source: "image://svgimage-custom-color/add.svg/" + root.accentColor - imageSourceHover: "image://svgimage-custom-color/add.svg/" + palette.brightText - text: qsTr("Add another link") - textColor: root.accentColor - textColorHovered: palette.brightText - contentsFont.bold: true - bgNormalColor: palette.button - bgHoverColor: root.accentColor - bgNormalOpacity: 1.0 - bgHoverOpacity: 1.0 - - visible: root.isLinkShare && root.canCreateLinkShares - enabled: visible - - onClicked: root.createNewLinkShare() - } + visible: root.isLinkShare && root.canCreateLinkShares + enabled: visible + + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + Layout.row: 0 + + onClicked: root.createNewLinkShare() } - } - footer: DialogButtonBox { - topPadding: 0 - bottomPadding: root.padding - rightPadding: root.padding - leftPadding: root.padding - alignment: Qt.AlignRight | Qt.AlignVCenter - contentWidth: (contentItem as ListView).contentWidth - visible: copyShareLinkButton.visible + SesCustomButton { + id: unshareButton + + font.pixelSize: pixelSize + font.weight: fontWeight + text: qsTr("Unshare") + textColor: Style.sesActionPressed - CustomButton { + bgColor: palette.highlight + bgNormalOpacity: 1.0 + + bgBorderWidth: 2 + bgBorderColor: Style.sesActionPressed + bgHoverOpacity: Style.hoverOpacity + + Layout.bottomMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 60 + Layout.row: 1 + onClicked: root.deleteShare() + } + + SesCustomButton { id: copyShareLinkButton function copyShareLink() { @@ -929,13 +903,14 @@ Page { property bool shareLinkCopied: false - height: Style.standardPrimaryButtonHeight + icon.source: Style.sesClipboard - icon.source: "image://svgimage-custom-color/copy.svg/" + palette.brightText + font.pixelSize: pixelSize + font.weight: fontWeight text: shareLinkCopied ? qsTr("Share link copied!") : qsTr("Copy share link") textColor: palette.brightText - contentsFont.bold: true - bgColor: shareLinkCopied ? Style.positiveColor : root.accentColor + + bgColor: Style.sesActionPressed bgNormalOpacity: 1.0 bgHoverOpacity: shareLinkCopied ? 1.0 : Style.hoverOpacity @@ -944,6 +919,11 @@ Page { onClicked: copyShareLink() + Layout.alignment: Qt.AlignRight + Layout.bottomMargin: 16 + Layout.rightMargin: 20 + Layout.row: 1 + Behavior on bgColor { ColorAnimation { duration: Style.shortAnimationDuration } } diff --git a/src/gui/filedetails/ShareView.qml b/src/gui/filedetails/ShareView.qml index c4c58fde14910..3ab3237db4be0 100644 --- a/src/gui/filedetails/ShareView.qml +++ b/src/gui/filedetails/ShareView.qml @@ -17,9 +17,10 @@ import QtQuick.Window 2.15 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 import "../tray" +import "../SesComponents" import "../" ColumnLayout { @@ -123,20 +124,14 @@ ColumnLayout { } } - ErrorBox { + SesErrorBox { id: errorBox Layout.fillWidth: true Layout.leftMargin: root.horizontalPadding Layout.rightMargin: root.horizontalPadding - showCloseButton: true visible: false - - onCloseButtonClicked: { - text = ""; - visible = false; - } } ShareeSearchField { @@ -144,6 +139,7 @@ ColumnLayout { Layout.fillWidth: true Layout.leftMargin: root.horizontalPadding Layout.rightMargin: root.horizontalPadding + Layout.preferredHeight: Style.sesSearchFieldHeight visible: root.userGroupSharingPossible enabled: visible && !root.loading && !root.shareModel.isShareDisabledEncryptedFolder && !shareeSearchField.isShareeFetchOngoing @@ -165,6 +161,7 @@ ColumnLayout { Layout.fillHeight: true Layout.leftMargin: root.horizontalPadding Layout.rightMargin: root.horizontalPadding + Layout.topMargin: Style.sesMediumMargin active: root.sharingPossible @@ -186,61 +183,71 @@ ColumnLayout { sourceModel: root.shareModel } - delegate: ShareDelegate { - id: shareDelegate - - Connections { - target: root.shareModel - // Though we try to handle this internally by listening to onPasswordChanged, - // with passwords we will get the same value from the model data when a - // password set has failed, meaning we won't be able to easily tell when we - // have had a response from the server in QML. So we listen to this signal - // directly from the model and do the reset of the password field manually. - function onPasswordSetError(shareId, errorCode, errorMessage) { - if(shareId !== model.shareId) { - return; + delegate: ColumnLayout{ + width: parent.width + ShareDelegate { + id: shareDelegate + + Connections { + target: root.shareModel + // Though we try to handle this internally by listening to onPasswordChanged, + // with passwords we will get the same value from the model data when a + // password set has failed, meaning we won't be able to easily tell when we + // have had a response from the server in QML. So we listen to this signal + // directly from the model and do the reset of the password field manually. + function onPasswordSetError(shareId, errorCode, errorMessage) { + if(shareId !== model.shareId) { + return; + } + shareDelegate.resetPasswordField(); + shareDelegate.showPasswordSetError(errorMessage); } - shareDelegate.resetPasswordField(); - shareDelegate.showPasswordSetError(errorMessage); - } - function onServerError() { - if(shareId !== model.shareId) { - return; + function onServerError() { + if(shareId !== model.shareId) { + return; + } + shareDelegate.resetMenu(); } - shareDelegate.resetMenu(); } - } - iconSize: root.iconSize - fileDetails: root.fileDetails - rootStackView: root.rootStackView - backgroundsVisible: root.backgroundsVisible - accentColor: root.accentColor - canCreateLinkShares: root.publicLinkSharingPossible - serverAllowsResharing: root.serverAllowsResharing - - onCreateNewLinkShare: { - root.waitingForSharesToChange = true; - shareModel.createNewLinkShare(); - } - onDeleteShare: { - root.waitingForSharesToChange = true; - shareModel.deleteShareFromQml(model.share); + iconSize: root.iconSize + fileDetails: root.fileDetails + rootStackView: root.rootStackView + backgroundsVisible: root.backgroundsVisible + accentColor: Style.sesIconColor + canCreateLinkShares: root.publicLinkSharingPossible + serverAllowsResharing: root.serverAllowsResharing + + onCreateNewLinkShare: { + root.waitingForSharesToChange = true; + shareModel.createNewLinkShare(); + } + onDeleteShare: { + root.waitingForSharesToChange = true; + shareModel.deleteShareFromQml(model.share); + } + + onToggleAllowEditing: shareModel.toggleShareAllowEditingFromQml(model.share, enable) + onToggleAllowResharing: shareModel.toggleShareAllowResharingFromQml(model.share, enable) + onToggleHideDownload: shareModel.toggleHideDownloadFromQml(model.share, enable) + onTogglePasswordProtect: shareModel.toggleSharePasswordProtectFromQml(model.share, enable) + onToggleExpirationDate: shareModel.toggleShareExpirationDateFromQml(model.share, enable) + onToggleNoteToRecipient: shareModel.toggleShareNoteToRecipientFromQml(model.share, enable) + onPermissionModeChanged: shareModel.changePermissionModeFromQml(model.share, permissionMode) + + onSetLinkShareLabel: shareModel.setLinkShareLabelFromQml(model.share, label) + onSetExpireDate: shareModel.setShareExpireDateFromQml(model.share, milliseconds) + onSetPassword: shareModel.setSharePasswordFromQml(model.share, password) + onSetNote: shareModel.setShareNoteFromQml(model.share, note) + width: parent.width } - onToggleAllowEditing: shareModel.toggleShareAllowEditingFromQml(model.share, enable) - onToggleAllowResharing: shareModel.toggleShareAllowResharingFromQml(model.share, enable) - onToggleHideDownload: shareModel.toggleHideDownloadFromQml(model.share, enable) - onTogglePasswordProtect: shareModel.toggleSharePasswordProtectFromQml(model.share, enable) - onToggleExpirationDate: shareModel.toggleShareExpirationDateFromQml(model.share, enable) - onToggleNoteToRecipient: shareModel.toggleShareNoteToRecipientFromQml(model.share, enable) - onPermissionModeChanged: shareModel.changePermissionModeFromQml(model.share, permissionMode) - - onSetLinkShareLabel: shareModel.setLinkShareLabelFromQml(model.share, label) - onSetExpireDate: shareModel.setShareExpireDateFromQml(model.share, milliseconds) - onSetPassword: shareModel.setSharePasswordFromQml(model.share, password) - onSetNote: shareModel.setShareNoteFromQml(model.share, note) + Rectangle{ + height: Style.sesMediumMargin + color: "transparent" + width: parent.width + } } Loader { diff --git a/src/gui/filedetails/ShareeDelegate.qml b/src/gui/filedetails/ShareeDelegate.qml index 40b169d87be1e..debc44f733eb0 100644 --- a/src/gui/filedetails/ShareeDelegate.qml +++ b/src/gui/filedetails/ShareeDelegate.qml @@ -17,7 +17,7 @@ import QtQuick.Window 2.15 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 import "../tray" @@ -62,6 +62,9 @@ ItemDelegate { Layout.preferredHeight: unifiedSearchResultSkeletonItemDetails.iconWidth Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + font.pixelSize: Style.sesFontPixelSize + font.weight: Style.sesFontNormalWeight + Layout.fillWidth: true horizontalAlignment: Text.AlignLeft diff --git a/src/gui/filedetails/ShareeSearchField.qml b/src/gui/filedetails/ShareeSearchField.qml index 4246d54e9ed8d..c23c6e0e3225b 100644 --- a/src/gui/filedetails/ShareeSearchField.qml +++ b/src/gui/filedetails/ShareeSearchField.qml @@ -17,7 +17,7 @@ import QtQuick.Window 2.15 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.15 -import com.nextcloud.desktopclient 1.0 +import com.ionos.hidrivenext.desktopclient 1.0 import Style 1.0 import "../tray" @@ -38,7 +38,7 @@ TextField { } readonly property int horizontalPaddingOffset: Style.trayHorizontalMargin - readonly property color placeholderColor: palette.dark + readonly property color placeholderColor: Style.sesSearchFieldContent readonly property double iconsScaleFactor: 0.6 function triggerSuggestionsVisibility() { @@ -48,6 +48,7 @@ TextField { placeholderText: enabled ? qsTr("Search for users or groups…") : qsTr("Sharing is not available for this folder") placeholderTextColor: placeholderColor + onActiveFocusChanged: triggerSuggestionsVisibility() onTextChanged: triggerSuggestionsVisibility() Keys.onPressed: { @@ -87,14 +88,14 @@ TextField { } } - leftPadding: searchIcon.width + searchIcon.anchors.leftMargin + horizontalPaddingOffset - rightPadding: clearTextButton.width + clearTextButton.anchors.rightMargin + horizontalPaddingOffset + leftPadding: searchIcon.width + searchIcon.anchors.leftMargin + horizontalPaddingOffset - 5 + rightPadding: root.text ? clearTextButton.width + clearTextButton.anchors.rightMargin + horizontalPaddingOffset : 5 background: Rectangle { radius: 5 - border.color: parent.activeFocus ? UserModel.currentUser.accentColor : palette.dark + border.color: Style.sesMenuBorder border.width: 1 - color: palette.base + } Image { @@ -114,7 +115,7 @@ TextField { fillMode: Image.PreserveAspectFit horizontalAlignment: Image.AlignLeft - source: "image://svgimage-custom-color/search.svg" + "/" + root.placeholderColor + source: "image://svgimage-custom-color/search.svg" + "/" + Style.sesSearchFieldContent sourceSize: Qt.size(parent.height * root.iconsScaleFactor, parent.height * root.iconsScaleFactor) visible: !root.shareeModel.fetchOngoing diff --git a/src/gui/filedetails/filedetails.cpp b/src/gui/filedetails/filedetails.cpp index 67ebb554cec29..7cb440179086b 100644 --- a/src/gui/filedetails/filedetails.cpp +++ b/src/gui/filedetails/filedetails.cpp @@ -20,7 +20,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcFileDetails, "nextcloud.gui.filedetails", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFileDetails, "hidrivenext.gui.filedetails", QtInfoMsg) FileDetails::FileDetails(QObject *parent) : QObject(parent) diff --git a/src/gui/filedetails/shareemodel.cpp b/src/gui/filedetails/shareemodel.cpp index 9710de710715b..9018bfa832839 100644 --- a/src/gui/filedetails/shareemodel.cpp +++ b/src/gui/filedetails/shareemodel.cpp @@ -23,7 +23,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcShareeModel, "com.nextcloud.shareemodel") +Q_LOGGING_CATEGORY(lcShareeModel, "com.ionos.hidrivenext.shareemodel") ShareeModel::ShareeModel(QObject *parent) : QAbstractListModel(parent) diff --git a/src/gui/filedetails/sharemodel.cpp b/src/gui/filedetails/sharemodel.cpp index ff849b84fb346..75f8ed7be27b3 100644 --- a/src/gui/filedetails/sharemodel.cpp +++ b/src/gui/filedetails/sharemodel.cpp @@ -36,7 +36,7 @@ static const auto secureFileDropPlaceholderLinkShareId = QStringLiteral("__secur namespace OCC { -Q_LOGGING_CATEGORY(lcShareModel, "com.nextcloud.sharemodel") +Q_LOGGING_CATEGORY(lcShareModel, "com.ionos.hidrivenext.sharemodel") ShareModel::ShareModel(QObject *parent) : QAbstractListModel(parent) @@ -674,7 +674,7 @@ QString ShareModel::iconUrlForShare(const SharePtr &share) const switch(share->getShareType()) { case Share::TypeInternalLink: - return QString(iconsPath + QStringLiteral("external.svg")); + return QString(iconsPath + QStringLiteral("public.svg")); case Share::TypePlaceholderLink: case Share::TypeSecureFileDropPlaceholderLink: case Share::TypeLink: @@ -989,7 +989,7 @@ void ShareModel::toggleShareNoteToRecipient(const SharePtr &share, const bool en return; } - const QString note = enable ? tr("Enter a note for the recipient") : QString(); + const QString note = enable ? tr(" ") : QString(); if (const auto linkShare = share.objectCast()) { linkShare->setNote(note); } else if (const auto userGroupShare = share.objectCast()) { diff --git a/src/gui/filedetails/sortedsharemodel.cpp b/src/gui/filedetails/sortedsharemodel.cpp index 9b9ef99a644bd..bcc5f3a06fd18 100644 --- a/src/gui/filedetails/sortedsharemodel.cpp +++ b/src/gui/filedetails/sortedsharemodel.cpp @@ -16,7 +16,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcSortedShareModel, "com.nextcloud.sortedsharemodel") +Q_LOGGING_CATEGORY(lcSortedShareModel, "com.ionos.hidrivenext.sortedsharemodel") SortedShareModel::SortedShareModel(QObject *parent) : QSortFilterProxyModel(parent) diff --git a/src/gui/filetagmodel.cpp b/src/gui/filetagmodel.cpp index 64cab4486a4f9..303c7436fe717 100644 --- a/src/gui/filetagmodel.cpp +++ b/src/gui/filetagmodel.cpp @@ -16,7 +16,7 @@ #include "libsync/networkjobs.h" -Q_LOGGING_CATEGORY(lcFileTagModel, "nextcloud.gui.filetagmodel", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFileTagModel, "hidrivenext.gui.filetagmodel", QtInfoMsg) namespace OCC { diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index d63f5980b0c15..810110ab07e9d 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -57,7 +57,7 @@ constexpr auto versionC = "version"; namespace OCC { -Q_LOGGING_CATEGORY(lcFolder, "nextcloud.gui.folder", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFolder, "hidrivenext.gui.folder", QtInfoMsg) Folder::Folder(const FolderDefinition &definition, AccountState *accountState, std::unique_ptr vfs, @@ -1604,7 +1604,7 @@ void Folder::registerFolderWatcher() } connect(_folderWatcher.data(), &FolderWatcher::filesLockImposed, this, &Folder::slotFilesLockImposed, Qt::UniqueConnection); _folderWatcher->init(path()); - _folderWatcher->startNotificatonTest(path() + QLatin1String(".nextcloudsync.log")); + _folderWatcher->startNotificatonTest(path() + QLatin1String(".hidrivenextsync.log")); connect(_engine.data(), &SyncEngine::lockFileDetected, _folderWatcher.data(), &FolderWatcher::slotLockFileDetectedExternally); } diff --git a/src/gui/foldercreationdialog.cpp b/src/gui/foldercreationdialog.cpp index a245c9f4dc461..ba7c0d01b06b8 100644 --- a/src/gui/foldercreationdialog.cpp +++ b/src/gui/foldercreationdialog.cpp @@ -13,6 +13,9 @@ */ #include "foldercreationdialog.h" + +#include "buttonstyle.h" +#include "ionostheme.h" #include "ui_foldercreationdialog.h" #include @@ -20,10 +23,13 @@ #include #include #include +#include +#include +#include namespace OCC { -Q_LOGGING_CATEGORY(lcFolderCreationDialog, "nextcloud.gui.foldercreationdialog", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFolderCreationDialog, "hidrivenext.gui.foldercreationdialog", QtInfoMsg) FolderCreationDialog::FolderCreationDialog(const QString &destination, QWidget *parent) : QDialog(parent) @@ -31,10 +37,13 @@ FolderCreationDialog::FolderCreationDialog(const QString &destination, QWidget * , _destination(destination) { ui->setupUi(this); + setWindowTitle(tr("%1 Create new folder").arg(Theme::instance()->appNameGUI())); + customizeStyle(); - ui->labelErrorMessage->setVisible(false); + ui->errorSnackbar->setVisible(false); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setWindowFlags(windowFlags() | Qt::Dialog | Qt::WindowMinMaxButtonsHint); connect(ui->newFolderNameEdit, &QLineEdit::textChanged, this, &FolderCreationDialog::slotNewFolderNameEditTextEdited); @@ -69,11 +78,6 @@ void FolderCreationDialog::accept() const auto fullPath = QString(_destination + "/" + ui->newFolderNameEdit->text()); - if (QDir(fullPath).exists()) { - ui->labelErrorMessage->setVisible(true); - return; - } - if (QDir(_destination).mkdir(ui->newFolderNameEdit->text())) { Q_EMIT folderCreated(fullPath); } else { @@ -86,10 +90,51 @@ void FolderCreationDialog::accept() void FolderCreationDialog::slotNewFolderNameEditTextEdited() { if (!ui->newFolderNameEdit->text().isEmpty() && QDir(_destination + "/" + ui->newFolderNameEdit->text()).exists()) { - ui->labelErrorMessage->setVisible(true); + ui->errorSnackbar->setVisible(true); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + sizeDialog(); + } else { - ui->labelErrorMessage->setVisible(false); + ui->errorSnackbar->setVisible(false); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + sizeDialog(); } } +void FolderCreationDialog::sizeDialog(){ + adjustSize(); + setFixedWidth(626); + setFixedHeight(sizeHint().height()); +} + +void FolderCreationDialog::customizeStyle() +{ + ui->buttonBox->setLayoutDirection(Qt::RightToLeft); + + QPushButton *okButton = ui->buttonBox->button(QDialogButtonBox::Ok); + okButton->setProperty("buttonStyle", QVariant::fromValue(OCC::ButtonStyleName::Primary)); + + QHBoxLayout* buttonlayout = qobject_cast(ui->buttonBox->layout()); + buttonlayout->setSpacing(16); + + ui->newFolderNameEdit->setStyleSheet( + QStringLiteral( + "color: %1; font-family: %2; font-size: %3; font-weight: %4; border-radius: %5; border: 1px " + "solid %6; padding: 0px 12px; text-align: left; vertical-align: middle; height: 40px; background: %7; ") + .arg(IonosTheme::folderWizardPathColor(), + IonosTheme::settingsFont(), + IonosTheme::settingsTextSize(), + IonosTheme::settingsTextWeight(), + IonosTheme::buttonRadius(), + IonosTheme::menuBorderColor(), + IonosTheme::white() + ) + ); + +#if defined(Q_OS_MAC) + buttonlayout->setSpacing(32); +#endif + + sizeDialog(); } +} \ No newline at end of file diff --git a/src/gui/foldercreationdialog.h b/src/gui/foldercreationdialog.h index 3d8642112f2c4..d03034028cd7b 100644 --- a/src/gui/foldercreationdialog.h +++ b/src/gui/foldercreationdialog.h @@ -43,6 +43,9 @@ private slots: Ui::FolderCreationDialog *ui; QString _destination; + + void customizeStyle(); + void sizeDialog(); }; } diff --git a/src/gui/foldercreationdialog.ui b/src/gui/foldercreationdialog.ui index 84d7c77e18654..de849b09215cd 100644 --- a/src/gui/foldercreationdialog.ui +++ b/src/gui/foldercreationdialog.ui @@ -1,100 +1,93 @@ - OCC::FolderCreationDialog - - - - 0 - 0 - 355 - 138 - - - - Create new folder - - - - - 0 - 90 - 341 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 20 - 30 - 321 - 22 - - - - Enter folder name - - - - - true - - - - 20 - 60 - 321 - 16 - - - - color: rgb(255, 0, 0) - - - Folder already exists - - - - - - - buttonBox - accepted() - OCC::FolderCreationDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - OCC::FolderCreationDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - + OCC::FolderCreationDialog + + + Create new folder + + + + + + Enter folder name + + + Qt::NoContextMenu + + + + + + + Folder already exists + + + false + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + + buttonBox + accepted() + OCC::FolderCreationDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OCC::FolderCreationDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + \ No newline at end of file diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index ffced3a51ccc0..d1a5673a47d6d 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -48,7 +48,7 @@ constexpr auto maxFoldersVersion = 1; namespace OCC { -Q_LOGGING_CATEGORY(lcFolderMan, "nextcloud.gui.folder.manager", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFolderMan, "hidrivenext.gui.folder.manager", QtInfoMsg) FolderMan *FolderMan::_instance = nullptr; diff --git a/src/gui/folderstatusdelegate.cpp b/src/gui/folderstatusdelegate.cpp index b356a3dd32b83..676fd5f5c1dca 100644 --- a/src/gui/folderstatusdelegate.cpp +++ b/src/gui/folderstatusdelegate.cpp @@ -19,7 +19,11 @@ #include "folderstatusview.h" #include "folderman.h" #include "accountstate.h" +#include "sesstyle.h" +#include "buttonstyle.h" + #include +#include #include #include @@ -27,14 +31,7 @@ #include #include #include - -inline static QFont makeAliasFont(const QFont &normalFont) -{ - QFont aliasFont = normalFont; - aliasFont.setBold(true); - aliasFont.setPointSize(normalFont.pointSize() + 2); - return aliasFont; -} +#include namespace { #ifdef Q_OS_MACOS @@ -44,6 +41,14 @@ namespace { namespace OCC { +inline static QFont makeAliasFont(const QFont &normalFont) +{ + QFont aliasFont = normalFont; + aliasFont.setWeight(IonosTheme::settingsTitleWeightDemiBold()); + aliasFont.setPixelSize(IonosTheme::settingsBigTitlePixel()); + return aliasFont; +} + FolderStatusDelegate::FolderStatusDelegate() : QStyledItemDelegate() { @@ -52,15 +57,22 @@ FolderStatusDelegate::FolderStatusDelegate() QString FolderStatusDelegate::addFolderText() { - return tr("Add Folder Sync Connection"); + return tr("Add Folder Sync"); +} + +QString FolderStatusDelegate::addInfoText() +{ + return tr("Synchronize any other local folder with your %1").arg(Theme::instance()->appNameGUI()); } // allocate each item size in listview. -QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option, +QSize FolderStatusDelegate::sizeHint( + const QStyleOptionViewItem &option, const QModelIndex &index) const { - QFont aliasFont = makeAliasFont(option.font); - QFont font = option.font; + QFont font = QFont(option.font); + font.setPixelSize(IonosTheme::settingsTextPixel()); + QFont aliasFont = makeAliasFont(font); QFontMetrics fm(font); QFontMetrics aliasFm(aliasFont); @@ -69,9 +81,10 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option, if (classif == FolderStatusModel::AddButton) { const int margins = aliasFm.height(); // same as 2*aliasMargin of paint QFontMetrics fm(qApp->font("QPushButton")); + QStyleOptionButton opt; static_cast(opt) = option; - opt.text = addFolderText(); + opt.text = addInfoText(); return QApplication::style()->sizeFromContents( QStyle::CT_PushButton, &opt, fm.size(Qt::TextSingleLine, opt.text)) .expandedTo(QApplication::globalStrut()) @@ -113,22 +126,72 @@ int FolderStatusDelegate::rootFolderHeightWithoutErrors(const QFontMetrics &fm, return h; } +void FolderStatusDelegate::drawAddButton(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QFont titleFont = option.font; + titleFont.setWeight(IonosTheme::settingsTitleWeightDemiBold()); + titleFont.setPixelSize(IonosTheme::settingsTitlePixel()); + QFontMetrics titleTextFm(titleFont); + const auto baseDistanceForCalculus = titleTextFm.height() / 2; + + QFont subtitleFont = option.font; + + QFontMetrics subtitleTextFm(subtitleFont); + const auto distanceToSubline = subtitleTextFm.height() / 4; + + auto iconBox = option.rect; + iconBox.setTop(iconBox.top() + baseDistanceForCalculus); + iconBox.setBottom(iconBox.top() + IonosTheme::treeViewIconSize()); + iconBox.setLeft(iconBox.left() + baseDistanceForCalculus); + iconBox.setWidth(iconBox.height()); + + auto titleBox = option.rect; + titleBox.setTop(iconBox.top()); + titleBox.setBottom(iconBox.bottom() - distanceToSubline); + titleBox.setRight(titleBox.right() - baseDistanceForCalculus); + titleBox.setLeft(iconBox.right() + baseDistanceForCalculus); + + auto subtitleBox = option.rect; + subtitleBox.setTop(titleBox.bottom() + distanceToSubline); + subtitleBox.setBottom(subtitleBox.top() + 4 * distanceToSubline); + subtitleBox.setLeft(iconBox.right() + baseDistanceForCalculus); + subtitleBox.setRight(subtitleBox.right() - baseDistanceForCalculus); + + auto titleText = addFolderText(); + auto subtitleText = addInfoText(); + auto addIcon = QIcon(IonosTheme::liveBackupPlusIcon()); + const auto addPixmap = addIcon.pixmap(iconBox.size(), QIcon::Normal); + + painter->save(); + painter->drawPixmap(QStyle::visualRect(option.direction, option.rect, iconBox).left(), iconBox.top(), addPixmap); + + drawElidedText(painter, option, titleTextFm, titleFont, titleText, titleBox); + + drawElidedText(painter, option, subtitleTextFm, subtitleFont, subtitleText, subtitleBox); + + painter->restore(); +} + void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + QStyleOptionViewItem opt = option; + QFont font = opt.font; + font.setPixelSize(IonosTheme::settingsTextPixel()); + opt.font = font; + + QStyledItemDelegate::paint(painter, opt, index); + if (index.data(AddButton).toBool()) { - const_cast(option).showDecorationSelected = false; + drawAddButton(painter, opt, index); + return; } - QStyledItemDelegate::paint(painter, option, index); - auto textAlign = Qt::AlignLeft; - const auto aliasFont = makeAliasFont(option.font); - const auto subFont = option.font; - const auto errorFont = subFont; - auto progressFont = subFont; + const auto aliasFont = makeAliasFont(opt.font); + const auto subFont = opt.font; - progressFont.setPointSize(subFont.pointSize() - 2); + const auto errorFont = subFont; QFontMetrics subFm(subFont); QFontMetrics aliasFm(aliasFont); @@ -136,26 +199,10 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & const auto aliasMargin = aliasFm.height() / 2; const auto margin = subFm.height() / 4; - if (index.data(AddButton).toBool()) { - QStyleOptionButton opt; - static_cast(opt) = option; - if (opt.state & QStyle::State_Enabled && opt.state & QStyle::State_MouseOver && index == _pressedIndex) { - opt.state |= QStyle::State_Sunken; - } else { - opt.state |= QStyle::State_Raised; - } - opt.text = addFolderText(); - opt.rect = addButtonRect(option.rect, option.direction); - painter->save(); - painter->setFont(qApp->font("QPushButton")); - QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter, option.widget); - painter->restore(); - return; - } - if (dynamic_cast(index.model())->classify(index) != FolderStatusModel::RootFolder) { return; } + painter->save(); auto statusIcon = qvariant_cast(index.data(FolderStatusIconRole)); @@ -165,7 +212,6 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & auto errorTexts = qvariant_cast(index.data(FolderErrorMsg)); auto infoTexts = qvariant_cast(index.data(FolderInfoMsg)); - auto overallPercent = qvariant_cast(index.data(SyncProgressOverallPercent)); auto overallString = qvariant_cast(index.data(SyncProgressOverallString)); auto itemString = qvariant_cast(index.data(SyncProgressItemString)); auto warningCount = qvariant_cast(index.data(WarningCount)); @@ -194,7 +240,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & localPathRect.setTop(remotePathRect.bottom() + margin); localPathRect.setBottom(localPathRect.top() + subFm.height()); - iconRect.setBottom(localPathRect.bottom()); + iconRect.setBottom(iconRect.top() + IonosTheme::treeViewIconSize()); iconRect.setWidth(iconRect.height()); const auto nextToIcon = iconRect.right() + aliasMargin; @@ -204,8 +250,6 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & const auto iconSize = iconRect.width(); - auto optionsButtonVisualRect = optionsButtonRect(option.rect, option.direction); - const auto statusPixmap = statusIcon.pixmap(iconSize, iconSize, syncEnabled ? QIcon::Normal : QIcon::Disabled); painter->drawPixmap(QStyle::visualRect(option.direction, option.rect, iconRect).left(), iconRect.top(), statusPixmap); @@ -237,37 +281,31 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & painter->setPen(palette.color(colourGroup, QPalette::Text)); } - const auto elidedAlias = aliasFm.elidedText(aliasText, Qt::ElideRight, aliasRect.width()); - painter->setFont(aliasFont); - painter->drawText(QStyle::visualRect(option.direction, option.rect, aliasRect), textAlign, elidedAlias); + drawElidedText(painter, option, aliasFm, aliasFont, aliasText, aliasRect); const auto showProgess = !overallString.isEmpty() || !itemString.isEmpty(); if (!showProgess) { - painter->setFont(subFont); - const auto elidedRemotePathText = subFm.elidedText(syncText, Qt::ElideRight, remotePathRect.width()); - painter->drawText(QStyle::visualRect(option.direction, option.rect, remotePathRect), textAlign, elidedRemotePathText); + drawElidedText(painter, option, subFm, subFont, syncText, remotePathRect); - const auto elidedPathText = subFm.elidedText(pathText, Qt::ElideMiddle, localPathRect.width()); - painter->drawText(QStyle::visualRect(option.direction, option.rect, localPathRect), textAlign, elidedPathText); + drawElidedText(painter, option, subFm, subFont, pathText, localPathRect); } - auto textBoxTop = iconRect.bottom() + margin; + auto textBoxTop = qMax(localPathRect.bottom(), remotePathRect.bottom()) + margin; // paint an error overlay if there is an error string or conflict string - auto drawTextBox = [&](const QStringList &texts, QColor color) { + auto drawTextBox = [&](const QStringList &texts, QColor color, QColor borderColor) { auto rect = localPathRect; rect.setLeft(iconRect.left()); rect.setTop(textBoxTop); rect.setHeight(texts.count() * subFm.height() + 2 * margin); - rect.setRight(option.rect.right() - margin); + rect.setRight(option.rect.right() - aliasMargin); // save previous state to not mess up colours with the background (fixes issue: https://github.com/nextcloud/desktop/issues/1237) painter->save(); painter->setBrush(color); - painter->setPen(QColor(0xaa, 0xaa, 0xaa)); - painter->drawRoundedRect(QStyle::visualRect(option.direction, option.rect, rect), - 4, 4); - painter->setPen(Qt::white); + painter->setPen(borderColor); + painter->drawRoundedRect(QStyle::visualRect(option.direction, option.rect, rect), 4, 4); + painter->setPen(Qt::black); painter->setFont(errorFont); QRect textRect(rect.left() + margin, rect.top() + margin, @@ -285,87 +323,144 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & }; if (!conflictTexts.isEmpty()) { - drawTextBox(conflictTexts, QColor(0xba, 0xba, 0x4d)); + drawTextBox(conflictTexts, QColor(IonosTheme::warningColor()), QColor(IonosTheme::warningBorderColor())); } if (!errorTexts.isEmpty()) { - drawTextBox(errorTexts, QColor(0xbb, 0x4d, 0x4d)); + drawTextBox(errorTexts, QColor(IonosTheme::errorColor()), QColor(IonosTheme::errorBorderColor())); } if (!infoTexts.isEmpty()) { - drawTextBox(infoTexts, QColor(0x4d, 0x4d, 0xba)); + drawTextBox(infoTexts, QColor(IonosTheme::infoColor()), QColor(IonosTheme::infoBorderColor())); } // Sync File Progress Bar: Show it if syncFile is not empty. if (showProgess) { - const auto fileNameTextHeight = subFm.boundingRect(tr("File")).height(); - constexpr auto barHeight = 7; // same height as quota bar - const auto overallWidth = option.rect.right() - aliasMargin - optionsButtonVisualRect.width() - nextToIcon; + drawSyncProgressBar(painter, opt, index, subFm, aliasMargin, remotePathRect, margin, nextToIcon); + } - painter->save(); + drawMoreOptionsButton(painter, option, index); +} + +void FolderStatusDelegate::drawSyncProgressBar(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const QFontMetrics &subFm, const int aliasMargin, const QRect &remotePathRect, const int margin, const int nextToIcon) const +{ + auto overallPercent = qvariant_cast(index.data(SyncProgressOverallPercent)); + auto overallString = qvariant_cast(index.data(SyncProgressOverallString)); + auto optionsButtonVisualRect = optionsButtonRect(option.rect, option.direction); - // Overall Progress Bar. - const auto progressBarRect = QRect(nextToIcon, - remotePathRect.top(), - overallWidth - 2 * margin, - barHeight); + const auto fileNameTextHeight = subFm.boundingRect(tr("File")).height(); + constexpr auto barHeight = 7; // same height as quota bar + const auto overallWidth = option.rect.right() - aliasMargin - optionsButtonVisualRect.width() - nextToIcon; - QStyleOptionProgressBar progressBarOpt; + QFont progressFont(option.font); + progressFont.setPixelSize(IonosTheme::settingsTextPixel()); + progressFont.setWeight(IonosTheme::settingsTitleWeightNormal()); + painter->save(); + + // Overall Progress Bar. + const auto progressBarRect = QRect(nextToIcon, + remotePathRect.top(), + overallWidth - 2 * margin, + barHeight); + + QStyleOptionProgressBar progressBarOpt; - progressBarOpt.state = option.state | QStyle::State_Horizontal; - progressBarOpt.minimum = 0; - progressBarOpt.maximum = 100; - progressBarOpt.progress = overallPercent; - progressBarOpt.orientation = Qt::Horizontal; - progressBarOpt.rect = QStyle::visualRect(option.direction, option.rect, progressBarRect); + progressBarOpt.state = option.state | QStyle::State_Horizontal; + progressBarOpt.minimum = 0; + progressBarOpt.maximum = 100; + progressBarOpt.progress = overallPercent; + progressBarOpt.orientation = Qt::Horizontal; + progressBarOpt.rect = QStyle::visualRect(option.direction, option.rect, progressBarRect); #ifdef Q_OS_MACOS - backupStyle->drawControl(QStyle::CE_ProgressBar, &progressBarOpt, painter, option.widget); + backupStyle->drawControl(QStyle::CE_ProgressBar, &progressBarOpt, painter, option.widget); #else - QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOpt, painter, option.widget); + QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOpt, painter, option.widget); #endif + // Overall Progress Text + QRect overallProgressRect; + overallProgressRect.setTop(progressBarRect.bottom() + margin); + overallProgressRect.setHeight(fileNameTextHeight); + overallProgressRect.setLeft(progressBarRect.left()); + overallProgressRect.setWidth(progressBarRect.width()); + painter->setFont(progressFont); - // Overall Progress Text - QRect overallProgressRect; - overallProgressRect.setTop(progressBarRect.bottom() + margin); - overallProgressRect.setHeight(fileNameTextHeight); - overallProgressRect.setLeft(progressBarRect.left()); - overallProgressRect.setWidth(progressBarRect.width()); - painter->setFont(progressFont); - - painter->drawText(QStyle::visualRect(option.direction, option.rect, overallProgressRect), Qt::AlignLeft | Qt::AlignVCenter, overallString); - painter->restore(); - } + painter->drawText(QStyle::visualRect(option.direction, option.rect, overallProgressRect), Qt::AlignLeft | Qt::AlignVCenter, overallString); painter->restore(); +} + +void FolderStatusDelegate::drawElidedText(QPainter *painter, QStyleOptionViewItem option, QFontMetrics fontMetric, QFont font, QString text, QRect rect) const{ + const auto elidedText = fontMetric.elidedText(text, Qt::ElideRight, rect.width()); + painter->setFont(font); + painter->drawText(QStyle::visualRect(option.direction, option.rect, rect), Qt::AlignLeft, elidedText); +} + +void FolderStatusDelegate::drawMoreOptionsButton(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + auto optionsButtonVisualRect = optionsButtonRect(option.rect, option.direction); + + QStyleOptionButton btnOpt; + btnOpt.state = option.state; + btnOpt.state &= ~(QStyle::State_Selected | QStyle::State_HasFocus | QStyle::State_MouseOver); + btnOpt.state |= QStyle::State_Raised; + + if(optionsButtonVisualRect.contains(MousePos) ) { - QStyleOptionToolButton btnOpt; - btnOpt.state = option.state; - btnOpt.state &= ~(QStyle::State_Selected | QStyle::State_HasFocus); + btnOpt.state |= QStyle::State_MouseOver; + } + + if (btnOpt.state & QStyle::State_Enabled && btnOpt.state & QStyle::State_MouseOver && index == _pressedIndex) { + btnOpt.state |= QStyle::State_Sunken; + } else { btnOpt.state |= QStyle::State_Raised; - btnOpt.arrowType = Qt::NoArrow; - btnOpt.subControls = QStyle::SC_ToolButton; - btnOpt.rect = optionsButtonVisualRect; - btnOpt.icon = _iconMore; - const auto buttonSize = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize); - btnOpt.iconSize = QSize(buttonSize, buttonSize); - QApplication::style()->drawComplexControl(QStyle::CC_ToolButton, &btnOpt, painter); } + + btnOpt.rect = optionsButtonVisualRect; + btnOpt.icon = _iconMore; + const auto iconSize = optionsButtonIconSize(); + btnOpt.iconSize = QSize(iconSize, iconSize); + QWidget buttonWidget; + buttonWidget.setProperty("buttonStyle", QVariant::fromValue(OCC::ButtonStyleName::MoreOptions)); + + QApplication::style()-> + drawControl( + static_cast(sesStyle::CE_TreeViewMoreOptions), &btnOpt, painter, &buttonWidget); } -bool FolderStatusDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index) +int FolderStatusDelegate::optionsButtonIconSize() { + // Using this calculation to use the DPI-Scaled values. The QStyleHelper::dpiScaled is not accessible from here. + return QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize) - QApplication::style()->pixelMetric(QStyle::PM_MenuScrollerHeight); +} + +bool FolderStatusDelegate::editorEvent( + QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) { switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseMove: if (const auto *view = qobject_cast(option.widget)) { auto *me = dynamic_cast(event); - QModelIndex index; + QModelIndex pressedIndex; if (me->buttons()) { - index = view->indexAt(me->pos()); + pressedIndex = view->indexAt(me->pos()); } - if (_pressedIndex != index) { - _pressedIndex = index; + if (_pressedIndex != pressedIndex) { + _pressedIndex = pressedIndex; + view->viewport()->update(); + } + auto optionsButtonVisualRect = optionsButtonRect(option.rect, option.direction); + + MousePos = me->pos(); + if(optionsButtonVisualRect.contains(MousePos)) + { + _hoveredIndex = index; + view->viewport()->update(); + } else if(_hoveredIndex.isValid()) + { + _hoveredIndex = QModelIndex(); view->viewport()->update(); } } @@ -381,37 +476,29 @@ bool FolderStatusDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, QRect FolderStatusDelegate::optionsButtonRect(QRect within, Qt::LayoutDirection direction) { - QFont font = QFont(); + QFont font = QFont(IonosTheme::settingsFont()); QFont aliasFont = makeAliasFont(font); QFontMetrics fm(font); QFontMetrics aliasFm(aliasFont); within.setHeight(FolderStatusDelegate::rootFolderHeightWithoutErrors(fm, aliasFm)); - QStyleOptionToolButton opt; - int e = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize); - opt.rect.setSize(QSize(e,e)); - QSize size = QApplication::style()->sizeFromContents(QStyle::CT_ToolButton, &opt, opt.rect.size()).expandedTo(QApplication::globalStrut()); - - int margin = QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); - QRect r(QPoint(within.right() - size.width() - margin, + QStyleOptionButton opt; + int iconSize = optionsButtonIconSize(); + opt.rect.setSize(QSize(iconSize,iconSize)); + QSize size = QApplication::style()->sizeFromContents( + static_cast(sesStyle::CT_TreeViewMoreOptions), &opt, opt.rect.size()).expandedTo(QApplication::globalStrut()); + + // Using PM_LargeIconSize as margin because it get DPI Scaled, which I canot access from here + int margin = QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize); + QRect r(QPoint(within.right() - size.width() - aliasFm.height() / 2, within.top() + within.height() / 2 - size.height() / 2), size); return QStyle::visualRect(direction, within, r); } -QRect FolderStatusDelegate::addButtonRect(QRect within, Qt::LayoutDirection direction) -{ - QFontMetrics fm(qApp->font("QPushButton")); - QStyleOptionButton opt; - opt.text = addFolderText(); - QSize size = QApplication::style()->sizeFromContents(QStyle::CT_PushButton, &opt, fm.size(Qt::TextSingleLine, opt.text)).expandedTo(QApplication::globalStrut()); - QRect r(QPoint(within.left(), within.top() + within.height() / 2 - size.height() / 2), size); - return QStyle::visualRect(direction, within, r); -} - QRect FolderStatusDelegate::errorsListRect(QRect within) { - QFont font = QFont(); + QFont font = QFont(IonosTheme::settingsFont()); QFont aliasFont = makeAliasFont(font); QFontMetrics fm(font); QFontMetrics aliasFm(aliasFont); @@ -426,7 +513,7 @@ void FolderStatusDelegate::slotStyleChanged() void FolderStatusDelegate::customizeStyle() { - _iconMore = Theme::createColorAwareIcon(QLatin1String(":/client/theme/more.svg")); + _iconMore = Theme::createColorAwareIcon(QLatin1String(":/client/theme/ses/ses-more.svg"), QSize(128, 128)); } } // namespace OCC diff --git a/src/gui/folderstatusdelegate.h b/src/gui/folderstatusdelegate.h index a8ed1c41de3b4..32b6786d094f1 100644 --- a/src/gui/folderstatusdelegate.h +++ b/src/gui/folderstatusdelegate.h @@ -27,6 +27,7 @@ class FolderStatusDelegate : public QStyledItemDelegate Q_OBJECT public: FolderStatusDelegate(); + QPoint MousePos; enum datarole { FolderAliasRole = Qt::UserRole + 100, HeaderRole, @@ -60,8 +61,9 @@ class FolderStatusDelegate : public QStyledItemDelegate /** * return the position of the option button within the item */ + + static QRect optionsButtonRect(QRect within, Qt::LayoutDirection direction); - static QRect addButtonRect(QRect within, Qt::LayoutDirection direction); static QRect errorsListRect(QRect within); static int rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm); @@ -70,9 +72,17 @@ public slots: private: void customizeStyle(); + void drawAddButton(QPainter *,const QStyleOptionViewItem &, const QModelIndex &) const; + void drawElidedText(QPainter *painter, QStyleOptionViewItem option, QFontMetrics fontMetric, QFont font, QString text, QRect rect) const; + void drawSyncProgressBar(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const QFontMetrics &subFm, const int aliasMargin, const QRect &remotePathRect, const int margin, const int nextToIcon) const; + void drawMoreOptionsButton(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + static int optionsButtonIconSize(); static QString addFolderText(); + static QString addInfoText(); QPersistentModelIndex _pressedIndex; + QPersistentModelIndex _hoveredIndex; QIcon _iconMore; }; diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp index 41d363490fd12..bfec5612871fa 100644 --- a/src/gui/folderstatusmodel.cpp +++ b/src/gui/folderstatusmodel.cpp @@ -29,7 +29,7 @@ Q_DECLARE_METATYPE(QPersistentModelIndex) namespace OCC { -Q_LOGGING_CATEGORY(lcFolderStatus, "nextcloud.gui.folder.model", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFolderStatus, "hidrivenext.gui.folder.model", QtInfoMsg) static const char propertyParentIndexC[] = "oc_parentIndex"; static const char propertyPermissionMap[] = "oc_permissionMap"; diff --git a/src/gui/folderstatusview.cpp b/src/gui/folderstatusview.cpp index a353bc49f269a..876d02bc36b50 100644 --- a/src/gui/folderstatusview.cpp +++ b/src/gui/folderstatusview.cpp @@ -14,11 +14,15 @@ #include "folderstatusview.h" #include "folderstatusdelegate.h" +#include "ionostheme.h" namespace OCC { FolderStatusView::FolderStatusView(QWidget *parent) : QTreeView(parent) { + #ifdef Q_OS_MAC + setPalette(QPalette(QPalette::ButtonText, IonosTheme::white())); + #endif } QModelIndex FolderStatusView::indexAt(const QPoint &point) const @@ -32,11 +36,7 @@ QModelIndex FolderStatusView::indexAt(const QPoint &point) const QRect FolderStatusView::visualRect(const QModelIndex &index) const { - QRect rect = QTreeView::visualRect(index); - if (index.data(FolderStatusDelegate::AddButton).toBool()) { - return FolderStatusDelegate::addButtonRect(rect, layoutDirection()); - } - return rect; + return QTreeView::visualRect(index); } } // namespace OCC diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index 93c2334506a8f..e16ee14e02299 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -46,7 +46,7 @@ constexpr auto lockChangeDebouncingTimerIntervalMs = 500; namespace OCC { -Q_LOGGING_CATEGORY(lcFolderWatcher, "nextcloud.gui.folderwatcher", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFolderWatcher, "hidrivenext.gui.folderwatcher", QtInfoMsg) FolderWatcher::FolderWatcher(Folder *folder) : QObject(folder) diff --git a/src/gui/folderwizard.cpp b/src/gui/folderwizard.cpp index 3d3efa2b1fa16..bef8377342108 100644 --- a/src/gui/folderwizard.cpp +++ b/src/gui/folderwizard.cpp @@ -13,16 +13,18 @@ */ #include "folderwizard.h" -#include "folderman.h" -#include "configfile.h" -#include "theme.h" -#include "networkjobs.h" #include "account.h" -#include "selectivesyncdialog.h" #include "accountstate.h" +#include "buttonstyle.h" +#include "common/asserts.h" +#include "configfile.h" #include "creds/abstractcredentials.h" +#include "folderman.h" +#include "networkjobs.h" +#include "selectivesyncdialog.h" +#include "theme.h" +#include "SesComponents/syncdirvalidation.h" #include "wizard/owncloudwizard.h" -#include "common/asserts.h" #include #include @@ -30,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -62,9 +65,9 @@ QString FormatWarningsWizardPage::formatWarnings(const QStringList &warnings) co { QString ret; if (warnings.count() == 1) { - ret = tr("Warning: %1").arg(warnings.first()); + ret = tr("%1").arg(warnings.first()); } else if (warnings.count() > 1) { - ret = tr("Warning:") + "