1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-08-04 14:55:43 +02:00

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
This commit is contained in:
Andreas Traczyk 2023-12-21 14:37:32 -05:00
parent 788ecaa496
commit 35482fa92f
52 changed files with 908 additions and 323 deletions

View file

@ -21,7 +21,7 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 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) if(APPLE)
project(Jami) project(Jami)
@ -75,6 +75,37 @@ if(NOT MSVC)
set(CMAKE_CXX_FLAGS_DEBUG "-Og -ggdb") set(CMAKE_CXX_FLAGS_DEBUG "-Og -ggdb")
endif() 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_AUTOMOC ON)
set(CMAKE_AUTORCC ON) set(CMAKE_AUTORCC ON)
@ -102,7 +133,7 @@ if(QT6_VER AND QT6_PATH)
find_package(QT NAMES Qt6 REQUIRED find_package(QT NAMES Qt6 REQUIRED
PATHS ${QT6_PATH} NO_DEFAULT_PATH) PATHS ${QT6_PATH} NO_DEFAULT_PATH)
else() 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) find_package(QT NAMES Qt6 REQUIRED)
endif() endif()
if (${QT_VERSION_MINOR} GREATER_EQUAL ${QT6_MINVER_MINOR}) if (${QT_VERSION_MINOR} GREATER_EQUAL ${QT6_MINVER_MINOR})

View file

@ -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()

View file

@ -66,4 +66,9 @@ RUN apt-get install -y -o Acquire::Retries=10 \
libssl-dev libssl-dev
RUN apt-get install -y pandoc \ RUN apt-get install -y pandoc \
googletest \ 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

View file

@ -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

View file

@ -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

View file

@ -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>

After

Width:  |  Height:  |  Size: 444 B

View file

@ -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>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -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>

After

Width:  |  Height:  |  Size: 328 B

View file

@ -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>

After

Width:  |  Height:  |  Size: 467 B

View file

@ -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>

After

Width:  |  Height:  |  Size: 559 B

View file

