diff --git a/.github/workflows/clang-tidy-review.yml b/.github/workflows/clang-tidy-review.yml index 547fcf415fe62..63a179250d571 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-appimage.yml b/.github/workflows/linux-appimage.yml index 020598b9ee6a3..576a7ead54a16 100644 --- a/.github/workflows/linux-appimage.yml +++ b/.github/workflows/linux-appimage.yml @@ -1,7 +1,6 @@ name: Linux Appimage Package on: - pull_request: - types: [opened, synchronize, reopened] + workflow_dispatch: jobs: build: name: Linux Appimage Package diff --git a/.github/workflows/linux-clang-compile-tests.yml b/.github/workflows/linux-clang-compile-tests.yml index ff3b4965102b6..e97864cc164a0 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 679ea2472c577..10c8ac628b03c 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 63652e10d89ed..9e0fdc24f6a1a 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 2aa869326675b..4d1b68b0f3460 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 ed0e17987fed5..579bacd987648 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,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. @@ -192,3 +192,7 @@ convert.exe *state-*.png theme.qrc *.AppImage + +.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..35bb85a720422 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,97 @@ +{ + // 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": "(Linux-IONOS-RelWithDebInfo) Launch HiDriveNext", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/linux-GCC-x64/RelWithDebInfo/bin/IONOS_HiDrive_Next", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + "name": "LD_LIBRARY_PATH", + "value": "/home/chaetty/CraftRoot/bin:${env:LD_LIBRARY_PATH}" + }, + { "name": "QML_IMPORT_TRACE", "value": "1" }, + { "name": "QT_LOGGING_RULES", "value": "qt.qml.debug=true" } + ], + "MIMode": "gdb", + "setupCommands": [ + { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } + ] + }, + { + "name": "(Linux-STRATO-RelWithDebInfo) Launch HiDriveNext", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/linux-GCC-x64/RelWithDebInfo/bin/STRATO_HiDrive_Next", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + "name": "LD_LIBRARY_PATH", + "value": "/home/chaetty/CraftRoot/bin:${env:LD_LIBRARY_PATH}" + }, + { "name": "QML_IMPORT_TRACE", "value": "1" }, + { "name": "QT_LOGGING_RULES", "value": "qt.qml.debug=true" } + ], + "MIMode": "gdb", + "setupCommands": [ + { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } + ] + }, + { + "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:/CraftRoot/bin;C:/Craft64/bin;%PATH%" + }, + { "name": "QML_IMPORT_TRACE", "value": "1" }, + { "name": "QT_LOGGING_RULES", "value": "qt.qml.debug=true" } + ], + }, + { + "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:/CraftRoot/bin;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:/CraftRoot/bin;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..0b78cf0be5532 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "cmake.generator": "Ninja", + "cmake.configureSettings": { + "CMAKE_PREFIX_PATH": [ + "C:/CraftRoot", + "C:/CraftRoot/dev-utils/bin", + "C:/Craft64", + "C:/Craft64/dev-utils/bin" + ], + "BUILD_TESTING":"OFF", + }, + "cmake.buildDirectory": "${workspaceFolder}/../build/${buildKitTargetOs}-${buildKitVendor}-${buildKitTargetArch}/${buildType}", + "files.associations": { + "qwizardpage": "cpp", + "xutility": "cpp" + }, +} \ 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 63403f560486e..b94d99db8ab1b 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() @@ -22,6 +24,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) set(QT_VERSION_MAJOR "6") set(REQUIRED_QT_VERSION "6.8.0") @@ -218,7 +221,7 @@ if(OWNCLOUD_5XX_NO_BLACKLIST) endif() if(APPLE) - set( SOCKETAPI_TEAM_IDENTIFIER_PREFIX "" CACHE STRING "SocketApi prefix (including a following dot) that must match the codesign key's TeamIdentifier/Organizational Unit" ) + set( SOCKETAPI_TEAM_IDENTIFIER_PREFIX "5TDLCVD243." CACHE STRING "SocketApi prefix (including a following dot) that must match the codesign key's TeamIdentifier/Organizational Unit" ) endif() if(BUILD_CLIENT) 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..40c8b3a6df33d --- /dev/null +++ b/IONOS.cmake @@ -0,0 +1,63 @@ +set( APPLICATION_REV_DOMAIN "com.ionos.hidrivenext.desktopclient" ) + +option(LOCALBUILD "Local developer build" OFF) + +if(LOCALBUILD) + ## Only needed for local build + message(STATUS "Building in LOCAL mode") + + set( APPLICATION_VIRTUALFILE_SUFFIX "${APPLICATION_SHORTNAME}_virtual" CACHE STRING "Virtual file suffix (not including the .)" FORCE) + + ## 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" ) + endif() + + if("${WHITELABEL_NAME}" STREQUAL "strato") + set( APPLICATION_NAME "STRATO HiDrive Next" ) + set( APPLICATION_SHORTNAME "STRATOHiDriveNext" ) + set( APPLICATION_EXECUTABLE "strato-hidrive-next" ) + set( APPLICATION_CONFIG_NAME "STRATO-HiDrive-Next" ) + set( APPLICATION_ICON_NAME "strato_hidrive_next" ) + set( APPLICATION_DOMAIN "strato.com" ) + set( APPLICATION_UPDATE_URL "https://customerupdates.nextcloud.com/client/" CACHE STRING "URL for updater" FORCE) + set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" FORCE) + 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" FORCE) + elseif("${WHITELABEL_NAME}" STREQUAL "ionos") + set( APPLICATION_NAME "IONOS HiDrive Next" ) + set( APPLICATION_SHORTNAME "IONOSHiDriveNext" ) + set( APPLICATION_EXECUTABLE "ionos-hidrive-next" ) + set( APPLICATION_CONFIG_NAME "IONOS-HiDrive-Next" ) + set( APPLICATION_ICON_NAME "ionos_hidrive_next" ) + set( APPLICATION_DOMAIN "ionos.com" ) + set( APPLICATION_UPDATE_URL "https://customerupdates.nextcloud.com/client/" CACHE STRING "URL for updater" FORCE) + set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" FORCE) + 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" FORCE) + endif() + +endif() + + +if(APPLE AND "${APPLICATION_NAME}" MATCHES "HiDrive Next") + set(APPLICATION_ICON_NAME "${APPLICATION_EXECUTABLE}-macOS") + message("Using macOS-specific application icon: ${APPLICATION_ICON_NAME}") +endif() + +if(APPLICATION_NAME STREQUAL "STRATO HiDrive Next") + set( APPLICATION_VENDOR "STRATO" ) + add_compile_definitions(STRATO_WL_BUILD) +elseif(APPLICATION_NAME STREQUAL "IONOS HiDrive Next") + set( APPLICATION_VENDOR "IONOS SE" ) + add_compile_definitions(IONOS_WL_BUILD) +endif() \ No newline at end of file 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/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..db2dcf5ea2e50 100644 --- a/NextcloudCPack.cmake +++ b/NextcloudCPack.cmake @@ -2,7 +2,7 @@ include( InstallRequiredSystemLibraries ) 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 8e9ac37721458..71ab9d71a9f60 100644 --- a/VERSION.cmake +++ b/VERSION.cmake @@ -25,6 +25,7 @@ set(NEXTCLOUD_SERVER_VERSION_SECURE_FILEDROP_MIN_SUPPORTED_PATCH 0) set(NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MAJOR 28) set(NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MINOR 0) set(NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_PATCH 3) +set(MIRALL_VERSION_SUFFIX "stable") # ------------------------------------ # Define default suffixes if not set diff --git a/admin/osx/ionos_macmaker/mac_craft.sh b/admin/osx/ionos_macmaker/mac_craft.sh new file mode 100755 index 0000000000000..35943bc4018a4 --- /dev/null +++ b/admin/osx/ionos_macmaker/mac_craft.sh @@ -0,0 +1,260 @@ +#!/bin/bash + + +# This script is used to build the Mac OS X version of the IONOS client. +# Parse the command line arguments +while getopts "b:p:s:n:k:civtu" opt; do + case ${opt} in + b )REL_BASE_DIR=$OPTARG;; + p )REL_PATH_TO_PKG=$OPTARG ;; + s )IONOS_TEAM_IDENTIFIER=$OPTARG ;; + n )NC_TEAM_IDENTIFIER=$OPTARG ;; + k )SPARKLE_KEY=$OPTARG ;; # not used + c )CLEAN_REBUILD=true ;; + i )PACKAGE_INSTALLER=true ;; + v )VERBOSE=true ;; + t )TEAM_PATCHING=true ;; + u )BUILD_UPDATER=true ;; + \? ) + echo "Usage: mac_craft.sh [-b ] [-p ] [-s ] [-n ] [-k ] [-c CLEAN_REBUILD] [-i PACKAGE_INSTALLER] [-v VERBOSE] [-t TEAM_PATCHING] [-u BUILD_UPDATER]" + exit 1 + ;; + esac +done + +if [ "$VERBOSE" = true ]; then + echo "VERBOSE MODE" + set -xe +fi + +if [ "$TEAM_PATCHING" == "true" ] && [ -z "$NC_TEAM_IDENTIFIER" ]; then + echo "Patching aktivated, but NC_TEAM_IDENTIFIER not set. Exiting." + exit 0 +fi + +# Check if CODE_SIGN_IDENTITY is set, if not exit +if [ -z "$IONOS_TEAM_IDENTIFIER" ]; then + echo "IONOS_TEAM_IDENTIFIER not set. Exiting." + exit 0 +fi + +if [ -z "$REL_BASE_DIR" ]; then + echo "REL_BASE_DIR not set. Exiting." + exit 0 +fi + +if [ -z "$REL_PATH_TO_PKG" ]; then + echo "REL_PATH_TO_PKG not set. Exiting." + exit 0 +fi + +# Some variables +BASE_DIR="$( cd "$REL_BASE_DIR" && pwd )" +PATH_TO_PKG="$( realpath "$REL_PATH_TO_PKG")" + +PKG_FULLNAME=$(basename "$PATH_TO_PKG") +PKG_FILENAME="${PKG_FULLNAME%.pkg}" +PRODUCT_NAME="IONOS HiDrive Next" +UNDERSCORE_PRODUCT_NAME="IONOS_HiDrive_Next" + +CODE_SIGN_IDENTITY="IONOS SE ($IONOS_TEAM_IDENTIFIER)" +INSTALLER_CERT="Developer ID Installer: $CODE_SIGN_IDENTITY" +APPLICATION_CERT="Developer ID Application: $CODE_SIGN_IDENTITY" + +WORK_DIR="ex" +EXTRACTED_DIR="${BASE_DIR%/}/$WORK_DIR" + +PRODUCT_DIR=$EXTRACTED_DIR/$UNDERSCORE_PRODUCT_NAME.pkg/Payload/Applications +SCRIPTS_DIR=$EXTRACTED_DIR/$UNDERSCORE_PRODUCT_NAME.pkg/Scripts +INNER_PKG=$EXTRACTED_DIR/$UNDERSCORE_PRODUCT_NAME.pkg +PAYLOAD_DIR=$EXTRACTED_DIR/$UNDERSCORE_PRODUCT_NAME.pkg/Payload +INSTALLER_PKG=${BASE_DIR%/}/INSTALLER.pkg +APP_PATH=$PRODUCT_DIR/$PRODUCT_NAME.app +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ADMIN_OSX="$( cd "$SCRIPT_DIR/.." && pwd )/macosx.entitlements.cmake" +MACCRAFTER_DIR="$( cd "$SCRIPT_DIR/../mac-crafter" && pwd )" + + +# Sparkle Variables +PACKAGE_PATH="${BASE_DIR%/}/$PKG_FILENAME.resigned.pkg" +SPARKLE_TBZ_PATH="${PACKAGE_PATH}.tbz" +SPARKLE_DIR="${BASE_DIR%/}/sparkle" +SPARKLE_DOWNLOAD_URI="https://github.com/sparkle-project/Sparkle/releases/download/1.27.3/Sparkle-1.27.3.tar.xz" + + +echo "Expanding original package..." + +if [ -d "$EXTRACTED_DIR" ]; then + + echo "$EXTRACTED_DIR already exits." + + if [ "$CLEAN_REBUILD" = true ]; then + echo "Clean Rebuild Enabled - Deleting folder: $EXTRACTED_DIR" + rm -rf "$EXTRACTED_DIR" + pkgutil --expand-full "$PATH_TO_PKG" "$EXTRACTED_DIR" + fi +else + pkgutil --expand-full "$PATH_TO_PKG" "$EXTRACTED_DIR" +fi + +# --------------------------------------------------- +# Patch Team Identifier + +# check wether patching is needed. ".com" is important because otherwise the ID in the signature will be found + +if [[ -n "$TEAM_PATCHING" ]]; then + echo "Team Patching Enabled - Start Patching Detection" + + PLIST_MATCHES=$(find "$APP_PATH" -name "*.plist" -exec grep -q "$NC_TEAM_IDENTIFIER.com" {} \; -print | wc -l) + BIN_MATCHES=$(find "$APP_PATH" -type f -exec grep -q --binary-files=text "$NC_TEAM_IDENTIFIER.com" {} \; -print | wc -l) + + if [[ "$PLIST_MATCHES" -gt 0 || "$BIN_MATCHES" -gt 0 ]]; then + # Ensure both IDs are same lengt + if [[ ${#NC_TEAM_IDENTIFIER} -ne ${#IONOS_TEAM_IDENTIFIER} ]]; then + echo "NC_TEAM_IDENTIFIER and IONOS_TEAM_IDENTIFIER must be the same length for binary-safe patching." + open $BASE_DIR + exit 1 + fi + + if [[ "$PLIST_MATCHES" -gt 0 ]]; then + # --- Replace in .plist files (plain XML) --- + echo "Replacing Team Identifier in .plist files..." + find "$APP_PATH" -name "*.plist" -exec grep -q "$NC_TEAM_IDENTIFIER" {} \; -exec sed -i '' "s/$NC_TEAM_IDENTIFIER/$IONOS_TEAM_IDENTIFIER/g" {} \; + fi + + if [[ "$BIN_MATCHES" -gt 0 ]]; then + # Find and patch all binaries containing the old ID + find "$APP_PATH" -type f -exec grep -q --binary-files=text "$NC_TEAM_IDENTIFIER" {} \; -print | while read -r file; do + echo "Patching Team Identifier in $file" + perl -pi -e "s/$NC_TEAM_IDENTIFIER/$IONOS_TEAM_IDENTIFIER/g" "$file" + done + fi + else + echo "Nothing to patch" + fi +fi + +# --------------------------------------------------- +# Sign the client + + + +echo "start signing the client" + +swift run --package-path "$MACCRAFTER_DIR" \ + mac-crafter codesign \ + -c "$CODE_SIGN_IDENTITY" \ + -e "$ADMIN_OSX" \ + "$APP_PATH" + +# 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 "$APP_PATH" 2>&1 | grep -q "TeamIdentifier=$IONOS_TEAM_IDENTIFIER"; then + echo "TeamIdentifier does not match. Exiting." + open $BASE_DIR + exit 0 +fi + +# --------------------------------------------------- +# Installer + +echo "start building the installer" + +# Build the installer, if enabled +if [ -z "$PACKAGE_INSTALLER" ]; then + echo "Installer packaging not enabled. Exiting." + open $BASE_DIR + exit 0 +fi + +echo "Renew BOM" +mkbom "$PAYLOAD_DIR" "$INNER_PKG/Bom" +echo "Reassembling the package..." +(cd "$PAYLOAD_DIR" && \ + find . | cpio -o --format odc | gzip -c) > $PAYLOAD_DIR.new + +rm -rf $PAYLOAD_DIR +mv $PAYLOAD_DIR.new $PAYLOAD_DIR + +(cd $EXTRACTED_DIR && \ + pkgutil --flatten $UNDERSCORE_PRODUCT_NAME.pkg $UNDERSCORE_PRODUCT_NAME.pkg.flat) + +rm -rf $INNER_PKG +mv $INNER_PKG.flat $INNER_PKG + +productsign --timestamp --sign "$INSTALLER_CERT" \ + $INNER_PKG \ + $INNER_PKG.signed + +rm -rf $INNER_PKG +mv $INNER_PKG.signed $INNER_PKG + +(cd $BASE_DIR && productbuild \ + --distribution ex/Distribution \ + --resources ex/Resources \ + --package-path ex \ + $INSTALLER_PKG.unsigned) + +productsign --timestamp --sign "$INSTALLER_CERT" $INSTALLER_PKG.unsigned "$PACKAGE_PATH" + +# catch the output of the notarytool command +OUTPUT=$(xcrun notarytool submit --wait "$PACKAGE_PATH"\ + --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." + open $BASE_DIR + exit 1 +fi + +# staple +xcrun stapler staple "$PACKAGE_PATH" +xcrun stapler validate "$PACKAGE_PATH" + + +# Sparkle + +SPARKLE_TBZ_PATH="${PACKAGE_PATH}.tbz" + +echo "Creating Sparkle package archive: $SPARKLE_TBZ_PATH" + +# Load Sparkle + +if [ "$BUILD_UPDATER" == "true" ]; then + echo "Creating Sparkle package archive: $SPARKLE_TBZ_PATH" + + if [ -d "$SPARKLE_DIR" ]; then + + echo "$SPARKLE_DIR already exits." + echo "Deleting..." + rm -rf "$SPARKLE_DIR" + fi + + echo "Download Sparkle" + + mkdir -p $SPARKLE_DIR + wget $SPARKLE_DOWNLOAD_URI -O ${SPARKLE_DIR%/}/Sparkle.tar.xz + tar -xvf ${SPARKLE_DIR%/}/Sparkle.tar.xz -C $SPARKLE_DIR + + if tar cf "$SPARKLE_TBZ_PATH" -C "$(dirname "$PACKAGE_PATH")" "$(basename "$PACKAGE_PATH")"; then + echo "✅ Sparkle package created successfully." + else + echo "❌ Could not create Sparkle package tbz!" >&2 + exit 1 + fi + + echo "Signing Sparkle package: $SPARKLE_TBZ_PATH" + if "${SPARKLE_DIR%/}/bin/sign_update" "$SPARKLE_TBZ_PATH"; then + echo "✅ Sparkle package signed successfully." + else + echo "❌ Could not sign Sparkle package tbz!" >&2 + exit 1 + fi + +fi + +open $BASE_DIR + diff --git a/admin/osx/ionos_macmaker/readme.md b/admin/osx/ionos_macmaker/readme.md new file mode 100644 index 0000000000000..ccbb3ec269c92 --- /dev/null +++ b/admin/osx/ionos_macmaker/readme.md @@ -0,0 +1,126 @@ +# mac_craft.sh + +This script automates the build and signing process for the **IONOS HiDrive Next** macOS client installer. It takes an existing `.pkg` package, optionally patches team identifiers, resigns the app, reassembles the installer, notarizes it with Apple, and finally creates a **Sparkle update package** for distribution. + +--- + +## Features + +* **Expand** a given `.pkg` file into a working directory +* **Patch** identifiers if required (team patching mode) +* **Resign** the app with the correct `Developer ID` certificates +* **Reassemble** the installer package with updated files +* **Notarize and staple** the final package with Apple’s notarization service +* **Build Sparkle update** archives for app updates + +--- + +## Usage + +```bash +./mac_craft.sh [-b ] [-p ] [-s ] [-n ] [-k ] [-c] [-i] [-v] [-t] [-u] +``` + +### Options + +* `-b ` : Base directory for build and output +* `-p ` : Path to the original `.pkg` installer +* `-s ` : IONOS Team Identifier (required for signing) +* `-n ` : Old Team Identifier (used for patching if needed) +* `-k ` : Sparkle signing key (**currently unused**) +* `-c` : Clean rebuild (delete old extracted directory before expanding) +* `-i` : Enable installer packaging (otherwise exits after signing) +* `-v` : Verbose mode (print debug output) +* `-t` : Enable team patching mode (replaces `NC_TEAM_IDENTIFIER` with `IONOS_TEAM_IDENTIFIER`) +* `-u` : Build Sparkle updater package + +--- + +## Workflow + +1. **Expand Original Package** + + * The `.pkg` is expanded into a working directory (`pkgutil --expand-full`). + * If `-c` is set, any previous working directory is removed first. + +2. **Patch Identifiers (Optional)** + + * If `-t` is used, the script searches `.plist` and binary files for the old team identifier. + * It replaces it with the new `IONOS_TEAM_IDENTIFIER`. + * Both IDs must have the same character length to ensure safe binary patching. + +3. **Resign Application** + + * The client `.app` is signed using the `mac-crafter` tool. + * Codesign identity: `Developer ID Application: IONOS SE (TEAM_ID)` + * Verifies that the signed app’s TeamIdentifier matches the expected one. + +4. **Reassemble Installer** + + * Recreates the package payload (`mkbom`, `cpio`, `pkgutil --flatten`). + * Signs the installer with: + `Developer ID Installer: IONOS SE (TEAM_ID)` + * Uses `productbuild` and `productsign` to generate the final signed package. + +5. **Notarization & Stapling** + + * Submits the package to Apple’s Notary Service (`xcrun notarytool`). + * Waits for the result, validates acceptance. + * Applies a **staple** to the installer (`xcrun stapler staple`). + +6. **Sparkle Update Build (Optional)** + + * Downloads Sparkle if not available. + * Archives the signed package as `.tbz`. + * Signs the archive using Sparkle’s `sign_update` tool. + +--- + +## Example + +```bash +./mac_craft.sh \ + -b /Users/developer/build \ + -p ./IONOS.pkg \ + -s ABC123XYZ \ + -n OLDTEAMID \ + -i -c -t -u -v +``` + +This command will: + +* Clean and rebuild the working directory +* Expand `IONOS.pkg` +* Patch from `OLDTEAMID` → `ABC123XYZ` +* Resign the app and installer +* Notarize and staple the package +* Build a signed Sparkle update archive +* Run in verbose mode + +--- + +## Requirements + +* macOS with Xcode tools installed +* Apple Developer account with: + + * **Developer ID Application** certificate + * **Developer ID Installer** certificate +* `mac-crafter` tool (Swift package, provided by Nextcloud) +* `pkgutil`, `productbuild`, `productsign`, `notarytool`, `stapler`, `xcrun`, `grep`, `sed`, `mkbom`, `cpio`, `gzip` +* `wget`, `tar`, `perl` for Sparkle integration + +--- + +## Output + +* Final notarized installer: + + ``` + /.resigned.pkg + ``` +* Sparkle update archive (if `-u` enabled): + + ``` + /.resigned.pkg.tbz + ``` diff --git a/admin/osx/ionos_macmaker/sign.sh b/admin/osx/ionos_macmaker/sign.sh new file mode 100755 index 0000000000000..f781a41c9cb4d --- /dev/null +++ b/admin/osx/ionos_macmaker/sign.sh @@ -0,0 +1,212 @@ +#!/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. +# Parse the command line arguments +while getopts "b:p:s:civt" opt; do + case ${opt} in + b )BASE_DIR=$OPTARG;; + p )PATH_TO_PKG=$OPTARG ;; + s )CODE_SIGN_IDENTITY=$OPTARG ;; + c )CLEAN_REBUILD=true ;; + i )PACKAGE_INSTALLER=true ;; + v )VERBOSE=true ;; + t )TEAM_PATCHING=true ;; + \? ) + echo "Usage: sign.sh [-b ] [-p ] [-s ] [-c clean-rebuild] [-i build-installer] [-v verbose]" + exit 1 + ;; + esac +done + +if [ "$VERBOSE" = true ]; then + set -xe +fi + +# Some variables +PKG_FULLNAME=$(basename "$PATH_TO_PKG") +PKG_FILENAME="${PKG_FULLNAME%.pkg}" +PRODUCT_NAME="IONOS HiDrive Next" +UNDERSCORE_PRODUCT_NAME="IONOS_HiDrive_Next" + +IONOS_TEAM_IDENTIFIER="5TDLCVD243" +NC_TEAM_IDENTIFIER="NKUJUXUJ3B" +INSTALLER_CERT="Developer ID Installer: $CODE_SIGN_IDENTITY" +APPLICATION_CERT="Developer ID Application: $CODE_SIGN_IDENTITY" + +WORK_DIR="ex" +EXTRACTED_DIR="${BASE_DIR%/}/$WORK_DIR" + +PRODUCT_DIR=$EXTRACTED_DIR/$UNDERSCORE_PRODUCT_NAME.pkg/Payload/Applications +SCRIPTS_DIR=$EXTRACTED_DIR/$UNDERSCORE_PRODUCT_NAME.pkg/Scripts +INNER_PKG=$EXTRACTED_DIR/$UNDERSCORE_PRODUCT_NAME.pkg +PAYLOAD_DIR=$EXTRACTED_DIR/$UNDERSCORE_PRODUCT_NAME.pkg/Payload +INSTALLER_PKG=$BASE_DIR/INSTALLER.pkg +APP_PATH=$PRODUCT_DIR/$PRODUCT_NAME.app + +echo "Expanding original package..." + +if [ -d "$EXTRACTED_DIR" ]; then + + echo "$EXTRACTED_DIR already exits." + + if [ "$CLEAN_REBUILD" = true ]; then + echo "Clean Rebuild Enabled - Deleting folder: $EXTRACTED_DIR" + rm -rf "$EXTRACTED_DIR" + pkgutil --expand-full "$PATH_TO_PKG" "$EXTRACTED_DIR" + fi +else + pkgutil --expand-full "$PATH_TO_PKG" "$EXTRACTED_DIR" +fi + +# --------------------------------------------------- +# Patch Team Identifier + +# check wether patching is needed. ".com" is important because otherwise the ID in the signature will be found + +if [[ -n "$TEAM_PATCHING" ]]; then + PLIST_MATCHES=$(find "$APP_PATH" -name "*.plist" -exec grep -q "$NC_TEAM_IDENTIFIER.com" {} \; -print | wc -l) + BIN_MATCHES=$(find "$APP_PATH" -type f -exec grep -q --binary-files=text "$NC_TEAM_IDENTIFIER.com" {} \; -print | wc -l) + + if [[ "$PLIST_MATCHES" -gt 0 || "$BIN_MATCHES" -gt 0 ]]; then + # Ensure both IDs are same lengt + if [[ ${#NC_TEAM_IDENTIFIER} -ne ${#IONOS_TEAM_IDENTIFIER} ]]; then + echo "NC_TEAM_IDENTIFIER and IONOS_TEAM_IDENTIFIER must be the same length for binary-safe patching." + open $BASE_DIR + exit 1 + fi + + if [[ "$PLIST_MATCHES" -gt 0 ]]; then + # --- Replace in .plist files (plain XML) --- + echo "Replacing Team Identifier in .plist files..." + find "$APP_PATH" -name "*.plist" -exec grep -q "$NC_TEAM_IDENTIFIER" {} \; -exec sed -i '' "s/$NC_TEAM_IDENTIFIER/$IONOS_TEAM_IDENTIFIER/g" {} \; + fi + + if [[ "$BIN_MATCHES" -gt 0 ]]; then + # Find and patch all binaries containing the old ID + find "$APP_PATH" -type f -exec grep -q --binary-files=text "$NC_TEAM_IDENTIFIER" {} \; -print | while read -r file; do + echo "Patching Team Identifier in $file" + perl -pi -e "s/$NC_TEAM_IDENTIFIER/$IONOS_TEAM_IDENTIFIER/g" "$file" + done + fi + fi +fi +# --------------------------------------------------- +# 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 $BASE_DIR + exit 0 +fi + +echo "start signing the client" + +CLIENT_CONTENTS_DIR=$APP_PATH/Contents +CLIENT_FRAMEWORKS_DIR=$CLIENT_CONTENTS_DIR/Frameworks +CLIENT_RESOURCES_DIR=$CLIENT_CONTENTS_DIR/Resources +CLIENT_PLUGINS_DIR=$CLIENT_CONTENTS_DIR/PlugIns + +for script in $SCRIPTS_DIR/*; do + echo "→ Signing script: $script" + codesign -s "$CODE_SIGN_IDENTITY" --force --preserve-metadata=entitlements --verbose=4 --options=runtime --timestamp "$script" +done + +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 "$APP_PATH" + +# Sign the client ---- Still needed? +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 "$APP_PATH" 2>&1 | grep -q "TeamIdentifier=$IONOS_TEAM_IDENTIFIER"; then + echo "TeamIdentifier does not match. Exiting." + open $BASE_DIR + exit 0 +fi + +# --------------------------------------------------- +# Installer + +echo "start building the installer" + +# Build the installer, if enabled +if [ -z "$PACKAGE_INSTALLER" ]; then + echo "Installer packaging not enabled. Exiting." + open $BASE_DIR + exit 0 +fi + +echo "Renew BOM" +mkbom $PAYLOAD_DIR $INNER_PKG/Bom +echo "Reassembling the package..." +(cd $PAYLOAD_DIR && \ + find . | cpio -o --format odc | gzip -c) > $PAYLOAD_DIR.new + +rm -rf $PAYLOAD_DIR +mv $PAYLOAD_DIR.new $PAYLOAD_DIR + +(cd $EXTRACTED_DIR && \ + pkgutil --flatten $UNDERSCORE_PRODUCT_NAME.pkg $UNDERSCORE_PRODUCT_NAME.pkg.flat) + +rm -rf $INNER_PKG +mv $INNER_PKG.flat $INNER_PKG + +productsign --timestamp --sign 'Developer ID Installer: IONOS SE (5TDLCVD243)' \ + $INNER_PKG \ + $INNER_PKG.signed + +rm -rf $INNER_PKG +mv $INNER_PKG.signed $INNER_PKG + +(cd $BASE_DIR && productbuild \ + --distribution ex/Distribution \ + --resources ex/Resources \ + --package-path ex \ + $INSTALLER_PKG.unsigned) + +productsign --timestamp --sign 'Developer ID Installer: IONOS SE (5TDLCVD243)' $INSTALLER_PKG.unsigned "$BASE_DIR$PKG_FILENAME.resigned.pkg" + +# catch the output of the notarytool command +OUTPUT=$(xcrun notarytool submit --wait "$BASE_DIR$PKG_FILENAME.resigned.pkg"\ + --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." + open $BASE_DIR + exit 1 +fi + +# staple +xcrun stapler staple "$BASE_DIR$PKG_FILENAME.resigned.pkg" +xcrun stapler validate "$BASE_DIR$PKG_FILENAME.resigned.pkg" + +open $BASE_DIR + diff --git a/admin/osx/ionos_macmaker/start.sh b/admin/osx/ionos_macmaker/start.sh new file mode 100755 index 0000000000000..7644c702c6fac --- /dev/null +++ b/admin/osx/ionos_macmaker/start.sh @@ -0,0 +1,190 @@ +#!/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 "a:b:s:cifou" opt; do + case ${opt} in + a ) ARCHITECTURE=$OPTARG ;; + b )BUILD_DIR=$OPTARG;; + s )CODE_SIGN_IDENTITY=$OPTARG ;; + c )CLEAN_REBUILD=true ;; + i )PACKAGE_INSTALLER=true ;; + f )BUILD_FILEPROVIDER=true ;; + o )OSX_BUNDLE=true ;; + u )BUILD_UPDATER=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 +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 ARCHITECTURE is set +if [ -z "$ARCHITECTURE" ]; then + echo "ARCHITECTURE not set. Add -a 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=$(if [ $OSX_BUNDLE == true ]; then echo "ON"; else echo "OFF"; fi) \ + -DCMAKE_OSX_ARCHITECTURES=$ARCHITECTURE \ + -DBUILD_FILE_PROVIDER_MODULE=$(if [ $BUILD_FILEPROVIDER == true ]; then echo "ON"; else echo "OFF"; fi) \ + -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/osx/mac-crafter/Sources/main.swift b/admin/osx/mac-crafter/Sources/main.swift index 11338ff51cf8b..3b55ba0b64c28 100644 --- a/admin/osx/mac-crafter/Sources/main.swift +++ b/admin/osx/mac-crafter/Sources/main.swift @@ -56,7 +56,7 @@ struct Build: ParsableCommand { var buildType = "RelWithDebInfo" @Option(name: [.long], help: "The application's branded name.") - var appName = "Nextcloud" + var appName = "IONOS HiDrive Next" @Option(name: [.long], help: "Sparkle download URL.") var sparkleDownloadUrl = @@ -286,7 +286,7 @@ struct Codesign: ParsableCommand { static let configuration = CommandConfiguration(abstract: "Codesigning script for the client.") @Argument(help: "Path to the Nextcloud Desktop Client app bundle.") - var appBundlePath = "\(FileManager.default.currentDirectoryPath)/product/Nextcloud.app" + var appBundlePath = "\(FileManager.default.currentDirectoryPath)/product/IONOS HiDrive Next.app" @Option(name: [.short, .long], help: "Code signing identity for desktop client and libs.") var codeSignIdentity: String @@ -323,7 +323,7 @@ struct Package: ParsableCommand { var craftBlueprintName = "nextcloud-client" @Option(name: [.long], help: "The application's branded name.") - var appName = "Nextcloud" + var appName = "IONOS HiDrive Next" @Option(name: [.long], help: "Apple ID, used for notarisation.") var appleId: String? diff --git a/admin/osx/macosx.entitlements.cmake b/admin/osx/macosx.entitlements.cmake index 49b409f036c1a..b206b92465afc 100644 --- a/admin/osx/macosx.entitlements.cmake +++ b/admin/osx/macosx.entitlements.cmake @@ -4,7 +4,7 @@ com.apple.security.application-groups - @SOCKETAPI_TEAM_IDENTIFIER_PREFIX@@APPLICATION_REV_DOMAIN@ + 5TDLCVD243.com.ionos.hidrivenext.desktopclient diff --git a/admin/win/msi/CMakeLists.txt b/admin/win/msi/CMakeLists.txt index 26385a88b385b..6d3229ad3fbe5 100644 --- a/admin/win/msi/CMakeLists.txt +++ b/admin/win/msi/CMakeLists.txt @@ -27,9 +27,9 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/make-msi.bat EnsureACL.js Platform.wxi - Nextcloud.wxs + Ionos.wxs ${CMAKE_CURRENT_BINARY_DIR}/RegistryCleanup.vbs RegistryCleanupCustomAction.wxs - gui/banner.bmp - gui/dialog.bmp + gui/banner_2.bmp + gui/dialog_2.bmp DESTINATION msi/) 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 similarity index 100% rename from admin/win/msi/Nextcloud.wxs rename to admin/win/msi/Nextcloud~~.wxs diff --git a/admin/win/msi/OEM.wxi.in b/admin/win/msi/OEM.wxi.in index e35451eef2904..101dfa1379f2e 100644 --- a/admin/win/msi/OEM.wxi.in +++ b/admin/win/msi/OEM.wxi.in @@ -50,7 +50,7 @@ - - + + diff --git a/admin/win/msi/gui/banner_2.bmp b/admin/win/msi/gui/banner_2.bmp new file mode 100644 index 0000000000000..5270a99dea084 Binary files /dev/null and b/admin/win/msi/gui/banner_2.bmp differ diff --git a/admin/win/msi/gui/dialog_2.bmp b/admin/win/msi/gui/dialog_2.bmp new file mode 100644 index 0000000000000..b870b472556df Binary files /dev/null and b/admin/win/msi/gui/dialog_2.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 7e996b4b7c009..7c89afae884b3 100644 --- a/cmake/modules/MacOSXBundleInfo.plist.in +++ b/cmake/modules/MacOSXBundleInfo.plist.in @@ -38,10 +38,8 @@ SUShowReleaseNotes - SUPublicDSAKeyFile - dsa_pub.pem SUPublicEDKey - c3RcfDWDayvsYSZW8FhZN1UOJhvPVN30zleb4zOqbtU= + FQ8Dq6AiSDDv4XpnyJ3b6mQBFYLPKgj9ziEg/+VNGHg= UTExportedTypeDeclarations diff --git a/config.h.in b/config.h.in index 464c2168933d6..b9381d0971250 100644 --- a/config.h.in +++ b/config.h.in @@ -13,6 +13,7 @@ #cmakedefine THEME_INCLUDE @THEME_INCLUDE@ #cmakedefine APPLICATION_NAME "@APPLICATION_NAME@" +#cmakedefine WHITELABEL_BRAND "@WHITELABEL_BRAND@" #cmakedefine APPLICATION_VENDOR "@APPLICATION_VENDOR@" #cmakedefine APPLICATION_DOMAIN "@APPLICATION_DOMAIN@" #cmakedefine APPLICATION_REV_DOMAIN "@APPLICATION_REV_DOMAIN@" 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 6036fa0e1bff0..d85ff0a581f09 100644 --- a/resources.qrc +++ b/resources.qrc @@ -57,6 +57,7 @@ src/gui/tray/ListItemLineAndSubline.qml src/gui/tray/TrayFoldersMenuButton.qml src/gui/tray/TrayFolderListItem.qml + src/gui/tray/HeaderLogo.qml src/gui/ResolveConflictsDialog.qml src/gui/ConflictDelegate.qml src/gui/ConflictItemFileInfo.qml @@ -66,5 +67,12 @@ 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 + src/gui/tray/TrayWindowAccountMenu.qml + src/gui/tray/PrimaryPillButton.qml + src/gui/tray/SecondaryPillButton.qml + src/gui/tray/IconButton.qml + src/gui/tray/AccountMenuItem.qml diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExt.entitlements b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExt.entitlements index eab912dc49600..8102f4fb7df19 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExt.entitlements +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExt.entitlements @@ -6,7 +6,7 @@ com.apple.security.application-groups - $(OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX)$(OC_APPLICATION_REV_DOMAIN) + 5TDLCVD243.com.ionos.hidrivenext.desktopclient com.apple.security.network.client diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift index ed24ee68ff06e..4201ac6d30b65 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift @@ -109,7 +109,7 @@ extension FileProviderExtension: NSFileProviderServicing, ChangeNotificationInte userId: String, serverUrl: String, password: String, - userAgent: String = "Nextcloud-macOS/FileProviderExt" + userAgent: String = "IONOS HiDrive Next/FileProviderExt" ) { let account = Account(user: user, id: userId, serverUrl: serverUrl, password: password) guard account != ncAccount else { return } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift index 687c08ca9b01d..d04dd3a8d220e 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 a572942a88d67..5d67a7e919b08 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 dfff3bf34ae7a..cacc61ccc9d2a 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/DocumentActionViewController.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/DocumentActionViewController.swift @@ -37,11 +37,11 @@ class DocumentActionViewController: FPUIActionExtensionViewController { Logger.actionViewController.info("Preparing action: \(actionIdentifier, privacy: .public)") switch (actionIdentifier) { - case "com.nextcloud.desktopclient.FileProviderUIExt.ShareAction": + case "com.ionos.hidrivenext.desktopclient.FileProviderUIExt.ShareAction": prepare(childViewController: ShareViewController(itemIdentifiers)) - case "com.nextcloud.desktopclient.FileProviderUIExt.LockFileAction": + case "com.ionos.hidrivenext.desktopclient.FileProviderUIExt.LockFileAction": prepare(childViewController: LockViewController(itemIdentifiers, locking: true)) - case "com.nextcloud.desktopclient.FileProviderUIExt.UnlockFileAction": + case "com.ionos.hidrivenext.desktopclient.FileProviderUIExt.UnlockFileAction": prepare(childViewController: LockViewController(itemIdentifiers, locking: false)) default: return diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Info.plist b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Info.plist index 31930b7627abc..eb627d452692a 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Info.plist +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Info.plist @@ -14,7 +14,7 @@ NSExtensionFileProviderActionIdentifier - com.nextcloud.desktopclient.FileProviderUIExt.UnlockFileAction + com.ionos.hidrivenext.desktopclient.FileProviderUIExt.UnlockFileAction NSExtensionFileProviderActionName Unlock file NSExtensionFileProviderActionActivationRule @@ -26,13 +26,13 @@ NSExtensionFileProviderActionName Lock file NSExtensionFileProviderActionIdentifier - com.nextcloud.desktopclient.FileProviderUIExt.LockFileAction + com.ionos.hidrivenext.desktopclient.FileProviderUIExt.LockFileAction 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/Locking/LockViewController.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Locking/LockViewController.swift index 4ab55ccb34d91..bed4d4c66b49b 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Locking/LockViewController.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Locking/LockViewController.swift @@ -179,7 +179,7 @@ class LockViewController: NSViewController { user: account.username, userId: account.id, password: account.password, - userAgent: "Nextcloud-macOS/FileProviderUIExt", + userAgent: "IONOS HiDrive Next/FileProviderUIExt", nextcloudVersion: 25, groupIdentifier: "" ) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Sharing/ShareTableViewDataSource.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Sharing/ShareTableViewDataSource.swift index f670d92edce49..f902472b43720 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Sharing/ShareTableViewDataSource.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Sharing/ShareTableViewDataSource.swift @@ -36,7 +36,7 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele private(set) var shares: [NKShare] = [] { didSet { Task { @MainActor in sharesTableView?.reloadData() } } } - private(set) var userAgent: String = "Nextcloud-macOS/FileProviderUIExt" + private(set) var userAgent: String = "IONOS HiDrive Next/FileProviderUIExt" private(set) var account: Account? { didSet { guard let account = account else { return } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Sharing/ShareViewController.xib b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Sharing/ShareViewController.xib index 7a61de7033656..f64580297a7d3 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Sharing/ShareViewController.xib +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Sharing/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 acd19d4c6cb77..6976023fd5f1c 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 77291d09d3164..6046b66218f62 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 9d73955f7c939..36bb66040018a 100644 --- a/src/common/filesystembase.cpp +++ b/src/common/filesystembase.cpp @@ -41,7 +41,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 a8333f114cab1..189ee087870c7 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 7afb4b98f6a08..421f0c32ff2be 100644 --- a/src/common/remotepermissions.cpp +++ b/src/common/remotepermissions.cpp @@ -25,7 +25,7 @@ namespace OCC { -Q_LOGGING_CATEGORY(lcRemotePermissions, "nextcloud.sync.remotepermissions", QtInfoMsg) +Q_LOGGING_CATEGORY(lcRemotePermissions, "hidrivenext.sync.remotepermissions", QtInfoMsg) static const char letters[] = " GWDNVCKRSMm"; 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 0813c12a17dc4..16784cc8c986c 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 b76f6cb6450bf..d4abb40c2d4f9 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_exclude.cpp b/src/csync/csync_exclude.cpp index 2c23656f2320f..247fd7c2e5ed2 100644 --- a/src/csync/csync_exclude.cpp +++ b/src/csync/csync_exclude.cpp @@ -153,7 +153,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 8f319a3e4b23f..fec107018c701 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 da677c692433d..093daa58389ee 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 a58117fcfe5b6..924b85873dddf 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,790 +1,826 @@ -project(gui) -find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS Widgets Svg Qml Quick QuickControls2 QuickWidgets Xml Network) -find_package(KF6Archive REQUIRED) -find_package(KF6GuiAddons) - -if (NOT TARGET Qt::GuiPrivate) - message(FATAL_ERROR "Could not find GuiPrivate component of Qt. It might be shipped as a separate package, please check that.") -endif() - -if(CMAKE_BUILD_TYPE MATCHES Debug) - add_definitions(-DQT_QML_DEBUG) -endif() - -IF(BUILD_UPDATER) - add_subdirectory(updater) -endif() - -configure_file(${CMAKE_SOURCE_DIR}/theme.qrc.in ${CMAKE_SOURCE_DIR}/theme.qrc) -set(theme_dir ${CMAKE_SOURCE_DIR}/theme) - -set(client_UI_SRCS - accountsettings.ui - conflictdialog.ui - invalidfilenamedialog.ui - caseclashfilenamedialog.ui - foldercreationdialog.ui - folderwizardsourcepage.ui - folderwizardtargetpage.ui - generalsettings.ui - legalnotice.ui - ignorelisteditor.ui - ignorelisttablewidget.ui - networksettings.ui - settingsdialog.ui - sslerrordialog.ui - addcertificatedialog.ui - passwordinputdialog.ui - proxyauthdialog.ui - mnemonicdialog.ui - vfsdownloaderrordialog.ui - wizard/flow2authwidget.ui - wizard/owncloudadvancedsetuppage.ui - wizard/owncloudconnectionmethoddialog.ui - wizard/owncloudhttpcredspage.ui - wizard/owncloudsetupnocredspage.ui - wizard/termsofservicecheckwidget.ui - wizard/webview.ui - wizard/welcomepage.ui -) - -qt_add_resources(client_UI_SRCS ../../resources.qrc ${CMAKE_SOURCE_DIR}/theme.qrc) - -set(client_SRCS - accountmanager.h - accountmanager.cpp - accountsettings.h - accountsettings.cpp - accountsetupfromcommandlinejob.h - accountsetupfromcommandlinejob.cpp - accountsetupcommandlinemanager.h - accountsetupcommandlinemanager.cpp - application.h - application.cpp - invalidfilenamedialog.h - invalidfilenamedialog.cpp - caseclashfilenamedialog.h - caseclashfilenamedialog.cpp - callstatechecker.h - callstatechecker.cpp - conflictdialog.h - conflictdialog.cpp - conflictsolver.h - conflictsolver.cpp - connectionvalidator.h - connectionvalidator.cpp - editlocallyjob.h - editlocallyjob.cpp - editlocallymanager.h - editlocallymanager.cpp - editlocallyverificationjob.h - editlocallyverificationjob.cpp - filetagmodel.h - filetagmodel.cpp - folder.h - folder.cpp - foldercreationdialog.h - foldercreationdialog.cpp - folderman.h - folderman.cpp - folderstatusmodel.h - folderstatusmodel.cpp - folderstatusdelegate.h - folderstatusdelegate.cpp - folderstatusview.h - folderstatusview.cpp - folderwatcher.h - folderwatcher.cpp - folderwizard.h - folderwizard.cpp - generalsettings.h - generalsettings.cpp - legalnotice.h - legalnotice.cpp - ignorelisteditor.h - ignorelisteditor.cpp - ignorelisttablewidget.h - ignorelisttablewidget.cpp - lockwatcher.h - lockwatcher.cpp - logbrowser.h - logbrowser.cpp - networksettings.h - networksettings.cpp - ocsnavigationappsjob.h - ocsnavigationappsjob.cpp - ocsjob.h - ocsjob.cpp - ocssharejob.h - ocssharejob.cpp - ocsshareejob.h - ocsshareejob.cpp - openfilemanager.h - openfilemanager.cpp - owncloudgui.h - owncloudgui.cpp - owncloudsetupwizard.h - owncloudsetupwizard.cpp - passwordinputdialog.h - passwordinputdialog.cpp - selectivesyncdialog.h - selectivesyncdialog.cpp - settingsdialog.h - settingsdialog.cpp - sharemanager.h - sharemanager.cpp - profilepagewidget.h - profilepagewidget.cpp - sharee.h - sharee.cpp - sslbutton.h - sslbutton.cpp - sslerrordialog.h - sslerrordialog.cpp - syncrunfilelog.h - syncrunfilelog.cpp - systray.h - systray.cpp - EncryptionTokenSelectionWindow.qml - userinfo.h - userinfo.cpp - vfsdownloaderrordialog.h - vfsdownloaderrordialog.cpp - accountstate.h - accountstate.cpp - addcertificatedialog.h - addcertificatedialog.cpp - authenticationdialog.h - authenticationdialog.cpp - proxyauthhandler.h - proxyauthhandler.cpp - proxyauthdialog.h - proxyauthdialog.cpp - tooltipupdater.h - tooltipupdater.cpp - notificationconfirmjob.h - notificationconfirmjob.cpp - guiutility.h - guiutility.cpp - elidedlabel.h - elidedlabel.cpp - iconutils.h - iconutils.cpp - remotewipe.h - remotewipe.cpp - userstatusselectormodel.h - userstatusselectormodel.cpp - emojimodel.h - emojimodel.cpp - syncconflictsmodel.h - syncconflictsmodel.cpp - fileactivitylistmodel.h - fileactivitylistmodel.cpp - filedetails/datefieldbackend.h - filedetails/datefieldbackend.cpp - filedetails/filedetails.h - filedetails/filedetails.cpp - filedetails/sharemodel.h - filedetails/sharemodel.cpp - filedetails/shareemodel.h - filedetails/shareemodel.cpp - filedetails/sortedsharemodel.h - filedetails/sortedsharemodel.cpp - tray/svgimageprovider.h - tray/svgimageprovider.cpp - tray/syncstatussummary.h - tray/syncstatussummary.cpp - tray/activitydata.h - tray/activitydata.cpp - tray/activitylistmodel.h - tray/activitylistmodel.cpp - tray/unifiedsearchresult.h - tray/asyncimageresponse.cpp - tray/unifiedsearchresult.cpp - tray/unifiedsearchresultslistmodel.h - tray/trayimageprovider.cpp - tray/unifiedsearchresultslistmodel.cpp - tray/usermodel.h - tray/usermodel.cpp - tray/notificationhandler.h - tray/notificationhandler.cpp - tray/sortedactivitylistmodel.h - tray/sortedactivitylistmodel.cpp - creds/credentialsfactory.h - tray/talkreply.cpp - creds/credentialsfactory.cpp - creds/httpcredentialsgui.h - creds/httpcredentialsgui.cpp - creds/flow2auth.h - creds/flow2auth.cpp - creds/webflowcredentials.h - creds/webflowcredentials.cpp - creds/webflowcredentialsdialog.h - creds/webflowcredentialsdialog.cpp - wizard/postfixlineedit.h - wizard/postfixlineedit.cpp - wizard/abstractcredswizardpage.h - wizard/abstractcredswizardpage.cpp - wizard/owncloudadvancedsetuppage.h - wizard/owncloudadvancedsetuppage.cpp - wizard/owncloudconnectionmethoddialog.h - wizard/owncloudconnectionmethoddialog.cpp - wizard/owncloudhttpcredspage.h - wizard/owncloudhttpcredspage.cpp - wizard/flow2authcredspage.h - wizard/flow2authcredspage.cpp - wizard/flow2authwidget.h - wizard/flow2authwidget.cpp - wizard/owncloudsetuppage.h - wizard/owncloudsetuppage.cpp - wizard/termsofservicecheckwidget.h - wizard/termsofservicecheckwidget.cpp - wizard/termsofservicewizardpage.h - wizard/termsofservicewizardpage.cpp - wizard/owncloudwizardcommon.h - wizard/owncloudwizardcommon.cpp - wizard/owncloudwizard.h - wizard/owncloudwizard.cpp - wizard/slideshow.h - wizard/slideshow.cpp - wizard/welcomepage.h - wizard/welcomepage.cpp - wizard/linklabel.h - wizard/linklabel.cpp - ) - -if (WITH_WEBENGINE) - list(APPEND client_SRCS - wizard/webviewpage.h - wizard/webviewpage.cpp - wizard/webview.h - wizard/webview.cpp - ) -endif() - -IF(BUILD_UPDATER) - set(updater_SRCS - updater/ocupdater.h - updater/ocupdater.cpp - updater/updateinfo.h - updater/updateinfo.cpp - updater/updater.h - updater/updater.cpp - ) -endif() - -IF( APPLE ) - list(APPEND client_SRCS cocoainitializer_mac.mm) - list(APPEND client_SRCS systray_mac_common.mm) - - list(APPEND client_SRCS systray_mac_usernotifications.mm) - - if (BUILD_FILE_PROVIDER_MODULE) - list(APPEND client_SRCS - # Symlinks to files in shell_integration/MacOSX/NextcloudIntegration/ - macOS/ClientCommunicationProtocol.h - # End of symlink files - macOS/fileprovider.h - macOS/fileprovider_mac.mm - macOS/fileproviderdomainmanager.h - macOS/fileproviderdomainmanager_mac.mm - macOS/fileproviderdomainsyncstatus.h - macOS/fileproviderdomainsyncstatus_mac.mm - macOS/fileprovidereditlocallyjob.h - macOS/fileprovidereditlocallyjob.cpp - macOS/fileprovidereditlocallyjob_mac.mm - macOS/fileprovideritemmetadata.h - macOS/fileprovideritemmetadata.cpp - macOS/fileprovideritemmetadata_mac.mm - macOS/fileprovidermaterialiseditemsmodel.h - macOS/fileprovidermaterialiseditemsmodel.cpp - macOS/fileprovidermaterialiseditemsmodel_mac.mm - macOS/fileprovidersettingscontroller.h - macOS/fileprovidersettingscontroller_mac.mm - macOS/fileprovidersocketcontroller.h - macOS/fileprovidersocketcontroller.cpp - macOS/fileprovidersocketserver.h - macOS/fileprovidersocketserver.cpp - macOS/fileprovidersocketserver_mac.mm - macOS/fileproviderstorageuseenumerationobserver.h - macOS/fileproviderstorageuseenumerationobserver.m - macOS/fileproviderutils.h - macOS/fileproviderutils_mac.mm - macOS/fileproviderxpc.h - macOS/fileproviderxpc_mac.mm - macOS/fileproviderxpc_mac_utils.h - macOS/fileproviderxpc_mac_utils.mm - macOS/progressobserver.h - macOS/progressobserver.m) - endif() - - list(APPEND client_SRCS foregroundbackground_interface.h) - list(APPEND client_SRCS foregroundbackground_mac.mm) - list(APPEND client_SRCS foregroundbackground_cocoa.mm) - - if(SPARKLE_FOUND AND BUILD_UPDATER) - # Define this, we need to check in updater.cpp - add_definitions(-DHAVE_SPARKLE) - list(APPEND updater_SRCS updater/sparkleupdater_mac.mm updater/sparkleupdater.h) - list(APPEND updater_DEPS ${SPARKLE_LIBRARY}) - - # Sparkle.framework is installed from here because macdeployqt's CopyFramework breaks on this bundle - # as its logic is tightly tailored around Qt frameworks - install(DIRECTORY "${SPARKLE_LIBRARY}" - DESTINATION "${OWNCLOUD_OSX_BUNDLE}/Contents/Frameworks" USE_SOURCE_PERMISSIONS) - - endif() -ENDIF() - -IF( NOT WIN32 AND NOT APPLE ) - set(client_SRCS ${client_SRCS} folderwatcher_linux.cpp) -ENDIF() -IF( WIN32 ) - set(client_SRCS - ${client_SRCS} - folderwatcher_win.cpp - navigationpanehelper.h - navigationpanehelper.cpp - shellextensionsserver.cpp - ${CMAKE_SOURCE_DIR}/src/common/shellextensionutils.cpp) -ENDIF() -IF( APPLE ) - list(APPEND client_SRCS folderwatcher_mac.cpp) -ENDIF() - -set(3rdparty_SRC - ../3rdparty/QProgressIndicator/QProgressIndicator.h - ../3rdparty/QProgressIndicator/QProgressIndicator.cpp - ../3rdparty/qtlockedfile/qtlockedfile.h - ../3rdparty/qtlockedfile/qtlockedfile.cpp - ../3rdparty/qtsingleapplication/qtlocalpeer.h - ../3rdparty/qtsingleapplication/qtlocalpeer.cpp - ../3rdparty/qtsingleapplication/qtsingleapplication.h - ../3rdparty/qtsingleapplication/qtsingleapplication.cpp - ../3rdparty/qtsingleapplication/qtsinglecoreapplication.h - ../3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp - ../3rdparty/kmessagewidget/kmessagewidget.h - ../3rdparty/kmessagewidget/kmessagewidget.cpp - ../3rdparty/kirigami/wheelhandler.h - ../3rdparty/kirigami/wheelhandler.cpp - ) - -set_property(SOURCE ../3rdparty/kmessagewidget/kmessagewidget.cpp PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) -set_property(SOURCE ../3rdparty/kirigami/wheelhandler.cpp PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) - -if(NOT WIN32) - list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_unix.cpp) - set_property(SOURCE ../3rdparty/qtlockedfile/qtlockedfile_unix.cpp PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) -else() - list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_win.cpp ) - set_property(SOURCE ../3rdparty/qtlockedfile/qtlockedfile_win.cpp PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) -endif() - -find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} COMPONENTS LinguistTools) -if(Qt${QT_MAJOR_VERSION}LinguistTools_FOUND) - qt_add_translation(client_I18N ${TRANSLATIONS}) -endif() - -IF( WIN32 ) - configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in - ${CMAKE_CURRENT_BINARY_DIR}/version.rc - @ONLY) - set(client_version ${CMAKE_CURRENT_BINARY_DIR}/version.rc) - IF(NOT MSVC) - set(client_manifest ${CMAKE_CURRENT_SOURCE_DIR}/manifest-mingw.rc) - ENDIF() -ENDIF() - -set( final_src - ${client_SRCS} - ${client_UI_SRCS} - ${guiMoc} - ${client_I18N} - ${3rdparty_SRC} - ${3rdparty_MOC} -) - -if(Qt${QT_MAJOR_VERSION}Keychain_FOUND) - list(APPEND libsync_LINK_TARGETS Qt6::keychain) -endif() - -# add executable icon on windows and osx -include(GenerateIconsUtils) - -# For historical reasons we can not use the application_shortname -# for ownCloud but must rather set it manually. -if (NOT DEFINED APPLICATION_ICON_NAME) - set(APPLICATION_ICON_NAME ${APPLICATION_SHORTNAME}) -endif() - -if(NOT DEFINED APPLICATION_FOLDER_ICON_INDEX) - set(APPLICATION_FOLDER_ICON_INDEX 0) -endif() - -set(STATE_ICONS_COLORS colored black white) - -foreach(state_icons_color ${STATE_ICONS_COLORS}) - set(STATE_ICONS_PATH "${theme_dir}/${state_icons_color}/") - - message("Generating state icons from SVG in path: ${STATE_ICONS_PATH}") - - file(GLOB_RECURSE STATE_ICONS_SVG "${STATE_ICONS_PATH}/state-*.svg") - - foreach(state_icon_svg ${STATE_ICONS_SVG}) - get_filename_component(status_icon_name_wle ${state_icon_svg} NAME_WLE) - foreach(size IN ITEMS 16;32;64;128;256) - generate_sized_png_from_svg(${state_icon_svg} ${size} OUTPUT_ICON_FULL_NAME_WLE "${status_icon_name_wle}-${size}") - endforeach() - endforeach() -endforeach() - -if ((APPLICATION_ICON_SET MATCHES "PNG") - AND - (NOT EXISTS "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon.svg")) - # We may have no svg application icon in when customers use PNG - # icons in brander, but theme.qrc expects a svg icon. - file(TOUCH "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon.svg") -endif() - -if(APPLE) - set(MACOS_SIDEBAR_ICON_SVG "${theme_dir}/colored/${APPLICATION_ICON_NAME}-sidebar.svg") - generate_sized_png_from_svg(${MACOS_SIDEBAR_ICON_SVG} 16) - generate_sized_png_from_svg(${MACOS_SIDEBAR_ICON_SVG} 32) - generate_sized_png_from_svg(${MACOS_SIDEBAR_ICON_SVG} 64) - generate_sized_png_from_svg(${MACOS_SIDEBAR_ICON_SVG} 128) - generate_sized_png_from_svg(${MACOS_SIDEBAR_ICON_SVG} 256) -endif() - -if(WIN32) - set(STARTMENU_ICON_SVG "${theme_dir}/colored/${APPLICATION_ICON_NAME}-w10startmenu.svg") - generate_sized_png_from_svg(${STARTMENU_ICON_SVG} 70) - generate_sized_png_from_svg(${STARTMENU_ICON_SVG} 150) -endif() - -set(APP_ICON_SVG "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon.svg") - -# generate secondary icon if available (currently for Windows only)-------------------------------------- -set(APP_SECONDARY_ICONS "${theme_dir}/colored/icons") -set(APP_ICON_WIN_FOLDER_SVG "${APP_SECONDARY_ICONS}/${APPLICATION_ICON_NAME}-icon-win-folder.svg") - -set(RC_DEPENDENCIES "") - -if(WIN32) - if (EXISTS ${APP_ICON_WIN_FOLDER_SVG}) - get_filename_component(output_icon_name_win ${APP_ICON_WIN_FOLDER_SVG} NAME_WLE) - # Product icon (for smallest size) - foreach(size IN ITEMS 16;20) - generate_sized_png_from_svg(${APP_ICON_SVG} ${size} OUTPUT_ICON_NAME ${output_icon_name_win} OUTPUT_ICON_PATH "${APP_SECONDARY_ICONS}/") - endforeach() - - # Product icon with Windows folder (for sizes larger than 20) - foreach(size IN ITEMS 24;32;40;48;64;128;256;512;1024) - generate_sized_png_from_svg(${APP_ICON_WIN_FOLDER_SVG} ${size} OUTPUT_ICON_NAME ${output_icon_name_win} OUTPUT_ICON_PATH "${APP_SECONDARY_ICONS}/") - endforeach() - - file(GLOB_RECURSE OWNCLOUD_ICONS_WIN_FOLDER "${APP_SECONDARY_ICONS}/*-${APPLICATION_ICON_NAME}-icon*") - set(APP_ICON_WIN_FOLDER_ICO_NAME "${APPLICATION_ICON_NAME}-win-folder") - set(RC_DEPENDENCIES "${RC_DEPENDENCIES} ${APP_ICON_WIN_FOLDER_ICO_NAME}.ico") - ecm_add_app_icon(APP_ICON_WIN_FOLDER ICONS "${OWNCLOUD_ICONS_WIN_FOLDER}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASENAME "${APP_ICON_WIN_FOLDER_ICO_NAME}" ICON_INDEX 2) - endif() -endif() -# -------------------------------------- - -if (NOT ${RC_DEPENDENCIES} STREQUAL "") - string(STRIP ${RC_DEPENDENCIES} RC_DEPENDENCIES) -endif() - -# generate primary icon from SVG (due to Win .ico vs .rc dependency issues, primary icon must always be generated last)-------------------------------------- -if(WIN32) - foreach(size IN ITEMS 16;20;24;32;40;48;64;128;256;512;1024) - generate_sized_png_from_svg(${APP_ICON_SVG} ${size}) - endforeach() -else() - foreach(size IN ITEMS 16;24;32;48;64;128;256;512;1024) - generate_sized_png_from_svg(${APP_ICON_SVG} ${size}) - endforeach() -endif() - -file(GLOB_RECURSE OWNCLOUD_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-icon*") - -if(APPLE) - file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-sidebar*") - MESSAGE(STATUS "OWNCLOUD_SIDEBAR_ICONS: ${APPLICATION_ICON_NAME}: ${OWNCLOUD_SIDEBAR_ICONS}") -endif() - -ecm_add_app_icon(APP_ICON RC_DEPENDENCIES ${RC_DEPENDENCIES} ICONS "${OWNCLOUD_ICONS}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASENAME "${APPLICATION_ICON_NAME}" ICON_INDEX 1) -# -------------------------------------- - -if(WIN32) -# merge *.rc.in files for Windows (multiple ICON resources must be placed in a single file, otherwise, this won't work de to a bug in Windows compiler https://developercommunity.visualstudio.com/t/visual-studio-2017-prof-1557-cvt1100-duplicate-res/363156) - function(merge_files IN_FILE OUT_FILE) - file(READ ${IN_FILE} CONTENTS) - message("Merging ${IN_FILE} into ${OUT_FILE}") - file(APPEND ${OUT_FILE} "${CONTENTS}") - endfunction() - message("APP_ICON is: ${APP_ICON}") - if(APP_ICON) - get_filename_component(RC_IN_FOLDER ${APP_ICON}} DIRECTORY) - - file(GLOB_RECURSE RC_IN_FILES "${RC_IN_FOLDER}/*rc.in") - - foreach(rc_in_file IN ITEMS ${RC_IN_FILES}) - get_filename_component(rc_in_file_name ${rc_in_file} NAME) - get_filename_component(app_icon_name "${APP_ICON}.in" NAME) - if(NOT "${rc_in_file_name}" STREQUAL "${app_icon_name}") - merge_files(${rc_in_file} "${APP_ICON}.in") - if (DEFINED APPLICATION_FOLDER_ICON_INDEX) - MATH(EXPR APPLICATION_FOLDER_ICON_INDEX "${APPLICATION_FOLDER_ICON_INDEX}+1") - message("APPLICATION_FOLDER_ICON_INDEX is now set to: ${APPLICATION_FOLDER_ICON_INDEX}") - endif() - endif() - endforeach() - endif() -endif() -# -------------------------------------- - -if(UNIX AND NOT APPLE) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIE") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie") -endif() - -if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib") -endif() - -add_library(nextcloudCore STATIC ${final_src}) - -target_link_libraries(nextcloudCore - PUBLIC - Nextcloud::sync - Qt::Widgets - Qt::GuiPrivate - Qt::Svg - Qt::Network - Qt::Xml - Qt::Qml - Qt::Quick - Qt::QuickControls2 - Qt::QuickWidgets - KF6::Archive - ) - -if(KF6GuiAddons_FOUND) - target_link_libraries(nextcloudCore - PUBLIC - KF6::GuiAddons - ) - add_definitions(-DHAVE_KGUIADDONS) -endif() - -add_subdirectory(socketapi) - -# skip unity inclusion for files which cause problems with a CMake unity build -set_property(SOURCE - ${CMAKE_CURRENT_SOURCE_DIR}/socketapi/socketapi.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/socketapi/socketuploadjob.cpp - PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) - -foreach(FILE IN LISTS client_UI_SRCS) - set_property(SOURCE ${FILE} PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) -endforeach() - -if(WITH_WEBENGINE) - target_link_libraries(nextcloudCore PUBLIC Qt::WebEngineWidgets Qt::WebEngineCore) -endif() - -set_target_properties(nextcloudCore - PROPERTIES - AUTOUIC ON - AUTOMOC ON -) - -target_include_directories(nextcloudCore - PUBLIC - ${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator - ${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile - ${CMAKE_SOURCE_DIR}/src/3rdparty/kirigami - ${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication - ${CMAKE_SOURCE_DIR}/src/3rdparty/kmessagewidget - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR} -) - -if(NOT BUILD_OWNCLOUD_OSX_BUNDLE) - if(NOT WIN32) - file(GLOB _icons "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-icon.png") - foreach(_file ${_icons}) - string(REPLACE "${theme_dir}/colored/" "" _res ${_file}) - string(REPLACE "-${APPLICATION_ICON_NAME}-icon.png" "" _res ${_res}) - install(FILES ${_file} RENAME ${APPLICATION_ICON_NAME}.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${_res}x${_res}/apps) - endforeach(_file) - install(FILES ${client_I18N} DESTINATION ${CMAKE_INSTALL_DATADIR}/${APPLICATION_EXECUTABLE}/i18n) - else() - file(GLOB_RECURSE VISUAL_ELEMENTS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-w10startmenu*") - install(FILES ${VISUAL_ELEMENTS} DESTINATION bin/visualelements) - install(FILES "${theme_dir}/${APPLICATION_EXECUTABLE}.VisualElementsManifest.xml" DESTINATION bin) - install(FILES ${client_I18N} DESTINATION i18n) - endif() - - # we may not add MACOSX_BUNDLE here, if not building one - add_executable(nextcloud WIN32 main.cpp ${client_version} ${client_manifest} ${APP_ICON}) - set_target_properties(nextcloud PROPERTIES - OUTPUT_NAME "${APPLICATION_EXECUTABLE}" - ) -else() - # set(CMAKE_INSTALL_PREFIX ".") # Examples use /Applications. hurmpf. - set(MACOSX_BUNDLE_ICON_FILE "${APPLICATION_ICON_NAME}.icns") - - # we must add MACOSX_BUNDLE only if building a bundle - add_executable(nextcloud WIN32 MACOSX_BUNDLE main.cpp ${APP_ICON}) - - if (BUILD_OWNCLOUD_OSX_BUNDLE) - set_target_properties(nextcloud PROPERTIES - OUTPUT_NAME "${APPLICATION_NAME}") - else() - set_target_properties(nextcloud PROPERTIES - OUTPUT_NAME "${APPLICATION_EXECUTABLE}") - endif() - - set (QM_DIR ${OWNCLOUD_OSX_BUNDLE}/Contents/Resources/Translations) - install(FILES ${client_I18N} DESTINATION ${QM_DIR}) - get_target_property(_qmake Qt::qmake LOCATION) - execute_process(COMMAND ${_qmake} -query QT_INSTALL_TRANSLATIONS - OUTPUT_VARIABLE QT_TRANSLATIONS_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - file(GLOB qt_I18N ${QT_TRANSLATIONS_DIR}/qt_??.qm ${QT_TRANSLATIONS_DIR}/qt_??_??.qm) - install(FILES ${qt_I18N} DESTINATION ${QM_DIR}) - file(GLOB qtbase_I18N ${QT_TRANSLATIONS_DIR}/qtbase_??.qm ${QT_TRANSLATIONS_DIR}/qt_??_??.qm) - install(FILES ${qtbase_I18N} DESTINATION ${QM_DIR}) - file(GLOB qtkeychain_I18N ${QT_TRANSLATIONS_DIR}/qtkeychain*.qm) - install(FILES ${qtkeychain_I18N} DESTINATION ${QM_DIR}) -endif() - -IF(BUILD_UPDATER) - add_library(updater STATIC ${updater_SRCS}) - target_link_libraries(updater Nextcloud::sync ${updater_DEPS} Qt::Widgets Qt::Svg Qt::Network Qt::Xml) - target_include_directories(updater PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - set_target_properties(updater PROPERTIES AUTOMOC ON) - target_link_libraries(nextcloudCore PUBLIC updater) -endif() - -set_target_properties(nextcloud PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} -) - -target_link_libraries(nextcloud PRIVATE nextcloudCore) - -if(TARGET PkgConfig::CLOUDPROVIDERS) - message("Building with libcloudproviderssupport") - target_sources(nextcloudCore PRIVATE cloudproviders/cloudprovidermanager.cpp cloudproviders/cloudproviderwrapper.cpp) - - string(TOLOWER "${APPLICATION_VENDOR}" DBUS_VENDOR) - string(REGEX REPLACE "[^A-z0-9]" "" DBUS_VENDOR "${DBUS_VENDOR}") - string(REGEX REPLACE "[^A-z0-9]" "" DBUS_APPLICATION_NAME "${APPLICATION_SHORTNAME}") - if(NOT DBUS_PREFIX) - set(DBUS_PREFIX "com") - endif(NOT DBUS_PREFIX) - set(LIBCLOUDPROVIDERS_DBUS_BUS_NAME "${DBUS_PREFIX}.${DBUS_VENDOR}.${DBUS_APPLICATION_NAME}") - set(LIBCLOUDPROVIDERS_DBUS_OBJECT_PATH "/${DBUS_PREFIX}/${DBUS_VENDOR}/${DBUS_APPLICATION_NAME}") - - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cloudproviders/cloudproviderconfig.h.in ${CMAKE_CURRENT_BINARY_DIR}/cloudproviderconfig.h) - target_compile_definitions(nextcloudCore PUBLIC -DWITH_LIBCLOUDPROVIDERS) - target_link_libraries(nextcloudCore - PRIVATE - PkgConfig::CLOUDPROVIDERS - PkgConfig::GLIB2 - PkgConfig::GIO - ) - - list(APPEND LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS "Implements=org.freedesktop.CloudProviders\n") - list(APPEND LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS "[org.freedesktop.CloudProviders]") - list(APPEND LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS "BusName=${LIBCLOUDPROVIDERS_DBUS_BUS_NAME}") - list(APPEND LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS "ObjectPath=${LIBCLOUDPROVIDERS_DBUS_OBJECT_PATH}") - list(JOIN LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS "\n" LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS) -endif() - -## handle DBUS for Fdo notifications -if( UNIX AND NOT APPLE ) - find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} COMPONENTS DBus) - target_link_libraries(nextcloudCore PUBLIC Qt::DBus) - target_compile_definitions(nextcloudCore PUBLIC "USE_FDO_NOTIFICATIONS") -endif() - -if (APPLE) - if (BUILD_FILE_PROVIDER_MODULE) - target_link_libraries(nextcloudCore PUBLIC "-framework UserNotifications -framework FileProvider") - else() - target_link_libraries(nextcloudCore PUBLIC "-framework UserNotifications") - endif() -endif() - -if(WITH_CRASHREPORTER) - target_link_libraries(nextcloudCore PUBLIC crashreporter-handler) - - if(UNIX AND NOT MAC) - find_package(Threads REQUIRED) - target_link_libraries(nextcloudCore PUBLIC Threads::Threads) - endif() -endif() - -install(TARGETS nextcloud - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - BUNDLE DESTINATION "." - ) - -if (WIN32) - install(FILES $ DESTINATION bin OPTIONAL) -endif() - -# FIXME: The following lines are dup in src/gui and src/cmd because it needs to be done after both are installed -#FIXME: find a nice solution to make the second if(BUILD_OWNCLOUD_OSX_BUNDLE) unnecessary -# currently it needs to be done because the code right above needs to be executed no matter -# if building a bundle or not and the install_qt4_executable needs to be called afterwards -# -# OSX: Run macdeployqt for src/gui and for src/cmd using the -executable option -if(BUILD_OWNCLOUD_OSX_BUNDLE AND NOT BUILD_LIBRARIES_ONLY) - get_target_property (QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION) - get_filename_component(QT_BIN_DIR "${QT_QMAKE_EXECUTABLE}" DIRECTORY) - find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS "${QT_BIN_DIR}") - - set(cmd_NAME ${APPLICATION_EXECUTABLE}cmd) - - if(CMAKE_BUILD_TYPE MATCHES Debug) - set(NO_STRIP "-no-strip") - else() - set(NO_STRIP "") - endif() - - add_custom_command(TARGET nextcloud POST_BUILD - COMMAND "${MACDEPLOYQT_EXECUTABLE}" - "$/../.." - -qmldir=${CMAKE_SOURCE_DIR}/src/gui - -always-overwrite - -executable="$/${cmd_NAME}" - ${NO_STRIP} - COMMAND "${CMAKE_COMMAND}" - -E rm -rf "${BIN_OUTPUT_DIRECTORY}/${OWNCLOUD_OSX_BUNDLE}/Contents/PlugIns/bearer" - COMMENT "Running macdeployqt..." - ) -endif() - -if(NOT BUILD_OWNCLOUD_OSX_BUNDLE AND NOT WIN32) - configure_file(${CMAKE_SOURCE_DIR}/mirall.desktop.in - ${CMAKE_CURRENT_BINARY_DIR}/${LINUX_APPLICATION_ID}.desktop) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${LINUX_APPLICATION_ID}.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications ) - - configure_file(owncloud.xml.in ${APPLICATION_EXECUTABLE}.xml) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages ) - - find_package(SharedMimeInfo) - if(SharedMimeInfo_FOUND) - update_xdg_mimetypes( ${CMAKE_INSTALL_DATADIR}/mime/packages ) - endif(SharedMimeInfo_FOUND) -endif() - -configure_file(configgui.h.in ${CMAKE_CURRENT_BINARY_DIR}/configgui.h) +project(gui) +find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS Widgets Svg Qml Quick QuickControls2 QuickWidgets Xml Network) +find_package(KF6Archive REQUIRED) +find_package(KF6GuiAddons) + +if (NOT TARGET Qt::Gui) + message(FATAL_ERROR "Could not find GuiPrivate component of Qt. It might be shipped as a separate package, please check that.") +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) +endif() + +configure_file(${CMAKE_SOURCE_DIR}/theme.qrc.in ${CMAKE_SOURCE_DIR}/theme.qrc) +set(theme_dir ${CMAKE_SOURCE_DIR}/theme) + +set(client_UI_SRCS + accountsettings.ui + conflictdialog.ui + invalidfilenamedialog.ui + caseclashfilenamedialog.ui + foldercreationdialog.ui + folderwizardsourcepage.ui + folderwizardtargetpage.ui + generalsettings.ui + legalnotice.ui + ignorelisteditor.ui + ignorelisttablewidget.ui + networksettings.ui + settingsdialog.ui + sslerrordialog.ui + addcertificatedialog.ui + passwordinputdialog.ui + proxyauthdialog.ui + mnemonicdialog.ui + vfsdownloaderrordialog.ui + wizard/flow2authwidget.ui + wizard/owncloudadvancedsetuppage.ui + wizard/owncloudconnectionmethoddialog.ui + wizard/owncloudhttpcredspage.ui + wizard/owncloudsetupnocredspage.ui + wizard/termsofservicecheckwidget.ui + wizard/webview.ui + wizard/welcomepage.ui + wizard/dataprotectionpage.ui + wizard/dataprotectionsettingspage.ui +) + +qt_add_resources(client_UI_SRCS ../../resources.qrc ${CMAKE_SOURCE_DIR}/theme.qrc) + +set(client_SRCS + accountmanager.h + accountmanager.cpp + accountsettings.h + accountsettings.cpp + accountsetupfromcommandlinejob.h + accountsetupfromcommandlinejob.cpp + accountsetupcommandlinemanager.h + accountsetupcommandlinemanager.cpp + application.h + application.cpp + invalidfilenamedialog.h + invalidfilenamedialog.cpp + caseclashfilenamedialog.h + caseclashfilenamedialog.cpp + callstatechecker.h + callstatechecker.cpp + conflictdialog.h + conflictdialog.cpp + conflictsolver.h + conflictsolver.cpp + connectionvalidator.h + connectionvalidator.cpp + editlocallyjob.h + editlocallyjob.cpp + editlocallymanager.h + editlocallymanager.cpp + editlocallyverificationjob.h + editlocallyverificationjob.cpp + filetagmodel.h + filetagmodel.cpp + folder.h + folder.cpp + foldercreationdialog.h + foldercreationdialog.cpp + folderman.h + folderman.cpp + folderstatusmodel.h + folderstatusmodel.cpp + folderstatusdelegate.h + folderstatusdelegate.cpp + folderstatusview.h + folderstatusview.cpp + folderwatcher.h + folderwatcher.cpp + folderwizard.h + folderwizard.cpp + clickablelabel.h + generalsettings.h + generalsettings.cpp + legalnotice.h + legalnotice.cpp + ignorelisteditor.h + ignorelisteditor.cpp + ignorelisttablewidget.h + ignorelisttablewidget.cpp + lockwatcher.h + lockwatcher.cpp + logbrowser.h + logbrowser.cpp + networksettings.h + networksettings.cpp + ocsnavigationappsjob.h + ocsnavigationappsjob.cpp + ocsjob.h + ocsjob.cpp + ocssharejob.h + ocssharejob.cpp + ocsshareejob.h + ocsshareejob.cpp + openfilemanager.h + openfilemanager.cpp + owncloudgui.h + owncloudgui.cpp + owncloudsetupwizard.h + owncloudsetupwizard.cpp + passwordinputdialog.h + passwordinputdialog.cpp + selectivesyncdialog.h + selectivesyncdialog.cpp + settingsdialog.h + settingsdialog.cpp + sharemanager.h + sharemanager.cpp + profilepagewidget.h + profilepagewidget.cpp + sharee.h + sharee.cpp + sslbutton.h + sslbutton.cpp + sslerrordialog.h + sslerrordialog.cpp + syncrunfilelog.h + syncrunfilelog.cpp + ionostheme.h + whitelabeltheme.h + stratotheme.h + basetheme.h + systray.h + systray.cpp + EncryptionTokenSelectionWindow.qml + buttonstyle.h + userinfo.h + userinfo.cpp + vfsdownloaderrordialog.h + vfsdownloaderrordialog.cpp + accountstate.h + accountstate.cpp + addcertificatedialog.h + addcertificatedialog.cpp + authenticationdialog.h + authenticationdialog.cpp + proxyauthhandler.h + proxyauthhandler.cpp + proxyauthdialog.h + proxyauthdialog.cpp + tooltipupdater.h + tooltipupdater.cpp + notificationconfirmjob.h + notificationconfirmjob.cpp + guiutility.h + guiutility.cpp + elidedlabel.h + elidedlabel.cpp + iconutils.h + iconutils.cpp + remotewipe.h + remotewipe.cpp + userstatusselectormodel.h + userstatusselectormodel.cpp + emojimodel.h + emojimodel.cpp + syncconflictsmodel.h + syncconflictsmodel.cpp + fileactivitylistmodel.h + fileactivitylistmodel.cpp + filedetails/datefieldbackend.h + filedetails/datefieldbackend.cpp + filedetails/filedetails.h + filedetails/filedetails.cpp + filedetails/sharemodel.h + filedetails/sharemodel.cpp + filedetails/shareemodel.h + filedetails/shareemodel.cpp + filedetails/sortedsharemodel.h + filedetails/sortedsharemodel.cpp + tray/svgimageprovider.h + tray/svgimageprovider.cpp + tray/syncstatussummary.h + tray/syncstatussummary.cpp + tray/activitydata.h + tray/activitydata.cpp + tray/activitylistmodel.h + tray/activitylistmodel.cpp + tray/unifiedsearchresult.h + tray/asyncimageresponse.cpp + tray/unifiedsearchresult.cpp + tray/unifiedsearchresultslistmodel.h + tray/trayimageprovider.cpp + tray/unifiedsearchresultslistmodel.cpp + tray/usermodel.h + tray/usermodel.cpp + tray/notificationhandler.h + tray/notificationhandler.cpp + tray/sortedactivitylistmodel.h + tray/sortedactivitylistmodel.cpp + creds/credentialsfactory.h + tray/talkreply.cpp + creds/credentialsfactory.cpp + creds/httpcredentialsgui.h + creds/httpcredentialsgui.cpp + creds/flow2auth.h + creds/flow2auth.cpp + creds/webflowcredentials.h + creds/webflowcredentials.cpp + creds/webflowcredentialsdialog.h + creds/webflowcredentialsdialog.cpp + ga4/datacollectionwrapper.cpp + ga4/datacollectionwrapper.h + ga4/ganalytics_worker.cpp + ga4/ganalytics_worker.h + ga4/ganalytics.cpp + ga4/ganalytics.h + sessnackbar.h + sessnackbar.cpp + sesstyle.h + sesstyle.cpp + linkbutton.h + linkbutton.cpp + buttonstylestrategy.h + sesFileIconProvider.h + sesFileIconProvider.cpp + pushbuttonstylehelper.h + pushbuttonstylehelper.cpp + moreoptionsbuttonstylehelper.h + moreoptionsbuttonstylehelper.cpp + SesComponents/syncdirvalidation.h + SesComponents/syncdirvalidation.cpp + wizard/postfixlineedit.h + wizard/postfixlineedit.cpp + wizard/abstractcredswizardpage.h + wizard/abstractcredswizardpage.cpp + wizard/owncloudadvancedsetuppage.h + wizard/owncloudadvancedsetuppage.cpp + wizard/owncloudconnectionmethoddialog.h + wizard/owncloudconnectionmethoddialog.cpp + wizard/owncloudhttpcredspage.h + wizard/owncloudhttpcredspage.cpp + wizard/flow2authcredspage.h + wizard/flow2authcredspage.cpp + wizard/flow2authwidget.h + wizard/flow2authwidget.cpp + wizard/owncloudsetuppage.h + wizard/owncloudsetuppage.cpp + wizard/termsofservicecheckwidget.h + wizard/termsofservicecheckwidget.cpp + wizard/termsofservicewizardpage.h + wizard/termsofservicewizardpage.cpp + wizard/owncloudwizardcommon.h + wizard/owncloudwizardcommon.cpp + wizard/owncloudwizard.h + wizard/owncloudwizard.cpp + wizard/slideshow.h + wizard/slideshow.cpp + wizard/welcomepage.h + wizard/welcomepage.cpp + wizard/dataprotectionpage.h + wizard/dataprotectionpage.cpp + wizard/dataprotectionsettingspage.h + wizard/dataprotectionsettingspage.cpp + wizard/linklabel.h + wizard/linklabel.cpp + ) + +if (WITH_WEBENGINE) + list(APPEND client_SRCS + wizard/webviewpage.h + wizard/webviewpage.cpp + wizard/webview.h + wizard/webview.cpp + ) +endif() + +IF(BUILD_UPDATER) + set(updater_SRCS + updater/ocupdater.h + updater/ocupdater.cpp + updater/updateinfo.h + updater/updateinfo.cpp + updater/updater.h + updater/updater.cpp + ) +endif() + +IF( APPLE ) + list(APPEND client_SRCS cocoainitializer_mac.mm) + list(APPEND client_SRCS systray_mac_common.mm) + + list(APPEND client_SRCS systray_mac_usernotifications.mm) + + if (BUILD_FILE_PROVIDER_MODULE) + list(APPEND client_SRCS + # Symlinks to files in shell_integration/MacOSX/NextcloudIntegration/ + macOS/ClientCommunicationProtocol.h + # End of symlink files + macOS/fileprovider.h + macOS/fileprovider_mac.mm + macOS/fileproviderdomainmanager.h + macOS/fileproviderdomainmanager_mac.mm + macOS/fileproviderdomainsyncstatus.h + macOS/fileproviderdomainsyncstatus_mac.mm + macOS/fileprovidereditlocallyjob.h + macOS/fileprovidereditlocallyjob.cpp + macOS/fileprovidereditlocallyjob_mac.mm + macOS/fileprovideritemmetadata.h + macOS/fileprovideritemmetadata.cpp + macOS/fileprovideritemmetadata_mac.mm + macOS/fileprovidermaterialiseditemsmodel.h + macOS/fileprovidermaterialiseditemsmodel.cpp + macOS/fileprovidermaterialiseditemsmodel_mac.mm + macOS/fileprovidersettingscontroller.h + macOS/fileprovidersettingscontroller_mac.mm + macOS/fileprovidersocketcontroller.h + macOS/fileprovidersocketcontroller.cpp + macOS/fileprovidersocketserver.h + macOS/fileprovidersocketserver.cpp + macOS/fileprovidersocketserver_mac.mm + macOS/fileproviderstorageuseenumerationobserver.h + macOS/fileproviderstorageuseenumerationobserver.m + macOS/fileproviderutils.h + macOS/fileproviderutils_mac.mm + macOS/fileproviderxpc.h + macOS/fileproviderxpc_mac.mm + macOS/fileproviderxpc_mac_utils.h + macOS/fileproviderxpc_mac_utils.mm + macOS/progressobserver.h + macOS/progressobserver.m) + endif() + + list(APPEND client_SRCS foregroundbackground_interface.h) + list(APPEND client_SRCS foregroundbackground_mac.mm) + list(APPEND client_SRCS foregroundbackground_cocoa.mm) + + if(SPARKLE_FOUND AND BUILD_UPDATER) + # Define this, we need to check in updater.cpp + add_definitions(-DHAVE_SPARKLE) + list(APPEND updater_SRCS updater/sparkleupdater_mac.mm updater/sparkleupdater.h) + list(APPEND updater_DEPS ${SPARKLE_LIBRARY}) + + # Sparkle.framework is installed from here because macdeployqt's CopyFramework breaks on this bundle + # as its logic is tightly tailored around Qt frameworks + install(DIRECTORY "${SPARKLE_LIBRARY}" + DESTINATION "${OWNCLOUD_OSX_BUNDLE}/Contents/Frameworks" USE_SOURCE_PERMISSIONS) + + endif() +ENDIF() + +IF( NOT WIN32 AND NOT APPLE ) + set(client_SRCS ${client_SRCS} folderwatcher_linux.cpp) +ENDIF() +IF( WIN32 ) + set(client_SRCS + ${client_SRCS} + folderwatcher_win.cpp + navigationpanehelper.h + navigationpanehelper.cpp + shellextensionsserver.cpp + ${CMAKE_SOURCE_DIR}/src/common/shellextensionutils.cpp) +ENDIF() +IF( APPLE ) + list(APPEND client_SRCS folderwatcher_mac.cpp) +ENDIF() + +set(3rdparty_SRC + ../3rdparty/QProgressIndicator/QProgressIndicator.h + ../3rdparty/QProgressIndicator/QProgressIndicator.cpp + ../3rdparty/qtlockedfile/qtlockedfile.h + ../3rdparty/qtlockedfile/qtlockedfile.cpp + ../3rdparty/qtsingleapplication/qtlocalpeer.h + ../3rdparty/qtsingleapplication/qtlocalpeer.cpp + ../3rdparty/qtsingleapplication/qtsingleapplication.h + ../3rdparty/qtsingleapplication/qtsingleapplication.cpp + ../3rdparty/qtsingleapplication/qtsinglecoreapplication.h + ../3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp + ../3rdparty/kmessagewidget/kmessagewidget.h + ../3rdparty/kmessagewidget/kmessagewidget.cpp + ../3rdparty/kirigami/wheelhandler.h + ../3rdparty/kirigami/wheelhandler.cpp + ) + +set_property(SOURCE ../3rdparty/kmessagewidget/kmessagewidget.cpp PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) +set_property(SOURCE ../3rdparty/kirigami/wheelhandler.cpp PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) + +if(NOT WIN32) + list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_unix.cpp) + set_property(SOURCE ../3rdparty/qtlockedfile/qtlockedfile_unix.cpp PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) +else() + list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_win.cpp ) + set_property(SOURCE ../3rdparty/qtlockedfile/qtlockedfile_win.cpp PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) +endif() + +find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} COMPONENTS LinguistTools) +if(Qt${QT_MAJOR_VERSION}LinguistTools_FOUND) + qt_add_translation(client_I18N ${TRANSLATIONS}) +endif() + +IF( WIN32 ) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + set(client_version ${CMAKE_CURRENT_BINARY_DIR}/version.rc) + IF(NOT MSVC) + set(client_manifest ${CMAKE_CURRENT_SOURCE_DIR}/manifest-mingw.rc) + ENDIF() +ENDIF() + +set( final_src + ${client_SRCS} + ${client_UI_SRCS} + ${guiMoc} + ${client_I18N} + ${3rdparty_SRC} + ${3rdparty_MOC} +) + +if(Qt${QT_MAJOR_VERSION}Keychain_FOUND) + list(APPEND libsync_LINK_TARGETS Qt6::keychain) +endif() + +# add executable icon on windows and osx +include(GenerateIconsUtils) + +# For historical reasons we can not use the application_shortname +# for ownCloud but must rather set it manually. +if (NOT DEFINED APPLICATION_ICON_NAME) + set(APPLICATION_ICON_NAME ${APPLICATION_SHORTNAME}) +endif() + +if(NOT DEFINED APPLICATION_FOLDER_ICON_INDEX) + set(APPLICATION_FOLDER_ICON_INDEX 0) +endif() + +set(STATE_ICONS_COLORS colored black white) + +foreach(state_icons_color ${STATE_ICONS_COLORS}) + set(STATE_ICONS_PATH "${theme_dir}/${state_icons_color}/") + + message("Generating state icons from SVG in path: ${STATE_ICONS_PATH}") + + file(GLOB_RECURSE STATE_ICONS_SVG "${STATE_ICONS_PATH}/state-*.svg") + + foreach(state_icon_svg ${STATE_ICONS_SVG}) + get_filename_component(status_icon_name_wle ${state_icon_svg} NAME_WLE) + foreach(size IN ITEMS 16;32;64;128;256) + generate_sized_png_from_svg(${state_icon_svg} ${size} OUTPUT_ICON_FULL_NAME_WLE "${status_icon_name_wle}-${size}") + endforeach() + endforeach() +endforeach() + +if ((APPLICATION_ICON_SET MATCHES "PNG") + AND + (NOT EXISTS "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon.svg")) + # We may have no svg application icon in when customers use PNG + # icons in brander, but theme.qrc expects a svg icon. + file(TOUCH "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon.svg") +endif() + +if(APPLE) + set(MACOS_SIDEBAR_ICON_SVG "${theme_dir}/colored/${APPLICATION_ICON_NAME}-sidebar.svg") + generate_sized_png_from_svg(${MACOS_SIDEBAR_ICON_SVG} 16) + generate_sized_png_from_svg(${MACOS_SIDEBAR_ICON_SVG} 32) + generate_sized_png_from_svg(${MACOS_SIDEBAR_ICON_SVG} 64) + generate_sized_png_from_svg(${MACOS_SIDEBAR_ICON_SVG} 128) + generate_sized_png_from_svg(${MACOS_SIDEBAR_ICON_SVG} 256) +endif() + +if(WIN32) + set(STARTMENU_ICON_SVG "${theme_dir}/colored/${APPLICATION_ICON_NAME}-w10startmenu.svg") + generate_sized_png_from_svg(${STARTMENU_ICON_SVG} 70) + generate_sized_png_from_svg(${STARTMENU_ICON_SVG} 150) +endif() + +set(APP_ICON_SVG "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon.svg") + +# generate secondary icon if available (currently for Windows only)-------------------------------------- +set(APP_SECONDARY_ICONS "${theme_dir}/colored/icons") +set(APP_ICON_WIN_FOLDER_SVG "${APP_SECONDARY_ICONS}/${APPLICATION_ICON_NAME}-icon-win-folder.svg") + +set(RC_DEPENDENCIES "") + +if(WIN32) + if (EXISTS ${APP_ICON_WIN_FOLDER_SVG}) + get_filename_component(output_icon_name_win ${APP_ICON_WIN_FOLDER_SVG} NAME_WLE) + # Product icon (for smallest size) + foreach(size IN ITEMS 16;20) + generate_sized_png_from_svg(${APP_ICON_SVG} ${size} OUTPUT_ICON_NAME ${output_icon_name_win} OUTPUT_ICON_PATH "${APP_SECONDARY_ICONS}/") + endforeach() + + # Product icon with Windows folder (for sizes larger than 20) + foreach(size IN ITEMS 24;32;40;48;64;128;256;512;1024) + generate_sized_png_from_svg(${APP_ICON_WIN_FOLDER_SVG} ${size} OUTPUT_ICON_NAME ${output_icon_name_win} OUTPUT_ICON_PATH "${APP_SECONDARY_ICONS}/") + endforeach() + + file(GLOB_RECURSE OWNCLOUD_ICONS_WIN_FOLDER "${APP_SECONDARY_ICONS}/*-${APPLICATION_ICON_NAME}-icon*") + set(APP_ICON_WIN_FOLDER_ICO_NAME "${APPLICATION_ICON_NAME}-win-folder") + set(RC_DEPENDENCIES "${RC_DEPENDENCIES} ${APP_ICON_WIN_FOLDER_ICO_NAME}.ico") + ecm_add_app_icon(APP_ICON_WIN_FOLDER ICONS "${OWNCLOUD_ICONS_WIN_FOLDER}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASENAME "${APP_ICON_WIN_FOLDER_ICO_NAME}" ICON_INDEX 2) + endif() +endif() +# -------------------------------------- + +if (NOT ${RC_DEPENDENCIES} STREQUAL "") + string(STRIP ${RC_DEPENDENCIES} RC_DEPENDENCIES) +endif() + +# generate primary icon from SVG (due to Win .ico vs .rc dependency issues, primary icon must always be generated last)-------------------------------------- +if(WIN32) + foreach(size IN ITEMS 16;20;24;32;40;48;64;128;256;512;1024) + generate_sized_png_from_svg(${APP_ICON_SVG} ${size}) + endforeach() +else() + foreach(size IN ITEMS 16;24;32;48;64;128;256;512;1024) + generate_sized_png_from_svg(${APP_ICON_SVG} ${size}) + endforeach() +endif() + +file(GLOB_RECURSE OWNCLOUD_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-icon*") + +if(APPLE) + file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-sidebar*") + MESSAGE(STATUS "OWNCLOUD_SIDEBAR_ICONS: ${APPLICATION_ICON_NAME}: ${OWNCLOUD_SIDEBAR_ICONS}") +endif() + +ecm_add_app_icon(APP_ICON RC_DEPENDENCIES ${RC_DEPENDENCIES} ICONS "${OWNCLOUD_ICONS}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASENAME "${APPLICATION_ICON_NAME}" ICON_INDEX 1) +# -------------------------------------- + +if(WIN32) +# merge *.rc.in files for Windows (multiple ICON resources must be placed in a single file, otherwise, this won't work de to a bug in Windows compiler https://developercommunity.visualstudio.com/t/visual-studio-2017-prof-1557-cvt1100-duplicate-res/363156) + function(merge_files IN_FILE OUT_FILE) + file(READ ${IN_FILE} CONTENTS) + message("Merging ${IN_FILE} into ${OUT_FILE}") + file(APPEND ${OUT_FILE} "${CONTENTS}") + endfunction() + message("APP_ICON is: ${APP_ICON}") + if(APP_ICON) + get_filename_component(RC_IN_FOLDER ${APP_ICON}} DIRECTORY) + + file(GLOB_RECURSE RC_IN_FILES "${RC_IN_FOLDER}/*rc.in") + + foreach(rc_in_file IN ITEMS ${RC_IN_FILES}) + get_filename_component(rc_in_file_name ${rc_in_file} NAME) + get_filename_component(app_icon_name "${APP_ICON}.in" NAME) + if(NOT "${rc_in_file_name}" STREQUAL "${app_icon_name}") + merge_files(${rc_in_file} "${APP_ICON}.in") + if (DEFINED APPLICATION_FOLDER_ICON_INDEX) + MATH(EXPR APPLICATION_FOLDER_ICON_INDEX "${APPLICATION_FOLDER_ICON_INDEX}+1") + message("APPLICATION_FOLDER_ICON_INDEX is now set to: ${APPLICATION_FOLDER_ICON_INDEX}") + endif() + endif() + endforeach() + endif() +endif() +# -------------------------------------- + +if(UNIX AND NOT APPLE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIE") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib") +endif() + +add_library(nextcloudCore STATIC ${final_src}) + +target_link_libraries(nextcloudCore + PUBLIC + Nextcloud::sync + Qt::Widgets + Qt::Gui + Qt::Svg + Qt::Network + Qt::Xml + Qt::Qml + Qt::Quick + Qt::QuickControls2 + Qt::QuickWidgets + KF6::Archive + ) + +if(KF6GuiAddons_FOUND) + target_link_libraries(nextcloudCore + PUBLIC + KF6::GuiAddons + ) + add_definitions(-DHAVE_KGUIADDONS) +endif() + +add_subdirectory(socketapi) + +# skip unity inclusion for files which cause problems with a CMake unity build +set_property(SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/socketapi/socketapi.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/socketapi/socketuploadjob.cpp + PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) + +foreach(FILE IN LISTS client_UI_SRCS) + set_property(SOURCE ${FILE} PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) +endforeach() + +if(WITH_WEBENGINE) + target_link_libraries(nextcloudCore PUBLIC Qt::WebEngineWidgets Qt::WebEngineCore) +endif() + +set_target_properties(nextcloudCore + PROPERTIES + AUTOUIC ON + AUTOMOC ON +) + +target_include_directories(nextcloudCore + PUBLIC + ${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator + ${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile + ${CMAKE_SOURCE_DIR}/src/3rdparty/kirigami + ${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication + ${CMAKE_SOURCE_DIR}/src/3rdparty/kmessagewidget + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} +) + +if(NOT BUILD_OWNCLOUD_OSX_BUNDLE) + if(NOT WIN32) + file(GLOB _icons "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-icon.png") + foreach(_file ${_icons}) + string(REPLACE "${theme_dir}/colored/" "" _res ${_file}) + string(REPLACE "-${APPLICATION_ICON_NAME}-icon.png" "" _res ${_res}) + install(FILES ${_file} RENAME ${APPLICATION_ICON_NAME}.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${_res}x${_res}/apps) + endforeach(_file) + install(FILES ${client_I18N} DESTINATION ${CMAKE_INSTALL_DATADIR}/${APPLICATION_EXECUTABLE}/i18n) + else() + file(GLOB_RECURSE VISUAL_ELEMENTS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-w10startmenu*") + install(FILES ${VISUAL_ELEMENTS} DESTINATION bin/visualelements) + install(FILES "${theme_dir}/${APPLICATION_EXECUTABLE}.VisualElementsManifest.xml" DESTINATION bin) + install(FILES ${client_I18N} DESTINATION i18n) + endif() + + # we may not add MACOSX_BUNDLE here, if not building one + add_executable(nextcloud WIN32 main.cpp ${client_version} ${client_manifest} ${APP_ICON}) + set_target_properties(nextcloud PROPERTIES + OUTPUT_NAME "${APPLICATION_EXECUTABLE}" + ) +else() + # set(CMAKE_INSTALL_PREFIX ".") # Examples use /Applications. hurmpf. + set(MACOSX_BUNDLE_ICON_FILE "${APPLICATION_ICON_NAME}.icns") + + # we must add MACOSX_BUNDLE only if building a bundle + add_executable(nextcloud WIN32 MACOSX_BUNDLE main.cpp ${APP_ICON}) + + if (BUILD_OWNCLOUD_OSX_BUNDLE) + set_target_properties(nextcloud PROPERTIES + OUTPUT_NAME "${APPLICATION_NAME}") + else() + set_target_properties(nextcloud PROPERTIES + OUTPUT_NAME "${APPLICATION_EXECUTABLE}") + endif() + + set (QM_DIR ${OWNCLOUD_OSX_BUNDLE}/Contents/Resources/Translations) + install(FILES ${client_I18N} DESTINATION ${QM_DIR}) + get_target_property(_qmake Qt::qmake LOCATION) + execute_process(COMMAND ${_qmake} -query QT_INSTALL_TRANSLATIONS + OUTPUT_VARIABLE QT_TRANSLATIONS_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + file(GLOB qt_I18N ${QT_TRANSLATIONS_DIR}/qt_??.qm ${QT_TRANSLATIONS_DIR}/qt_??_??.qm) + install(FILES ${qt_I18N} DESTINATION ${QM_DIR}) + file(GLOB qtbase_I18N ${QT_TRANSLATIONS_DIR}/qtbase_??.qm ${QT_TRANSLATIONS_DIR}/qt_??_??.qm) + install(FILES ${qtbase_I18N} DESTINATION ${QM_DIR}) + file(GLOB qtkeychain_I18N ${QT_TRANSLATIONS_DIR}/qtkeychain*.qm) + install(FILES ${qtkeychain_I18N} DESTINATION ${QM_DIR}) +endif() + +IF(BUILD_UPDATER) + add_library(updater STATIC ${updater_SRCS}) + target_link_libraries(updater Nextcloud::sync ${updater_DEPS} Qt::Widgets Qt::Svg Qt::Network Qt::Xml) + target_include_directories(updater PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + set_target_properties(updater PROPERTIES AUTOMOC ON) + target_link_libraries(nextcloudCore PUBLIC updater) +endif() + +set_target_properties(nextcloud PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} +) + +target_link_libraries(nextcloud PRIVATE nextcloudCore) + +if(TARGET PkgConfig::CLOUDPROVIDERS) + message("Building with libcloudproviderssupport") + target_sources(nextcloudCore PRIVATE cloudproviders/cloudprovidermanager.cpp cloudproviders/cloudproviderwrapper.cpp) + + string(TOLOWER "${APPLICATION_VENDOR}" DBUS_VENDOR) + string(REGEX REPLACE "[^A-z0-9]" "" DBUS_VENDOR "${DBUS_VENDOR}") + string(REGEX REPLACE "[^A-z0-9]" "" DBUS_APPLICATION_NAME "${APPLICATION_SHORTNAME}") + if(NOT DBUS_PREFIX) + set(DBUS_PREFIX "com") + endif(NOT DBUS_PREFIX) + set(LIBCLOUDPROVIDERS_DBUS_BUS_NAME "${DBUS_PREFIX}.${DBUS_VENDOR}.${DBUS_APPLICATION_NAME}") + set(LIBCLOUDPROVIDERS_DBUS_OBJECT_PATH "/${DBUS_PREFIX}/${DBUS_VENDOR}/${DBUS_APPLICATION_NAME}") + + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cloudproviders/cloudproviderconfig.h.in ${CMAKE_CURRENT_BINARY_DIR}/cloudproviderconfig.h) + target_compile_definitions(nextcloudCore PUBLIC -DWITH_LIBCLOUDPROVIDERS) + target_link_libraries(nextcloudCore + PRIVATE + PkgConfig::CLOUDPROVIDERS + PkgConfig::GLIB2 + PkgConfig::GIO + ) + + list(APPEND LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS "Implements=org.freedesktop.CloudProviders\n") + list(APPEND LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS "[org.freedesktop.CloudProviders]") + list(APPEND LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS "BusName=${LIBCLOUDPROVIDERS_DBUS_BUS_NAME}") + list(APPEND LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS "ObjectPath=${LIBCLOUDPROVIDERS_DBUS_OBJECT_PATH}") + list(JOIN LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS "\n" LIBCLOUDPROVIDERS_DESKTOP_IMPLEMENTS) +endif() + +## handle DBUS for Fdo notifications +if( UNIX AND NOT APPLE ) + find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} COMPONENTS DBus) + target_link_libraries(nextcloudCore PUBLIC Qt::DBus) + target_compile_definitions(nextcloudCore PUBLIC "USE_FDO_NOTIFICATIONS") +endif() + +if (APPLE) + if (BUILD_FILE_PROVIDER_MODULE) + target_link_libraries(nextcloudCore PUBLIC "-framework UserNotifications -framework FileProvider") + else() + target_link_libraries(nextcloudCore PUBLIC "-framework UserNotifications") + endif() +endif() + +if(WITH_CRASHREPORTER) + target_link_libraries(nextcloudCore PUBLIC crashreporter-handler) + + if(UNIX AND NOT MAC) + find_package(Threads REQUIRED) + target_link_libraries(nextcloudCore PUBLIC Threads::Threads) + endif() +endif() + +install(TARGETS nextcloud + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + BUNDLE DESTINATION "." + ) + +if (WIN32) + install(FILES $ DESTINATION bin OPTIONAL) +endif() + +# FIXME: The following lines are dup in src/gui and src/cmd because it needs to be done after both are installed +#FIXME: find a nice solution to make the second if(BUILD_OWNCLOUD_OSX_BUNDLE) unnecessary +# currently it needs to be done because the code right above needs to be executed no matter +# if building a bundle or not and the install_qt4_executable needs to be called afterwards +# +# OSX: Run macdeployqt for src/gui and for src/cmd using the -executable option +if(BUILD_OWNCLOUD_OSX_BUNDLE AND NOT BUILD_LIBRARIES_ONLY) + get_target_property (QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION) + get_filename_component(QT_BIN_DIR "${QT_QMAKE_EXECUTABLE}" DIRECTORY) + find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS "${QT_BIN_DIR}") + + set(cmd_NAME ${APPLICATION_EXECUTABLE}cmd) + + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(NO_STRIP "-no-strip") + else() + set(NO_STRIP "") + endif() + + add_custom_command(TARGET nextcloud POST_BUILD + COMMAND "${MACDEPLOYQT_EXECUTABLE}" + "$/../.." + -qmldir=${CMAKE_SOURCE_DIR}/src/gui + -always-overwrite + -executable=$/${cmd_NAME} + ${NO_STRIP} + COMMAND "${CMAKE_COMMAND}" + -E rm -rf "${BIN_OUTPUT_DIRECTORY}/${OWNCLOUD_OSX_BUNDLE}/Contents/PlugIns/bearer" + COMMENT "Running macdeployqt..." + ) +endif() + +if(NOT BUILD_OWNCLOUD_OSX_BUNDLE AND NOT WIN32) + configure_file(${CMAKE_SOURCE_DIR}/mirall.desktop.in + ${CMAKE_CURRENT_BINARY_DIR}/${LINUX_APPLICATION_ID}.desktop) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${LINUX_APPLICATION_ID}.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications ) + + configure_file(owncloud.xml.in ${APPLICATION_EXECUTABLE}.xml) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages ) + + find_package(SharedMimeInfo) + if(SharedMimeInfo_FOUND) + update_xdg_mimetypes( ${CMAKE_INSTALL_DATADIR}/mime/packages ) + endif(SharedMimeInfo_FOUND) +endif() + +configure_file(configgui.h.in ${CMAKE_CURRENT_BINARY_DIR}/configgui.h) diff --git a/src/gui/ConflictDelegate.qml b/src/gui/ConflictDelegate.qml index 07ebea17836c5..b9aeee4b88ce1 100644 --- a/src/gui/ConflictDelegate.qml +++ b/src/gui/ConflictDelegate.qml @@ -17,7 +17,7 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls import Style -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import "./tray" Item { diff --git a/src/gui/EmojiPicker.qml b/src/gui/EmojiPicker.qml index 486965ebd79f7..8741b2bc6f6ee 100644 --- a/src/gui/EmojiPicker.qml +++ b/src/gui/EmojiPicker.qml @@ -17,7 +17,7 @@ import QtQuick.Controls import QtQuick.Layouts import Style -import com.nextcloud.desktopclient 1.0 as NC +import com.ionos.hidrivenext.desktopclient as NC import "./tray" ColumnLayout { diff --git a/src/gui/EncryptionTokenSelectionWindow.qml b/src/gui/EncryptionTokenSelectionWindow.qml index 8872d978335a5..bd3a2b3be80bb 100644 --- a/src/gui/EncryptionTokenSelectionWindow.qml +++ b/src/gui/EncryptionTokenSelectionWindow.qml @@ -17,7 +17,7 @@ import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import QtQml.Models 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/ResolveConflictsDialog.qml b/src/gui/ResolveConflictsDialog.qml index 529181d6c1b32..cc41249ae1f5f 100644 --- a/src/gui/ResolveConflictsDialog.qml +++ b/src/gui/ResolveConflictsDialog.qml @@ -19,7 +19,7 @@ import QtQuick.Layouts import QtQuick.Controls import QtQml.Models import Style -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import "./tray" ApplicationWindow { @@ -147,15 +147,26 @@ ApplicationWindow { } DialogButtonBox { + id: buttonBox Layout.fillWidth: true - Button { + readonly property int pixelSize: Style.sesFontPixelSize + readonly property int fontWeight: Style.sesFontNormalWeight + + PrimaryPillButton { + font.pixelSize: pixelSize + font.weight: fontWeight text: qsTr("Resolve conflicts") - DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + + onClicked: buttonBox.onAccepted() } - Button { + + SecondaryPillButton { + font.pixelSize: pixelSize + font.weight: fontWeight text: qsTr("Cancel") - DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + + onClicked: buttonBox.onRejected() } onAccepted: function() { @@ -168,10 +179,4 @@ ApplicationWindow { } } } - - Rectangle { - color: palette.base - anchors.fill: parent - z: 1 - } } 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..94d3e7e23babf --- /dev/null +++ b/src/gui/SesComponents/SesTrayHeader.qml @@ -0,0 +1,70 @@ +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Layouts +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 + + +Rectangle { + + height: Style.sesTrayHeaderHeight + Style.sesHeaderTopMargin * 2 + color: Style.sesBackgroundColor + radius: 0.0 + clip: true + + RowLayout { + id: trayWindowHeaderLayout + + anchors.fill: parent + anchors.leftMargin: Style.sesTrayHeaderMargin + anchors.rightMargin: Style.sesTrayHeaderMargin + anchors.topMargin: Style.sesHeaderTopMargin + anchors.bottomMargin: Style.sesHeaderTopMargin + + TrayWindowAccountMenu{ + id: currentAccountHeaderButton + 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..3357866fb897f --- /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(), Qt::SkipEmptyParts); + QStringList appDataPathComponents = appDataPath.split(QDir::separator(), Qt::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 736dbe5a86a05..88dd179131b80 100644 --- a/src/gui/UserStatusSelector.qml +++ b/src/gui/UserStatusSelector.qml @@ -18,7 +18,7 @@ import QtQuick.Layouts import QtQuick.Controls import QtQuick.Window -import com.nextcloud.desktopclient as NC +import com.ionos.hidrivenext.desktopclient as NC import Style import "./tray" diff --git a/src/gui/UserStatusSelectorPage.qml b/src/gui/UserStatusSelectorPage.qml index a60423f9a77f5..fa545d6c6abb0 100644 --- a/src/gui/UserStatusSelectorPage.qml +++ b/src/gui/UserStatusSelectorPage.qml @@ -16,7 +16,7 @@ import QtQuick import QtQuick.Controls import Style -import com.nextcloud.desktopclient as NC +import com.ionos.hidrivenext.desktopclient as NC Page { id: page diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index 1a1a04eea4848..5a440411530e7 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -90,7 +90,7 @@ constexpr auto generalC = "General"; 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 551d60450575c..7cbbc4b9111aa 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -20,6 +20,9 @@ #include "ui_accountsettings.h" #include "theme.h" +#include "whitelabeltheme.h" +#include "buttonstyle.h" +#include "account.h" #include "foldercreationdialog.h" #include "folderman.h" #include "folderwizard.h" @@ -65,8 +68,6 @@ #include "macOS/fileprovider.h" #endif -#include "account.h" - namespace { constexpr auto propertyFolder = "folder"; constexpr auto propertyPath = "path"; @@ -81,17 +82,15 @@ 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::horizontal { border: 1px solid grey; border-radius: 5px; text-align: center; background-color: %1; }" + "QProgressBar::chunk { background-color: %2; width: 1px; }" + ); +} void showEnableE2eeWithVirtualFilesWarningDialog(std::function onAccept) { @@ -164,7 +163,7 @@ class MouseCursorChanger : public QObject 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))) { + FolderStatusDelegate::optionsButtonRect(folderList->visualRect(index), folderList->layoutDirection()).contains(pos))) { shape = Qt::PointingHandCursor; } folderList->setCursor(shape); @@ -181,6 +180,8 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) , _userInfo(accountState, false, true) { _ui->setupUi(this); + this->setAutoFillBackground(true); + setPalette(QPalette(QPalette::Window, WLTheme.dialogBackgroundColor())); _model->setAccountState(_accountState); _model->setParent(this); @@ -190,6 +191,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(WLTheme.settingsFontDefault()); + _ui->_folderList->header()->hide(); _ui->_folderList->setItemDelegate(delegate); _ui->_folderList->setModel(_model); @@ -210,6 +213,16 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) fpSettingsLayout->setContentsMargins(0, 0, 0, 0); fpSettingsLayout->addWidget(fpSettingsWidget); fileProviderTab->setLayout(fpSettingsLayout); + + _ui->tabWidget->setStyleSheet(QStringLiteral("QTabWidget::pane { background-color: %1; }").arg(WLTheme.white())); + _ui->tabWidget->tabBar()->setStyleSheet("QTabBar::tab {\ + color: #000000;\ + }\ + QTabBar::tab:selected {\ + color: #ffffff;\ + }"); + } else { + disguiseTabWidget(); } #else const auto tabWidget = _ui->tabWidget; @@ -227,6 +240,15 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) connectionSettingsLayout->addWidget(networkSettings); connectionSettingsTab->setLayout(connectionSettingsLayout); + const auto connectionSettingsTabIndex = _ui->tabWidget->indexOf(connectionSettingsTab); + if(connectionSettingsTabIndex >= 0){ + _ui->tabWidget->removeTab(connectionSettingsTabIndex); + } + _ui->tabWidget->setCurrentIndex(0); +#ifndef BUILD_FILE_PROVIDER_MODULE + _ui->tabWidget->tabBar()->hide(); +#endif + const auto mouseCursorChanger = new MouseCursorChanger(this); mouseCursorChanger->folderList = _ui->_folderList; mouseCursorChanger->model = _model; @@ -234,6 +256,11 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) _ui->_folderList->setAttribute(Qt::WA_Hover, true); _ui->_folderList->installEventFilter(mouseCursorChanger); +#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, @@ -276,11 +303,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); @@ -293,6 +315,8 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) connect(&_userInfo, &UserInfo::quotaUpdated, this, &AccountSettings::slotUpdateQuota); + connect(_ui->expandMemoryButton, &QAbstractButton::clicked, this, &AccountSettings::slotExpandMemoryClicked); + customizeStyle(); connect(_accountState->account()->e2e(), &ClientSideEncryption::startingDiscoveryEncryptionUsbToken, @@ -560,10 +584,12 @@ void AccountSettings::openIgnoredFilesDialog(const QString & absFolderPath) const QString ignoreFile{absFolderPath + ".sync-exclude.lst"}; const auto layout = new QVBoxLayout(); const auto ignoreListWidget = new IgnoreListTableWidget(this); + ignoreListWidget->setFont(WLTheme.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(); @@ -577,9 +603,28 @@ void AccountSettings::openIgnoredFilesDialog(const QString & absFolderPath) }); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::close); + dialog->setPalette(QPalette(QPalette::Window, WLTheme.white())); + dialog->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + dialog->open(); } +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); @@ -642,11 +687,63 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index ac = availabilityMenu->addAction(Utility::vfsFreeSpaceActionText()); 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: %7; " + "font-family: %8; " + "font-size: %9; " + "font-weight: %10; " + "}" + "QMenu::item {" + "background-color: transparent;" + "padding: 16px 18px; " + "color: %3; " + "border-radius: 8px; " + "}" + "QMenu::item:selected {" + "background-color: %5; " + "color: %3; " + "border-radius: 8px; " + "}" + "QMenu::item:pressed {" + "background-color: %6; " + "color: %4; " + "border-radius: 8px; " + "}" + ).arg( + WLTheme.white(), + WLTheme.menuBorderColor(), + WLTheme.menuTextColor(), + WLTheme.menuPressedTextColor(), + WLTheme.menuSelectedItemColor(), + WLTheme.menuPressedItemColor(), + WLTheme.menuBorderRadius(), + WLTheme.contextMenuFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTextWeight() + ) + ); +} + void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) { const auto treeView = _ui->_folderList; @@ -677,6 +774,13 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) 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")); @@ -723,6 +827,8 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) ac = menu->addAction(tr("Disable virtual file support …")); connect(ac, &QAction::triggered, this, &AccountSettings::slotDisableVfsCurrentFolder); ac->setDisabled(Theme::instance()->enforceVirtualFilesSyncFolder()); + + styleCustomContextMenu(availabilityMenu); } if (const auto mode = bestAvailableVfsMode(); @@ -737,6 +843,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) } } + styleCustomContextMenu(menu); menu->popup(treeView->mapToGlobal(pos)); } @@ -745,16 +852,6 @@ 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->itemDelegateForIndex(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(); @@ -889,8 +986,22 @@ void AccountSettings::slotRemoveCurrentFolder() .arg(shortGuiLocalPath), QMessageBox::NoButton, this); + + messageBox->setStyleSheet( + QStringLiteral("QMessageBox QLabel { %1; } QDialog { background-color: %2; }").arg( + WLTheme.fontConfigurationCss( + WLTheme.settingsFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTextWeight(), + WLTheme.titleColor() + ), + WLTheme.dialogBackgroundColor() + ) + ); + 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]{ if (messageBox->clickedButton() == yesButton) { @@ -1010,7 +1121,22 @@ void AccountSettings::slotDisableVfsCurrentFolder() "will become available again." "\n\n" "This action will abort any currently running synchronization.")); + + msgBox->setStyleSheet(QStringLiteral( + "QMessageBox QLabel { %1 background-color: %2; }").arg( + WLTheme.fontConfigurationCss( + WLTheme.settingsFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTextWeight(), + WLTheme.titleColor() + ), + WLTheme.white() + ) + ); + 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(); @@ -1158,6 +1284,9 @@ void AccountSettings::migrateCertificateForAccount(const AccountPtr &account) 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;"); @@ -1171,12 +1300,13 @@ void AccountSettings::showConnectionLabel(const QString &message, QStringList er errors.prepend(message); auto userFriendlyMsg = errors.join(QLatin1String("
")); qCDebug(lcAccountSettings) << userFriendlyMsg; - Theme::replaceLinkColorString(userFriendlyMsg, QColor(0xc1c8e6)); + Theme::replaceLinkColorString(userFriendlyMsg, QColor("#c1c8e6")); _ui->connectLabel->setText(userFriendlyMsg); _ui->connectLabel->setToolTip({}); _ui->connectLabel->setStyleSheet(errStyle); } - _ui->accountStatus->setVisible(!message.isEmpty()); + #endif + _ui->accountStatus->setVisible(false); } void AccountSettings::slotEnableCurrentFolder(bool terminate) @@ -1203,9 +1333,30 @@ void AccountSettings::slotEnableCurrentFolder(bool terminate) QMessageBox::Yes | QMessageBox::No, this); msgbox->setAttribute(Qt::WA_DeleteOnClose); msgbox->setDefaultButton(QMessageBox::Yes); + 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( + WLTheme.fontConfigurationCss( + WLTheme.settingsFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTextWeight(), + WLTheme.titleColor() + ) + ) + ); + msgbox->open(); return; } @@ -1275,6 +1426,7 @@ void AccountSettings::slotUpdateQuota(qint64 total, qint64 used) _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({}); @@ -1450,6 +1602,11 @@ void AccountSettings::slotHideSelectiveSyncWidget() _ui->selectiveSyncLabel->hide(); } +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) @@ -1632,9 +1789,9 @@ void AccountSettings::refreshSelectiveSyncStatus() QString infoString; if (!unsyncedFoldersString.isEmpty()) { - infoString += !cfg.confirmExternalStorage() ? tr("There are folders that were not synchronized because they are too big: ") - : !cfg.newBigFolderSizeLimit().first ? tr("There are folders that were not synchronized because they are external storages: ") - : tr("There are folders that were not synchronized because they are too big or external storages: "); + infoString += !cfg.confirmExternalStorage() ? tr("There are folders that were not synchronized because they are too big:") + " " + : !cfg.newBigFolderSizeLimit().first ? tr("There are folders that were not synchronized because they are external storages:") + " " + : tr("There are folders that were not synchronized because they are too big or external storages:") + " "; infoString += unsyncedFoldersString; } @@ -1682,12 +1839,51 @@ 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(WLTheme.dialogBackgroundColor(), WLTheme.quotaProgressColor())); + + _ui->quotaInfoLabel->setStyleSheet( + WLTheme.fontConfigurationCss( + WLTheme.settingsFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTitleWeight600(), + WLTheme.titleColor() + ) + ); + + _ui->quotaInfo2Label->setStyleSheet( + WLTheme.fontConfigurationCss( + WLTheme.settingsFont(), + WLTheme.settingsSmallTextSize(), + WLTheme.settingsTextWeight(), + WLTheme.titleColor() + ) + ); + + _ui->_folderList->setStyleSheet( + QStringLiteral("background: %1; %2;").arg( + WLTheme.white(), + WLTheme.fontConfigurationCss( + WLTheme.settingsFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTextWeight(), + WLTheme.titleColor() + ) + ) + ); + +#if defined(Q_OS_MAC) + _ui->selectiveSyncLabel->setStyleSheet(QString("color: %1;").arg(WLTheme.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()->isInitialized()) { slotE2eEncryptionMnemonicReady(); @@ -1763,6 +1959,14 @@ void AccountSettings::initializeE2eEncryptionSettingsMessage() connect(actionEnableE2e, &QAction::triggered, this, &AccountSettings::slotE2eEncryptionGenerateKeys); } +void AccountSettings::disguiseTabWidget() const +{ + // Ensure all elements of the tab widget are hidden. + // Document mode lets the child view take up the whole view. + _ui->tabWidget->setDocumentMode(true); + _ui->tabWidget->tabBar()->hide(); +} + } // namespace OCC #include "accountsettings.moc" diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index 592e7175e1d5c..c09b068f0cc2e 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" @@ -134,8 +135,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); @@ -144,6 +147,10 @@ private slots: /// Returns the alias of the selected folder, empty string if none [[nodiscard]] QString selectedFolderAlias() const; + 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 2db413221b169..88e9c17168390 100644 --- a/src/gui/accountsettings.ui +++ b/src/gui/accountsettings.ui @@ -13,9 +13,21 @@ Form - + + 32 + + + 32 + + + 32 + + + 32 + + - + @@ -38,6 +50,8 @@ + + @@ -52,7 +66,7 @@ 16777215 - 7 + 8 @@ -62,6 +76,43 @@ -1 + false + + + + + + + + 0 + 0 + + + + Expand Memory + + + + + + + + + + 0 + 0 + + + + + + + Storage space: … + + + Qt::PlainText + + false @@ -263,7 +314,7 @@ QTabWidget::Rounded - 0 + 1 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 dd0f4066f2346..a148b8580594a 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 a59fed2731750..92ddc85da8c76 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -40,7 +40,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 78a9a1c1f3c57..f2310c4cf187a 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 @@ -74,7 +76,7 @@ class QSocket; namespace OCC { -Q_LOGGING_CATEGORY(lcApplication, "nextcloud.gui.application", QtInfoMsg) +Q_LOGGING_CATEGORY(lcApplication, "hidrivenext.gui.application", QtInfoMsg) namespace { @@ -489,6 +491,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); @@ -636,6 +662,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 @@ -657,6 +691,8 @@ void Application::slotAccountStateAdded(AccountState *accountState) connect(accountState->account().data(), &Account::serverVersionChanged, _folderManager.data(), &FolderMan::slotServerVersionChanged); + startTracking(); + _gui->slotTrayMessageIfServerUnsupported(accountState->account()); } @@ -741,9 +777,9 @@ void Application::setupLogging() logger->setLogDebug(true); #endif - logger->enterNextLogFile(QStringLiteral("nextcloud.log"), OCC::Logger::LogType::Log); + logger->enterNextLogFile(QStringLiteral("hidrivenext.log"), OCC::Logger::LogType::Log); logger->enterNextLogFile(QStringLiteral("permanent_delete.log"), OCC::Logger::LogType::DeleteLog); - + qCInfo(lcApplication) << "##################" << _theme->appName() << "locale:" << QLocale::system().name() << "ui_lang:" << property("ui_lang") diff --git a/src/gui/application.h b/src/gui/application.h index c3936da43a45e..aedbb803c3618 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -109,7 +109,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/basetheme.h b/src/gui/basetheme.h new file mode 100644 index 0000000000000..79613bd07c42d --- /dev/null +++ b/src/gui/basetheme.h @@ -0,0 +1,526 @@ +#ifndef _BASETHEME_H +#define _BASETHEME_H + +#include +#include +#include "theme.h" + +namespace OCC { + +class BaseTheme : public QObject{ + Q_OBJECT + Q_PROPERTY(QString dialogBackgroundColor READ dialogBackgroundColor CONSTANT) + Q_PROPERTY(QString trayFontColor READ trayFontColor CONSTANT) + Q_PROPERTY(QString trayBorderColor READ trayBorderColor CONSTANT) + Q_PROPERTY(QString trayInputFieldBorderColor READ trayInputFieldBorderColor CONSTANT) + Q_PROPERTY(QString trayBackgroundColor READ trayBackgroundColor CONSTANT) + Q_PROPERTY(QString iconDarkColor READ iconDarkColor CONSTANT) + Q_PROPERTY(QString buttonIconColor READ buttonIconColor CONSTANT) + Q_PROPERTY(QString buttonHoveredColor READ buttonHoveredColor CONSTANT) + Q_PROPERTY(QString buttonPressedColor READ buttonPressedColor CONSTANT) + Q_PROPERTY(QString toolButtonHoveredColor READ toolButtonHoveredColor CONSTANT) + Q_PROPERTY(QString toolButtonPressedColor READ toolButtonPressedColor CONSTANT) + Q_PROPERTY(QString pillButtonPrimaryColor READ pillButtonPrimaryColor CONSTANT) + Q_PROPERTY(QString pillButtonSecondaryColor READ pillButtonSecondaryColor CONSTANT) + Q_PROPERTY(QString pillButtonBorderColor READ pillButtonBorderColor CONSTANT) + Q_PROPERTY(QString clipboardBackgroundColor READ clipboardBackgroundColor CONSTANT) + Q_PROPERTY(QString trayErrorBorderColor READ trayErrorBorderColor CONSTANT) + Q_PROPERTY(QString trayErrorTextColor READ trayErrorTextColor CONSTANT) + Q_PROPERTY(QString sesHeaderLogoIcon READ sesHeaderLogoIcon CONSTANT) + Q_PROPERTY(QString websiteIcon READ websiteIcon CONSTANT) + Q_PROPERTY(QString folderIcon READ folderIcon CONSTANT) + Q_PROPERTY(QString moreIcon READ moreIcon CONSTANT) + Q_PROPERTY(QString moreHoverIcon READ moreHoverIcon CONSTANT) + Q_PROPERTY(QString avatarIcon READ avatarIcon CONSTANT) + Q_PROPERTY(QString plusIcon READ plusIcon CONSTANT) + Q_PROPERTY(QString lightPlusIcon READ lightPlusIcon CONSTANT) + Q_PROPERTY(QString quitIcon READ quitIcon CONSTANT) + Q_PROPERTY(QString resumeIcon READ resumeIcon CONSTANT) + Q_PROPERTY(QString pauseIcon READ pauseIcon CONSTANT) + Q_PROPERTY(QString settingsIcon READ settingsIcon CONSTANT) + Q_PROPERTY(QString logoutIcon READ logoutIcon CONSTANT) + Q_PROPERTY(QString deleteIcon READ deleteIcon CONSTANT) + Q_PROPERTY(QString clipboardIcon READ clipboardIcon CONSTANT) + Q_PROPERTY(QString lightClipboardIcon READ lightClipboardIcon CONSTANT) + Q_PROPERTY(QString chevronIcon READ chevronIcon CONSTANT) + Q_PROPERTY(QString syncSuccessIcon READ syncSuccessIcon CONSTANT) + Q_PROPERTY(QString syncErrorIcon READ syncErrorIcon CONSTANT) + Q_PROPERTY(QString syncOfflineIcon READ syncOfflineIcon CONSTANT) + Q_PROPERTY(QString snackbarErrorIcon READ snackbarErrorIcon CONSTANT) + Q_PROPERTY(QString activityIcon READ activityIcon CONSTANT) + +public: + + virtual ~BaseTheme() = default; + + virtual QString themePrefix(QString context = "qml") const { + if (context == "qml") { + return QString("qrc:///client/theme/"); + } + return QString(Theme::themePrefix); + } + + virtual QString additionalThemePrefix() const { return QStringLiteral(""); } + + virtual QString avatarIcon(QString context = "qml") const { + return themePrefix(context) + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-settingsAvatar.svg"); + } + + virtual QString roundAvatarIcon() const { + return QString(Theme::themePrefix) + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-settingsAvatarRound.svg"); + } + + virtual QString folderIcon(QString context = "qml") const { + return themePrefix(context) + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-folderIcon.svg"); + } + + virtual QString syncArrows() const { + return QString(Theme::themePrefix) + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-syncArrows.svg"); + } + + virtual QString questionCircleIcon() const { + return QString(Theme::themePrefix) + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-questionMark.svg"); + } + + virtual QString liveBackupPlusIcon() const { + return QString(Theme::themePrefix) + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-addlivebackup.svg"); + } + + virtual QString websiteIcon(QString context = "qml") const { + return themePrefix(context) + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-website.svg"); + } + + virtual QString moreIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-more.svg"); + } + + virtual QString moreHoverIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-more-hover.svg"); + } + + virtual QString plusIcon(QString context = "qml") const { + return themePrefix(context) + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-darkPlus.svg"); + } + + virtual QString lightPlusIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-lightPlus.svg"); + } + + virtual QString quitIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-accountQuit.svg"); + } + + virtual QString resumeIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-accountResume.svg"); + } + + virtual QString pauseIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-accountPause.svg"); + } + + virtual QString settingsIcon(QString context = "qml") const { + return themePrefix(context) + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-settings.svg"); + } + + virtual QString logoutIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-accountLogout.svg"); + } + + virtual QString sesHeaderLogoIcon() const = 0; + + virtual QString deleteIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-accountDelete.svg"); + } + + virtual QString activityDeleteIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-activityDelete.svg"); + } + + virtual QString refreshIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-refresh.svg"); + } + + virtual QString infoIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-info.svg"); + } + + virtual QString clipboardIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-clipboard.svg"); + } + + virtual QString lightClipboardIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-lightClipboard.svg"); + } + + virtual QString chevronIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-chevron.svg"); + } + + virtual QString syncSuccessIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-syncstate-success.svg"); + } + + virtual QString syncErrorIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-syncstate-error.svg"); + } + + virtual QString syncOfflineIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-state-offline.svg"); + } + + virtual QString snackbarErrorIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-snackbar-error.svg"); + } + + virtual QString activityIcon() const { + return themePrefix() + _sesFolder + additionalThemePrefix() + QStringLiteral("ses-activity.svg"); + } + + virtual int treeViewIconSize() const { + return 32; + } + + //Control Configuration: Sizes + virtual QString toolbarActionBorderRadius() const { + return "8px"; + } + + virtual QString toolbarSideMargin() const { + return "10px"; + } + + virtual int toolbarIconSize() const { + return 24; + } + + virtual QString buttonRadius() const { + return "4px"; + } + + virtual int buttonRadiusInt() const { + return 4; + } + + virtual QString buttonPadding() const { + return "10px"; + } + + virtual QString smallMargin() const { + return "8"; + } + + virtual int minimalSettingsDialogWidth() const { + return 740; + } + + virtual int wizardFixedWidth() const { + return 576; + } + + virtual int wizardFixedHeight() const { + return 704; + } + + virtual int LoginPageSpacer() const { + return 45; + } + + //Font Configuration + virtual QString settingsFont() const { + return "Segoe UI"; + } + + virtual QString contextMenuFont() const { + //TODO + return ":/client/fonts/OpenSans-Regular.ttf"; + } + + virtual QString settingsSmallTextSize() const { + return "14px"; + } + + virtual int settingsTextPixel() const { + return 16; + } + + virtual QString settingsTextSize() const { + return QString::number(settingsTextPixel()) + "px"; + } + + virtual int settingsTitlePixel() const { + return 20; + } + + virtual QString settingsTitleSize() const { + return QString::number(settingsTitlePixel()) + "px"; + } + + virtual int settingsBigTitlePixel() const { + return 24; + } + + virtual QString settingsBigTitleSize() const { + return QString::number(settingsBigTitlePixel()) + "px"; + } + + virtual QString onboardingTitle() const { + return "28px"; + } + + virtual QString settingsTextWeight() const { + return "400"; + } + + virtual QString settingsTitleWeight400() const { + return "400"; + } + + virtual QString settingsTitleWeight500() const { + return "500"; + } + + virtual QString settingsTitleWeight600() const { + return "600"; + } + + virtual QFont::Weight settingsTitleWeightDemiBold() const { + return QFont::DemiBold; + } + + virtual QFont::Weight settingsTitleWeightNormal() const { + return QFont::Normal; + } + + virtual QFont settingsFontDefault() const { + QFont defaultFont(settingsFont()); + defaultFont.setPixelSize(settingsTextPixel()); + defaultFont.setWeight(settingsTitleWeightNormal()); + return defaultFont; + } + + virtual QString titleColor() const { + return "#000000"; + } + + virtual QString folderWizardSubtitleColor() const { + return "#104996"; + } + + virtual QString folderWizardPathColor() const { + return "#97A3B4"; + } + + virtual QString loginWizardFontGrey() const { + return "#616161"; + } + + virtual QString loginWizardFontLightGrey() const { + return "#BDBDBD"; + } + + virtual QString trayFontColor() const { + return "#001B41"; + } + + virtual QString trayBorderColor() const { + return "#D7D7D7"; + } + + virtual QString trayInputFieldBorderColor() const { + return "#718095"; + } + + virtual QString fontConfigurationCss(QString font, QString size, QString weight, QString color) const { + return QString("font-family: %1; font-size: %2; font-weight: %3; color: %4; ").arg( + font, + size, + weight, + color); + } + + //Colors + virtual QString settingsLinkColor() const { + return "#02306A"; + } + + virtual QString quotaProgressColor() const { + return "#308cc6"; + } + + virtual QString syncProgressColor() const { + return "#359ada"; + } + + virtual QString buttonPrimaryColor() const { + return "#0F6CBD"; + } + + virtual QString buttonSecondaryColor() const { + return "#FFFFFF"; + } + + virtual QString buttonSecondaryBorderColor() const { + return "#D1D1D1"; + } + + virtual QString buttonDisabledColor() const { + return "#F0F0F0"; + } + + virtual QString buttonPrimaryHoverColor() const { + return "#115EA3"; + } + + virtual QString buttonSecondaryHoverColor() const { + return "#F5F5F5"; + } + + virtual QString buttonPrimaryPressedColor() const { + return "#0C3B5E"; + } + + virtual QString buttonSecondaryPressedColor() const { + return "#E0E0E0"; + } + + virtual QString buttonPrimaryFocusedBorderColor() const { + return "#000000"; + } + + virtual QString buttonSecondaryFocusedBorderColor() const { + return "#000000"; + } + + virtual QString buttonDisabledFontColor() const { + return "#BDBDBD"; + } + + virtual QString pillButtonPrimaryColor() const { + return "#0B2A63"; + } + + virtual QString pillButtonSecondaryColor() const { + return "#FFFFFF"; + } + + virtual QString pillButtonBorderColor() const { + return "#0B2A63"; + } + + virtual QString clipboardBackgroundColor() const { + return "#FFFFFF"; + } + + virtual QString white() const { + return "#FFFFFF"; + } + + virtual QString black() const { + return "#000000"; + } + + virtual QString dialogBackgroundColor() const { + return "#FAFAFA"; + } + + virtual QString trayBackgroundColor() const { + return "#FFFFFF"; + } + + virtual QString menuBorderColor() const { + return "#2E4360"; + } + + virtual QString menuTextColor() const { + return "#001B41"; + } + + virtual QString menuPressedTextColor() const { + return "#001B41"; + } + + virtual QString iconDarkColor() const { + return "#001B41"; + } + + virtual QString menuSelectedItemColor() const { + return "#F4F7FA"; + } + + virtual QString menuPressedItemColor() const { + return "#F4F7FA"; + } + + virtual QString menuBorderRadius() const { + return "16px"; + } + + virtual QString buttonIconColor() const { + return "#1474C4"; + } + + virtual QString buttonIconHoverColor() const { + return "#FFFFFF"; + } + + virtual QString buttonPressedColor() const { + return "#0B2A63"; + } + + virtual QString buttonHoveredColor() const { + return "#1474C4"; + } + + virtual QString toolButtonHoveredColor() const { + return "#DBEDF8"; + } + + virtual QString toolButtonPressedColor() const { + return "#95CAEB"; + } + + virtual QString errorColor() const { + return "#FDF3F4"; + } + + virtual QString errorBorderColor() const { + return "#EEACB2"; + } + + virtual QString trayErrorBorderColor() const { + return "#F50C00"; + } + + virtual QString trayErrorTextColor() const { + return "#C80A00"; + } + + virtual QString warningBorderColor() const { + return "#F4BFAB"; + } + + virtual QString warningColor() const { + return "#FDF6F3"; + } + + virtual QString successBorderColor() const { + return "#9FD89F"; + } + + virtual QString successColor() const { + return "#F1FAF1"; + } + + virtual QString infoBorderColor() const { + return "#11C7E6"; + } + + virtual QString infoColor() const { + return "#E6F9FC"; + } + + private: + inline static const QString _sesFolder = QStringLiteral("ses/"); +}; +} +#endif // _BASETHEME_H \ No newline at end of file diff --git a/src/gui/buttonstyle.h b/src/gui/buttonstyle.h new file mode 100644 index 0000000000000..087db1560af04 --- /dev/null +++ b/src/gui/buttonstyle.h @@ -0,0 +1,339 @@ + +#ifndef _BUTTONSTYLE_H +#define _BUTTONSTYLE_H + +#include "whitelabeltheme.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; + //Icon + virtual QString buttonIconDefaultColor() const = 0; + virtual QString buttonIconHoverColor() 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::WLTheme.buttonPrimaryColor(); + } + + QString buttonDefaultBorderColor() const override + { + return OCC::WLTheme.buttonPrimaryColor(); + } + + //Hover + QString buttonHoverColor() const override + { + return OCC::WLTheme.buttonPrimaryHoverColor(); + } + + QString buttonHoverBorderColor() const override + { + return OCC::WLTheme.buttonPrimaryHoverColor(); + } + + // Pressed + QString buttonPressedColor() const override + { + return OCC::WLTheme.buttonPrimaryPressedColor(); + } + + QString buttonPressedBorderColor() const override + { + return OCC::WLTheme.buttonPrimaryPressedColor(); + } + + // Disabled + QString buttonDisabledColor() const override + { + return OCC::WLTheme.buttonDisabledColor(); + } + + QString buttonDisabledBorderColor() const override + { + return OCC::WLTheme.buttonDisabledColor(); + } + + // Focused + QString buttonFocusedColor() const override + { + return OCC::WLTheme.buttonPrimaryColor(); + } + + QString buttonFocusedBorderColor() const override + { + return OCC::WLTheme.buttonPrimaryFocusedBorderColor(); + } + + // Font + QString buttonDisabledFontColor() const override + { + return OCC::WLTheme.buttonDisabledFontColor(); + } + + QString buttonFontColor() const override + { + return OCC::WLTheme.white(); + } + + // Icon (Three Dots) + QString buttonIconDefaultColor() const override + { + return OCC::WLTheme.white(); + } + + QString buttonIconHoverColor() const override + { + return OCC::WLTheme.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::WLTheme.buttonSecondaryColor(); + } + + QString buttonDefaultBorderColor() const override + { + return OCC::WLTheme.buttonSecondaryBorderColor(); + } + + // Hover + QString buttonHoverColor() const override + { + return OCC::WLTheme.buttonSecondaryHoverColor(); + } + + QString buttonHoverBorderColor() const override + { + return OCC::WLTheme.buttonSecondaryBorderColor(); + } + + // Pressed + QString buttonPressedColor() const override + { + return OCC::WLTheme.buttonSecondaryPressedColor(); + } + + QString buttonPressedBorderColor() const override + { + return OCC::WLTheme.buttonSecondaryBorderColor(); + } + + // Disabled + QString buttonDisabledColor() const override + { + return OCC::WLTheme.buttonDisabledColor(); + } + + QString buttonDisabledBorderColor() const override + { + return OCC::WLTheme.buttonDisabledColor(); + } + + // Focused + QString buttonFocusedColor() const override + { + return OCC::WLTheme.white(); + } + + QString buttonFocusedBorderColor() const override + { + return OCC::WLTheme.buttonSecondaryFocusedBorderColor(); + } + + // Font + QString buttonDisabledFontColor() const override + { + return OCC::WLTheme.buttonDisabledFontColor(); + } + + QString buttonFontColor() const override + { + return OCC::WLTheme.black(); + } + + // Icon (Three Dots) + QString buttonIconDefaultColor() const override + { + return OCC::WLTheme.white(); + } + + QString buttonIconHoverColor() const override + { + return OCC::WLTheme.white(); + } +}; + +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::WLTheme.white(); + } + + QString buttonDefaultBorderColor() const override + { + return OCC::WLTheme.white(); + } + + // Hover + QString buttonHoverColor() const override + { + return OCC::WLTheme.buttonHoveredColor(); + } + + QString buttonHoverBorderColor() const override + { + return OCC::WLTheme.buttonHoveredColor(); + } + + // Pressed + QString buttonPressedColor() const override + { + return OCC::WLTheme.buttonPressedColor(); + } + + QString buttonPressedBorderColor() const override + { + return OCC::WLTheme.buttonPressedColor(); + } + + // Disabled + QString buttonDisabledColor() const override + { + return OCC::WLTheme.buttonDisabledColor(); + } + + QString buttonDisabledBorderColor() const override + { + return OCC::WLTheme.buttonDisabledColor(); + } + + // Focused + QString buttonFocusedColor() const override + { + return OCC::WLTheme.white(); + } + + QString buttonFocusedBorderColor() const override + { + return OCC::WLTheme.black(); + } + + // Font + QString buttonDisabledFontColor() const override + { + return OCC::WLTheme.buttonDisabledFontColor(); + } + + QString buttonFontColor() const override + { + return OCC::WLTheme.black(); + } + + // Icon (Three Dots) + QString buttonIconDefaultColor() const + { + return OCC::WLTheme.buttonIconColor(); + } + + QString buttonIconHoverColor() const + { + return OCC::WLTheme.buttonIconHoverColor(); + } +}; +} + +#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 92f3c768962b9..371854d1d8bf4 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 "common/filesystembase.h" #include @@ -67,7 +68,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, @@ -84,6 +85,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(); @@ -99,6 +102,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())); @@ -150,10 +154,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)); } }); @@ -167,6 +174,8 @@ CaseClashFilenameDialog::CaseClashFilenameDialog(AccountPtr account, }); checkIfAllowedToRename(); + + customizeStyle(); } CaseClashFilenameDialog::~CaseClashFilenameDialog() = default; @@ -286,5 +295,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( + WLTheme.dialogBackgroundColor(), + WLTheme.black(), + WLTheme.fontConfigurationCss( + WLTheme.settingsFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTextWeight(), + WLTheme.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( + WLTheme.folderWizardPathColor(), + WLTheme.settingsFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTextWeight(), + WLTheme.buttonRadius(), + WLTheme.menuBorderColor(), + WLTheme.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..7477525cbe60a 100644 --- a/src/gui/caseclashfilenamedialog.ui +++ b/src/gui/caseclashfilenamedialog.ui @@ -120,6 +120,9 @@ Open existing file + + true + @@ -226,6 +229,9 @@ Open clashing file + + true + diff --git a/src/gui/clickablelabel.h b/src/gui/clickablelabel.h new file mode 100644 index 0000000000000..f0ae1628cb596 --- /dev/null +++ b/src/gui/clickablelabel.h @@ -0,0 +1,28 @@ +#ifndef CLICKABLELABEL_H +#define CLICKABLELABEL_H + +#include +#include +#include + +class ClickableLabel : public QLabel { + Q_OBJECT + +public: + explicit ClickableLabel(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) + : QLabel(parent, f) {} + explicit ClickableLabel(const QString &text, QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) + : QLabel(text, parent, f) {} +signals: + void clicked(); + +protected: + void mousePressEvent(QMouseEvent* event) override { + if (event->button() == Qt::LeftButton) { + emit clicked(); + } + QLabel::mousePressEvent(event); + } +}; + +#endif // CLICKABLELABEL_H \ No newline at end of file diff --git a/src/gui/cloudproviders/cloudproviderwrapper.cpp b/src/gui/cloudproviders/cloudproviderwrapper.cpp index 39d24b7956f5b..52033ad57053e 100644 --- a/src/gui/cloudproviders/cloudproviderwrapper.cpp +++ b/src/gui/cloudproviders/cloudproviderwrapper.cpp @@ -125,7 +125,7 @@ void CloudProviderWrapper::slotUpdateProgress(const QString &folder, const Progr qint64 currentFile = progress.currentFile(); qint64 totalFileCount = qMax(progress.totalFiles(), currentFile); if (progress.trustEta()) { - msg = tr("Syncing %1 of %2 (%3 left)") + msg = tr("Syncing %1 of %2 (%3 left)") .arg(currentFile) .arg(totalFileCount) .arg(Utility::durationToDescriptiveString2(progress.totalProgress().estimatedEta)); diff --git a/src/gui/conflictdialog.cpp b/src/gui/conflictdialog.cpp index bae7b190f957a..fda1ac8db85ab 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( + WLTheme.dialogBackgroundColor(), + WLTheme.black(), + WLTheme.fontConfigurationCss( + WLTheme.settingsFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTextWeight(), + WLTheme.titleColor() + ) + ) + ); + + #ifdef Q_OS_MAC + _ui->buttonBox->layout()->setSpacing(24); + _ui->buttonBox->setLayoutDirection(Qt::LeftToRight); + + _ui->localVersionRadio->setStyleSheet( + QStringLiteral("QCheckBox {color: %1;}").arg(WLTheme.black()) + ); + + _ui->remoteVersionRadio->setStyleSheet( + QStringLiteral("QCheckBox {color: %1;}").arg(WLTheme.black()) + ); + #endif } } // namespace OCC diff --git a/src/gui/conflictdialog.h b/src/gui/conflictdialog.h index b102b8de7797f..f82066d176474 100644 --- a/src/gui/conflictdialog.h +++ b/src/gui/conflictdialog.h @@ -46,6 +46,7 @@ public slots: private: void updateWidgets(); void updateButtonStates(); + void customizeStyle(); QString _baseFilename; QScopedPointer _ui; diff --git a/src/gui/conflictsolver.cpp b/src/gui/conflictsolver.cpp index 5292d48e3280e..60ac7f35d5d38 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 6c1bec008ad41..c052952f5ccbd 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 b3cc7c937ac41..c133a6dc61b3a 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 d5618307247be..0acfb801d02a0 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 4178b0bd73586..07131815afcbd 100644 --- a/src/gui/creds/webflowcredentialsdialog.cpp +++ b/src/gui/creds/webflowcredentialsdialog.cpp @@ -6,7 +6,7 @@ #include "application.h" #include "owncloudgui.h" #include "wizard/owncloudwizardcommon.h" - +#include "whitelabeltheme.h" #ifdef WITH_WEBENGINE #include "wizard/webview.h" #endif // WITH_WEBENGINE @@ -24,6 +24,11 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlo { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setStyleSheet(QStringLiteral("QDialog { background-color: %1; }").arg(WLTheme.dialogBackgroundColor())); + + setFixedWidth(646); + setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); + _layout = new QVBoxLayout(this); int spacing = _layout->spacing(); auto margin = _layout->contentsMargins(); @@ -35,12 +40,24 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlo _containerLayout->setContentsMargins(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(WLTheme.fontConfigurationCss( + WLTheme.settingsFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTitleWeight600(), + WLTheme.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); @@ -117,7 +134,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 cac6de6d12ec2..eacaedf1b2e98 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 AccountStatePtr &accountState, const QString &relPath, diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index e2c3d3c7ecf5f..4f50c87761ea6 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -24,7 +24,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/editlocallyverificationjob.cpp b/src/gui/editlocallyverificationjob.cpp index dd6417e3defc3..195382a41a370 100644 --- a/src/gui/editlocallyverificationjob.cpp +++ b/src/gui/editlocallyverificationjob.cpp @@ -32,7 +32,7 @@ QString prefixSlashToPath(const QString &path) namespace OCC { -Q_LOGGING_CATEGORY(lcEditLocallyVerificationJob, "nextcloud.gui.editlocallyverificationjob", QtInfoMsg) +Q_LOGGING_CATEGORY(lcEditLocallyVerificationJob, "hidrivenext.gui.editlocallyverificationjob", QtInfoMsg) EditLocallyVerificationJob::EditLocallyVerificationJob(const AccountStatePtr &accountState, const QString &relPath, 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 904bee76240be..3341736014cc6 100644 --- a/src/gui/filedetails/FileActivityView.qml +++ b/src/gui/filedetails/FileActivityView.qml @@ -16,7 +16,7 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style import "../tray" diff --git a/src/gui/filedetails/FileDetailsPage.qml b/src/gui/filedetails/FileDetailsPage.qml index 5b281720e952c..28d3f7e5f4249 100644 --- a/src/gui/filedetails/FileDetailsPage.qml +++ b/src/gui/filedetails/FileDetailsPage.qml @@ -16,7 +16,7 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style import "../tray" @@ -45,7 +45,12 @@ Page { localPath: root.localPath } - Connections { + font.family: Style.sesOpenSansRegular + font.pixelSize: Style.sesFontPixelSize + font.weight: Style.sesFontNormalWeight + palette.windowText: Style.sesTrayFontColor + + Connections { target: Systray function onShowFileDetailsPage(fileLocalPath, page) { if (!root.fileDetails.sharingAvailable && page == Systray.FileDetailsPage.Sharing) { @@ -69,12 +74,11 @@ Page { bottomPadding: intendedPadding background: Rectangle { - color: palette.base - visible: root.backgroundsVisible + color: Style.sesBackgroundColor } header: ColumnLayout { - spacing: root.intendedPadding + spacing: Style.sesMediumMargin GridLayout { id: headerGridLayout @@ -108,7 +112,7 @@ Page { Layout.rowSpan: headerGridLayout.rows Layout.preferredWidth: Style.trayListItemIconSize - Layout.leftMargin: root.intendedPadding + Layout.leftMargin: Style.sesMediumMargin Layout.fillHeight: true verticalAlignment: Image.AlignVCenter @@ -123,26 +127,34 @@ 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 } - Button { + IconButton { id: closeButton + customHoverEnabled: false + Layout.rowSpan: headerGridLayout.rows - Layout.preferredWidth: Style.activityListButtonWidth - Layout.preferredHeight: Style.activityListButtonHeight - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.preferredWidth: Style.iconButtonWidth + Layout.preferredHeight: width Layout.rightMargin: headerGridLayout.textRightMargin - icon.source: "image://svgimage-custom-color/clear.svg" + "/" + palette.buttonText - icon.width: Style.activityListButtonIconSize - icon.height: Style.activityListButtonIconSize + iconSource: Style.sesAccountQuit + toolTipText: qsTr("Dismiss") + + font.pixelSize: Style.sesFontPixelSize + font.weight: Style.sesFontNormalWeight + visible: root.showCloseButton + onClicked: root.closeButtonClicked() } @@ -150,9 +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}` wrapMode: Text.Wrap } @@ -163,8 +179,12 @@ Page { Layout.rightMargin: headerGridLayout.textRightMargin text: root.fileDetails.lockExpireString + color: palette.midlight wrapMode: Text.Wrap visible: headerGridLayout.showFileLockedString + + font.pixelSize: Style.sesFontHintPixelSize + font.weight: Style.sesFontNormalWeight } Row { @@ -207,37 +227,13 @@ Page { ToolTip { 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") - 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") - checked: swipeView.currentIndex === shareViewLoader.swipeIndex - onClicked: swipeView.currentIndex = shareViewLoader.swipeIndex - visible: root.fileDetails.sharingAvailable - } - } } SwipeView { @@ -246,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 991da7d2b91c1..9413c16873612 100644 --- a/src/gui/filedetails/FileDetailsView.qml +++ b/src/gui/filedetails/FileDetailsView.qml @@ -16,7 +16,7 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style StackView { @@ -31,7 +31,7 @@ StackView { property bool backgroundsVisible: true background: Rectangle { - color: palette.base + color: palette.window visible: root.backgroundsVisible } diff --git a/src/gui/filedetails/FileDetailsWindow.qml b/src/gui/filedetails/FileDetailsWindow.qml index f7f5eee41e420..bc4a8f2f45f0e 100644 --- a/src/gui/filedetails/FileDetailsWindow.qml +++ b/src/gui/filedetails/FileDetailsWindow.qml @@ -17,7 +17,7 @@ import QtQuick.Window import QtQuick.Layouts import QtQuick.Controls -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style ApplicationWindow { @@ -26,9 +26,6 @@ ApplicationWindow { property var accountState property string localPath: "" - LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeft - LayoutMirroring.childrenInherit: true - width: 400 height: 500 minimumWidth: 300 diff --git a/src/gui/filedetails/NCInputDateField.qml b/src/gui/filedetails/NCInputDateField.qml index b39eed3273065..3dd83175cd2ac 100644 --- a/src/gui/filedetails/NCInputDateField.qml +++ b/src/gui/filedetails/NCInputDateField.qml @@ -14,7 +14,7 @@ import QtQuick import QtQuick.Controls -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient NCInputTextField { id: root diff --git a/src/gui/filedetails/NCInputTextArea.qml b/src/gui/filedetails/NCInputTextArea.qml index d3e917ff5113f..4bdc32065c851 100644 --- a/src/gui/filedetails/NCInputTextArea.qml +++ b/src/gui/filedetails/NCInputTextArea.qml @@ -16,7 +16,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style TextArea { @@ -43,6 +43,11 @@ TextArea { width: height height: parent.height + + background: Rectangle { + radius: width / 2 + color: textFieldBorder.color + } flat: true icon.source: "image://svgimage-custom-color/confirm.svg" + "/" + root.secondaryColor diff --git a/src/gui/filedetails/NCInputTextField.qml b/src/gui/filedetails/NCInputTextField.qml index be1ad2558bce5..825f4c964db4a 100644 --- a/src/gui/filedetails/NCInputTextField.qml +++ b/src/gui/filedetails/NCInputTextField.qml @@ -16,7 +16,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style TextField { @@ -32,6 +32,14 @@ TextField { rightPadding: submitButton.width selectByMouse: true + + background: Rectangle { + id: textFieldBorder + radius: Style.slightlyRoundedButtonRadius + border.width: Style.normalBorderWidth + border.color: root.activeFocus ? root.validInput ? root.accentColor : Style.errorBoxBackgroundColor : root.secondaryColor + color: palette.base + } Button { id: submitButton diff --git a/src/gui/filedetails/NCTabButton.qml b/src/gui/filedetails/NCTabButton.qml index 48e2fecccabec..ee17cb052a467 100644 --- a/src/gui/filedetails/NCTabButton.qml +++ b/src/gui/filedetails/NCTabButton.qml @@ -17,7 +17,7 @@ import QtQuick.Window import QtQuick.Layouts import QtQuick.Controls -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style import "../tray" diff --git a/src/gui/filedetails/ShareDelegate.qml b/src/gui/filedetails/ShareDelegate.qml index d6bb454a94090..70ee664e0d8c5 100644 --- a/src/gui/filedetails/ShareDelegate.qml +++ b/src/gui/filedetails/ShareDelegate.qml @@ -18,7 +18,7 @@ import QtQuick.Layouts import QtQuick.Controls import Qt5Compat.GraphicalEffects -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style import "../tray" import "../" @@ -49,7 +49,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 @@ -139,6 +139,7 @@ GridLayout { Layout.column: 1 text: root.detailText + elide: Text.ElideRight visible: text !== "" } @@ -151,17 +152,19 @@ GridLayout { spacing: 0 - Button { + IconButton { id: createLinkButton Layout.alignment: Qt.AlignCenter - Layout.preferredWidth: Style.activityListButtonWidth - Layout.preferredHeight: Style.activityListButtonHeight + Layout.preferredWidth: Style.iconButtonWidth + Layout.preferredHeight: width + + toolTipText: qsTr("Create a new share link") - icon.source: "image://svgimage-custom-color/add.svg/" + palette.buttonText - icon.width: Style.activityListButtonIconSize - icon.height: Style.activityListButtonIconSize - display: AbstractButton.IconOnly + iconSource: Style.sesDarkPlus + palette.buttonText + icon.width: Style.smallIconSize + icon.height: Style.smallIconSize + customHoverEnabled: false visible: (root.isPlaceholderLinkShare || root.isSecureFileDropPlaceholderLinkShare) && root.canCreateLinkShares enabled: visible @@ -169,7 +172,7 @@ GridLayout { onClicked: root.createNewLinkShare() } - Button { + SecondaryPillButton { id: copyLinkButton function copyShareLink() { @@ -184,19 +187,27 @@ GridLayout { property bool shareLinkCopied: false - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.preferredWidth: shareLinkCopied ? implicitWidth : Style.activityListButtonWidth - Layout.preferredHeight: Style.activityListButtonHeight + removeBorder: true + + Layout.alignment: Qt.AlignCenter + Layout.preferredWidth: shareLinkCopied ? implicitWidth : Style.iconButtonWidth + Layout.preferredHeight: Style.iconButtonWidth + + toolTipText: qsTr("Copy share link location") text: shareLinkCopied ? qsTr("Copied!") : "" + textColor: Style.sesDarkGreen + backgroundColor: Style.clipboardBackgroundColor + + iconSource: shareLinkCopied ? Style.sesSyncSuccessIcon + Style.positiveColor : + Style.sesClipboard + palette.brightText + + icon.width: Style.smallIconSize + icon.height: Style.smallIconSize - icon.source: shareLinkCopied ? "image://svgimage-custom-color/copy.svg/" + palette.brightText : - "image://svgimage-custom-color/copy.svg/" + palette.buttonText - icon.width: Style.activityListButtonIconSize - icon.height: Style.activityListButtonIconSize - display: AbstractButton.IconOnly visible: root.isLinkShare || root.isInternalLinkShare enabled: visible + onClicked: copyShareLink() Behavior on Layout.preferredWidth { @@ -215,19 +226,26 @@ GridLayout { } } - Button { + IconButton { id: moreButton - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.preferredWidth: Style.activityListButtonWidth - Layout.preferredHeight: Style.activityListButtonHeight + property bool isHovered: moreButton.hovered || moreButton.visualFocus + property bool isActive: moreButton.pressed + + Layout.alignment: Qt.AlignCenter + Layout.preferredWidth: Style.iconButtonWidth + Layout.preferredHeight: width + + toolTipText: qsTr("Share options") + + iconSource: Style.sesMore + iconSourceHovered: Style.sesMoreHover + width: Style.smallIconSize + height: Style.smallIconSize - icon.source: "image://svgimage-custom-color/more.svg/" + palette.buttonText - icon.width: Style.activityListButtonIconSize - icon.height: Style.activityListButtonIconSize - display: AbstractButton.IconOnly visible: !root.isPlaceholderLinkShare && !root.isSecureFileDropPlaceholderLinkShare && !root.isInternalLinkShare enabled: visible + onClicked: root.rootStackView.push(shareDetailsPageComponent, {}, StackView.PushTransition) Component { diff --git a/src/gui/filedetails/ShareDetailsPage.qml b/src/gui/filedetails/ShareDetailsPage.qml index 8035b40d9d195..535e5a2f05a3c 100644 --- a/src/gui/filedetails/ShareDetailsPage.qml +++ b/src/gui/filedetails/ShareDetailsPage.qml @@ -18,9 +18,10 @@ import QtQuick.Layouts import QtQuick.Controls import Qt5Compat.GraphicalEffects -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style import "../tray" +import "../SesComponents/" import "../" Page { @@ -43,6 +44,12 @@ Page { signal setPassword(string password) signal setNote(string note) + + font.family: Style.sesOpenSansRegular + font.pixelSize: Style.sesFontPixelSize + font.weight: Style.sesFontNormalWeight + palette.windowText: Style.sesTrayFontColor + property bool backgroundsVisible: true property color accentColor: Style.ncBlue @@ -80,6 +87,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 @@ -94,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."); @@ -105,8 +123,8 @@ Page { } function resetLinkShareLabelField() { - linkShareLabelTextField.text = linkShareLabel; - waitingForLinkShareLabelChange = false; + // linkShareLabelTextField.text = linkShareLabel; + // waitingForLinkShareLabelChange = false; } function resetPasswordField() { @@ -174,8 +192,7 @@ Page { padding: Style.standardSpacing * 2 background: Rectangle { - color: palette.base - visible: root.backgroundsVisible + color: Style.sesBackgroundColor } header: ColumnLayout { @@ -210,38 +227,45 @@ Page { } EnforcedPlainTextLabel { - id: headLabel + id: fileNameLabel Layout.fillWidth: true + Layout.rightMargin: headerGridLayout.textRightMargin - text: qsTr("Edit share") - font.bold: true - elide: Text.ElideRight + text: root.fileDetails.name + + font.pixelSize: titlePixelSize + font.weight: titleFontWeight + + wrapMode: Text.Wrap } - Button { - id: closeButton + IconButton { + id: placeholder + + customHoverEnabled: false Layout.rowSpan: headerGridLayout.rows - Layout.preferredWidth: Style.activityListButtonWidth - Layout.preferredHeight: Style.activityListButtonHeight - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.preferredWidth: Style.iconButtonWidth + Layout.preferredHeight: width Layout.rightMargin: root.padding - icon.source: "image://svgimage-custom-color/clear.svg" + "/" + palette.buttonText - icon.width: Style.activityListButtonIconSize - icon.height: Style.activityListButtonIconSize + iconSource: Style.sesAccountQuit + onClicked: root.closeShareDetails() } EnforcedPlainTextLabel { - id: secondaryLabel + id: fileDetailsLabel Layout.fillWidth: true - Layout.rightMargin: root.padding + Layout.rightMargin: headerGridLayout.textRightMargin - text: root.fileDetails.name + text: `${root.fileDetails.sizeString}, ${root.fileDetails.lastChangedString}` wrapMode: Text.Wrap + + font.pixelSize: hintPixelSize + font.weight: hintFontWeight } } } @@ -259,62 +283,255 @@ Page { readonly property int itemPadding: Style.smallSpacing width: parent.width - spacing: Style.smallSpacing - RowLayout { + + CheckBox { + id: passwordProtectEnabledMenuItem + Layout.fillWidth: true - height: visible ? implicitHeight : 0 + spacing: scrollContentsColumn.indicatorSpacing + padding: scrollContentsColumn.itemPadding + indicator.width: scrollContentsColumn.indicatorItemWidth + indicator.height: scrollContentsColumn.indicatorItemWidth + + checkable: true + checked: root.passwordProtectEnabled + text: qsTr("Password protection") + visible: root.shareSupportsPassword + enabled: visible && + !root.waitingForPasswordProtectEnabledChange && + !root.passwordEnforced + + onClicked: { + root.togglePasswordProtect(checked); + root.waitingForPasswordProtectEnabledChange = true; + } + } + + Loader { + id: passwordErrorBoxLoader + + property string message: "" + + Layout.fillWidth: true + height: message !== "" ? implicitHeight : 0 + + active: message !== "" + visible: active + + sourceComponent: Item { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + // Artificially add vertical padding + implicitHeight: passwordErrorBox.implicitHeight + (Style.smallSpacing * 2) + + SesErrorBox { + id: passwordErrorBox + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + text: passwordErrorBoxLoader.message + } + } + } + + 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 + + Layout.fillWidth: true + font.pixelSize: pixelSize + font.weight: fontWeight + + spacing: scrollContentsColumn.indicatorSpacing + padding: scrollContentsColumn.itemPadding + indicator.width: scrollContentsColumn.indicatorItemWidth + indicator.height: scrollContentsColumn.indicatorItemWidth + + checkable: true + checked: root.expireDateEnabled + text: qsTr("Set expiration date") + enabled: !root.waitingForExpireDateEnabledChange && !root.expireDateEnforced - visible: root.isLinkShare + onClicked: { + root.toggleExpirationDate(checked); + root.waitingForExpireDateEnabledChange = true; + } + } - Image { - Layout.preferredWidth: scrollContentsColumn.indicatorItemWidth - Layout.fillHeight: true + NCInputDateField { + id: expireDateField - verticalAlignment: Image.AlignVCenter - horizontalAlignment: Image.AlignHCenter - fillMode: Image.Pad + font.pixelSize: pixelSize + font.weight: fontWeight - source: "image://svgimage-custom-color/edit.svg/" + palette.windowText - sourceSize.width: scrollContentsColumn.rowIconWidth - sourceSize.height: scrollContentsColumn.rowIconWidth + 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 UTC + return Date.UTC(currentYear, currentMonth, currentMonthDay + 1); } - NCInputTextField { - id: linkShareLabelTextField + enabled: root.expireDateEnabled && + !root.waitingForExpireDateChange && + !root.waitingForExpireDateEnabledChange + + onUserAcceptedDate: { + root.setExpireDate(dateInMs); + root.waitingForExpireDateChange = true; + } + + Rectangle { + id: dateTextBorder + anchors.fill: parent + radius: Style.slightlyRoundedButtonRadius + border.width: Style.thickBorderWidth + border.color: Style.sesTrayInputField + color: palette.base + z: -1 + } + } + + ColumnLayout { + Layout.fillWidth: true + height: visible ? implicitHeight : 0 + spacing: Style.extraSmallSpacing + + CheckBox { + id: noteEnabledMenuItem Layout.fillWidth: true - height: visible ? implicitHeight : 0 - text: root.linkShareLabel - placeholderText: qsTr("Share label") + 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 - enabled: root.isLinkShare && - !root.waitingForLinkShareLabelChange + onClicked: { + root.toggleNoteToRecipient(checked); + root.waitingForNoteEnabledChange = true; + } + } + + 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 + } - onAccepted: if(text !== root.linkShareLabel) { - root.setLinkShareLabel(text); - root.waitingForLinkShareLabelChange = true; + TextEdit { + id: noteTextEdit + visible: root.noteEnabled + font.family: root.font.family + font.pixelSize: pixelSize + font.weight: fontWeight + color: Style.sesTrayFontColor + Layout.fillWidth: true + 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; } - NCBusyIndicator { + Rectangle { + id: noteTextBorder anchors.fill: parent - visible: root.waitingForLinkShareLabelChange - running: visible - z: 1 + radius: Style.slightlyRoundedButtonRadius + border.width: Style.thickBorderWidth + border.color: Style.sesTrayInputField + color: palette.base + z: -1 } } - } + } Loader { Layout.fillWidth: true active: !root.isFolderItem && !root.isEncryptedItem visible: active sourceComponent: CheckBox { + + font.pixelSize: pixelSize + font.weight: fontWeight + spacing: scrollContentsColumn.indicatorSpacing - leftPadding: scrollContentsColumn.itemPadding - rightPadding: scrollContentsColumn.itemPadding + padding: scrollContentsColumn.itemPadding indicator.width: scrollContentsColumn.indicatorItemWidth indicator.height: scrollContentsColumn.indicatorItemWidth @@ -324,13 +541,6 @@ Page { enabled: !root.isSharePermissionChangeInProgress onClicked: root.toggleAllowEditing(checked) - - NCBusyIndicator { - anchors.fill: parent - visible: root.isSharePermissionChangeInProgress - running: visible - z: 1 - } } } @@ -340,82 +550,108 @@ Page { visible: active sourceComponent: ColumnLayout { id: permissionRadioButtonsLayout - spacing: Layout.smallSpacing + spacing: 0 width: parent.width ButtonGroup { id: permissionModeRadioButtonsGroup } - RadioButton { + 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 - leftPadding: scrollContentsColumn.itemPadding - rightPadding: scrollContentsColumn.itemPadding + padding: scrollContentsColumn.itemPadding onClicked: root.permissionModeChanged(permissionMode) + visible: customPermissionsCheckBox.checked + font.pixelSize: pixelSize + font.weight: fontWeight } - RadioButton { + 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 - leftPadding: scrollContentsColumn.itemPadding - rightPadding: scrollContentsColumn.itemPadding + padding: scrollContentsColumn.itemPadding onClicked: root.permissionModeChanged(permissionMode) + visible: customPermissionsCheckBox.checked + font.pixelSize: pixelSize + font.weight: fontWeight } - RadioButton { + 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 - leftPadding: scrollContentsColumn.itemPadding - rightPadding: scrollContentsColumn.itemPadding + padding: scrollContentsColumn.itemPadding onClicked: root.permissionModeChanged(permissionMode) + visible: customPermissionsCheckBox.checked && !root.isInternalShare // Removed SES-307 + font.pixelSize: pixelSize + font.weight: fontWeight } + } + } - CheckBox { - id: allowResharingCheckBox + CheckBox { + id: allowResharingCheckBox - Layout.fillWidth: true + Layout.fillWidth: true - spacing: scrollContentsColumn.indicatorSpacing - leftPadding: scrollContentsColumn.itemPadding - rightPadding: scrollContentsColumn.itemPadding - indicator.width: scrollContentsColumn.indicatorItemWidth - indicator.height: scrollContentsColumn.indicatorItemWidth + font.pixelSize: pixelSize + font.weight: fontWeight - 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 - } - } - } + spacing: scrollContentsColumn.indicatorSpacing + padding: scrollContentsColumn.itemPadding + indicator.width: scrollContentsColumn.indicatorItemWidth + indicator.height: scrollContentsColumn.indicatorItemWidth - NCBusyIndicator { - anchors.fill: parent - visible: root.isSharePermissionChangeInProgress - running: visible - z: 1 + 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 } } @@ -431,9 +667,11 @@ Page { anchors.left: parent.left anchors.right: parent.right + font.pixelSize: pixelSize + font.weight: fontWeight + spacing: scrollContentsColumn.indicatorSpacing - leftPadding: scrollContentsColumn.itemPadding - rightPadding: scrollContentsColumn.itemPadding + padding: scrollContentsColumn.itemPadding indicator.width: scrollContentsColumn.indicatorItemWidth indicator.height: scrollContentsColumn.indicatorItemWidth @@ -441,320 +679,52 @@ Page { 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 - - spacing: scrollContentsColumn.indicatorSpacing - leftPadding: scrollContentsColumn.itemPadding - rightPadding: scrollContentsColumn.itemPadding - indicator.width: scrollContentsColumn.indicatorItemWidth - indicator.height: scrollContentsColumn.indicatorItemWidth - - checkable: true - checked: root.passwordProtectEnabled - text: qsTr("Password protection") - visible: root.shareSupportsPassword - 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.windowText - 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 { - id: passwordErrorBoxLoader - - property string message: "" - - Layout.fillWidth: true - height: message !== "" ? implicitHeight : 0 - - active: message !== "" - visible: active - - sourceComponent: Item { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - // Artificially add vertical padding - implicitHeight: passwordErrorBox.implicitHeight + (Style.smallSpacing * 2) - - ErrorBox { - id: passwordErrorBox - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - text: passwordErrorBoxLoader.message - } - } - } - - CheckBox { - id: expireDateEnabledMenuItem - - Layout.fillWidth: true - - spacing: scrollContentsColumn.indicatorSpacing - leftPadding: scrollContentsColumn.itemPadding - rightPadding: scrollContentsColumn.itemPadding - indicator.width: scrollContentsColumn.indicatorItemWidth - indicator.height: scrollContentsColumn.indicatorItemWidth - - checkable: true - checked: root.expireDateEnabled - text: qsTr("Set expiration date") - enabled: !root.waitingForExpireDateEnabledChange && !root.expireDateEnforced - - onClicked: { - root.toggleExpirationDate(checked); - root.waitingForExpireDateEnabledChange = true; - } - - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForExpireDateEnabledChange - running: visible - z: 1 - } - } - - RowLayout { - Layout.fillWidth: true - height: visible ? implicitHeight : 0 - spacing: scrollContentsColumn.indicatorSpacing - - visible: root.expireDateEnabled - - Image { - Layout.preferredWidth: scrollContentsColumn.indicatorItemWidth - Layout.fillHeight: true - - verticalAlignment: Image.AlignVCenter - horizontalAlignment: Image.AlignHCenter - fillMode: Image.Pad - - source: "image://svgimage-custom-color/calendar.svg/" + palette.windowText - sourceSize.width: scrollContentsColumn.rowIconWidth - sourceSize.height: scrollContentsColumn.rowIconWidth - } - - NCInputDateField { - id: expireDateField - - 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); - } - - enabled: root.expireDateEnabled && - !root.waitingForExpireDateChange && - !root.waitingForExpireDateEnabledChange - - onUserAcceptedDate: { - root.setExpireDate(dateInMs); - root.waitingForExpireDateChange = true; - } - - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForExpireDateEnabledChange || - root.waitingForExpireDateChange - running: visible - z: 1 - } - } - } - - CheckBox { - id: noteEnabledMenuItem - - Layout.fillWidth: true - - spacing: scrollContentsColumn.indicatorSpacing - leftPadding: scrollContentsColumn.itemPadding - rightPadding: scrollContentsColumn.itemPadding - indicator.width: scrollContentsColumn.indicatorItemWidth - indicator.height: scrollContentsColumn.indicatorItemWidth - - checkable: true - checked: root.noteEnabled - text: qsTr("Note to recipient") - enabled: !root.waitingForNoteChange - - onClicked: { - if (!checked && root.note !== "") { - root.setNote(""); - root.waitingForNoteChange = true; - } - } - - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForNoteChange && !noteEnabledMenuItem.checked - running: visible - z: 1 - } - } - - RowLayout { - Layout.fillWidth: true - height: visible ? implicitHeight : 0 - spacing: scrollContentsColumn.indicatorSpacing - - visible: noteEnabledMenuItem.checked + footer: GridLayout { + id: buttonGrid - Image { - Layout.preferredWidth: scrollContentsColumn.indicatorItemWidth - Layout.fillHeight: true + columns: 1 + rows: 2 - verticalAlignment: Image.AlignVCenter - horizontalAlignment: Image.AlignHCenter - fillMode: Image.Pad + PrimaryPillButton { + Layout.columnSpan: buttonGrid.columns - source: "image://svgimage-custom-color/edit.svg/" + palette.windowText - sourceSize.width: scrollContentsColumn.rowIconWidth - sourceSize.height: scrollContentsColumn.rowIconWidth - } + iconSource: Style.sesLightPlus - NCInputTextArea { - id: noteTextArea + font.pixelSize: pixelSize + font.weight: fontWeight + text: qsTr("Add another link") - Layout.fillWidth: true - // no height here -- let the textarea figure it out how much it needs - submitButton.height: Math.min(Style.talkReplyTextFieldPreferredHeight, height - 2) + visible: root.isLinkShare && root.canCreateLinkShares + enabled: visible - text: root.note - placeholderText: qsTr("Enter a note for the recipient") - enabled: noteEnabledMenuItem.checked && !root.waitingForNoteChange + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + Layout.row: 0 - onEditingFinished: if (text !== "" && text !== root.note) { - root.setNote(text); - root.waitingForNoteChange = true; - } + onClicked: root.createNewLinkShare() + } - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForNoteChange && noteEnabledMenuItem.checked - running: visible - z: 1 - } - } - } + SecondaryPillButton { + id: unshareButton - Button { - height: Style.standardPrimaryButtonHeight - icon.source: "image://svgimage-custom-color/close.svg/" + palette.buttonText - icon.height: Style.extraSmallIconSize - text: qsTr("Unshare") - onClicked: root.deleteShare() - } + font.pixelSize: pixelSize + font.weight: fontWeight + text: qsTr("Unshare") - Button { - height: Style.standardPrimaryButtonHeight - icon.source: "image://svgimage-custom-color/add.svg/" + palette.buttonText - icon.height: Style.extraSmallIconSize - text: qsTr("Add another link") - visible: root.isLinkShare && root.canCreateLinkShares - enabled: visible - onClicked: root.createNewLinkShare() - } + Layout.bottomMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 60 + Layout.row: 1 + onClicked: root.deleteShare() } - } - 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 - - background: Rectangle { color: "transparent" } - - Button { + PrimaryPillButton { id: copyShareLinkButton function copyShareLink() { @@ -769,21 +739,20 @@ Page { property bool shareLinkCopied: false - height: Style.standardPrimaryButtonHeight - - Layout.preferredWidth: Style.activityListButtonWidth - Layout.preferredHeight: Style.activityListButtonHeight - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + iconSource: Style.sesLightClipboard - icon.source: "image://svgimage-custom-color/copy.svg/" + palette.brightText - icon.width: Style.smallIconSize - icon.height: Style.smallIconSize text: shareLinkCopied ? qsTr("Share link copied!") : qsTr("Copy share link") + visible: root.isLinkShare enabled: visible onClicked: copyShareLink() + Layout.alignment: Qt.AlignRight + Layout.bottomMargin: 16 + Layout.rightMargin: 20 + Layout.row: 1 + Behavior on Layout.preferredWidth { SmoothedAnimation { duration: Style.shortAnimationDuration } } diff --git a/src/gui/filedetails/ShareView.qml b/src/gui/filedetails/ShareView.qml index 1b7b9ca7de067..9ac100067a640 100644 --- a/src/gui/filedetails/ShareView.qml +++ b/src/gui/filedetails/ShareView.qml @@ -17,9 +17,10 @@ import QtQuick.Window import QtQuick.Layouts import QtQuick.Controls -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style import "../tray" +import "../SesComponents" import "../" ColumnLayout { @@ -87,6 +88,10 @@ ColumnLayout { close(); } + background: Rectangle { + color: Style.sesBackgroundColor + } + anchors.centerIn: parent width: parent.width * 0.8 @@ -123,20 +128,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; - } } RowLayout { @@ -173,6 +172,7 @@ ColumnLayout { Layout.topMargin: Style.smallSpacing Layout.leftMargin: root.horizontalPadding Layout.rightMargin: root.horizontalPadding + Layout.preferredHeight: Style.sesSearchFieldHeight visible: root.userGroupSharingPossible enabled: visible && !root.loading && !root.shareModel.isShareDisabledEncryptedFolder && !shareeSearchField.isShareeFetchOngoing @@ -194,6 +194,7 @@ ColumnLayout { Layout.fillHeight: true Layout.leftMargin: root.horizontalPadding Layout.rightMargin: root.horizontalPadding + Layout.topMargin: Style.sesMediumMargin active: root.sharingPossible @@ -215,7 +216,9 @@ ColumnLayout { sourceModel: root.shareModel } - delegate: ShareDelegate { + delegate: ColumnLayout{ + width: parent.width + ShareDelegate { id: shareDelegate Connections { @@ -245,7 +248,7 @@ ColumnLayout { fileDetails: root.fileDetails rootStackView: root.rootStackView backgroundsVisible: root.backgroundsVisible - accentColor: root.accentColor + accentColor: Style.sesIconColor canCreateLinkShares: root.publicLinkSharingPossible serverAllowsResharing: root.serverAllowsResharing @@ -269,6 +272,14 @@ ColumnLayout { onSetExpireDate: shareModel.setShareExpireDateFromQml(model.share, milliseconds) onSetPassword: shareModel.setSharePasswordFromQml(model.share, password) onSetNote: shareModel.setShareNoteFromQml(model.share, note) + width: parent.width + } + + Rectangle{ + height: Style.sesMediumMargin + color: "transparent" + width: parent.width + } } Loader { @@ -311,6 +322,7 @@ ColumnLayout { id: sharingDisabledLabel width: parent.width text: qsTr("Sharing is disabled") + color: palette.midlight wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -318,6 +330,7 @@ ColumnLayout { EnforcedPlainTextLabel { width: parent.width text: qsTr("This item cannot be shared.") + color: palette.midlight wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -326,6 +339,7 @@ ColumnLayout { EnforcedPlainTextLabel { width: parent.width text: qsTr("Sharing is disabled.") + color: palette.midlight wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter diff --git a/src/gui/filedetails/ShareeDelegate.qml b/src/gui/filedetails/ShareeDelegate.qml index b46b3bb81e78a..5f8701544395d 100644 --- a/src/gui/filedetails/ShareeDelegate.qml +++ b/src/gui/filedetails/ShareeDelegate.qml @@ -17,7 +17,7 @@ import QtQuick.Window import QtQuick.Layouts import QtQuick.Controls -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style 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 adda7a987ea9d..74aaf768f24cd 100644 --- a/src/gui/filedetails/ShareeSearchField.qml +++ b/src/gui/filedetails/ShareeSearchField.qml @@ -17,7 +17,7 @@ import QtQuick.Window import QtQuick.Layouts import QtQuick.Controls -import com.nextcloud.desktopclient +import com.ionos.hidrivenext.desktopclient import Style import "../tray" @@ -38,6 +38,7 @@ TextField { } readonly property int horizontalPaddingOffset: Style.trayHorizontalMargin + readonly property color placeholderColor: Style.sesSearchFieldContent readonly property double iconsScaleFactor: 0.6 function triggerSuggestionsVisibility() { @@ -45,9 +46,11 @@ TextField { } placeholderText: enabled ? qsTr("Search for users or groups…") : qsTr("Sharing is not available for this folder") + placeholderTextColor: placeholderColor verticalAlignment: Qt.AlignVCenter implicitHeight: Math.max(Style.talkReplyTextFieldPreferredHeight, contentHeight) + onActiveFocusChanged: triggerSuggestionsVisibility() onTextChanged: triggerSuggestionsVisibility() Keys.onPressed: { @@ -87,8 +90,15 @@ 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: Style.sesMenuBorder + border.width: 1 + + } Image { id: searchIcon @@ -107,7 +117,7 @@ TextField { fillMode: Image.PreserveAspectFit horizontalAlignment: Image.AlignLeft - source: "image://svgimage-custom-color/search.svg" + "/" + palette.placeholderText + 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 bc8f2d4895c84..5034a89c6a32e 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..cd32318912edd 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.hidrivenext.shareemodel") ShareeModel::ShareeModel(QObject *parent) : QAbstractListModel(parent) diff --git a/src/gui/filedetails/sharemodel.cpp b/src/gui/filedetails/sharemodel.cpp index 8c55a7b8c92e2..914a3b25a1ed7 100644 --- a/src/gui/filedetails/sharemodel.cpp +++ b/src/gui/filedetails/sharemodel.cpp @@ -37,7 +37,7 @@ static const auto secureFileDropPlaceholderLinkShareId = QStringLiteral("__secur namespace OCC { -Q_LOGGING_CATEGORY(lcShareModel, "com.nextcloud.sharemodel") +Q_LOGGING_CATEGORY(lcShareModel, "com.hidrivenext.sharemodel") ShareModel::ShareModel(QObject *parent) : QAbstractListModel(parent) @@ -782,7 +782,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: diff --git a/src/gui/filedetails/sortedsharemodel.cpp b/src/gui/filedetails/sortedsharemodel.cpp index 9b9ef99a644bd..0b02c7dfdbaab 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.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 24585c1e60f13..d3aee549b7d6b 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -58,7 +58,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, @@ -1408,10 +1408,10 @@ void Folder::slotNewBigFolderDiscovered(const QString &newF, bool isExternal) journal->setSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, undecidedList); emit newBigFolderDiscovered(newFolder); } - QString message = !isExternal ? (tr("A new folder larger than %1 MB has been added: %2.\n") + QString message = !isExternal ? QString(tr("A new folder larger than %1 MB has been added: %2.") + "\n") .arg(ConfigFile().newBigFolderSizeLimit().second) - .arg(newF)) - : (tr("A folder from an external storage has been added.\n")); + .arg(newF) + : (tr("A folder from an external storage has been added.") + "\n"); message += tr("Please go in the settings to select it if you wish to download it."); auto logger = Logger::instance(); @@ -1694,7 +1694,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 2d4f3c4cc4a98..2cb44c3db770e 100644 --- a/src/gui/foldercreationdialog.cpp +++ b/src/gui/foldercreationdialog.cpp @@ -13,6 +13,9 @@ */ #include "foldercreationdialog.h" + +#include "buttonstyle.h" +#include "whitelabeltheme.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,54 @@ 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); + + this->setAutoFillBackground(true); + setPalette(QPalette(QPalette::Window, WLTheme.dialogBackgroundColor())); + + 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(WLTheme.folderWizardPathColor(), + WLTheme.settingsFont(), + WLTheme.settingsTextSize(), + WLTheme.settingsTextWeight(), + WLTheme.buttonRadius(), + WLTheme.menuBorderColor(), + WLTheme.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 d671dd2b87a4e..c0521d248f40c 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -55,7 +55,7 @@ int numberOfSyncJournals(const QString &path) namespace OCC { -Q_LOGGING_CATEGORY(lcFolderMan, "nextcloud.gui.folder.manager", QtInfoMsg) +Q_LOGGING_CATEGORY(lcFolderMan, "hidrivenext.gui.folder.manager", QtInfoMsg) FolderMan *FolderMan::_instance = nullptr; @@ -1435,12 +1435,12 @@ QString FolderMan::getBackupName(QString fullPathName) const if (fullPathName.isEmpty()) return QString(); - QString newName = fullPathName + tr(" (backup)"); + QString newName = fullPathName + " " + tr("(backup)"); QFileInfo fi(newName); int cnt = 2; do { if (fi.exists()) { - newName = fullPathName + tr(" (backup %1)").arg(cnt++); + newName = fullPathName + " " + tr("(backup %1)").arg(cnt++); fi.setFile(newName); } } while (fi.exists()); diff --git a/src/gui/folderstatusdelegate.cpp b/src/gui/folderstatusdelegate.cpp index 45350b469fabb..f573d5d82635d 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(WLTheme.settingsTitleWeightDemiBold()); + aliasFont.setPixelSize(WLTheme.settingsBigTitlePixel()); + return aliasFont; +} + FolderStatusDelegate::FolderStatusDelegate() : QStyledItemDelegate() { @@ -52,15 +57,21 @@ 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, const QModelIndex &index) const { - QFont aliasFont = makeAliasFont(option.font); - QFont font = option.font; + QFont font = QFont(option.font); + font.setPixelSize(WLTheme.settingsTextPixel()); + QFont aliasFont = makeAliasFont(font); QFontMetrics fm(font); QFontMetrics aliasFm(aliasFont); @@ -71,7 +82,7 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option, 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)) + QSize(0, margins); } @@ -111,22 +122,78 @@ 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(WLTheme.settingsTitleWeightDemiBold()); + titleFont.setPixelSize(WLTheme.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() + WLTheme.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(WLTheme.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::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::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + QStyleOptionViewItem opt = option; + QFont font = opt.font; + font.setPixelSize(WLTheme.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); @@ -134,23 +201,6 @@ 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; } @@ -163,7 +213,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)); @@ -192,7 +241,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() + WLTheme.treeViewIconSize()); iconRect.setWidth(iconRect.height()); const auto nextToIcon = iconRect.right() + aliasMargin; @@ -202,8 +251,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); @@ -235,37 +282,32 @@ 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->setPen(borderColor); painter->drawRoundedRect(QStyle::visualRect(option.direction, option.rect, rect), 4, 4); - painter->setPen(Qt::white); + painter->setPen(Qt::black); painter->setFont(errorFont); QRect textRect(rect.left() + margin, rect.top() + margin, @@ -283,73 +325,124 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & }; if (!conflictTexts.isEmpty()) { - drawTextBox(conflictTexts, QColor(0xba, 0xba, 0x4d)); + drawTextBox(conflictTexts, QColor(WLTheme.warningColor()), QColor(WLTheme.warningBorderColor())); } if (!errorTexts.isEmpty()) { - drawTextBox(errorTexts, QColor(0xbb, 0x4d, 0x4d)); + drawTextBox(errorTexts, QColor(WLTheme.errorColor()), QColor(WLTheme.errorBorderColor())); } if (!infoTexts.isEmpty()) { - drawTextBox(infoTexts, QColor(0x4d, 0x4d, 0xba)); + drawTextBox(infoTexts, QColor(WLTheme.infoColor()), QColor(WLTheme.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); + + 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; + + QFont progressFont(option.font); + progressFont.setPixelSize(WLTheme.settingsTextPixel()); + progressFont.setWeight(WLTheme.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.state = QStyle::StateFlag::State_Horizontal; - progressBarOpt.rect = QStyle::visualRect(option.direction, option.rect, progressBarRect); + 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.state = QStyle::StateFlag::State_Horizontal; + progressBarOpt.rect = QStyle::visualRect(option.direction, option.rect, progressBarRect); + QPalette paletteTmp = progressBarOpt.palette; + paletteTmp.setColor(QPalette::Base, WLTheme.white()); + paletteTmp.setColor(QPalette::Highlight, WLTheme.syncProgressColor()); + progressBarOpt.palette = paletteTmp; + #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 - // itemString is e.g. Syncing fileName1, filename2 - // syncText is Synchronizing files in local folders or Synchronizing virtual files in local folder - const auto generalSyncStatus = !itemString.isEmpty() ? itemString : syncText; - QRect generalSyncStatusRect; - generalSyncStatusRect.setTop(progressBarRect.bottom() + margin); - generalSyncStatusRect.setHeight(fileNameTextHeight); - generalSyncStatusRect.setLeft(progressBarRect.left()); - generalSyncStatusRect.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->drawText(QStyle::visualRect(option.direction, option.rect, generalSyncStatusRect), Qt::AlignLeft | Qt::AlignVCenter, generalSyncStatus); + painter->setFont(progressFont); - painter->restore(); - } + painter->drawText(QStyle::visualRect(option.direction, option.rect, overallProgressRect), Qt::AlignLeft | Qt::AlignVCenter, overallString); + + // // itemString is e.g. Syncing fileName1, filename2 + // // syncText is Synchronizing files in local folders or Synchronizing virtual files in local folder + // const auto generalSyncStatus = !itemString.isEmpty() ? itemString : syncText; + // QRect generalSyncStatusRect; + // generalSyncStatusRect.setTop(progressBarRect.bottom() + margin); + // generalSyncStatusRect.setHeight(fileNameTextHeight); + // generalSyncStatusRect.setLeft(progressBarRect.left()); + // generalSyncStatusRect.setWidth(progressBarRect.width()); + // // painter->setFont(progressFont); + + // painter->drawText(QStyle::visualRect(option.direction, option.rect, generalSyncStatusRect), Qt::AlignLeft | Qt::AlignVCenter, generalSyncStatus); painter->restore(); +} + +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); +} + +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, @@ -360,12 +453,24 @@ bool FolderStatusDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, 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 +486,29 @@ bool FolderStatusDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, QRect FolderStatusDelegate::optionsButtonRect(QRect within, Qt::LayoutDirection direction) { - QFont font = QFont(); + QFont font = QFont(WLTheme.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()); - - 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()); + + // 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)); - 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(WLTheme.settingsFont()); QFont aliasFont = makeAliasFont(font); QFontMetrics fm(font); QFontMetrics aliasFm(aliasFont); @@ -426,7 +523,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 d1211d7412c71..a13a6d2527268 100644 --- a/src/gui/folderstatusmodel.cpp +++ b/src/gui/folderstatusmodel.cpp @@ -28,7 +28,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..c97ac29a888e7 100644 --- a/src/gui/folderstatusview.cpp +++ b/src/gui/folderstatusview.cpp @@ -14,11 +14,15 @@ #include "folderstatusview.h" #include "folderstatusdelegate.h" +#include "whitelabeltheme.h" namespace OCC { FolderStatusView::FolderStatusView(QWidget *parent) : QTreeView(parent) { + #ifdef Q_OS_MAC + setPalette(QPalette(QPalette::ButtonText, WLTheme.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 e8bed199bdef6..40f5af80458a0 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 9f31cf4b78b40..f72472c7c7bfa 100644 --- a/src/gui/folderwizard.cpp +++ b/src/gui/folderwizard.cpp @@ -20,7 +20,9 @@ #include "account.h" #include "selectivesyncdialog.h" #include "accountstate.h" +#include "buttonstyle.h" #include "creds/abstractcredentials.h" +#include "SesComponents/syncdirvalidation.h" #include "wizard/owncloudwizard.h" #include "common/asserts.h" @@ -30,11 +32,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -63,9 +67,9 @@ QString FormatWarningsWizardPage::formatWarnings(const QStringList &warnings) co { QString formattedWarning; if (warnings.count() == 1) { - formattedWarning = warnings.first(); + formattedWarning = QString("%1").arg(warnings.first()); } else if (warnings.count() > 1) { - formattedWarning = "