Files
neochat/qml/main.qml
Nicolas Fella 55847cb9cc Refactor window handling code
Currently when we want to show/raise the window in reaction to the tray icon/notification being clicked etc we do this by emitting a signal on the controller.
This is connected to in main.qml, which does some things, then calls back to controller to do more things.

This is quite convoluted. Instead introduce a new class WindowController that is responsible for all things window, in particular showing/raising and config saving
2022-09-05 19:27:55 +00:00

454 lines
15 KiB
QML

// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Page 1.0
import NeoChat.Panel 1.0
Kirigami.ApplicationWindow {
id: root
property int columnWidth: Kirigami.Units.gridUnit * 13
minimumWidth: Kirigami.Units.gridUnit * 15
minimumHeight: Kirigami.Units.gridUnit * 15
visible: false // Will be overridden in Component.onCompleted
wideScreen: width > columnWidth * 5
pageStack.initialPage: LoadingPage {}
pageStack.globalToolBar.canContainHandles: true
property bool roomListLoaded: false
property RoomPage roomPage
Connections {
target: root.quitAction
function onTriggered() {
Qt.quit()
}
}
Loader {
active: Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile
source: Qt.resolvedUrl("qrc:/imports/NeoChat/Menu/GlobalMenu.qml")
}
// 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: Controller.saveWindowGeometry()
}
Connections {
id: saveWindowGeometryConnections
enabled: false // Disable on startup to avoid writing wrong values if the window is hidden
target: root
function onClosing() { Controller.saveWindowGeometry(); }
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 { }
}
Connections {
target: RoomManager
function onPushRoom(room, event) {
root.roomPage = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml");
root.roomPage.forceActiveFocus();
if (event.length > 0) {
roomPage.goToEvent(event);
}
}
function onReplaceRoom(room, event) {
const roomItem = pageStack.get(pageStack.depth - 1);
pageStack.currentIndex = pageStack.depth - 1;
root.roomPage.forceActiveFocus();
if (event.length > 0) {
roomItem.goToEvent(event);
}
}
function goToEvent(event) {
if (event.length > 0) {
roomItem.goToEvent(event);
}
roomItem.forceActiveFocus();
}
function onPushWelcomePage() {
// TODO
}
function onOpenRoomInNewWindow(room) {
const secondayWindow = roomWindow.createObject(applicationWindow(), {currentRoom: room});
secondayWindow.width = root.width - pageStack.get(0).width;
secondayWindow.show();
}
function onShowUserDetail(user) {
const roomItem = pageStack.get(pageStack.depth - 1);
roomItem.showUserDetail(user);
}
function onAskDirectChatConfirmation(user) {
askDirectChatConfirmationComponent.createObject(QQC2.ApplicationWindow.overlay, {
user: user,
}).open();
}
function onWarning(title, message) {
if (RoomManager.currentRoom) {
const roomItem = pageStack.get(pageStack.depth - 1);
roomItem.warning(title, message);
} else {
showPassiveNotification(i18n("Warning: %1", message));
}
}
}
function pushReplaceLayer(page, args) {
if (pageStack.layers.depth === 2) {
pageStack.layers.replace(page, args);
} else {
pageStack.layers.push(page, args);
}
}
contextDrawer: RoomDrawer {
id: contextDrawer
modal: !root.wideScreen || !enabled
onEnabledChanged: drawerOpen = enabled && !modal
onModalChanged: {
if (Config.autoRoomInfoDrawer) {
drawerOpen = !modal
dim = false
}
}
enabled: RoomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3
handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3 && (root.wideScreen || pageStack.currentIndex > 0)
}
readonly property int defaultPageWidth: Kirigami.Units.gridUnit * 17
readonly property int minPageWidth: Kirigami.Units.gridUnit * 10
readonly property int collapsedPageWidth: Kirigami.Units.gridUnit * 3 - Kirigami.Units.smallSpacing * 3
readonly property bool shouldUseSidebars: RoomManager.hasOpenRoom && (Config.roomListPageWidth > minPageWidth ? root.width >= Kirigami.Units.gridUnit * 35 : root.width > Kirigami.Units.gridUnit * 27) && roomListLoaded
readonly property int pageWidth: {
if (Config.roomListPageWidth === -1) {
return defaultPageWidth;
} else if (Config.roomListPageWidth < minPageWidth) {
return collapsedPageWidth;
} else {
return Config.roomListPageWidth;
}
}
pageStack.defaultColumnWidth: pageWidth
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
pageStack.globalToolBar.showNavigationButtons: pageStack.currentIndex > 0 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : 0
pageStack.columnView.columnResizeMode: shouldUseSidebars ? Kirigami.ColumnView.FixedColumns : Kirigami.ColumnView.SingleColumn
MouseArea {
visible: root.pageStack.wideMode
z: 500
anchors.top: parent.top
anchors.bottom: parent.bottom
x: root.pageStack.defaultColumnWidth - (width / 2)
width: 2
property int _lastX: -1
enabled: !Kirigami.Settings.isMobile
cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitHCursor : undefined
onPressed: _lastX = mouseX
onReleased: Config.save();
onPositionChanged: {
if (_lastX == -1) {
return;
}
if (mouse.x > _lastX) {
// we moved to the right
if (Config.roomListPageWidth === root.collapsedPageWidth && root.pageWidth + (mouse.x - _lastX) >= root.minPageWidth) {
// Here we get back directly to a more wide mode.
Config.roomListPageWidth = root.minPageWidth;
if (root.width < Kirigami.Units.gridUnit * 35) {
root.width = Kirigami.Units.gridUnit * 35;
}
} else if (Config.roomListPageWidth !== root.collapsedPageWidth) {
// Increase page width
Config.roomListPageWidth = Math.min(root.defaultPageWidth, root.pageWidth + (mouse.x - _lastX));
}
} else if (mouse.x < _lastX) {
const tmpWidth = root.pageWidth - (_lastX - mouse.x);
if (tmpWidth < root.minPageWidth) {
Config.roomListPageWidth = root.collapsedPageWidth;
} else {
Config.roomListPageWidth = tmpWidth;
}
}
}
}
globalDrawer: Kirigami.GlobalDrawer {
property bool hasLayer
contentItem.implicitWidth: columnWidth
isMenu: true
actions: [
Kirigami.Action {
text: i18n("Explore rooms")
icon.name: "compass"
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": Controller.activeConnection})
enabled: pageStack.layers.currentItem.title !== i18n("Explore Rooms") && Controller.accountCount > 0
},
Kirigami.Action {
text: i18n("Start a Chat")
icon.name: "irc-join-channel"
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {"connection": Controller.activeConnection})
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
},
Kirigami.Action {
text: i18n("Create a Room")
icon.name: "irc-join-channel"
onTriggered: {
let dialog = createRoomDialog.createObject(root.overlay);
dialog.open();
}
shortcut: StandardKey.New
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
},
Kirigami.Action {
text: i18n("Configure NeoChat...")
icon.name: "settings-configure"
onTriggered: pageStack.pushDialogLayer("qrc:/imports/NeoChat/Settings/SettingsPage.qml", {}, {
title: i18n("Configure")
})
enabled: pageStack.layers.currentItem.title !== i18n("Configure NeoChat...")
shortcut: StandardKey.Preferences
},
Kirigami.Action {
text: i18n("Logout")
icon.name: "list-remove-user"
enabled: Controller.accountCount > 0
onTriggered: Controller.logout(Controller.activeConnection, true)
},
Kirigami.Action {
text: i18n("Quit")
icon.name: "gtk-quit"
shortcut: StandardKey.Quit
onTriggered: Qt.quit()
}
]
}
Component.onCompleted: {
Controller.setBlur(pageStack, Config.blur && !Config.compactLayout);
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() {
Controller.setBlur(pageStack, Config.blur && !Config.compactLayout);
}
function onCompactLayoutChanged() {
Controller.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
Connections {
target: root.roomPage
function onSwitchRoomUp() {
roomList.goToPreviousRoom();
}
function onSwitchRoomDown() {
roomList.goToNextRoom();
}
}
}
}
Connections {
target: AccountRegistry
function onRowsRemoved() {
if (AccountRegistry.rowCount() === 0) {
RoomManager.reset();
pageStack.clear();
roomListLoaded = false;
pageStack.push("qrc:/imports/NeoChat/Page/WelcomePage.qml");
}
}
}
Connections {
target: Controller
function onInitiated() {
if (Controller.accountCount === 0) {
pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml", {});
} else if (!roomListLoaded) {
pageStack.replace(roomListComponent, {
activeConnection: Controller.activeConnection
});
roomListLoaded = true;
RoomManager.loadInitialRoom();
}
}
function onGlobalErrorOccured(error, detail) {
showPassiveNotification(i18n("%1: %2", error, detail));
}
function onUserConsentRequired(url) {
consentSheet.url = url
consentSheet.open()
}
}
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;
}
}
Connections {
target: Controller.activeConnection
function onDirectChatAvailable(directChat) {
RoomManager.enterRoom(Controller.activeConnection.room(directChat.id));
}
}
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 {}
}
Component {
id: roomWindow
RoomWindow {}
}
Component {
id: userDialog
UserDetailDialog {}
}
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 alias text: linkText.text
opacity: text.length > 0 ? 1 : 0
z: 20
x: 0
y: parent.height - implicitHeight
contentItem: QQC2.Label {
id: linkText
}
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
}
}