@ -73,17 +73,26 @@ QtObject {
function saveWindowSettings() { function saveWindowSettings() {
// If closed-to-tray or minimized or fullscreen, save the cached windowedVisibility // If closed-to-tray or minimized or fullscreen, save the cached windowedVisibility
// value instead. // value instead.
if (isHidden || isFullScreen) { const visibilityToSave = isHidden || isFullScreen ? priv.windowedVisibility : visibility;
AppSettingsManager.setValue(Settings.WindowState, priv.windowedVisibility)
} else {
AppSettingsManager.setValue(Settings.WindowState, visibility)
}
// Likewise, don't save fullscreen geometry. // Likewise, don't save fullscreen geometry.
const geometry = isFullScreen ? const geometry = isFullScreen ?
priv.windowedGeometry : priv.windowedGeometry :
Qt.rect(appWindow.x, appWindow.y, 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) AppSettingsManager.setValue(Settings.WindowGeometry, geometry)
} }
@ -111,6 +120,8 @@ QtObject {
const visibilityStr = AppSettingsManager.getValue(Settings.WindowState) const visibilityStr = AppSettingsManager.getValue(Settings.WindowState)
var visibilitySetting = parseInt(visibilityStr) 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 // We should never restore a hidden or fullscreen state here. Default to normal
// windowed state in such a case. This shouldn't happen. // windowed state in such a case. This shouldn't happen.
if (visibilitySetting === Window.Hidden || visibilitySetting === Window.FullScreen) { if (visibilitySetting === Window.Hidden || visibilitySetting === Window.FullScreen) {

View file

@ -24,29 +24,30 @@ import QtQuick.Window
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import net.jami.Models 1.1 import net.jami.Models 1.1
import net.jami.Adapters 1.1 import net.jami.Adapters 1.1
import net.jami.Enums 1.1 import net.jami.Enums 1.1
import net.jami.Helpers 1.1 import net.jami.Helpers 1.1
import net.jami.Constants 1.1 import net.jami.Constants 1.1
import "mainview" import "mainview"
import "mainview/components" import "mainview/components"
import "wizardview" import "wizardview"
import "commoncomponents" import "commoncomponents"
import QWindowKit
ApplicationWindow { ApplicationWindow {
id: root id: appWindow
property bool isRTL: UtilsAdapter.isRTL property bool isRTL: UtilsAdapter.isRTL
LayoutMirroring.enabled: isRTL LayoutMirroring.enabled: isRTL
LayoutMirroring.childrenInherit: isRTL LayoutMirroring.childrenInherit: isRTL
enum LoadedSource { // This needs to be set from the start.
MainView, readonly property bool useFrameless: UtilsAdapter.getAppValue(Settings.Key.UseFramelessWindow)
AccountMigrationView,
None
}
onActiveFocusItemChanged: { onActiveFocusItemChanged: {
focusOverlay.margin = -5; focusOverlay.margin = -5;
@ -70,7 +71,7 @@ ApplicationWindow {
sourceComponent: GenericErrorsRow { sourceComponent: GenericErrorsRow {
id: genericError id: genericError
text: CurrentAccount.enabled ? JamiStrings.noNetworkConnectivity : JamiStrings.disabledAccount text: CurrentAccount.enabled ? JamiStrings.noNetworkConnectivity : JamiStrings.disabledAccount
height: visible? JamiTheme.chatViewHeaderPreferredHeight : 0 height: visible? JamiTheme.qwkTitleBarHeight : 0
} }
} }
@ -88,37 +89,31 @@ ApplicationWindow {
border.color: JamiTheme.tintedBlue border.color: JamiTheme.tintedBlue
} }
property ApplicationWindow appWindow: root // Used to manage full screen mode and save/restore window geometry.
property LayoutManager layoutManager: LayoutManager { LayoutManager {
appContainer: appContainer id: layoutManager
} appContainer: fullscreenContainer
property ViewManager viewManager: ViewManager {
}
property ViewCoordinator viewCoordinator: ViewCoordinator {
viewManager: root.viewManager
} }
// Used to manage dynamic view loading and unloading.
ViewManager {
id: viewManager
}
// Used to manage the view stack and the current view.
ViewCoordinator {
id: viewCoordinator
}
// 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 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 property bool allowVisibleWindow: true
function checkLoadedSource() {
var sourceString = mainApplicationLoader.source.toString();
if (sourceString === JamiQmlUtils.mainViewLoadPath)
return MainApplicationWindow.LoadedSource.MainView;
return MainApplicationWindow.LoadedSource.None;
}
function startClient() {
setMainLoaderSource(JamiQmlUtils.mainViewLoadPath);
}
function setMainLoaderSource(source) {
if (checkLoadedSource() === MainApplicationWindow.LoadedSource.MainView) {
cleanupMainView();
}
mainApplicationLoader.setSource(source);
}
function cleanupMainView() { function cleanupMainView() {
// Save the main view window size if loading anything else. // Save the main view window size if loading anything else.
layoutManager.saveWindowSettings(); layoutManager.saveWindowSettings();
@ -139,88 +134,148 @@ ApplicationWindow {
title: JamiStrings.appTitle title: JamiStrings.appTitle
visible: mainApplicationLoader.status === Loader.Ready && windowSettingsLoaded && allowVisibleWindow visible: mainViewLoader.status === Loader.Ready && windowSettingsLoaded && allowVisibleWindow
// To facilitate reparenting of the callview during Connections {
// fullscreen mode, we need QQuickItem based object. id: connectionMigrationEnded
Item {
id: appContainer
anchors.fill: parent target: CurrentAccountToMigrate
function onAccountNeedsMigration(accountId) {
viewCoordinator.present("AccountMigrationView");
}
function onAllMigrationsFinished() {
viewCoordinator.dismiss("AccountMigrationView");
viewCoordinator.present("WelcomePage");
}
}
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);
}
}
// Set the viewCoordinator's root item.
viewCoordinator.init(view);
// 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();
}
// 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);
}
}
// Handle a start URI if set as start option.
MainApplication.handleUriAction();
// This will allow visible to become true if not starting minimized.
windowSettingsLoaded = true;
}
Component.onCompleted: {
// QWK: setup
if (useFrameless) {
windowAgent.setup(appWindow);
}
mainViewLoader.active = true;
// Dbus error handler for Linux.
if (Qt.platform.os.toString() !== "windows" && Qt.platform.os.toString() !== "osx")
DBusErrorHandler.setActive(true);
} }
Loader { Loader {
id: mainApplicationLoader id: mainViewLoader
active: false
source: "qrc:/mainview/MainView.qml"
anchors.fill: parent anchors.fill: parent
z: -1 onLoaded: initMainView(item)
}
asynchronous: true // Use this as a parent for fullscreen items.
visible: status == Loader.Ready Item {
id: fullscreenContainer
anchors.fill: parent
}
Connections { // QWK: Provide spacing for widgets that may be occluded by the system buttons.
id: connectionMigrationEnded 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
}
target: CurrentAccountToMigrate // QWK: Window Title bar
Item {
function onAccountNeedsMigration(accountId) { id: titleBar
viewCoordinator.present("AccountMigrationView"); height: JamiTheme.qwkTitleBarHeight
} anchors {
top: parent.top
function onAllMigrationsFinished() { right: parent.right
viewCoordinator.dismiss("AccountMigrationView"); left: parent.left
startClient();
}
} }
// Set `visible = false` when loading a new QML file. // On Windows and Linux, use custom system buttons.
onSourceChanged: windowSettingsLoaded = false Loader {
id: sysBtnsLoader
onLoaded: { active: Qt.platform.os.toString() !== "osx" && useFrameless
if (UtilsAdapter.getAccountListSize() === 0) { height: titleBar.height
layoutManager.restoreWindowSettings(); anchors {
if (!viewCoordinator.rootView) top: parent.top
// Set the viewCoordinator's root item. right: parent.right
viewCoordinator.init(item); // Note: leave these margins, they prevent image scaling artifacts
viewCoordinator.present("WizardView"); topMargin: 1
} else { rightMargin: 1
// 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") { source: "qrc:/commoncomponents/QWKSystemButtonGroup.qml"
MainApplication.setEventFilter();
}
// This will trigger `visible = true`.
windowSettingsLoaded = 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);
}
}
// Handle a start URI if set as start option.
MainApplication.handleUriAction();
} }
} }
// QWK: Main interop component.
WindowAgent {
id: windowAgent
}
Connections { Connections {
target: LRCInstance target: LRCInstance
@ -339,11 +394,5 @@ ApplicationWindow {
} }
} }
onClosing: root.close() onClosing: appWindow.close()
Component.onCompleted: {
startClient();
if (Qt.platform.os.toString() !== "windows" && Qt.platform.os.toString() !== "osx")
DBusErrorHandler.setActive(true);
}
} }

