From 35482fa92f5f85deae74d6f6988bb81eb2305865 Mon Sep 17 00:00:00 2001 From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> Date: Thu, 21 Dec 2023 14:37:32 -0500 Subject: [PATCH] misc: implement frameless window Several major changes to the layout have been made. - The chat search bar is moved into the message search layout. - The Searchbar component is stripped of unused features. - Some remaining logic that was used to switch main loader components is removed. - ViewCoordinator.getView gets a "force create" parameter and we no longer preload low-cost views. NOTE: the option to use a frameless window is available within general settings Gitlab: #1524 (Frameless Window) Change-Id: Iec6bdf162cb0335d3ae3d9bd09dd9783991a4a57 --- CMakeLists.txt | 35 ++- extras/build/cmake/contrib_tools.cmake | 84 ++++++ .../docker/Dockerfile.client-qt-gnulinux | 7 +- extras/patches/0001-fix-fedora-fc-build.patch | 25 ++ .../0002-workaround-right-margin.patch | 34 +++ resources/icons/window-bar_close.svg | 15 ++ resources/icons/window-bar_fullscreen.svg | 11 + resources/icons/window-bar_maximize.svg | 12 + resources/icons/window-bar_minimize.svg | 11 + resources/icons/window-bar_restore.svg | 16 ++ src/app/LayoutManager.qml | 23 +- src/app/MainApplicationWindow.qml | 251 +++++++++++------- src/app/ViewCoordinator.qml | 62 ++++- src/app/appsettingsmanager.h | 11 +- src/app/commoncomponents/BaseView.qml | 2 + src/app/commoncomponents/QWKButton.qml | 82 ++++++ .../QWKSetParentHitTestVisible.qml | 39 +++ .../commoncomponents/QWKSystemButtonGroup.qml | 60 +++++ src/app/commoncomponents/SidePanelBase.qml | 45 +++- src/app/constant/JamiQmlUtils.qml | 6 +- src/app/constant/JamiStrings.qml | 1 + src/app/constant/JamiTheme.qml | 4 +- src/app/mainapplication.cpp | 18 +- src/app/mainapplication.h | 3 - src/app/mainview/ConversationView.qml | 4 +- src/app/mainview/MainView.qml | 5 - src/app/mainview/components/ChatView.qml | 8 +- .../mainview/components/ChatViewHeader.qml | 96 +++---- src/app/mainview/components/MainOverlay.qml | 7 +- .../components/MessagesResearchPanel.qml | 26 +- src/app/mainview/components/NewSwarmPage.qml | 4 +- src/app/mainview/components/Searchbar.qml | 52 +--- src/app/mainview/components/SidePanel.qml | 2 +- src/app/mainview/components/WelcomePage.qml | 4 +- src/app/settingsview/SettingsSidePanel.qml | 8 +- src/app/settingsview/SettingsView.qml | 7 +- .../components/AppearanceSettingsPage.qml | 9 + .../components/SettingsHeader.qml | 2 +- .../components/SettingsPageBase.qml | 2 + .../components/SystemSettingsPage.qml | 2 +- src/app/utilsadapter.cpp | 2 + src/app/utilsadapter.h | 1 + src/app/webengine/map/MapPosition.qml | 4 +- src/app/wizardview/WizardView.qml | 3 - .../components/CreateAccountPage.qml | 2 +- src/app/wizardview/components/WelcomePage.qml | 2 +- tests/qml/main.cpp | 2 +- tests/qml/resources.qrc | 14 + tests/qml/src/TestWrapper.qml | 39 +++ tests/qml/src/tst_NewSwarmPage.qml | 4 +- tests/qml/src/tst_OngoingCallPage.qml | 7 +- tests/qml/src/tst_WelcomePage.qml | 42 +-- 52 files changed, 901 insertions(+), 316 deletions(-) create mode 100644 extras/build/cmake/contrib_tools.cmake create mode 100644 extras/patches/0001-fix-fedora-fc-build.patch create mode 100644 extras/patches/0002-workaround-right-margin.patch create mode 100644 resources/icons/window-bar_close.svg create mode 100644 resources/icons/window-bar_fullscreen.svg create mode 100644 resources/icons/window-bar_maximize.svg create mode 100644 resources/icons/window-bar_minimize.svg create mode 100644 resources/icons/window-bar_restore.svg create mode 100644 src/app/commoncomponents/QWKButton.qml create mode 100644 src/app/commoncomponents/QWKSetParentHitTestVisible.qml create mode 100644 src/app/commoncomponents/QWKSystemButtonGroup.qml create mode 100644 tests/qml/src/TestWrapper.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 6436cccf9..fda15aaf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.19) if(APPLE) project(Jami) @@ -75,6 +75,37 @@ if(NOT MSVC) set(CMAKE_CXX_FLAGS_DEBUG "-Og -ggdb") endif() +include(${PROJECT_SOURCE_DIR}/extras/build/cmake/contrib_tools.cmake) +set(EXTRA_PATCHES_DIR ${PROJECT_SOURCE_DIR}/extras/patches) + +list(APPEND QWINDOWKIT_OPTIONS + QWINDOWKIT_BUILD_WIDGETS OFF + QWINDOWKIT_INSTALL OFF + QWINDOWKIT_BUILD_STATIC ON +) + +if(WIN32) + list(APPEND QWINDOWKIT_PATCHES ${EXTRA_PATCHES_DIR}/0002-workaround-right-margin.patch) + list(APPEND QWINDOWKIT_OPTIONS QWINDOWKIT_ENABLE_WINDOWS_SYSTEM_BORDERS OFF) +endif() + +# qmsetup uses the wrong package dir on Fedora at least. +check_redhat_based(IS_REDHAT_BASED) +if(IS_REDHAT_BASED) + list(APPEND QWINDOWKIT_PATCHES ${EXTRA_PATCHES_DIR}/0001-fix-fedora-fc-build.patch) + set(qmsetup_cmake_path ${CMAKE_BINARY_DIR}/_install/lib64/cmake/qmsetup) +endif() + +# qwindowkit (frameless window) +add_fetch_content( + TARGET qwindowkit + URL https://github.com/stdware/qwindowkit.git + BRANCH 79b1f3110754f9c21af2d7dacbd07b1a9dbaf6ef + PATCHES ${QWINDOWKIT_PATCHES} + OPTIONS ${QWINDOWKIT_OPTIONS} +) +list(APPEND CLIENT_INCLUDE_DIRS ${QWindowKit_BINARY_DIR}/include) +list(APPEND CLIENT_LIBS QWindowKit::Quick) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) @@ -102,7 +133,7 @@ if(QT6_VER AND QT6_PATH) find_package(QT NAMES Qt6 REQUIRED PATHS ${QT6_PATH} NO_DEFAULT_PATH) else() - message(STATUS "Looking for Qt 6" ${CMAKE_PREFIX_PATH}) + message(STATUS "Looking for Qt 6 in ${CMAKE_PREFIX_PATH}") find_package(QT NAMES Qt6 REQUIRED) endif() if (${QT_VERSION_MINOR} GREATER_EQUAL ${QT6_MINVER_MINOR}) diff --git a/extras/build/cmake/contrib_tools.cmake b/extras/build/cmake/contrib_tools.cmake new file mode 100644 index 000000000..8759c4518 --- /dev/null +++ b/extras/build/cmake/contrib_tools.cmake @@ -0,0 +1,84 @@ +# Copyright (C) 2024 Savoir-faire Linux Inc. +# +# 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 3 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +include(FetchContent) +include(CMakeParseArguments) + +# Helper function to check if the current distribution is Red Hat-based +function(check_redhat_based IS_REDHAT_BASED) + set(${IS_REDHAT_BASED} FALSE PARENT_SCOPE) + # Check for the existence of /etc/os-release + if(EXISTS "/etc/os-release") + # Read the content of the file + file(READ "/etc/os-release" OS_RELEASE_CONTENT) + # Check if the distribution is Fedora or Red Hat-based + string(REGEX MATCH "ID=fedora|ID_LIKE=\"rhel fedora\"" MATCH_RESULT "${OS_RELEASE_CONTENT}") + if(MATCH_RESULT) + set(${IS_REDHAT_BASED} TRUE PARENT_SCOPE) + message(STATUS "Running on a Red Hat-based distribution (Fedora, RHEL, CentOS, etc.)") + else() + message(STATUS "Not a Red Hat-based distribution") + endif() + else() + message(STATUS "Cannot determine the distribution type: /etc/os-release not found") + endif() +endfunction() + +# Helper function to add external content with patches and options. +# Parameters: +# TARGET: Name of the target to create +# URL: URL of the git repository +# BRANCH: Branch to checkout +# PATCHES: List of patch files to apply +# OPTIONS: List of options to set prior to calling FetchContent_MakeAvailable +function(add_fetch_content) + # Parse function arguments + set(oneValueArgs TARGET URL BRANCH) + set(multiValueArgs PATCHES OPTIONS) + cmake_parse_arguments(PARSE_ARGV 0 AFCWP "" "${oneValueArgs}" "${multiValueArgs}") + + # Create a string for the patch command + set(patch_cmd "") + # If patches is not empty, start the command with "git apply" + if(NOT "${AFCWP_PATCHES}" STREQUAL "") + set(patch_cmd git apply) + endif() + foreach(patch_file IN LISTS AFCWP_PATCHES) + list(APPEND patch_cmd "${patch_file}") + endforeach() + + # Declare the external content + FetchContent_Declare( + ${AFCWP_TARGET} + GIT_REPOSITORY ${AFCWP_URL} + GIT_TAG ${AFCWP_BRANCH} + PATCH_COMMAND ${patch_cmd} + UPDATE_DISCONNECTED 1 + ) + + # Apply options + list(LENGTH AFCWP_OPTIONS options_length) + math(EXPR max_idx "${options_length} - 1") + foreach(idx RANGE 0 ${max_idx} 2) + list(GET AFCWP_OPTIONS ${idx} key) + math(EXPR value_idx "${idx} + 1") + list(GET AFCWP_OPTIONS ${value_idx} value) + set(${key} ${value} CACHE STRING "${key}" FORCE) + endforeach() + + # Make the content available + FetchContent_MakeAvailable(${AFCWP_TARGET}) +endfunction() diff --git a/extras/build/docker/Dockerfile.client-qt-gnulinux b/extras/build/docker/Dockerfile.client-qt-gnulinux index 2e0910d59..44a86858b 100644 --- a/extras/build/docker/Dockerfile.client-qt-gnulinux +++ b/extras/build/docker/Dockerfile.client-qt-gnulinux @@ -66,4 +66,9 @@ RUN apt-get install -y -o Acquire::Retries=10 \ libssl-dev RUN apt-get install -y pandoc \ googletest \ - libgtest-dev + libgtest-dev \ + wget + +# Install a recent version of CMake +ADD extras/packaging/gnu-linux/scripts/install-cmake.sh /opt/install-cmake.sh +RUN /opt/install-cmake.sh \ No newline at end of file diff --git a/extras/patches/0001-fix-fedora-fc-build.patch b/extras/patches/0001-fix-fedora-fc-build.patch new file mode 100644 index 000000000..268069f6d --- /dev/null +++ b/extras/patches/0001-fix-fedora-fc-build.patch @@ -0,0 +1,25 @@ +From 161d28abb6784115ad71fcb6977e112e9d5756d4 Mon Sep 17 00:00:00 2001 +From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> +Date: Tue, 23 Jan 2024 15:38:34 -0500 +Subject: [PATCH] fix-fedora-fc-build + +--- + CMakeLists.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 0fb89c8..3a6ad6d 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -65,7 +65,7 @@ if(NOT TARGET qmsetup::library) + ) + + # Find package again +- find_package(qmsetup REQUIRED PATHS ${_package_path}) ++ find_package(qmsetup REQUIRED PATHS ${_package_path} ${qmsetup_cmake_path}) + + # Update import path + set(qmsetup_DIR ${_package_path} CACHE PATH "" FORCE) +-- +2.34.1 + diff --git a/extras/patches/0002-workaround-right-margin.patch b/extras/patches/0002-workaround-right-margin.patch new file mode 100644 index 000000000..4ebe48372 --- /dev/null +++ b/extras/patches/0002-workaround-right-margin.patch @@ -0,0 +1,34 @@ +From ca2be6466c150d1b82a646d97b27df35b45d90f1 Mon Sep 17 00:00:00 2001 +From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> +Date: Tue, 9 Jan 2024 15:25:19 -0500 +Subject: [PATCH] workaround right margin + +--- + src/core/contexts/win32windowcontext.cpp | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp +index 3f6623e..9ee7752 100644 +--- a/src/core/contexts/win32windowcontext.cpp ++++ b/src/core/contexts/win32windowcontext.cpp +@@ -402,6 +402,17 @@ namespace QWK { + return true; + } + } ++ ++#if !QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) ++ if (msg->message == WM_MOVE || msg->message == WM_SIZE || ++ (msg->message == WM_IME_SETCONTEXT && (GetForegroundWindow() == msg->hwnd))) { ++ static const auto flags = SWP_FRAMECHANGED | SWP_NOMOVE | ++ SWP_NOSIZE | SWP_NOZORDER | ++ SWP_NOOWNERZORDER; ++ SetWindowPos(msg->hwnd, NULL, 0, 0, 0, 0, flags); ++ } ++#endif ++ + return false; + } + +-- +2.7.4 + diff --git a/resources/icons/window-bar_close.svg b/resources/icons/window-bar_close.svg new file mode 100644 index 000000000..103d04ef4 --- /dev/null +++ b/resources/icons/window-bar_close.svg @@ -0,0 +1,15 @@ +<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="10.88" height="10.88" + viewBox="0 0 10.88 10.88"> + <defs> + <style> + .cls-1 { + fill: none; + stroke: white; + stroke-miterlimit: 10; + stroke-width: 1.25px; + } + </style> + </defs> + <line class="cls-1" x1="0.44" y1="0.44" x2="10.44" y2="10.44" /> + <line class="cls-1" x1="0.44" y1="10.44" x2="10.44" y2="0.44" /> +</svg> \ No newline at end of file diff --git a/resources/icons/window-bar_fullscreen.svg b/resources/icons/window-bar_fullscreen.svg new file mode 100644 index 000000000..fff0898c5 --- /dev/null +++ b/resources/icons/window-bar_fullscreen.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1594017175519" + class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1933" + xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"> + <defs> + <style type="text/css"></style> + </defs> + <path + d="M874.666667 128h-170.666667a21.333333 21.333333 0 0 0 0 42.666667h119.168l-176.917333 176.917333a21.333333 21.333333 0 1 0 30.165333 30.165333L853.333333 200.832V320a21.333333 21.333333 0 0 0 42.666667 0V149.333333a21.333333 21.333333 0 0 0-21.333333-21.333333zM347.584 646.250667L170.666667 823.168V704a21.333333 21.333333 0 0 0-42.666667 0v170.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h170.666667a21.333333 21.333333 0 0 0 0-42.666667H200.832l176.917333-176.917333a21.333333 21.333333 0 0 0-30.165333-30.165333zM874.666667 682.666667a21.333333 21.333333 0 0 0-21.333334 21.333333v119.168l-176.917333-176.917333a21.333333 21.333333 0 0 0-30.165333 30.165333L823.168 853.333333H704a21.333333 21.333333 0 0 0 0 42.666667h170.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-170.666667a21.333333 21.333333 0 0 0-21.333333-21.333333zM200.832 170.666667H320a21.333333 21.333333 0 0 0 0-42.666667H149.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v170.666667a21.333333 21.333333 0 0 0 42.666667 0V200.832l176.917333 176.917333a21.333333 21.333333 0 0 0 30.165333-30.165333z" + fill="#ffffff" p-id="1934"></path> +</svg> \ No newline at end of file diff --git a/resources/icons/window-bar_maximize.svg b/resources/icons/window-bar_maximize.svg new file mode 100644 index 000000000..a50c90958 --- /dev/null +++ b/resources/icons/window-bar_maximize.svg @@ -0,0 +1,12 @@ +<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10"> + <defs> + <style> + .cls-1 { + fill: none; + stroke: white; + stroke-miterlimit: 10; + } + </style> + </defs> + <rect class="cls-1" x="0.5" y="0.5" width="9" height="9" /> +</svg> \ No newline at end of file diff --git a/resources/icons/window-bar_minimize.svg b/resources/icons/window-bar_minimize.svg new file mode 100644 index 000000000..e4e4bfdd9 --- /dev/null +++ b/resources/icons/window-bar_minimize.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" + y="0px" viewBox="0 0 10 10" style="enable-background:new 0 0 10 10;" xml:space="preserve"> + <style type="text/css"> + .st0 { + fill: white; + } + </style> + <rect y="4.5" class="st0" width="10" height="1" /> +</svg> \ No newline at end of file diff --git a/resources/icons/window-bar_restore.svg b/resources/icons/window-bar_restore.svg new file mode 100644 index 000000000..bb6e245cb --- /dev/null +++ b/resources/icons/window-bar_restore.svg @@ -0,0 +1,16 @@ +<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"> + <defs> + <style> + .cls-1 { + fill: none; + stroke: white; + stroke-miterlimit: 10; + } + </style> + </defs> + <rect class="cls-1" x="0.5" y="2.5" width="9" height="9" /> + <line class="cls-1" x1="2.5" y1="2.5" x2="2.5" y2="0.5" /> + <line class="cls-1" x1="12" y1="0.5" x2="2" y2="0.5" /> + <line class="cls-1" x1="11.5" y1="10" x2="11.5" /> + <line class="cls-1" x1="10" y1="9.5" x2="12" y2="9.5" /> +</svg> \ No newline at end of file diff --git a/src/app/LayoutManager.qml b/src/app/LayoutManager.qml index a9ac2a6c0..f36b2b685 100644 --- a/src/app/LayoutManager.qml +++ b/src/app/LayoutManager.qml @@ -73,17 +73,26 @@ QtObject { function saveWindowSettings() { // If closed-to-tray or minimized or fullscreen, save the cached windowedVisibility // value instead. - if (isHidden || isFullScreen) { - AppSettingsManager.setValue(Settings.WindowState, priv.windowedVisibility) - } else { - AppSettingsManager.setValue(Settings.WindowState, visibility) - } + const visibilityToSave = isHidden || isFullScreen ? priv.windowedVisibility : visibility; // Likewise, don't save fullscreen geometry. const geometry = isFullScreen ? priv.windowedGeometry : Qt.rect(appWindow.x, appWindow.y, - appWindow.width, appWindow.height) + appWindow.width, appWindow.height); + + // QWK: Account for the frameless window's offset. + if (appWindow.useFrameless) { + if (Qt.platform.os.toString() !== "osx") { + // Add [7, 30, 0, 0] on Windows and GNU/Linux. + geometry.x += 7; + geometry.y += 30; + } + } + + console.debug("Saving window: " + JSON.stringify(geometry) + " " + visibilityToSave); + + AppSettingsManager.setValue(Settings.WindowState, visibilityToSave) AppSettingsManager.setValue(Settings.WindowGeometry, geometry) } @@ -111,6 +120,8 @@ QtObject { const visibilityStr = AppSettingsManager.getValue(Settings.WindowState) var visibilitySetting = parseInt(visibilityStr) + console.debug("Restoring window: " + JSON.stringify(geometry) + " " + visibilitySetting) + // We should never restore a hidden or fullscreen state here. Default to normal // windowed state in such a case. This shouldn't happen. if (visibilitySetting === Window.Hidden || visibilitySetting === Window.FullScreen) { diff --git a/src/app/MainApplicationWindow.qml b/src/app/MainApplicationWindow.qml index 06d38ce4f..31b9dee72 100644 --- a/src/app/MainApplicationWindow.qml +++ b/src/app/MainApplicationWindow.qml @@ -24,29 +24,30 @@ import QtQuick.Window import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects + import net.jami.Models 1.1 import net.jami.Adapters 1.1 import net.jami.Enums 1.1 import net.jami.Helpers 1.1 import net.jami.Constants 1.1 + import "mainview" import "mainview/components" import "wizardview" import "commoncomponents" +import QWindowKit + ApplicationWindow { - id: root + id: appWindow property bool isRTL: UtilsAdapter.isRTL LayoutMirroring.enabled: isRTL LayoutMirroring.childrenInherit: isRTL - enum LoadedSource { - MainView, - AccountMigrationView, - None - } + // This needs to be set from the start. + readonly property bool useFrameless: UtilsAdapter.getAppValue(Settings.Key.UseFramelessWindow) onActiveFocusItemChanged: { focusOverlay.margin = -5; @@ -70,7 +71,7 @@ ApplicationWindow { sourceComponent: GenericErrorsRow { id: genericError text: CurrentAccount.enabled ? JamiStrings.noNetworkConnectivity : JamiStrings.disabledAccount - height: visible? JamiTheme.chatViewHeaderPreferredHeight : 0 + height: visible? JamiTheme.qwkTitleBarHeight : 0 } } @@ -88,36 +89,30 @@ ApplicationWindow { border.color: JamiTheme.tintedBlue } - property ApplicationWindow appWindow: root - property LayoutManager layoutManager: LayoutManager { - appContainer: appContainer + // Used to manage full screen mode and save/restore window geometry. + LayoutManager { + id: layoutManager + appContainer: fullscreenContainer } - property ViewManager viewManager: ViewManager { - } - property ViewCoordinator viewCoordinator: ViewCoordinator { - viewManager: root.viewManager - } - - property bool windowSettingsLoaded: false - property bool allowVisibleWindow: true - function checkLoadedSource() { - var sourceString = mainApplicationLoader.source.toString(); - if (sourceString === JamiQmlUtils.mainViewLoadPath) - return MainApplicationWindow.LoadedSource.MainView; - return MainApplicationWindow.LoadedSource.None; + // Used to manage dynamic view loading and unloading. + ViewManager { + id: viewManager } - function startClient() { - setMainLoaderSource(JamiQmlUtils.mainViewLoadPath); + // Used to manage the view stack and the current view. + ViewCoordinator { + id: viewCoordinator } - function setMainLoaderSource(source) { - if (checkLoadedSource() === MainApplicationWindow.LoadedSource.MainView) { - cleanupMainView(); - } - mainApplicationLoader.setSource(source); - } + // Used to prevent the window from being visible until the + // window geometry has been restored and the view stack has + // been loaded. + property bool windowSettingsLoaded: false + + // This setting can be used to block a loading Jami instance + // from showNormal() and showMaximized() when starting minimized. + property bool allowVisibleWindow: true function cleanupMainView() { // Save the main view window size if loading anything else. @@ -139,88 +134,148 @@ ApplicationWindow { title: JamiStrings.appTitle - visible: mainApplicationLoader.status === Loader.Ready && windowSettingsLoaded && allowVisibleWindow - - // To facilitate reparenting of the callview during - // fullscreen mode, we need QQuickItem based object. - Item { - id: appContainer + visible: mainViewLoader.status === Loader.Ready && windowSettingsLoaded && allowVisibleWindow - anchors.fill: parent - } + Connections { + id: connectionMigrationEnded - Loader { - id: mainApplicationLoader + target: CurrentAccountToMigrate - anchors.fill: parent - z: -1 + function onAccountNeedsMigration(accountId) { + viewCoordinator.present("AccountMigrationView"); + } - asynchronous: true - visible: status == Loader.Ready + function onAllMigrationsFinished() { + viewCoordinator.dismiss("AccountMigrationView"); + viewCoordinator.present("WelcomePage"); + } + } - Connections { - id: connectionMigrationEnded + function initMainView(view) { + console.info("Initializing main view"); + + // Main window, load any valid app settings, and allow the + // layoutManager to handle as much as possible. + layoutManager.restoreWindowSettings(); + + // QWK: setup + if (useFrameless) { + windowAgent.setTitleBar(titleBar); + // Now register the system buttons (non-macOS). + if (sysBtnsLoader.item) { + const sysBtns = sysBtnsLoader.item; + windowAgent.setSystemButton(WindowAgent.Minimize, sysBtns.minButton); + windowAgent.setSystemButton(WindowAgent.Maximize, sysBtns.maxButton); + windowAgent.setSystemButton(WindowAgent.Close, sysBtns.closeButton); + } + } - target: CurrentAccountToMigrate + // Set the viewCoordinator's root item. + viewCoordinator.init(view); - function onAccountNeedsMigration(accountId) { + // Navigate to something. + if (UtilsAdapter.getAccountListSize() > 0) { + // Already have an account. + if (CurrentAccountToMigrate.accountToMigrateListSize > 0) + // Do we need to migrate any accounts? viewCoordinator.present("AccountMigrationView"); - } + else + // Okay now just start the client normally. + viewCoordinator.present("WelcomePage"); + } else { + // No account, so start the wizard. + viewCoordinator.present("WizardView"); + } + + // Set up the event filter for macOS. + if (Qt.platform.os.toString() === "osx") { + MainApplication.setEventFilter(); + } - function onAllMigrationsFinished() { - viewCoordinator.dismiss("AccountMigrationView"); - startClient(); + // Quiet check for updates on start if set to. + if (Qt.platform.os.toString() === "windows") { + if (UtilsAdapter.getAppValue(Settings.AutoUpdate)) { + AppVersionManager.checkForUpdates(true); + AppVersionManager.setAutoUpdateCheck(true); } } - // Set `visible = false` when loading a new QML file. - onSourceChanged: windowSettingsLoaded = false + // Handle a start URI if set as start option. + MainApplication.handleUriAction(); - onLoaded: { - if (UtilsAdapter.getAccountListSize() === 0) { - layoutManager.restoreWindowSettings(); - if (!viewCoordinator.rootView) - // Set the viewCoordinator's root item. - viewCoordinator.init(item); - viewCoordinator.present("WizardView"); - } else { - // Main window, load any valid app settings, and allow the - // layoutManager to handle as much as possible. - layoutManager.restoreWindowSettings(); - - // Present the welcome view once the viewCoordinator is setup. - viewCoordinator.initialized.connect(function () { - viewCoordinator.preload("SidePanel"); - viewCoordinator.preload("SettingsSidePanel"); - viewCoordinator.present("WelcomePage"); - viewCoordinator.preload("ConversationView"); - }); - if (!viewCoordinator.rootView) - // Set the viewCoordinator's root item. - viewCoordinator.init(item); - if (CurrentAccountToMigrate.accountToMigrateListSize > 0) - viewCoordinator.present("AccountMigrationView"); - } - if (Qt.platform.os.toString() === "osx") { - MainApplication.setEventFilter(); - } + // This will allow visible to become true if not starting minimized. + windowSettingsLoaded = true; + } + + Component.onCompleted: { + // QWK: setup + if (useFrameless) { + windowAgent.setup(appWindow); + } - // This will trigger `visible = true`. - windowSettingsLoaded = true; + mainViewLoader.active = true; - // Quiet check for updates on start if set to. - if (Qt.platform.os.toString() === "windows") { - if (UtilsAdapter.getAppValue(Settings.AutoUpdate)) { - AppVersionManager.checkForUpdates(true); - AppVersionManager.setAutoUpdateCheck(true); - } - } + // Dbus error handler for Linux. + if (Qt.platform.os.toString() !== "windows" && Qt.platform.os.toString() !== "osx") + DBusErrorHandler.setActive(true); + } + + Loader { + id: mainViewLoader + active: false + source: "qrc:/mainview/MainView.qml" + anchors.fill: parent + onLoaded: initMainView(item) + } - // Handle a start URI if set as start option. - MainApplication.handleUriAction(); + // Use this as a parent for fullscreen items. + Item { + id: fullscreenContainer + anchors.fill: parent + } + + // QWK: Provide spacing for widgets that may be occluded by the system buttons. + QtObject { + id: qwkSystemButtonSpacing + readonly property bool isMacOS: Qt.platform.os.toString() === "osx" + readonly property bool isFullscreen: layoutManager.isFullScreen + // macOS buttons are on the left. + readonly property real left: useFrameless && isMacOS && viewCoordinator.isInSinglePaneMode ? 80 : 0 + // Windows and Linux buttons are on the right. + readonly property real right: useFrameless && !isMacOS && !isFullscreen ? sysBtnsLoader.width + 24 : 0 + } + + // QWK: Window Title bar + Item { + id: titleBar + height: JamiTheme.qwkTitleBarHeight + anchors { + top: parent.top + right: parent.right + left: parent.left + } + + // On Windows and Linux, use custom system buttons. + Loader { + id: sysBtnsLoader + active: Qt.platform.os.toString() !== "osx" && useFrameless + height: titleBar.height + anchors { + top: parent.top + right: parent.right + // Note: leave these margins, they prevent image scaling artifacts + topMargin: 1 + rightMargin: 1 + } + source: "qrc:/commoncomponents/QWKSystemButtonGroup.qml" } } + // QWK: Main interop component. + WindowAgent { + id: windowAgent + } + Connections { target: LRCInstance @@ -339,11 +394,5 @@ ApplicationWindow { } } - onClosing: root.close() - - Component.onCompleted: { - startClient(); - if (Qt.platform.os.toString() !== "windows" && Qt.platform.os.toString() !== "osx") - DBusErrorHandler.setActive(true); - } + onClosing: appWindow.close() } diff --git a/src/app/ViewCoordinator.qml b/src/app/ViewCoordinator.qml index 1e0bcdb20..4bd71e751 100644 --- a/src/app/ViewCoordinator.qml +++ b/src/app/ViewCoordinator.qml @@ -24,14 +24,6 @@ import "commoncomponents" QtObject { id: root - required property QtObject viewManager - - signal initialized - - function requestAppWindowWizardView() { - viewCoordinator.present("WizardView"); - } - // A map of view names to file paths for QML files that define each view. property variant resources: { "SidePanel": "mainview/components/SidePanel.qml", @@ -47,12 +39,44 @@ QtObject { // The `main` view of the application window. property StackView rootView - property var currentViewName: rootView && rootView.currentItem && rootView.currentItem.objectName || null + readonly property Item currentView: rootView && rootView.currentItem || null + readonly property var currentViewName: currentView && currentView.objectName || null + readonly property bool isDualPane: currentView && currentView instanceof DualPaneView + readonly property bool isInSinglePaneMode: !isDualPane || currentView.isSinglePane + readonly property bool isRTL: Qt.application.layoutDirection === Qt.RightToLeft + // A list of the current visible views. This could be a single view or two views in + // dual pane mode. The list is ordered [minor, major] where major is the view on the + // right side when not in RTL and should represent the main or content-type view. + readonly property var visibleViews: { + if (!currentView) + return [] + if (isDualPane) { + if (isInSinglePaneMode) + return [currentView.rightPaneItem] + return [currentView.leftPaneItem, currentView.rightPaneItem] + } + return [currentView] + } + // Aggregate this info and expose it as a single string for convenience. + // JSON indented by 2 spaces. + readonly property string currentViewInfo: { + var info = { + currentViewName: currentViewName, + isDualPane: isDualPane, + isInSinglePaneMode: isInSinglePaneMode, + visibleViews: visibleViews.map(function(view) { + return view && view.objectName || null; + }), + visibleViewWidths: visibleViews.map(function(view) { + return view && view.width || null; + }), + }; + return JSON.stringify(info, null, 2); + } function init(mainStackView) { rootView = Qt.createQmlObject(`import QtQuick; import QtQuick.Controls StackView { anchors.fill: parent }`, mainStackView); - initialized(); } function deinit() { @@ -171,6 +195,8 @@ QtObject { var objectName = view ? view.objectName : obj.objectName; if (!viewManager.destroyView(resources[objectName])) { print("could not destroy view:", objectName); + } else { + print("destroyed view:", objectName); } } else view.dismissed(); @@ -197,8 +223,20 @@ QtObject { } } - function getView(viewName) { - return viewManager.getView(viewName); + function getView(viewName, forceCreate = false) { + // If the view is already loaded, return it. + var view = viewManager.getView(viewName); + if (view) + return view; + if (!forceCreate) + return null; + // Otherwise, create it. + view = viewManager.createView(resources[viewName], null); + if (!view) { + console.log("Failed to load view: " + viewName); + return null; + } + return view; } // Load a view without presenting it. diff --git a/src/app/appsettingsmanager.h b/src/app/appsettingsmanager.h index ac2f57974..a31fd941d 100644 --- a/src/app/appsettingsmanager.h +++ b/src/app/appsettingsmanager.h @@ -33,6 +33,14 @@ extern const QString defaultDownloadPath; // clang-format off + +// Define USE_FRAMELESS_WINDOW_DEFAULT based on the platform +#ifdef Q_OS_LINUX +#define USE_FRAMELESS_WINDOW_DEFAULT false +#else +#define USE_FRAMELESS_WINDOW_DEFAULT true +#endif + // Common key-value pairs for both APPSTORE and non-APPSTORE builds #define COMMON_KEYS \ X(MinimizeOnClose, false) \ @@ -66,7 +74,8 @@ extern const QString defaultDownloadPath; X(ChatViewEnterIsNewLine, false) \ X(ShowSendOption, false) \ X(EnablePtt, false) \ - X(PttKeys, 32) + X(PttKeys, 32) \ + X(UseFramelessWindow, USE_FRAMELESS_WINDOW_DEFAULT) #ifdef APPSTORE #define KEYS COMMON_KEYS #else diff --git a/src/app/commoncomponents/BaseView.qml b/src/app/commoncomponents/BaseView.qml index e0ddaac84..86f74e457 100644 --- a/src/app/commoncomponents/BaseView.qml +++ b/src/app/commoncomponents/BaseView.qml @@ -35,10 +35,12 @@ Rectangle { signal dismissed Component.onCompleted: { + console.debug("Created", objectName); if (managed) presented(); } Component.onDestruction: { + console.debug("Destroyed", objectName); if (managed) dismissed(); } diff --git a/src/app/commoncomponents/QWKButton.qml b/src/app/commoncomponents/QWKButton.qml new file mode 100644 index 000000000..4b8c2e9ef --- /dev/null +++ b/src/app/commoncomponents/QWKButton.qml @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2024 Savoir-faire Linux Inc. + * + * 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 3 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import QtQuick +import QtQuick.Controls +import Qt5Compat.GraphicalEffects + +import net.jami.Adapters 1.1 +import net.jami.Constants 1.1 + +Button { + id: control + + width: height * 0.9156626506024096 + leftInset: 0 + topInset: 0 + rightInset: 0 + bottomInset: 0 + padding: 0 + + function calculateLuminance(color) { + return 0.299 * color.r + 0.587 * color.g + 0.114 * color.b; + } + + property alias source: image.source + contentItem: Item { + Image { + id: image + anchors.centerIn: parent + mipmap: true + width: 12 + height: 12 + layer.enabled: true + layer.effect: ColorOverlay { + color: { + // We may force this color to be white, when the background is dark. + var backgroundIsDark = calculateLuminance(control.background.color) > 0.25; + // This includes when we are in a call (which has a dark background). + backgroundIsDark = backgroundIsDark || CurrentConversation.hasCall; + return backgroundIsDark ? "white" : JamiTheme.primaryForegroundColor; + } + } + } + } + + property color baseColor: { + // Avoid transparent if the background is dark i.e. in a call. + if (CurrentConversation.hasCall) + return Qt.rgba(1, 1, 1, 0.5); + return JamiTheme.darkTheme ? Qt.rgba(1, 1, 1, 0.15) : Qt.rgba(0, 0, 0, 0.15); + } + readonly property color pressedColor: { + const darker = Qt.darker(baseColor, 1.3); + return Qt.rgba(darker.r, darker.g, darker.b, baseColor.a * 1.3); + } + background: Rectangle { + color: { + if (!control.enabled) + return "gray"; + if (control.pressed) + return control.pressedColor; + if (control.hovered) + return control.baseColor; + return "transparent"; + } + Behavior on color { ColorAnimation { duration: 100 } } + } +} diff --git a/src/app/commoncomponents/QWKSetParentHitTestVisible.qml b/src/app/commoncomponents/QWKSetParentHitTestVisible.qml new file mode 100644 index 000000000..e8ce46833 --- /dev/null +++ b/src/app/commoncomponents/QWKSetParentHitTestVisible.qml @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 Savoir-faire Linux Inc. + * + * 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 3 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import QtQuick + +Item { + // Wait for it's parent to be created, then set it to be hit test visible. + // This avoids having to edit Component.onCompleted of the parent. + // Note: this is experimental. TBD if this is a good way to do this. + // This technique makes it clear and simple to implement, but may have + // side effects beyond just adding a dummy item component. + // Best alternatives: + // - Wrap the parent in a custom component that is hit test visible. + // - Edit the parent's Component.onCompleted to set it to be hit test visible. + Component.onCompleted: Qt.callLater(function() { + if (appWindow && appWindow.useFrameless) + windowAgent.setHitTestVisible(parent, true); + }); + + // Likewise, wait for it's parent to be destroyed, then set it to be hit test invisible. + Component.onDestruction: { + if (appWindow && appWindow.useFrameless) + windowAgent.setHitTestVisible(parent, false); + } +} diff --git a/src/app/commoncomponents/QWKSystemButtonGroup.qml b/src/app/commoncomponents/QWKSystemButtonGroup.qml new file mode 100644 index 000000000..e24267501 --- /dev/null +++ b/src/app/commoncomponents/QWKSystemButtonGroup.qml @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Savoir-faire Linux Inc. + * + * 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 3 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import QtQuick +import QtQuick.Controls + +import net.jami.Constants 1.1 + +import QWindowKit + +Row { + id: root + + property alias minButton: minButton + property alias maxButton: maxButton + property alias closeButton: closeButton + + component SystemButton : QWKButton { + height: parent.height + } + + visible: appWindow.visibility !== Window.FullScreen + + SystemButton { + id: minButton + source: JamiResources.window_bar_minimize_svg + onClicked: appWindow.showMinimized() + } + + SystemButton { + id: maxButton + source: appWindow.visibility === Window.Maximized ? + JamiResources.window_bar_restore_svg : + JamiResources.window_bar_maximize_svg + onClicked: appWindow.visibility === Window.Maximized ? + appWindow.showNormal() : + appWindow.showMaximized() + } + + SystemButton { + id: closeButton + source: JamiResources.window_bar_close_svg + baseColor: "#e81123" + onClicked: appWindow.close() + } +} diff --git a/src/app/commoncomponents/SidePanelBase.qml b/src/app/commoncomponents/SidePanelBase.qml index 353c4c762..0ad6da949 100644 --- a/src/app/commoncomponents/SidePanelBase.qml +++ b/src/app/commoncomponents/SidePanelBase.qml @@ -1,10 +1,53 @@ +/* + * Copyright (C) 2022-2024 Savoir-faire Linux Inc. + * + * 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 3 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + import QtQuick +import QtQuick.Controls -Rectangle { +Page { id: root anchors.fill: parent + property color color: "transparent" + + // QWK: Title bar spacing for macOS and single pane mode. + // Not using topMargin here on purpose, to make is simple to + // keep the theme coloring without wrapping components that + // derive from SidePanelBase. + header: Rectangle { + id: titleBarSpacer + height: { + if (!appWindow.useFrameless) + return 0; + var extraHeight = 0; + if (Qt.platform.os.toString() === "osx") + extraHeight = 24; + else if (viewCoordinator.isInSinglePaneMode) + extraHeight = titleBar.height; + return extraHeight; + } + color: root.color + } + + background: Rectangle { + color: root.color + } + // Override these if needed. property var select: function () {} property var deselect: function () {} diff --git a/src/app/constant/JamiQmlUtils.qml b/src/app/constant/JamiQmlUtils.qml index 28f16f8bb..eb272fb93 100644 --- a/src/app/constant/JamiQmlUtils.qml +++ b/src/app/constant/JamiQmlUtils.qml @@ -24,9 +24,6 @@ import net.jami.Enums 1.1 Item { property string qmlFilePrefix: "file:/" - - readonly property string mainViewLoadPath: "qrc:/mainview/MainView.qml" - readonly property string wizardViewLoadPath: "qrc:/wizardview/WizardView.qml" readonly property string base64StringTitle: "data:image/png;base64," property var accountCreationInputParaObject: ({}) @@ -81,8 +78,7 @@ Item { function onDonationCampaignSettingsChanged() { // Changing any of the donation campaign settings will trigger a recompute // of the banner visibility. - updateIsDonationBannerVisible(); - } + updateIsDonationBannerVisible(); } } function updateIsDonationBannerVisible() { diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml index b72253e07..611564a22 100644 --- a/src/app/constant/JamiStrings.qml +++ b/src/app/constant/JamiStrings.qml @@ -477,6 +477,7 @@ Item { property string enableNotifications: qsTr("Enable notifications") property string showNotifications: qsTr("Show notifications") property string keepMinimized: qsTr("Minimize on close") + property string useNativeWindowFrame: qsTr("Use native window frame (requires restart)") property string tipRunStartup: qsTr("Run at system startup") property string runStartup: qsTr("Launch at startup") property string downloadFolder: qsTr("Choose download directory") diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml index 05a6c9521..e976bb7de 100644 --- a/src/app/constant/JamiTheme.qml +++ b/src/app/constant/JamiTheme.qml @@ -474,7 +474,6 @@ Item { // MessageWebView property real chatViewHairLineSize: 1 property real chatViewMaximumWidth: 900 - property real chatViewHeaderPreferredHeight: 50 property real chatViewFooterPreferredHeight: 35 property real chatViewFooterMaximumHeight: 315 property real chatViewFooterRowSpacing: 4 @@ -710,4 +709,7 @@ Item { property color chatSettingButtonBackgroundColor: darkTheme ? "#303030" : "#F0EFEF" property color chatSettingButtonBorderColor: darkTheme ? "#03B9E9" : "#005699" property color chatSettingButtonTextColor: textColor + + // QWK + property real qwkTitleBarHeight: 50 } diff --git a/src/app/mainapplication.cpp b/src/app/mainapplication.cpp index c118d1a3e..144bfbc62 100644 --- a/src/app/mainapplication.cpp +++ b/src/app/mainapplication.cpp @@ -27,6 +27,8 @@ #include "systemtray.h" #include "videoprovider.h" +#include <QWKQuick/qwkquickglobal.h> + #include <QAction> #include <QCommandLineParser> #include <QCoreApplication> @@ -138,7 +140,6 @@ ScreenInfo::onPhysicalDotsPerInchChanged() MainApplication::MainApplication(int& argc, char** argv) : QApplication(argc, argv) - , isCleanupped(false) { const char* qtVersion = qVersion(); if (strncmp(qtVersion, QT_VERSION_STR, strnlen(qtVersion, sizeof qtVersion)) != 0) { @@ -166,8 +167,6 @@ MainApplication::MainApplication(int& argc, char** argv) // the logging features. qInstallMessageHandler(messageHandler); - QObject::connect(this, &QApplication::aboutToQuit, this, &MainApplication::cleanup); - qCInfo(app_) << "Using Qt runtime version:" << qtVersion; } @@ -184,6 +183,8 @@ MainApplication::init() // performing unnecessary tasks, like initializing the webengine. engine_.reset(new QQmlApplicationEngine(this)); + QWK::registerTypes(engine_.get()); + connectivityMonitor_ = new ConnectivityMonitor(this); settingsManager_ = new AppSettingsManager(this); systemTray_ = new SystemTray(settingsManager_, this); @@ -466,17 +467,6 @@ MainApplication::initSystray() systemTray_->show(); } -void -MainApplication::cleanup() -{ - // In Qt 6.5, QApplication::exit(0) will signal aboutToQuit, and aboutToQuit is connected to cleanup - // TODO: delete cleanup. - if (!isCleanupped) { - isCleanupped = true; - QApplication::exit(0); - } -} - void MainApplication::setEventFilter() { diff --git a/src/app/mainapplication.h b/src/app/mainapplication.h index 79f218aed..5c039e882 100644 --- a/src/app/mainapplication.h +++ b/src/app/mainapplication.h @@ -108,7 +108,6 @@ private: void setApplicationFont(); void initQmlLayer(); void initSystray(); - void cleanup(); private: std::map<Option, QVariant> runOptions_; @@ -123,6 +122,4 @@ private: AppSettingsManager* settingsManager_; ScreenInfo screenInfo_; - - bool isCleanupped; }; diff --git a/src/app/mainview/ConversationView.qml b/src/app/mainview/ConversationView.qml index 7d96ce6b2..9b8b7f0da 100644 --- a/src/app/mainview/ConversationView.qml +++ b/src/app/mainview/ConversationView.qml @@ -45,9 +45,11 @@ ListSelectionView { color: JamiTheme.transparentColor - leftPaneItem: viewCoordinator.getView("SidePanel") + leftPaneItem: viewCoordinator.getView("SidePanel", true) rightPaneItem: StackLayout { + objectName: "ConversationLayout" + currentIndex: !CurrentConversation.hasCall ? 0 : 1 onCurrentIndexChanged: chatView.parent = currentIndex === 1 ? callStackView.chatViewContainer : chatViewContainer diff --git a/src/app/mainview/MainView.qml b/src/app/mainview/MainView.qml index 93ef1380c..ed71fe399 100644 --- a/src/app/mainview/MainView.qml +++ b/src/app/mainview/MainView.qml @@ -36,13 +36,8 @@ import "js/keyboardshortcuttablecreation.js" as KeyboardShortcutTableCreation Rectangle { id: mainView - objectName: "mainView" - // To calculate tab bar bottom border hidden rect left margin. - property int tabBarLeftMargin: 8 - property int tabButtonShrinkSize: 8 - property string currentConvId: CurrentConversation.id onCurrentConvIdChanged: { if (currentConvId !== '') { diff --git a/src/app/mainview/components/ChatView.qml b/src/app/mainview/components/ChatView.qml index eff5ab59e..6e7860488 100644 --- a/src/app/mainview/components/ChatView.qml +++ b/src/app/mainview/components/ChatView.qml @@ -115,8 +115,8 @@ Rectangle { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight - Layout.maximumHeight: JamiTheme.chatViewHeaderPreferredHeight + Layout.preferredHeight: JamiTheme.qwkTitleBarHeight + Layout.maximumHeight: JamiTheme.qwkTitleBarHeight Layout.minimumWidth: JamiTheme.mainViewPaneMinWidth DropArea { @@ -180,14 +180,14 @@ Rectangle { ConversationErrorsRow { id: errorRect Layout.fillWidth: true - Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight + Layout.preferredHeight: JamiTheme.qwkTitleBarHeight visible: false } NotificationArea { id: notificationArea Layout.fillWidth: true - Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight + Layout.preferredHeight: JamiTheme.qwkTitleBarHeight visible: CurrentConversation.activeCalls.length > 0 && !root.inCallView } diff --git a/src/app/mainview/components/ChatViewHeader.qml b/src/app/mainview/components/ChatViewHeader.qml index b2b923203..d33ab24cc 100644 --- a/src/app/mainview/components/ChatViewHeader.qml +++ b/src/app/mainview/components/ChatViewHeader.qml @@ -46,7 +46,7 @@ Rectangle { } } - property bool interactionButtonsVisibility: { + readonly property bool interactionButtonsVisibility: { if (CurrentConversation.inCall) return false; if (LRCInstance.currentAccountType === Profile.Type.SIP) @@ -74,16 +74,17 @@ Rectangle { id: messagingHeaderRectRowLayout anchors.fill: parent - anchors.rightMargin: 8 + // QWK: spacing + anchors.leftMargin: qwkSystemButtonSpacing.left + anchors.rightMargin: 10 + qwkSystemButtonSpacing.right spacing: 16 - JamiPushButton { + JamiPushButton { QWKSetParentHitTestVisible {} id: backToWelcomeViewButton Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.leftMargin: 8 - //preferredSize: 24 mirror: UtilsAdapter.isRTL source: JamiResources.back_24dp_svg @@ -106,10 +107,11 @@ Rectangle { color: JamiTheme.transparentColor - ColumnLayout { + ColumnLayout { QWKSetParentHitTestVisible {} id: userNameOrIdColumnLayout + objectName: "userNameOrIdColumnLayout" - anchors.fill: parent + height: parent.height spacing: 0 @@ -144,63 +146,30 @@ Rectangle { } } - Searchbar { - id: rowSearchBar - - reductionEnabled: true - - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - Layout.preferredHeight: 30 - Layout.preferredWidth: 40 + (isOpen ? JamiTheme.searchbarSize : 0) - colorSearchBar: JamiTheme.backgroundColor - - hoverButtonRadius: JamiTheme.chatViewHeaderButtonRadius - - Behavior on Layout.preferredWidth { - NumberAnimation { - duration: 150 - } - } - - visible: root.swarmDetailsVisibility - - onSearchBarTextChanged: function (text) { - MessagesAdapter.searchbarPrompt = text; - } - - onSearchClicked: extrasPanel.switchToPanel(ChatView.MessagesResearchPanel) - - Shortcut { - sequence: "Ctrl+Shift+F" - context: Qt.ApplicationShortcut - enabled: rowSearchBar.visible - onActivated: { - rowSearchBar.openSearchBar(); - } - } - } - - JamiPushButton { + JamiPushButton { QWKSetParentHitTestVisible {} id: startAAudioCallButton - visible: interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm)) + visible: interactionButtonsVisibility && + (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm)) source: JamiResources.place_audiocall_24dp_svg toolTipText: JamiStrings.placeAudioCall onClicked: CallAdapter.placeAudioOnlyCall() } - JamiPushButton { + JamiPushButton { QWKSetParentHitTestVisible {} id: startAVideoCallButton - visible: CurrentAccount.videoEnabled_Video && interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm)) + visible: interactionButtonsVisibility && + CurrentAccount.videoEnabled_Video && + (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm)) source: JamiResources.videocam_24dp_svg toolTipText: JamiStrings.placeVideoCall onClicked: CallAdapter.placeCall() } - JamiPushButton { + JamiPushButton { QWKSetParentHitTestVisible {} id: addParticipantsButton checkable: true @@ -212,7 +181,7 @@ Rectangle { onClicked: extrasPanel.switchToPanel(ChatView.AddMemberPanel) } - JamiPushButton { + JamiPushButton { QWKSetParentHitTestVisible {} id: selectPluginButton visible: PluginAdapter.chatHandlersListCount && interactionButtonsVisibility @@ -222,7 +191,7 @@ Rectangle { onClicked: pluginSelector() } - JamiPushButton { + JamiPushButton { QWKSetParentHitTestVisible {} id: sendContactRequestButton objectName: "sendContactRequestButton" @@ -230,16 +199,39 @@ Rectangle { source: JamiResources.add_people_24dp_svg toolTipText: JamiStrings.addToConversations - onClicked: CurrentConversation.isBanned ? MessagesAdapter.unbanConversation(CurrentConversation.id) : MessagesAdapter.sendConversationRequest() + onClicked: CurrentConversation.isBanned ? + MessagesAdapter.unbanConversation(CurrentConversation.id) : + MessagesAdapter.sendConversationRequest() + } + + JamiPushButton { QWKSetParentHitTestVisible {} + id: searchMessagesButton + objectName: "searchMessagesButton" + + checkable: true + checked: extrasPanel.isOpen(ChatView.MessagesResearchPanel) + visible: root.swarmDetailsVisibility + source: JamiResources.ic_baseline_search_24dp_svg + toolTipText: JamiStrings.search + + onClicked: extrasPanel.switchToPanel(ChatView.MessagesResearchPanel) + + Shortcut { + sequence: "Ctrl+Shift+F" + context: Qt.ApplicationShortcut + enabled: parent.visible + onActivated: extrasPanel.switchToPanel(ChatView.MessagesResearchPanel) + } } - JamiPushButton { + JamiPushButton { QWKSetParentHitTestVisible {} id: detailsButton objectName: "detailsButton" checkable: true checked: extrasPanel.isOpen(ChatView.SwarmDetailsPanel) - visible: interactionButtonsVisibility && (swarmDetailsVisibility || LRCInstance.currentAccountType === Profile.Type.SIP) // TODO if SIP not a request + visible: interactionButtonsVisibility && + (swarmDetailsVisibility || LRCInstance.currentAccountType === Profile.Type.SIP) // TODO if SIP not a request source: JamiResources.swarm_details_panel_svg toolTipText: JamiStrings.details diff --git a/src/app/mainview/components/MainOverlay.qml b/src/app/mainview/components/MainOverlay.qml index 07545b99e..aabf85e54 100644 --- a/src/app/mainview/components/MainOverlay.qml +++ b/src/app/mainview/components/MainOverlay.qml @@ -127,9 +127,12 @@ Item { id: overlayUpperPartRect anchors.top: parent.top - - width: parent.width height: 50 + anchors.left: parent.left + anchors.right: parent.right + // QWK: spacing + anchors.leftMargin: qwkSystemButtonSpacing.left + anchors.rightMargin: qwkSystemButtonSpacing.right RowLayout { anchors.fill: parent diff --git a/src/app/mainview/components/MessagesResearchPanel.qml b/src/app/mainview/components/MessagesResearchPanel.qml index 5bb556e25..cbe1195e9 100644 --- a/src/app/mainview/components/MessagesResearchPanel.qml +++ b/src/app/mainview/components/MessagesResearchPanel.qml @@ -26,13 +26,33 @@ import net.jami.Constants 1.1 import "../../commoncomponents" import "../../settingsview/components" -Rectangle { +Page { id: root - color: JamiTheme.chatviewBgColor + header: Item { + height: 45 + Searchbar { + onVisibleChanged: { + if (visible) { + clearText(); + forceActiveFocus(); + } + } + anchors.fill: parent + anchors.margins: 5 + onSearchBarTextChanged: function (text) { + MessagesAdapter.searchbarPrompt = text; + } + } + } + + background: Rectangle { + color: JamiTheme.backgroundColor + } ColumnLayout { anchors.fill: parent + anchors.topMargin: 10 TabBar { id: researchTabBar @@ -88,7 +108,7 @@ Rectangle { Rectangle { id: view - color: JamiTheme.chatviewBgColor + color: JamiTheme.backgroundColor Layout.fillWidth: true Layout.fillHeight: true diff --git a/src/app/mainview/components/NewSwarmPage.qml b/src/app/mainview/components/NewSwarmPage.qml index e1df3410f..354999808 100644 --- a/src/app/mainview/components/NewSwarmPage.qml +++ b/src/app/mainview/components/NewSwarmPage.qml @@ -41,9 +41,11 @@ DualPaneView { splitViewStateKey: "Main" inhibits: ["ConversationView"] - leftPaneItem: viewCoordinator.getView("SidePanel") + leftPaneItem: viewCoordinator.getView("SidePanel", true) rightPaneItem: Rectangle { id: root + objectName: "NewSwarmLayout" + color: JamiTheme.chatviewBgColor anchors.fill: parent diff --git a/src/app/mainview/components/Searchbar.qml b/src/app/mainview/components/Searchbar.qml index c48ee7b31..5ee100890 100644 --- a/src/app/mainview/components/Searchbar.qml +++ b/src/app/mainview/components/Searchbar.qml @@ -26,31 +26,19 @@ Rectangle { signal searchBarTextChanged(string text) signal returnPressedWhileSearching - signal searchClicked - property bool reductionEnabled: false property alias textContent: textArea.text property alias placeHolderText: textArea.placeholderText - property real hoverButtonRadius: JamiTheme.chatViewHeaderButtonRadius - - property var colorSearchBar: JamiTheme.secondaryBackgroundColor - property string currentConversationId: CurrentConversation.id - property bool isOpen: reductionEnabled ? extrasPanel.isOpen(ChatView.MessagesResearchPanel) : true - onIsOpenChanged: { - if (isOpen) - textArea.forceActiveFocus(); - } - function clearText() { textArea.clear(); textArea.forceActiveFocus(); } radius: JamiTheme.primaryRadius - color: isOpen ? colorSearchBar : "transparent" + color: JamiTheme.secondaryBackgroundColor onFocusChanged: { if (focus) { @@ -64,32 +52,14 @@ Rectangle { lineEditObj: textArea } - PushButton { + ResponsiveImage { id: startSearch anchors.verticalCenter: root.verticalCenter anchors.left: root.left anchors.leftMargin: 10 - hoverEnabled: reductionEnabled - enabled: reductionEnabled - radius: hoverButtonRadius - hoveredColor: JamiTheme.hoveredButtonColor source: JamiResources.ic_baseline_search_24dp_svg - toolTipText: JamiStrings.search - normalColor: JamiTheme.primaryBackgroundColor - imageColor: { - if (reductionEnabled) { - if (hovered) { - JamiTheme.chatviewButtonColor; - } else { - JamiTheme.chatViewFooterImgColor; - } - } else { - JamiTheme.chatviewButtonColor; - } - } - - onClicked: root.searchClicked() + color: JamiTheme.chatviewButtonColor } Rectangle { @@ -100,21 +70,7 @@ Rectangle { anchors.right: root.right anchors.verticalCenter: root.verticalCenter color: "transparent" - - opacity: isOpen - visible: opacity - Behavior on opacity { - NumberAnimation { - duration: 150 - } - } - - width: isOpen ? JamiTheme.searchbarSize : 0 - Behavior on width { - NumberAnimation { - duration: 150 - } - } + width: JamiTheme.searchbarSize TextField { id: textArea diff --git a/src/app/mainview/components/SidePanel.qml b/src/app/mainview/components/SidePanel.qml index a3dafb77a..0037f7ebd 100644 --- a/src/app/mainview/components/SidePanel.qml +++ b/src/app/mainview/components/SidePanel.qml @@ -173,7 +173,7 @@ SidePanelBase { color: JamiTheme.backgroundColor } - header: AccountComboBox { + header: AccountComboBox { QWKSetParentHitTestVisible {} id: accountComboBox Shortcut { sequence: "Ctrl+J" diff --git a/src/app/mainview/components/WelcomePage.qml b/src/app/mainview/components/WelcomePage.qml index b62b4eaae..352b40040 100644 --- a/src/app/mainview/components/WelcomePage.qml +++ b/src/app/mainview/components/WelcomePage.qml @@ -35,7 +35,7 @@ ListSelectionView { color: JamiTheme.secondaryBackgroundColor onPresented: LRCInstance.deselectConversation() - leftPaneItem: viewCoordinator.getView("SidePanel") + leftPaneItem: viewCoordinator.getView("SidePanel", true) property variant uiCustomization: CurrentAccount.uiCustomization @@ -120,6 +120,8 @@ ListSelectionView { rightPaneItem: JamiFlickable { id: root + objectName: "WelcomeLayout" + anchors.fill: parent property int thresholdSize: 700 property int thresholdHeight: 570 diff --git a/src/app/settingsview/SettingsSidePanel.qml b/src/app/settingsview/SettingsSidePanel.qml index fd26c07b0..1f5cf88e3 100644 --- a/src/app/settingsview/SettingsSidePanel.qml +++ b/src/app/settingsview/SettingsSidePanel.qml @@ -263,7 +263,13 @@ SidePanelBase { background: null - header: AccountComboBox { + header: AccountComboBox { QWKSetParentHitTestVisible {} + id: accountComboBox + Shortcut { + sequence: "Ctrl+J" + context: Qt.ApplicationShortcut + onActivated: accountComboBox.togglePopup() + } } ListView { diff --git a/src/app/settingsview/SettingsView.qml b/src/app/settingsview/SettingsView.qml index e6a6ef848..4b2225e97 100644 --- a/src/app/settingsview/SettingsView.qml +++ b/src/app/settingsview/SettingsView.qml @@ -56,7 +56,7 @@ ListSelectionView { splitViewStateKey: "Main" inhibits: ["ConversationView"] - leftPaneItem: viewCoordinator.getView("SettingsSidePanel") + leftPaneItem: viewCoordinator.getView("SettingsSidePanel", true) Component.onCompleted: { leftPaneItem.updateModel(); @@ -76,7 +76,7 @@ ListSelectionView { // Currently needed when changing the show link preview setting. CurrentConversation.reloadInteractions(); if (UtilsAdapter.getAccountListSize() === 0) { - viewCoordinator.requestAppWindowWizardView(); + viewCoordinator.present("WizardView"); } else { AccountAdapter.changeAccount(0); } @@ -85,8 +85,7 @@ ListSelectionView { property int selectedMenu: index rightPaneItem: StackView { - id: settingsView - objectName: "settingsView" + objectName: "SettingsLayout" property var currentIndex: selectedMenu !== -1 ? selectedMenu : 0 anchors.fill: parent diff --git a/src/app/settingsview/components/AppearanceSettingsPage.qml b/src/app/settingsview/components/AppearanceSettingsPage.qml index c341cbf48..198b70214 100644 --- a/src/app/settingsview/components/AppearanceSettingsPage.qml +++ b/src/app/settingsview/components/AppearanceSettingsPage.qml @@ -237,6 +237,15 @@ SettingsPageBase { } } + ToggleSwitch { + id: useNativeWindowFrameCheckBox + Layout.fillWidth: true + + checked: !UtilsAdapter.getAppValue(Settings.Key.UseFramelessWindow) + labelText: JamiStrings.useNativeWindowFrame + onSwitchToggled: UtilsAdapter.setAppValue(Settings.Key.UseFramelessWindow, !checked) + } + MaterialButton { id: defaultSettings diff --git a/src/app/settingsview/components/SettingsHeader.qml b/src/app/settingsview/components/SettingsHeader.qml index ec686c803..9b203db2c 100644 --- a/src/app/settingsview/components/SettingsHeader.qml +++ b/src/app/settingsview/components/SettingsHeader.qml @@ -29,7 +29,7 @@ RowLayout { signal backArrowClicked spacing: 10 - BackButton { + BackButton { QWKSetParentHitTestVisible {} id: backToSettingsMenuButton Layout.preferredWidth: JamiTheme.preferredFieldHeight diff --git a/src/app/settingsview/components/SettingsPageBase.qml b/src/app/settingsview/components/SettingsPageBase.qml index 37af8fcf2..75c438660 100644 --- a/src/app/settingsview/components/SettingsPageBase.qml +++ b/src/app/settingsview/components/SettingsPageBase.qml @@ -25,11 +25,13 @@ import "../../commoncomponents" JamiSplitView { id: root + required property Item flickableContent property real contentFlickableWidth: Math.min(JamiTheme.maximumWidthSettingsView, settingsPage.width - 2 * JamiTheme.preferredSettingsMarginSize) property alias title: settingsPage.title property color backgroundColor: JamiTheme.secondaryBackgroundColor property alias pageContainer: settingsPage + Page { id: settingsPage SplitView.maximumWidth: root.width diff --git a/src/app/settingsview/components/SystemSettingsPage.qml b/src/app/settingsview/components/SystemSettingsPage.qml index a598eed5c..3ea004118 100644 --- a/src/app/settingsview/components/SystemSettingsPage.qml +++ b/src/app/settingsview/components/SystemSettingsPage.qml @@ -108,7 +108,7 @@ SettingsPageBase { } ToggleSwitch { - id: applicationOnStartUpCheckBox + id: runOnStartUpCheckBox Layout.fillWidth: true checked: UtilsAdapter.checkStartupLink() diff --git a/src/app/utilsadapter.cpp b/src/app/utilsadapter.cpp index a9689eb72..d15758687 100644 --- a/src/app/utilsadapter.cpp +++ b/src/app/utilsadapter.cpp @@ -94,6 +94,8 @@ UtilsAdapter::setAppValue(const Settings::Key key, const QVariant& value) Q_EMIT chatviewPositionChanged(); else if (key == Settings::Key::AppTheme) Q_EMIT appThemeChanged(); + else if (key == Settings::Key::UseFramelessWindow) + Q_EMIT useFramelessWindowChanged(); #ifndef APPSTORE // Any donation campaign-related keys can trigger a donation campaign check else if (key == Settings::Key::IsDonationVisible diff --git a/src/app/utilsadapter.h b/src/app/utilsadapter.h index 3361a9f71..2f1b6abb7 100644 --- a/src/app/utilsadapter.h +++ b/src/app/utilsadapter.h @@ -172,6 +172,7 @@ Q_SIGNALS: void showExperimentalCallSwarm(); void changeLanguage(); void donationCampaignSettingsChanged(); + void useFramelessWindowChanged(); private: QClipboard* clipboard_; diff --git a/src/app/webengine/map/MapPosition.qml b/src/app/webengine/map/MapPosition.qml index a5a5fe9ec..f349321bf 100644 --- a/src/app/webengine/map/MapPosition.qml +++ b/src/app/webengine/map/MapPosition.qml @@ -107,7 +107,7 @@ Item { property real windowSize: windowPreferedSize > JamiTheme.minimumMapWidth ? windowPreferedSize : JamiTheme.minimumMapWidth property real windowPreferedSize: root.maxWidth > root.maxHeight ? root.maxHeight / 3 : root.maxWidth / 3 property real xPos: 0 - property real yPos: root.isUnpin ? 0 : JamiTheme.chatViewHeaderPreferredHeight + property real yPos: root.isUnpin ? 0 : JamiTheme.qwkTitleBarHeight states: [ State { @@ -125,7 +125,7 @@ Item { target: mapObject parent: parentPin x: xPos - y: JamiTheme.chatViewHeaderPreferredHeight + y: JamiTheme.qwkTitleBarHeight } } ] diff --git a/src/app/wizardview/WizardView.qml b/src/app/wizardview/WizardView.qml index e4e0fe8d0..2a1f9b738 100644 --- a/src/app/wizardview/WizardView.qml +++ b/src/app/wizardview/WizardView.qml @@ -55,10 +55,7 @@ BaseView { function onCloseWizardView() { root.dismiss(); - viewCoordinator.preload("SidePanel"); - viewCoordinator.preload("SettingsSidePanel"); viewCoordinator.present("WelcomePage"); - viewCoordinator.preload("ConversationView"); } } diff --git a/src/app/wizardview/components/CreateAccountPage.qml b/src/app/wizardview/components/CreateAccountPage.qml index 9dd84d3f6..7628f55e5 100644 --- a/src/app/wizardview/components/CreateAccountPage.qml +++ b/src/app/wizardview/components/CreateAccountPage.qml @@ -402,7 +402,7 @@ Rectangle { } } - JamiPushButton { + JamiPushButton { QWKSetParentHitTestVisible {} id: backButton objectName: "createAccountPageBackButton" diff --git a/src/app/wizardview/components/WelcomePage.qml b/src/app/wizardview/components/WelcomePage.qml index 126c42e16..6595c79df 100644 --- a/src/app/wizardview/components/WelcomePage.qml +++ b/src/app/wizardview/components/WelcomePage.qml @@ -382,7 +382,7 @@ Rectangle { } } - JamiPushButton { + JamiPushButton { QWKSetParentHitTestVisible {} id: backButton objectName: "welcomePageBackButton" diff --git a/tests/qml/main.cpp b/tests/qml/main.cpp index 43bb41484..a604a5c72 100644 --- a/tests/qml/main.cpp +++ b/tests/qml/main.cpp @@ -90,7 +90,7 @@ public Q_SLOTS: // Create 2 Account QSignalSpy accountStatusChangedSpy(&lrcInstance_->accountModel(), - &AccountModel::accountStatusChanged); + &AccountModel::accountStatusChanged); QSignalSpy accountAddedSpy(&lrcInstance_->accountModel(), &AccountModel::accountAdded); aliceId = lrcInstance_->accountModel().createNewAccount(profile::Type::JAMI, "Alice"); diff --git a/tests/qml/resources.qrc b/tests/qml/resources.qrc index ca3275042..bc3d31a38 100644 --- a/tests/qml/resources.qrc +++ b/tests/qml/resources.qrc @@ -3,8 +3,22 @@ <file>src/tst_WizardView.qml</file> <file>src/tst_NewSwarmPage.qml</file> <file>src/tst_MessageOptions.qml</file> + <file>src/tst_OngoingCallPage.qml</file> + <file>src/tst_MainView.qml</file> + <file>src/tst_MaterialTextEdit.qml</file> + <file>src/tst_ChatView.qml</file> + <file>src/tst_ChatViewFooter.qml</file> + <file>src/tst_CachedImage.qml</file> + <file>src/tst_CallMessageDelegate.qml</file> + <file>src/tst_DataTransferMessageDelegate.qml</file> + <file>src/tst_FilesToSendContainer.qml</file> + <file>src/tst_PresenceIndicator.qml</file> + <file>src/tst_RecordBox.qml</file> + <file>src/tst_SettingsSidePanel.qml</file> + <file>src/tst_WelcomePage.qml</file> <file>src/resources/gif_test.gif</file> <file>src/resources/gz_test.gz</file> <file>src/resources/png_test.png</file> + <file>src/TestWrapper.qml</file> </qresource> </RCC> diff --git a/tests/qml/src/TestWrapper.qml b/tests/qml/src/TestWrapper.qml new file mode 100644 index 000000000..3d22ce6c3 --- /dev/null +++ b/tests/qml/src/TestWrapper.qml @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 Savoir-faire Linux Inc. + * + * 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 3 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import QtQuick +import QtQuick.Controls + +import "../../../src/app/" + +// The purpose of this component is to fake the ApplicationWindow and prevent +// each UUT from having to manage its own top level app management objects +// (currently ViewManager, ViewCoordinator, and ApplicationWindow). +Item { + // This will be our UUT. + required default property var uut + + // These are our fake app management objects. The caveat is that they + // must be maintained in sync with the actual objects in the app for now. + // The benefit, is that this should be the single place where we need to + // sync them. + property ViewManager viewManager: ViewManager {} + property ViewCoordinator viewCoordinator: ViewCoordinator {} + property ApplicationWindow appWindow: ApplicationWindow { + property bool useFrameless: false + } +} diff --git a/tests/qml/src/tst_NewSwarmPage.qml b/tests/qml/src/tst_NewSwarmPage.qml index 3551d822f..2cebc7dc2 100644 --- a/tests/qml/src/tst_NewSwarmPage.qml +++ b/tests/qml/src/tst_NewSwarmPage.qml @@ -39,9 +39,7 @@ ColumnLayout { id: uut property ViewManager viewManager: ViewManager {} - property ViewCoordinator viewCoordinator: ViewCoordinator { - viewManager: uut.viewManager - } + property ViewCoordinator viewCoordinator: ViewCoordinator {} Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: root.width diff --git a/tests/qml/src/tst_OngoingCallPage.qml b/tests/qml/src/tst_OngoingCallPage.qml index f1a57532e..62f9a3754 100644 --- a/tests/qml/src/tst_OngoingCallPage.qml +++ b/tests/qml/src/tst_OngoingCallPage.qml @@ -32,11 +32,8 @@ OngoingCallPage { height: 600 property QtObject appWindow - property ViewManager viewManager: ViewManager { - } - property ViewCoordinator viewCoordinator: ViewCoordinator { - viewManager: uut.viewManager - } + property ViewManager viewManager: ViewManager {} + property ViewCoordinator viewCoordinator: ViewCoordinator {} TestCase { name: "Check basic visibility of action bar during a call" diff --git a/tests/qml/src/tst_WelcomePage.qml b/tests/qml/src/tst_WelcomePage.qml index f4b0908a0..d7cc95883 100644 --- a/tests/qml/src/tst_WelcomePage.qml +++ b/tests/qml/src/tst_WelcomePage.qml @@ -16,40 +16,26 @@ */ import QtQuick +import QtQuick.Controls + import QtTest import "../../../src/app/" import "../../../src/app/mainview/components" -WelcomePage { - id: uut - - width: 800 - height: 600 - - // The appWindow, viewManager and viewCoordinator properties - // are required in order for the "aboutJami" button to work. - property Item appWindow: uut - property ViewManager viewManager: ViewManager { - } - property ViewCoordinator viewCoordinator: ViewCoordinator { - viewManager: uut.viewManager - } - - TestCase { - name: "Open 'About Jami' popup" - - function test_openAboutPopup() { - compare(viewManager.viewCount(), 0) - - var aboutJamiButton = findChild(uut, "aboutJami") - aboutJamiButton.clicked() +TestWrapper { + uut: WelcomePage { + TestCase { + name: "Open 'About Jami' popup" - compare(viewManager.viewCount(), 1) + function test_openAboutPopup() { + var aboutJamiButton = findChild(uut, "aboutJami") + aboutJamiButton.clicked() - var aboutJamiPopup = viewManager.getView("AboutPopUp") - verify(aboutJamiPopup !== null) - compare(aboutJamiPopup.visible, true) + var aboutJamiPopup = viewManager.getView("AboutPopUp") + verify(aboutJamiPopup !== null, "About Jami popup should be created") + compare(aboutJamiPopup.visible, true, "About Jami popup should be visible") + } } } -} \ No newline at end of file +} -- GitLab