// SPDX-FileCopyrightText: 2018-2020 Black Hat // SPDX-FileCopyrightText: 2020 Carl Schwan // SPDX-License-Identifier: GPL-3.0-only import QtQuick import QtQuick.Controls as QQC2 import org.kde.kirigami as Kirigami import org.kde.neochat import org.kde.neochat.login import org.kde.neochat.settings Kirigami.ApplicationWindow { id: root property NeoChatConnection connection: Controller.activeConnection minimumWidth: Kirigami.Units.gridUnit * 20 minimumHeight: Kirigami.Units.gridUnit * 15 visible: false // Will be overridden in Component.onCompleted wideScreen: width > Kirigami.Units.gridUnit * 65 pageStack { initialPage: WelcomePage { showExisting: true onConnectionChosen: root.load() } globalToolBar.canContainHandles: true globalToolBar { style: Kirigami.ApplicationHeaderStyle.ToolBar showNavigationButtons: pageStack.currentIndex > 0 || pageStack.layers.depth > 1 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : 0 } } onConnectionChanged: { CustomEmojiModel.connection = root.connection; MatrixImageProvider.connection = root.connection; SpaceHierarchyCache.connection = root.connection; if (ShareHandler.text && root.connection) { root.handleShare(); } } Connections { target: LoginHelper function onLoaded() { root.load(); } } Connections { target: root.quitAction function onTriggered() { Qt.quit(); } } Loader { active: Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile sourceComponent: GlobalMenu { connection: root.connection } } // This timer allows to batch update the window size change to reduce // the io load and also work around the fact that x/y/width/height are // changed when loading the page and overwrite the saved geometry from // the previous session. Timer { id: saveWindowGeometryTimer interval: 1000 onTriggered: WindowController.saveGeometry() } Connections { id: saveWindowGeometryConnections enabled: false // Disable on startup to avoid writing wrong values if the window is hidden target: root function onClosing() { WindowController.saveGeometry(); } function onWidthChanged() { saveWindowGeometryTimer.restart(); } function onHeightChanged() { saveWindowGeometryTimer.restart(); } function onXChanged() { saveWindowGeometryTimer.restart(); } function onYChanged() { saveWindowGeometryTimer.restart(); } } Loader { id: quickView active: !Kirigami.Settings.isMobile sourceComponent: QuickSwitcher { connection: root.connection } } Connections { target: RoomManager function onAskJoinRoom(room) { joinRoomDialog.createObject(applicationWindow(), { room: room, connection: root.connection }).open(); } function onShowUserDetail(user) { root.showUserDetail(user); } function goToEvent(event) { if (event.length > 0) { roomItem.goToEvent(event); } roomItem.forceActiveFocus(); } function onAskDirectChatConfirmation(user) { askDirectChatConfirmationComponent.createObject(QQC2.ApplicationWindow.overlay, { user: user }).open(); } function onExternalUrl(url) { let dialog = Qt.createComponent("org.kde.neochat", "ConfirmUrlDialog").createObject(applicationWindow()); dialog.link = url; dialog.open(); } } function pushReplaceLayer(page, args) { if (pageStack.layers.depth === 2) { pageStack.layers.replace(page, args); } else { pageStack.layers.push(page, args); } } function openRoomDrawer() { pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomDrawerPage'), { connection: root.connection }); } contextDrawer: RoomDrawer { id: contextDrawer // This is a memory for all user initiated actions on the drawer, i.e. clicking the button // It is used to ensure that user choice is remembered when changing pages and expanding and contracting the window width property bool drawerUserState: Config.autoRoomInfoDrawer connection: root.connection handleOpenIcon.source: "arrow-right" handleClosedIcon.source: "arrow-left" // Connect to the onClicked function of the RoomDrawer handle button Connections { target: contextDrawer.handle.children[0] function onClicked() { contextDrawer.drawerUserState = contextDrawer.drawerOpen; } } modal: (!root.wideScreen || !enabled) onEnabledChanged: drawerOpen = enabled && !modal onModalChanged: { if (Config.autoRoomInfoDrawer) { drawerOpen = !modal && drawerUserState; dim = false; } } enabled: RoomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3 && (pageStack.visibleItems.length > 1 || pageStack.currentIndex > 0) && !Kirigami.Settings.isMobile && root.pageStack.wideMode handleVisible: enabled } Component.onCompleted: { CustomEmojiModel.connection = root.connection; MatrixImageProvider.connection = root.connection; SpaceHierarchyCache.connection = root.connection; WindowController.setBlur(pageStack, Config.blur && !Config.compactLayout); if (ShareHandler.text && root.connection) { root.handleShare() } if (Config.minimizeToSystemTrayOnStartup && !Kirigami.Settings.isMobile && Controller.supportSystemTray && Config.systemTray) { restoreWindowGeometryConnections.enabled = true; // To restore window size and position } else { visible = true; saveWindowGeometryConnections.enabled = true; } } Connections { target: Config function onBlurChanged() { WindowController.setBlur(pageStack, Config.blur && !Config.compactLayout); } function onCompactLayoutChanged() { WindowController.setBlur(pageStack, Config.blur && !Config.compactLayout); } } // blur effect color: Config.blur && !Config.compactLayout ? "transparent" : Kirigami.Theme.backgroundColor // we need to apply the translucency effect separately on top of the color background: Rectangle { color: Config.blur && !Config.compactLayout ? Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 1 - Config.transparency) : "transparent" } Component { id: roomListComponent RoomListPage { id: roomList connection: root.connection Shortcut { sequences: ["Ctrl+PgUp", "Ctrl+Backtab", "Alt+Up"] onActivated: { roomList.goToPreviousRoom(); } } Shortcut { sequences: ["Ctrl+PgDown", "Ctrl+Tab", "Alt+Down"] onActivated: { roomList.goToNextRoom(); } } Shortcut { sequence: "Alt+Shift+Up" onActivated: { roomList.goToPreviousUnreadRoom(); } } Shortcut { sequence: "Alt+Shift+Down" onActivated: { roomList.goToNextUnreadRoom(); } } } } Connections { target: AccountRegistry function onRowsRemoved() { if (AccountRegistry.rowCount() === 0) { pageStack.clear(); pageStack.push(Qt.createComponent('org.kde.neochat', 'WelcomePage')); } } } Connections { target: Controller function onErrorOccured(error, detail) { showPassiveNotification(detail.length > 0 ? i18n("%1: %2", error, detail) : error); } } Connections { id: restoreWindowGeometryConnections enabled: false target: root function onVisibleChanged() { if (!visible) { return; } Controller.restoreWindowGeometry(root); restoreWindowGeometryConnections.enabled = false; // Only restore window geometry for the first time saveWindowGeometryConnections.enabled = true; } } Component { id: keyVerificationDialogComponent KeyVerificationDialog {} } Connections { target: root.connection function onDirectChatAvailable(directChat) { RoomManager.resolveResource(directChat.id); } function onNewKeyVerificationSession(session) { applicationWindow().pageStack.pushDialogLayer(keyVerificationDialogComponent, { session: session }, { title: i18nc("@title:window", "Session Verification") }); } function onUserConsentRequired(url) { let consent = consentSheetComponent.createObject(QQC2.ApplicationWindow.overlay); consent.url = url; consent.open(); } } Component { id: consentSheetComponent Kirigami.OverlaySheet { id: consentSheet property string url: "" title: i18n("User consent") QQC2.Label { id: label text: i18n("Your homeserver requires you to agree to its terms and conditions before being able to use it. Please click the button below to read them.") wrapMode: Text.WordWrap width: parent.width } footer: QQC2.Button { text: i18n("Open") onClicked: UrlHelper.openUrl(consentSheet.url) } } } Component { id: createRoomDialog CreateRoomDialog { connection: root.connection } } Component { id: joinRoomDialog JoinRoomDialog {} } Component { id: askDirectChatConfirmationComponent Kirigami.OverlaySheet { id: askDirectChatConfirmation required property var user parent: QQC2.ApplicationWindow.overlay title: i18n("Start a chat") contentItem: QQC2.Label { text: i18n("Do you want to start a chat with %1?", user.displayName) wrapMode: Text.WordWrap } footer: QQC2.DialogButtonBox { standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel onAccepted: { user.requestDirectChat(); askDirectChatConfirmation.close(); } onRejected: askDirectChatConfirmation.close() } } } property Item hoverLinkIndicator: QQC2.Control { parent: overlay.parent property string text opacity: linkText.text.length > 0 ? 1 : 0 z: 20 x: 0 Accessible.ignored: true y: parent.height - implicitHeight contentItem: QQC2.Label { id: linkText text: parent.text.startsWith("https://matrix.to/") ? "" : parent.text Accessible.description: i18nc("@info screenreader", "The currently selected link") } Kirigami.Theme.colorSet: Kirigami.Theme.View background: Rectangle { color: Kirigami.Theme.backgroundColor } } Shortcut { sequence: "Ctrl+Shift+," onActivated: { pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'NeoChatSettings'), { connection: root.connection }, { title: i18n("Configure"), width: Kirigami.Units.gridUnit * 50, height: Kirigami.Units.gridUnit * 42 }); } } Connections { target: ShareHandler function onTextChanged(): void { if (root.connection && ShareHandler.text.length > 0) { handleShare(); } } } function handleShare(): void { const dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), { connection: root.connection }, { title: i18nc("@title", "Share"), width: Kirigami.Units.gridUnit * 25 }) dialog.chosen.connect(function(targetRoomId) { RoomManager.resolveResource(targetRoomId) ShareHandler.room = targetRoomId dialog.closeDialog() }) } function showUserDetail(user) { userDetailDialog.createObject(root.QQC2.ApplicationWindow.window, { room: RoomManager.currentRoom ? RoomManager.currentRoom : null, user: RoomManager.currentRoom ? RoomManager.currentRoom.getUser(user.id) : QmlUtils.getUser(user), connection: root.connection }).open(); } function load() { pageStack.replace(roomListComponent); RoomManager.loadInitialRoom(); let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), { connection: root.connection }); roomPage.forceActiveFocus(); } Component { id: userDetailDialog UserDetailDialog {} } }