View file

@ -24,14 +24,6 @@ import "commoncomponents"
QtObject { QtObject {
id: root 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. // A map of view names to file paths for QML files that define each view.
property variant resources: { property variant resources: {
"SidePanel": "mainview/components/SidePanel.qml", "SidePanel": "mainview/components/SidePanel.qml",
@ -47,12 +39,44 @@ QtObject {
// The `main` view of the application window. // The `main` view of the application window.
property StackView rootView 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) { function init(mainStackView) {
rootView = Qt.createQmlObject(`import QtQuick; import QtQuick.Controls rootView = Qt.createQmlObject(`import QtQuick; import QtQuick.Controls
StackView { anchors.fill: parent }`, mainStackView); StackView { anchors.fill: parent }`, mainStackView);
initialized();
} }
function deinit() { function deinit() {
@ -171,6 +195,8 @@ QtObject {
var objectName = view ? view.objectName : obj.objectName; var objectName = view ? view.objectName : obj.objectName;
if (!viewManager.destroyView(resources[objectName])) { if (!viewManager.destroyView(resources[objectName])) {
print("could not destroy view:", objectName); print("could not destroy view:", objectName);
} else {
print("destroyed view:", objectName);
} }
} else } else
view.dismissed(); view.dismissed();
@ -197,8 +223,20 @@ QtObject {
} }
} }
function getView(viewName) { function getView(viewName, forceCreate = false) {
return viewManager.getView(viewName); // 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. // Load a view without presenting it.

View file

@ -33,6 +33,14 @@
extern const QString defaultDownloadPath; extern const QString defaultDownloadPath;
// clang-format off // 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 // Common key-value pairs for both APPSTORE and non-APPSTORE builds
#define COMMON_KEYS \ #define COMMON_KEYS \
X(MinimizeOnClose, false) \ X(MinimizeOnClose, false) \
@ -66,7 +74,8 @@ extern const QString defaultDownloadPath;
X(ChatViewEnterIsNewLine, false) \ X(ChatViewEnterIsNewLine, false) \
X(ShowSendOption, false) \ X(ShowSendOption, false) \
X(EnablePtt, false) \ X(EnablePtt, false) \
X(PttKeys, 32) X(PttKeys, 32) \
X(UseFramelessWindow, USE_FRAMELESS_WINDOW_DEFAULT)
#ifdef APPSTORE #ifdef APPSTORE
#define KEYS COMMON_KEYS #define KEYS COMMON_KEYS
#else #else

View file

@ -35,10 +35,12 @@ Rectangle {
signal dismissed signal dismissed
Component.onCompleted: { Component.onCompleted: {
console.debug("Created", objectName);
if (managed) if (managed)
presented(); presented();
} }
Component.onDestruction: { Component.onDestruction: {
console.debug("Destroyed", objectName);
if (managed) if (managed)
dismissed(); dismissed();
} }

View file

@ -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 } }
}
}

View file

@ -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);
}
}

View file

@ -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()
}
}

View file

