// SPDX-FileCopyrightText: 2018-2020 Black Hat // SPDX-FileCopyrightText: 2020 Carl Schwan // SPDX-License-Identifier: GPL-3.0-only import QtQuick import org.kde.kirigami as Kirigami import org.kde.config as KConfig import org.kde.neochat import org.kde.neochat.login import org.kde.neochat.settings Kirigami.ApplicationWindow { id: root property NeoChatConnection connection: Controller.activeConnection readonly property HoverLinkIndicator hoverLinkIndicator: linkIndicator property bool initialized: false readonly property QuickSwitcher quickSwitcher: QuickSwitcher { connection: root.connection window: root } title: { if (NeoChatConfig.windowTitleFocus) { return activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : ""); } else if (RoomManager.currentRoom) { return RoomManager.currentRoom.displayName; } else { return Application.displayName; } } 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() } columnView.columnResizeMode: pageStack.wideMode ? Kirigami.ColumnView.DynamicColumns : Kirigami.ColumnView.SingleColumn 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; SpaceHierarchyCache.connection = root.connection; NeoChatSettingsView.connection = root.connection; if (ShareHandler.text && root.connection) { root.handleShare(); } } Connections { target: LoginHelper function onLoaded() { root.load(); } } Connections { target: Registration 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 appWindow: root } } KConfig.WindowStateSaver { configGroupName: "MainWindow" } Connections { target: RoomManager function onCurrentRoomChanged() { if (RoomManager.currentRoom && root.pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) { let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage')); roomPage.forceActiveFocus(); roomPage.backRequested.connect(event => { RoomManager.clearCurrentRoom(); }); } } function onAskJoinRoom(room) { (Qt.createComponent("org.kde.neochat", "JoinRoomDialog").createObject(root, { room: room, connection: root.connection }) as JoinRoomDialog).open(); } function onShowUserDetail(user, room) { root.showUserDetail(user, room); } function onAskDirectChatConfirmation(user) { (Qt.createComponent("org.kde.neochat", "AskDirectChatConfirmation").createObject(this, { user: user }) as AskDirectChatConfirmation).open(); } function onExternalUrl(url) { (Qt.createComponent("org.kde.neochat", "ConfirmUrlDialog").createObject(this, { link: url }) as ConfirmUrlDialog).open(); } } function pushReplaceLayer(page, args) { if (pageStack.layers.depth === 2) { pageStack.layers.replace(page, args); } else { pageStack.layers.push(page, args); } } function openRoomDrawer() { const page = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomDrawerPage'), { connection: root.connection, room: RoomManager.currentRoom, userListModel: RoomManager.userListModel, mediaMessageFilterModel: RoomManager.mediaMessageFilterModel }); page.resolveResource.connect((idOrUri, action) => RoomManager.resolveResource(idOrUri, action)) } 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: NeoChatConfig.autoRoomInfoDrawer room: RoomManager.currentRoom connection: root.connection userListModel: RoomManager.userListModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel onResolveResource: (idOrUri, action) => RoomManager.resolveResource(idOrUri, action) roomDrawerWidth: NeoChatConfig.roomDrawerWidth onRoomDrawerWidthChanged: { NeoChatConfig.roomDrawerWidth = actualWidth; NeoChatConfig.save(); } handleClosedIcon.source: "documentinfo-symbolic" handleClosedToolTip: i18nc("@action:button", "Show Room Information") // Default icon is fine, only need to override the tooltip text handleOpenToolTip: i18nc("@action:button", "Close Room Information Drawer") // 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 (NeoChatConfig.autoRoomInfoDrawer) { drawerOpen = !modal && drawerUserState; dim = false; } } enabled: RoomManager.hasOpenRoom && root.pageStack.layers.depth < 2 && root.pageStack.depth < 3 && (root.pageStack.visibleItems.length > 1 || root.pageStack.currentIndex > 0) && !Kirigami.Settings.isMobile && root.pageStack.wideMode handleVisible: enabled } Component.onCompleted: { CustomEmojiModel.connection = root.connection; SpaceHierarchyCache.connection = root.connection; RoomSettingsView.window = root; NeoChatSettingsView.window = root; NeoChatSettingsView.connection = root.connection; WindowController.setBlur(pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout); if (ShareHandler.text && root.connection) { root.handleShare() } const hasSystemTray = Controller.supportSystemTray && NeoChatConfig.systemTray; if (Kirigami.Settings.isMobile || !(hasSystemTray && NeoChatConfig.minimizeToSystemTrayOnStartup)) { visible = true; } } Connections { target: NeoChatConfig function onBlurChanged() { WindowController.setBlur(root.pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout); } function onCompactLayoutChanged() { WindowController.setBlur(root.pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout); } } // blur effect color: NeoChatConfig.blur && !NeoChatConfig.compactLayout ? "transparent" : Kirigami.Theme.backgroundColor // we need to apply the translucency effect separately on top of the color background: Rectangle { color: NeoChatConfig.blur && !NeoChatConfig.compactLayout ? Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 1 - NeoChatConfig.transparency) : "transparent" } Component { id: roomListComponent RoomListPage { id: roomList onSearch: root.quickSwitcher.open() 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: root.connection function onLoggedOut(): void { root.pageStack.clear(); let page = root.pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), { showExisting: true, }) as WelcomePage; page.connectionChosen.connect(() => root.load()) } } Connections { target: AccountRegistry function onRowsRemoved() { if (AccountRegistry.rowCount() === 0) { root.pageStack.clear(); root.pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage')); } } } Connections { target: Controller function onErrorOccured(error) { root.showPassiveNotification(error, "short"); } } Connections { target: root.connection function onNewKeyVerificationSession(session) { root.pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "KeyVerificationDialog"), { session: session }, { title: i18nc("@title:window", "Session Verification") }); } function onUserConsentRequired(url) { (Qt.createComponent("org.kde.neochat", "ConsentDialog").createObject(this, { url: url }) as ConsentDialog).open(); } } HoverLinkIndicator { id: linkIndicator anchors { bottom: parent.bottom left: parent.left right: parent.right rightMargin: Kirigami.Units.largeSpacing } } Shortcut { sequence: "Ctrl+Shift+," onActivated: { NeoChatSettingsView.open(); } } Connections { target: ShareHandler function onTextChanged(): void { if (root.connection && ShareHandler.text.length > 0) { root.handleShare(); } } } function handleShare(): void { const dialog = root.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, room) { const dialog = Qt.createComponent("org.kde.neochat", "UserDetailDialog").createObject(root, { room: room, user: user, connection: root.connection, }) as UserDetailDialog; // FIXME: The reason why we don't want the focusedWindowItem for the room null case (aka QR codes) is because it will parent it to the soon-to-be-destroyed window item. // But this won't be a problem if we turn it into a Kirigami.Dialog or some other in-scene item, which it really should be. if (room != null) { dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again } dialog.open(); } function load() { pageStack.replace(roomListComponent); RoomManager.loadInitialRoom(); if (!Kirigami.Settings.isMobile) { let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage')); roomPage.forceActiveFocus(); } initialized = true; } }