@ -1,10 +1,53 @@
import QtQuick /*
* 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/>.
*/
Rectangle { import QtQuick
import QtQuick.Controls
Page {
id: root id: root
anchors.fill: parent 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. // Override these if needed.
property var select: function () {} property var select: function () {}
property var deselect: function () {} property var deselect: function () {}

View file

@ -24,9 +24,6 @@ import net.jami.Enums 1.1
Item { Item {
property string qmlFilePrefix: "file:/" 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," readonly property string base64StringTitle: "data:image/png;base64,"
property var accountCreationInputParaObject: ({}) property var accountCreationInputParaObject: ({})
@ -81,8 +78,7 @@ Item {
function onDonationCampaignSettingsChanged() { function onDonationCampaignSettingsChanged() {
// Changing any of the donation campaign settings will trigger a recompute // Changing any of the donation campaign settings will trigger a recompute
// of the banner visibility. // of the banner visibility.
updateIsDonationBannerVisible(); updateIsDonationBannerVisible(); }
}
} }
function updateIsDonationBannerVisible() { function updateIsDonationBannerVisible() {

View file

@ -477,6 +477,7 @@ Item {
property string enableNotifications: qsTr("Enable notifications") property string enableNotifications: qsTr("Enable notifications")
property string showNotifications: qsTr("Show notifications") property string showNotifications: qsTr("Show notifications")
property string keepMinimized: qsTr("Minimize on close") 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 tipRunStartup: qsTr("Run at system startup")
property string runStartup: qsTr("Launch at startup") property string runStartup: qsTr("Launch at startup")
property string downloadFolder: qsTr("Choose download directory") property string downloadFolder: qsTr("Choose download directory")

View file

@ -474,7 +474,6 @@ Item {
// MessageWebView // MessageWebView
property real chatViewHairLineSize: 1 property real chatViewHairLineSize: 1
property real chatViewMaximumWidth: 900 property real chatViewMaximumWidth: 900
property real chatViewHeaderPreferredHeight: 50
property real chatViewFooterPreferredHeight: 35 property real chatViewFooterPreferredHeight: 35
property real chatViewFooterMaximumHeight: 315 property real chatViewFooterMaximumHeight: 315
property real chatViewFooterRowSpacing: 4 property real chatViewFooterRowSpacing: 4
@ -710,4 +709,7 @@ Item {
property color chatSettingButtonBackgroundColor: darkTheme ? "#303030" : "#F0EFEF" property color chatSettingButtonBackgroundColor: darkTheme ? "#303030" : "#F0EFEF"
property color chatSettingButtonBorderColor: darkTheme ? "#03B9E9" : "#005699" property color chatSettingButtonBorderColor: darkTheme ? "#03B9E9" : "#005699"
property color chatSettingButtonTextColor: textColor property color chatSettingButtonTextColor: textColor
// QWK
property real qwkTitleBarHeight: 50
} }

View file

@ -27,6 +27,8 @@
#include "systemtray.h" #include "systemtray.h"
#include "videoprovider.h" #include "videoprovider.h"
#include <QWKQuick/qwkquickglobal.h>
#include <QAction> #include <QAction>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QCoreApplication> #include <QCoreApplication>
@ -138,7 +140,6 @@ ScreenInfo::onPhysicalDotsPerInchChanged()
MainApplication::MainApplication(int& argc, char** argv) MainApplication::MainApplication(int& argc, char** argv)
: QApplication(argc, argv) : QApplication(argc, argv)
, isCleanupped(false)
{ {
const char* qtVersion = qVersion(); const char* qtVersion = qVersion();
if (strncmp(qtVersion, QT_VERSION_STR, strnlen(qtVersion, sizeof qtVersion)) != 0) { if (strncmp(qtVersion, QT_VERSION_STR, strnlen(qtVersion, sizeof qtVersion)) != 0) {
@ -166,8 +167,6 @@ MainApplication::MainApplication(int& argc, char** argv)
// the logging features. // the logging features.
qInstallMessageHandler(messageHandler); qInstallMessageHandler(messageHandler);
QObject::connect(this, &QApplication::aboutToQuit, this, &MainApplication::cleanup);
qCInfo(app_) << "Using Qt runtime version:" << qtVersion; qCInfo(app_) << "Using Qt runtime version:" << qtVersion;
} }
@ -184,6 +183,8 @@ MainApplication::init()
// performing unnecessary tasks, like initializing the webengine. // performing unnecessary tasks, like initializing the webengine.
engine_.reset(new QQmlApplicationEngine(this)); engine_.reset(new QQmlApplicationEngine(this));
QWK::registerTypes(engine_.get());
connectivityMonitor_ = new ConnectivityMonitor(this); connectivityMonitor_ = new ConnectivityMonitor(this);
settingsManager_ = new AppSettingsManager(this); settingsManager_ = new AppSettingsManager(this);
systemTray_ = new SystemTray(settingsManager_, this); systemTray_ = new SystemTray(settingsManager_, this);
@ -466,17 +467,6 @@ MainApplication::initSystray()
systemTray_->show(); 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 void
MainApplication::setEventFilter() MainApplication::setEventFilter()
{ {

View file

@ -108,7 +108,6 @@ private:
void setApplicationFont(); void setApplicationFont();
void initQmlLayer(); void initQmlLayer();
void initSystray(); void initSystray();
void cleanup();
private: private:
std::map<Option, QVariant> runOptions_; std::map<Option, QVariant> runOptions_;
@ -123,6 +122,4 @@ private:
AppSettingsManager* settingsManager_; AppSettingsManager* settingsManager_;
ScreenInfo screenInfo_; ScreenInfo screenInfo_;
bool isCleanupped;
}; };

View file

@ -45,9 +45,11 @@ ListSelectionView {
color: JamiTheme.transparentColor color: JamiTheme.transparentColor
leftPaneItem: viewCoordinator.getView("SidePanel") leftPaneItem: viewCoordinator.getView("SidePanel", true)
rightPaneItem: StackLayout { rightPaneItem: StackLayout {
objectName: "ConversationLayout"
currentIndex: !CurrentConversation.hasCall ? 0 : 1 currentIndex: !CurrentConversation.hasCall ? 0 : 1
onCurrentIndexChanged: chatView.parent = currentIndex === 1 ? callStackView.chatViewContainer : chatViewContainer onCurrentIndexChanged: chatView.parent = currentIndex === 1 ? callStackView.chatViewContainer : chatViewContainer

View file

@ -36,13 +36,8 @@ import "js/keyboardshortcuttablecreation.js" as KeyboardShortcutTableCreation
Rectangle { Rectangle {
id: mainView id: mainView
objectName: "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 property string currentConvId: CurrentConversation.id
onCurrentConvIdChanged: { onCurrentConvIdChanged: {
if (currentConvId !== '') { if (currentConvId !== '') {

View file

@ -115,8 +115,8 @@ Rectangle {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight Layout.preferredHeight: JamiTheme.qwkTitleBarHeight
Layout.maximumHeight: JamiTheme.chatViewHeaderPreferredHeight Layout.maximumHeight: JamiTheme.qwkTitleBarHeight
Layout.minimumWidth: JamiTheme.mainViewPaneMinWidth Layout.minimumWidth: JamiTheme.mainViewPaneMinWidth
DropArea { DropArea {
@ -180,14 +180,14 @@ Rectangle {
ConversationErrorsRow { ConversationErrorsRow {
id: errorRect id: errorRect
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight Layout.preferredHeight: JamiTheme.qwkTitleBarHeight
visible: false visible: false
} }
NotificationArea { NotificationArea {
id: notificationArea id: notificationArea
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight Layout.preferredHeight: JamiTheme.qwkTitleBarHeight
visible: CurrentConversation.activeCalls.length > 0 && !root.inCallView visible: CurrentConversation.activeCalls.length > 0 && !root.inCallView
} }

View file

@ -46,7 +46,7 @@ Rectangle {
} }
} }
property bool interactionButtonsVisibility: { readonly property bool interactionButtonsVisibility: {
if (CurrentConversation.inCall) if (CurrentConversation.inCall)
return false; return false;
if (LRCInstance.currentAccountType === Profile.Type.SIP) if (LRCInstance.currentAccountType === Profile.Type.SIP)
@ -74,16 +74,17 @@ Rectangle {
id: messagingHeaderRectRowLayout id: messagingHeaderRectRowLayout
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 8 // QWK: spacing
anchors.leftMargin: qwkSystemButtonSpacing.left
anchors.rightMargin: 10 + qwkSystemButtonSpacing.right
spacing: 16 spacing: 16
JamiPushButton { JamiPushButton { QWKSetParentHitTestVisible {}
id: backToWelcomeViewButton id: backToWelcomeViewButton
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.leftMargin: 8 Layout.leftMargin: 8
//preferredSize: 24
mirror: UtilsAdapter.isRTL mirror: UtilsAdapter.isRTL
source: JamiResources.back_24dp_svg source: JamiResources.back_24dp_svg
@ -106,10 +107,11 @@ Rectangle {
color: JamiTheme.transparentColor color: JamiTheme.transparentColor
ColumnLayout { ColumnLayout { QWKSetParentHitTestVisible {}
id: userNameOrIdColumnLayout id: userNameOrIdColumnLayout
objectName: "userNameOrIdColumnLayout"
anchors.fill: parent height: parent.height
spacing: 0 spacing: 0
@ -144,63 +146,30 @@ Rectangle {
} }
} }
Searchbar { JamiPushButton { QWKSetParentHitTestVisible {}
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 {
id: startAAudioCallButton id: startAAudioCallButton
visible: interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm)) visible: interactionButtonsVisibility &&
(!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
source: JamiResources.place_audiocall_24dp_svg source: JamiResources.place_audiocall_24dp_svg
toolTipText: JamiStrings.placeAudioCall toolTipText: JamiStrings.placeAudioCall
onClicked: CallAdapter.placeAudioOnlyCall() onClicked: CallAdapter.placeAudioOnlyCall()
} }
JamiPushButton { JamiPushButton { QWKSetParentHitTestVisible {}
id: startAVideoCallButton 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 source: JamiResources.videocam_24dp_svg
toolTipText: JamiStrings.placeVideoCall toolTipText: JamiStrings.placeVideoCall
onClicked: CallAdapter.placeCall() onClicked: CallAdapter.placeCall()
} }
JamiPushButton { JamiPushButton { QWKSetParentHitTestVisible {}
id: addParticipantsButton id: addParticipantsButton
checkable: true checkable: true
@ -212,7 +181,7 @@ Rectangle {
onClicked: extrasPanel.switchToPanel(ChatView.AddMemberPanel) onClicked: extrasPanel.switchToPanel(ChatView.AddMemberPanel)
} }
JamiPushButton { JamiPushButton { QWKSetParentHitTestVisible {}
id: selectPluginButton id: selectPluginButton
visible: PluginAdapter.chatHandlersListCount && interactionButtonsVisibility visible: PluginAdapter.chatHandlersListCount && interactionButtonsVisibility
@ -222,7 +191,7 @@ Rectangle {
onClicked: pluginSelector() onClicked: pluginSelector()
} }
JamiPushButton { JamiPushButton { QWKSetParentHitTestVisible {}
id: sendContactRequestButton id: sendContactRequestButton
objectName: "sendContactRequestButton" objectName: "sendContactRequestButton"
@ -230,16 +199,39 @@ Rectangle {
source: JamiResources.add_people_24dp_svg source: JamiResources.add_people_24dp_svg
toolTipText: JamiStrings.addToConversations toolTipText: JamiStrings.addToConversations
onClicked: CurrentConversation.isBanned ? MessagesAdapter.unbanConversation(CurrentConversation.id) : MessagesAdapter.sendConversationRequest() onClicked: CurrentConversation.isBanned ?
MessagesAdapter.unbanConversation(CurrentConversation.id) :
MessagesAdapter.sendConversationRequest()
} }
JamiPushButton { 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 { QWKSetParentHitTestVisible {}
id: detailsButton id: detailsButton
objectName: "detailsButton" objectName: "detailsButton"
checkable: true checkable: true
checked: extrasPanel.isOpen(ChatView.SwarmDetailsPanel) 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 source: JamiResources.swarm_details_panel_svg
toolTipText: JamiStrings.details toolTipText: JamiStrings.details

View file

@ -127,9 +127,12 @@ Item {
id: overlayUpperPartRect id: overlayUpperPartRect
anchors.top: parent.top anchors.top: parent.top
width: parent.width
height: 50 height: 50
anchors.left: parent.left
anchors.right: parent.right
// QWK: spacing
anchors.leftMargin: qwkSystemButtonSpacing.left
anchors.rightMargin: qwkSystemButtonSpacing.right
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent

View file

@ -26,13 +26,33 @@ import net.jami.Constants 1.1
import "../../commoncomponents" import "../../commoncomponents"
import "../../settingsview/components" import "../../settingsview/components"
Rectangle { Page {
id: root 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 { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.topMargin: 10
TabBar { TabBar {
id: researchTabBar id: researchTabBar
@ -88,7 +108,7 @@ Rectangle {
Rectangle { Rectangle {
id: view id: view
color: JamiTheme.chatviewBgColor color: JamiTheme.backgroundColor
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true

View file

@ -41,9 +41,11 @@ DualPaneView {
splitViewStateKey: "Main" splitViewStateKey: "Main"
inhibits: ["ConversationView"] inhibits: ["ConversationView"]
leftPaneItem: viewCoordinator.getView("SidePanel") leftPaneItem: viewCoordinator.getView("SidePanel", true)
rightPaneItem: Rectangle { rightPaneItem: Rectangle {
id: root id: root
objectName: "NewSwarmLayout"
color: JamiTheme.chatviewBgColor color: JamiTheme.chatviewBgColor
anchors.fill: parent anchors.fill: parent

View file

@ -26,31 +26,19 @@ Rectangle {
signal searchBarTextChanged(string text) signal searchBarTextChanged(string text)
signal returnPressedWhileSearching signal returnPressedWhileSearching
signal searchClicked
property bool reductionEnabled: false
property alias textContent: textArea.text property alias textContent: textArea.text
property alias placeHolderText: textArea.placeholderText property alias placeHolderText: textArea.placeholderText
property real hoverButtonRadius: JamiTheme.chatViewHeaderButtonRadius property real hoverButtonRadius: JamiTheme.chatViewHeaderButtonRadius
property var colorSearchBar: JamiTheme.secondaryBackgroundColor
property string currentConversationId: CurrentConversation.id property string currentConversationId: CurrentConversation.id
property bool isOpen: reductionEnabled ? extrasPanel.isOpen(ChatView.MessagesResearchPanel) : true
onIsOpenChanged: {
if (isOpen)
textArea.forceActiveFocus();
}
function clearText() { function clearText() {
textArea.clear(); textArea.clear();
textArea.forceActiveFocus(); textArea.forceActiveFocus();
} }
radius: JamiTheme.primaryRadius radius: JamiTheme.primaryRadius
color: isOpen ? colorSearchBar : "transparent" color: JamiTheme.secondaryBackgroundColor
onFocusChanged: { onFocusChanged: {
if (focus) { if (focus) {
@ -64,32 +52,14 @@ Rectangle {
lineEditObj: textArea lineEditObj: textArea
} }
PushButton { ResponsiveImage {
id: startSearch id: startSearch
anchors.verticalCenter: root.verticalCenter anchors.verticalCenter: root.verticalCenter
anchors.left: root.left anchors.left: root.left
anchors.leftMargin: 10 anchors.leftMargin: 10
hoverEnabled: reductionEnabled
enabled: reductionEnabled
radius: hoverButtonRadius
hoveredColor: JamiTheme.hoveredButtonColor
source: JamiResources.ic_baseline_search_24dp_svg source: JamiResources.ic_baseline_search_24dp_svg
toolTipText: JamiStrings.search color: JamiTheme.chatviewButtonColor
normalColor: JamiTheme.primaryBackgroundColor
imageColor: {
if (reductionEnabled) {
if (hovered) {
JamiTheme.chatviewButtonColor;
} else {
JamiTheme.chatViewFooterImgColor;
}
} else {
JamiTheme.chatviewButtonColor;
}
}
onClicked: root.searchClicked()
} }
Rectangle { Rectangle {
@ -100,21 +70,7 @@ Rectangle {
anchors.right: root.right anchors.right: root.right
anchors.verticalCenter: root.verticalCenter anchors.verticalCenter: root.verticalCenter
color: "transparent" color: "transparent"
width: JamiTheme.searchbarSize
opacity: isOpen
visible: opacity
Behavior on opacity {
NumberAnimation {
duration: 150
}
}
width: isOpen ? JamiTheme.searchbarSize : 0
Behavior on width {
NumberAnimation {
duration: 150
}
}
TextField { TextField {
id: textArea id: textArea

View file

@ -173,7 +173,7 @@ SidePanelBase {
color: JamiTheme.backgroundColor color: JamiTheme.backgroundColor
} }
header: AccountComboBox { header: AccountComboBox { QWKSetParentHitTestVisible {}
id: accountComboBox id: accountComboBox
Shortcut { Shortcut {
sequence: "Ctrl+J" sequence: "Ctrl+J"

View file

@ -35,7 +35,7 @@ ListSelectionView {
color: JamiTheme.secondaryBackgroundColor color: JamiTheme.secondaryBackgroundColor
onPresented: LRCInstance.deselectConversation() onPresented: LRCInstance.deselectConversation()
leftPaneItem: viewCoordinator.getView("SidePanel") leftPaneItem: viewCoordinator.getView("SidePanel", true)
property variant uiCustomization: CurrentAccount.uiCustomization property variant uiCustomization: CurrentAccount.uiCustomization
@ -120,6 +120,8 @@ ListSelectionView {
rightPaneItem: JamiFlickable { rightPaneItem: JamiFlickable {
id: root id: root
objectName: "WelcomeLayout"
anchors.fill: parent anchors.fill: parent
property int thresholdSize: 700 property int thresholdSize: 700
property int thresholdHeight: 570 property int thresholdHeight: 570

View file

@ -263,7 +263,13 @@ SidePanelBase {
background: null background: null
header: AccountComboBox { header: AccountComboBox { QWKSetParentHitTestVisible {}
id: accountComboBox
Shortcut {
sequence: "Ctrl+J"
context: Qt.ApplicationShortcut
onActivated: accountComboBox.togglePopup()
}
} }
ListView { ListView {

View file

@ -56,7 +56,7 @@ ListSelectionView {
splitViewStateKey: "Main" splitViewStateKey: "Main"
inhibits: ["ConversationView"] inhibits: ["ConversationView"]
leftPaneItem: viewCoordinator.getView("SettingsSidePanel") leftPaneItem: viewCoordinator.getView("SettingsSidePanel", true)
Component.onCompleted: { Component.onCompleted: {
leftPaneItem.updateModel(); leftPaneItem.updateModel();
@ -76,7 +76,7 @@ ListSelectionView {
// Currently needed when changing the show link preview setting. // Currently needed when changing the show link preview setting.
CurrentConversation.reloadInteractions(); CurrentConversation.reloadInteractions();
if (UtilsAdapter.getAccountListSize() === 0) { if (UtilsAdapter.getAccountListSize() === 0) {
viewCoordinator.requestAppWindowWizardView(); viewCoordinator.present("WizardView");
} else { } else {
AccountAdapter.changeAccount(0); AccountAdapter.changeAccount(0);
} }
@ -85,8 +85,7 @@ ListSelectionView {
property int selectedMenu: index property int selectedMenu: index
rightPaneItem: StackView { rightPaneItem: StackView {
id: settingsView objectName: "SettingsLayout"
objectName: "settingsView"
property var currentIndex: selectedMenu !== -1 ? selectedMenu : 0 property var currentIndex: selectedMenu !== -1 ? selectedMenu : 0
anchors.fill: parent anchors.fill: parent

View file

@ -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 { MaterialButton {
id: defaultSettings id: defaultSettings

View file

@ -29,7 +29,7 @@ RowLayout {
signal backArrowClicked signal backArrowClicked
spacing: 10 spacing: 10
BackButton { BackButton { QWKSetParentHitTestVisible {}
id: backToSettingsMenuButton id: backToSettingsMenuButton
Layout.preferredWidth: JamiTheme.preferredFieldHeight Layout.preferredWidth: JamiTheme.preferredFieldHeight

View file

@ -25,11 +25,13 @@ import "../../commoncomponents"
JamiSplitView { JamiSplitView {
id: root id: root
required property Item flickableContent required property Item flickableContent
property real contentFlickableWidth: Math.min(JamiTheme.maximumWidthSettingsView, settingsPage.width - 2 * JamiTheme.preferredSettingsMarginSize) property real contentFlickableWidth: Math.min(JamiTheme.maximumWidthSettingsView, settingsPage.width - 2 * JamiTheme.preferredSettingsMarginSize)
property alias title: settingsPage.title property alias title: settingsPage.title
property color backgroundColor: JamiTheme.secondaryBackgroundColor property color backgroundColor: JamiTheme.secondaryBackgroundColor
property alias pageContainer: settingsPage property alias pageContainer: settingsPage
Page { Page {
id: settingsPage id: settingsPage
SplitView.maximumWidth: root.width SplitView.maximumWidth: root.width

View file

@ -108,7 +108,7 @@ SettingsPageBase {
} }
ToggleSwitch { ToggleSwitch {
id: applicationOnStartUpCheckBox id: runOnStartUpCheckBox
Layout.fillWidth: true Layout.fillWidth: true
checked: UtilsAdapter.checkStartupLink() checked: UtilsAdapter.checkStartupLink()

View file

@ -94,6 +94,8 @@ UtilsAdapter::setAppValue(const Settings::Key key, const QVariant& value)
Q_EMIT chatviewPositionChanged(); Q_EMIT chatviewPositionChanged();
else if (key == Settings::Key::AppTheme) else if (key == Settings::Key::AppTheme)
Q_EMIT appThemeChanged(); Q_EMIT appThemeChanged();
else if (key == Settings::Key::UseFramelessWindow)
Q_EMIT useFramelessWindowChanged();
#ifndef APPSTORE #ifndef APPSTORE
// Any donation campaign-related keys can trigger a donation campaign check // Any donation campaign-related keys can trigger a donation campaign check
else if (key == Settings::Key::IsDonationVisible else if (key == Settings::Key::IsDonationVisible

View file

@ -172,6 +172,7 @@ Q_SIGNALS:
void showExperimentalCallSwarm(); void showExperimentalCallSwarm();
void changeLanguage(); void changeLanguage();
void donationCampaignSettingsChanged(); void donationCampaignSettingsChanged();
void useFramelessWindowChanged();
private: private:
QClipboard* clipboard_; QClipboard* clipboard_;

View file

@ -107,7 +107,7 @@ Item {
property real windowSize: windowPreferedSize > JamiTheme.minimumMapWidth ? windowPreferedSize : JamiTheme.minimumMapWidth property real windowSize: windowPreferedSize > JamiTheme.minimumMapWidth ? windowPreferedSize : JamiTheme.minimumMapWidth
property real windowPreferedSize: root.maxWidth > root.maxHeight ? root.maxHeight / 3 : root.maxWidth / 3 property real windowPreferedSize: root.maxWidth > root.maxHeight ? root.maxHeight / 3 : root.maxWidth / 3
property real xPos: 0 property real xPos: 0
property real yPos: root.isUnpin ? 0 : JamiTheme.chatViewHeaderPreferredHeight property real yPos: root.isUnpin ? 0 : JamiTheme.qwkTitleBarHeight
states: [ states: [
State { State {
@ -125,7 +125,7 @@ Item {
target: mapObject target: mapObject
parent: parentPin parent: parentPin
x: xPos x: xPos
y: JamiTheme.chatViewHeaderPreferredHeight y: JamiTheme.qwkTitleBarHeight
} }
} }
] ]

View file

@ -55,10 +55,7 @@ BaseView {
function onCloseWizardView() { function onCloseWizardView() {
root.dismiss(); root.dismiss();
viewCoordinator.preload("SidePanel");
viewCoordinator.preload("SettingsSidePanel");
viewCoordinator.present("WelcomePage"); viewCoordinator.present("WelcomePage");
viewCoordinator.preload("ConversationView");
} }
} }

View file

@ -402,7 +402,7 @@ Rectangle {
} }
} }
JamiPushButton { JamiPushButton { QWKSetParentHitTestVisible {}
id: backButton id: backButton
objectName: "createAccountPageBackButton" objectName: "createAccountPageBackButton"

View file

@ -382,7 +382,7 @@ Rectangle {
} }
} }
JamiPushButton { JamiPushButton { QWKSetParentHitTestVisible {}
id: backButton id: backButton
objectName: "welcomePageBackButton" objectName: "welcomePageBackButton"

View file

@ -90,7 +90,7 @@ public Q_SLOTS:
// Create 2 Account // Create 2 Account
QSignalSpy accountStatusChangedSpy(&lrcInstance_->accountModel(), QSignalSpy accountStatusChangedSpy(&lrcInstance_->accountModel(),
&AccountModel::accountStatusChanged); &AccountModel::accountStatusChanged);
QSignalSpy accountAddedSpy(&lrcInstance_->accountModel(), &AccountModel::accountAdded); QSignalSpy accountAddedSpy(&lrcInstance_->accountModel(), &AccountModel::accountAdded);
aliceId = lrcInstance_->accountModel().createNewAccount(profile::Type::JAMI, "Alice"); aliceId = lrcInstance_->accountModel().createNewAccount(profile::Type::JAMI, "Alice");

View file

@ -3,8 +3,22 @@
<file>src/tst_WizardView.qml</file> <file>src/tst_WizardView.qml</file>
<file>src/tst_NewSwarmPage.qml</file> <file>src/tst_NewSwarmPage.qml</file>
<file>src/tst_MessageOptions.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/gif_test.gif</file>
<file>src/resources/gz_test.gz</file> <file>src/resources/gz_test.gz</file>
<file>src/resources/png_test.png</file> <file>src/resources/png_test.png</file>
<file>src/TestWrapper.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -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
}
}

View file

@ -39,9 +39,7 @@ ColumnLayout {
id: uut id: uut
property ViewManager viewManager: ViewManager {} property ViewManager viewManager: ViewManager {}
property ViewCoordinator viewCoordinator: ViewCoordinator { property ViewCoordinator viewCoordinator: ViewCoordinator {}
viewManager: uut.viewManager
}
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: root.width Layout.preferredWidth: root.width

View file

@ -32,11 +32,8 @@ OngoingCallPage {
height: 600 height: 600
property QtObject appWindow property QtObject appWindow
property ViewManager viewManager: ViewManager { property ViewManager viewManager: ViewManager {}
} property ViewCoordinator viewCoordinator: ViewCoordinator {}
property ViewCoordinator viewCoordinator: ViewCoordinator {
viewManager: uut.viewManager
}
TestCase { TestCase {
name: "Check basic visibility of action bar during a call" name: "Check basic visibility of action bar during a call"

View file

@ -16,40 +16,26 @@
*/ */
import QtQuick import QtQuick
import QtQuick.Controls
import QtTest import QtTest
import "../../../src/app/" import "../../../src/app/"
import "../../../src/app/mainview/components" import "../../../src/app/mainview/components"
WelcomePage { TestWrapper {
id: uut uut: WelcomePage {
TestCase {
name: "Open 'About Jami' popup"
width: 800 function test_openAboutPopup() {
height: 600 var aboutJamiButton = findChild(uut, "aboutJami")
aboutJamiButton.clicked()
// The appWindow, viewManager and viewCoordinator properties var aboutJamiPopup = viewManager.getView("AboutPopUp")
// are required in order for the "aboutJami" button to work. verify(aboutJamiPopup !== null, "About Jami popup should be created")
property Item appWindow: uut compare(aboutJamiPopup.visible, true, "About Jami popup should be visible")
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()
compare(viewManager.viewCount(), 1)
var aboutJamiPopup = viewManager.getView("AboutPopUp")
verify(aboutJamiPopup !== null)
compare(aboutJamiPopup.visible, true)
} }
} }
} }