diff --git a/components/kube/qml/Kube.qml b/components/kube/qml/Kube.qml index 34c2a59e..3a970733 100644 --- a/components/kube/qml/Kube.qml +++ b/components/kube/qml/Kube.qml @@ -1,309 +1,311 @@ /* * Copyright (C) 2017 Michael Bohlender, * Copyright (C) 2017 Christian Mollekopf, * * 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 2 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. */ import QtQuick 2.7 import QtQuick.Layouts 1.3 import QtQuick.Window 2.0 import QtQuick.Controls 2.0 as Controls2 import org.kube.framework 1.0 as Kube Controls2.ApplicationWindow { id: app property int sidebarWidth: Kube.Units.gridUnit + Kube.Units.largeSpacing height: Screen.desktopAvailableHeight * 0.8 width: Screen.desktopAvailableWidth * 0.8 visible: true //Application default font font.family: Kube.Font.fontFamily //Application context property variant currentFolder onCurrentFolderChanged: { if (!!currentFolder) { Kube.Fabric.postMessage(Kube.Messages.synchronize, {"folder": currentFolder}) } } property variant currentAccount onCurrentAccountChanged: { if (!!currentAccount) { Kube.Fabric.postMessage(Kube.Messages.synchronize, {"accountId": currentAccount}) } } //Interval sync Timer { id: intervalSync //5min interval: 300000 running: !!app.currentFolder repeat: true onTriggered: Kube.Fabric.postMessage(Kube.Messages.synchronize, {"folder": app.currentFolder}) } Kube.StartupCheck { id: startupCheck } Accounts { } //Listener Kube.Listener { filter: Kube.Messages.accountSelection onMessageReceived: app.currentAccount = message.accountId } Kube.Listener { filter: Kube.Messages.folderSelection onMessageReceived: app.currentFolder = message.folder } Kube.Listener { filter: Kube.Messages.notification onMessageReceived: { - notificationPopup.notify(message.message); + if (message.message) { + notificationPopup.notify(message.message); + } } } //BEGIN Shortcuts Shortcut { sequence: StandardKey.Quit onActivated: Qt.quit() } Shortcut { onActivated: Kube.Fabric.postMessage(Kube.Messages.search, {}) sequence: StandardKey.Find } Shortcut { id: syncShortcut sequence: StandardKey.Refresh onActivated: !!app.currentFolder ? Kube.Fabric.postMessage(Kube.Messages.synchronize, {"folder": app.currentFolder}) : Kube.Fabric.postMessage(Kube.Messages.synchronize, {"accountId": app.currentAccount}) } //END Shortcuts //BEGIN background Rectangle { anchors.fill: parent color: Kube.Colors.backgroundColor } //END background //BEGIN Main content RowLayout { id: mainContent spacing: 0 anchors.fill: parent Rectangle { id: sideBar anchors { top: mainContent.top bottom: mainContent.bottom } width: app.sidebarWidth color: Kube.Colors.textColor Rectangle { anchors.right: parent.right width: 1 height: parent.height color: Kube.Colors.viewBackgroundColor opacity: 0.3 } Controls2.ButtonGroup { id: viewButtonGroup } Column { anchors { top: parent.top topMargin: Kube.Units.smallSpacing horizontalCenter: parent.horizontalCenter } spacing: Kube.Units.largeSpacing - Kube.Units.smallSpacing Repeater { model: Kube.ExtensionModel { id: extensionModel sortOrder: ["composer", "conversation", "people"] } Kube.IconButton { id: button iconName: model.icon onClicked: kubeViews.showView(model.name) activeFocusOnTab: true checkable: true Controls2.ButtonGroup.group: viewButtonGroup tooltip: model.tooltip checked: kubeViews.currentViewName == model.name } } } Column { anchors { bottom: parent.bottom bottomMargin: Kube.Units.smallSpacing horizontalCenter: parent.horizontalCenter } spacing: Kube.Units.largeSpacing - Kube.Units.smallSpacing Kube.Outbox { height: Kube.Units.gridUnit * 1.5 width: height Kube.ToolTip { text: qsTr("outbox") visible: parent.hovered } } Kube.IconButton { id: logButton iconName: Kube.Icons.info_inverted onClicked: kubeViews.showView("log") activeFocusOnTab: true checkable: true alert: kubeViews.getView("log").pendingError checked: kubeViews.currentViewName == "log" Controls2.ButtonGroup.group: viewButtonGroup tooltip: qsTr("logview") } Kube.IconButton { id: accountsButton iconName: Kube.Icons.menu_inverted onClicked: kubeViews.showView("accounts") activeFocusOnTab: true checkable: true checked: kubeViews.currentViewName == "accounts" Controls2.ButtonGroup.group: viewButtonGroup tooltip: qsTr("settings") } } } ViewManager { id: kubeViews anchors { top: mainContent.top bottom: mainContent.bottom } Layout.fillWidth: true extensionModel: extensionModel Component.onCompleted: { dontFocus = true showView("conversation") if (startupCheck.noAccount) { showView("accounts") } dontFocus = false } Kube.Listener { filter: Kube.Messages.reply onMessageReceived: kubeViews.replaceView("composer", {message: message.mail, loadType: Kube.ComposerController.Reply}) } Kube.Listener { filter: Kube.Messages.forward onMessageReceived: kubeViews.replaceView("composer", {message: message.mail, loadType: Kube.ComposerController.Forward}) } Kube.Listener { filter: Kube.Messages.edit onMessageReceived: kubeViews.replaceView("composer", {message: message.mail, loadType: Kube.ComposerController.Draft}) } Kube.Listener { filter: Kube.Messages.compose onMessageReceived: kubeViews.replaceView("composer", {newMessage: true, recipients: message.recipients}) } Kube.Listener { filter: Kube.Messages.requestAccountsConfiguration onMessageReceived: kubeViews.showView("accounts") } Kube.Listener { filter: Kube.Messages.componentDone onMessageReceived: { kubeViews.closeView() } } Kube.Listener { filter: Kube.Messages.requestLogin onMessageReceived: { var view = loginView.createObject(kubeViews, {accountId: message.accountId}) view.forceActiveFocus() } } Component { id: loginView Kube.Popup { id: popup property alias accountId: login.accountId visible: true parent: Controls2.ApplicationWindow.overlay height: app.height width: app.width - app.sidebarWidth x: app.sidebarWidth y: 0 modal: true closePolicy: Controls2.Popup.NoAutoClose Kube.LoginAccount { id: login anchors { fill: parent bottomMargin: Kube.Units.largeSpacing } onDone: { kubeViews.currentItem.forceActiveFocus() popup.destroy() } } } } } } //END Main content //BEGIN Notification Kube.NotificationPopup { id: notificationPopup anchors { left: parent.left leftMargin: app.sidebarWidth - 3 // so it does not align with the border bottom: parent.bottom bottomMargin: Kube.Units.gridUnit * 4 } } //END Notification } diff --git a/views/log/qml/View.qml b/views/log/qml/View.qml index 7e95e20f..9b2ac24a 100644 --- a/views/log/qml/View.qml +++ b/views/log/qml/View.qml @@ -1,411 +1,415 @@ /* * Copyright (C) 2017 Michael Bohlender, * Copyright (C) 2017 Christian Mollekopf, * * 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 2 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. */ import QtQuick 2.4 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.3 as Controls import QtQuick.Controls 2.0 as Controls2 import org.kube.framework 1.0 as Kube Controls.SplitView { id: root property bool pendingError: false; Controls2.StackView.onActivated: { root.pendingError = false; //Always select the latest notification listView.currentIndex = 0 } Item { id: accountList width: parent.width/3 Layout.fillHeight: true Kube.Listener { filter: Kube.Messages.notification onMessageReceived: { + //Ignore noise that we can't usefully render anyways + if (!message.message) { + return + } if (message.type == Kube.Notifications.error) { root.pendingError = true } var error = { timestamp: new Date(), message: message.message, details: message.details, resource: message.resource, // TODO: if we passed entities as a list, it would get // converted to a ListModel, in all likelihood because of // ListDelegate, which we should rewrite in C++ entities: {elements: message.entities} } if (logModel.count > 0) { var lastEntry = logModel.get(0) //Merge if we get an entry of the same subtype if (lastEntry.subtype && lastEntry.subtype == message.subtype) { logModel.set(0, {type: message.type, subtype: message.subtype, errors: [error].concat(lastEntry.errors)}) return } } logModel.insert(0, {type: message.type, subtype: message.subtype, errors: [error]}) } } Kube.Label { anchors.centerIn: parent visible: listView.count == 0 text: qsTr("Nothing here...") } Kube.ListView { id: listView anchors { fill: parent } clip: true model: ListModel { id: logModel objectName: "logModel" } onCurrentItemChanged: { var error = currentItem.currentData.errors.get(0) if (!!error.resource) { details.resourceId = error.resource } details.message = error.message + "\n" + error.details details.timestamp = error.timestamp if (!!currentItem.currentData.subtype) { details.subtype = currentItem.currentData.subtype } else { details.subtype = "" } details.entities = error.entities } delegate: Kube.ListDelegate { border.color: Kube.Colors.buttonColor border.width: 1 Kube.Label { id: description anchors { top: parent.top topMargin: Kube.Units.smallSpacing left: parent.left leftMargin: Kube.Units.largeSpacing } height: Kube.Units.gridUnit width: parent.width - Kube.Units.largeSpacing * 2 text: model.type == Kube.Notifications.error ? qsTr("Error") : qsTr("Info") } Kube.Label { id: message anchors { topMargin: Kube.Units.smallSpacing top: description.bottom left: parent.left leftMargin: Kube.Units.largeSpacing } height: Kube.Units.gridUnit width: parent.width - Kube.Units.largeSpacing * 2 maximumLineCount: 1 elide: Text.ElideRight color: Kube.Colors.disabledTextColor text: model.errors.get(0).message } Kube.Label { id: date anchors { right: parent.right bottom: parent.bottom rightMargin: Kube.Units.smallSpacing } text: Qt.formatDateTime(model.errors.get(0).timestamp, " hh:mm:ss dd MMM yyyy") font.italic: true color: Kube.Colors.disabledTextColor font.pointSize: Kube.Units.smallFontSize } } } } Item { id: details property string subtype: "" property date timestamp property string message: "" property string resourceId: "" property var entities: [] Kube.ModelIndexRetriever { id: retriever model: Kube.AccountsModel { resourceId: details.resourceId } } Loader { id: detailsLoader visible: message != "" clip: true anchors { fill: parent margins: Kube.Units.largeSpacing } property date timestamp: details.timestamp property string message: details.message property string resourceId: details.resourceId property string accountId: retriever.currentData ? retriever.currentData.accountId : "" property string accountName: retriever.currentData ? retriever.currentData.name : "" property var entities: details.entities function getComponent(subtype) { if (subtype == Kube.Notifications.loginError) { return loginErrorComponent } if (subtype == Kube.Notifications.hostNotFoundError) { return hostNotFoundErrorComponent } if (subtype == Kube.Notifications.connectionError) { return hostNotFoundErrorComponent } if (subtype == Kube.Notifications.transmissionError) { return transmissionErrorComponent } return detailsComponent } sourceComponent: getComponent(details.subtype) } } Component { id: detailsComponent Rectangle { color: Kube.Colors.viewBackgroundColor GridLayout { id: gridLayout Layout.minimumWidth: 0 anchors { top: parent.top left: parent.left right: parent.right } columns: 2 Kube.Label { text: qsTr("Account:") visible: accountName } Kube.Label { Layout.fillWidth: true text: accountName visible: accountName elide: Text.ElideRight } Kube.Label { text: qsTr("Account Id:") visible: accountId } Kube.Label { text: accountId visible: accountId Layout.fillWidth: true elide: Text.ElideRight } Kube.Label { text: qsTr("Resource Id:") visible: resourceId } Kube.Label { text: resourceId visible: resourceId Layout.fillWidth: true elide: Text.ElideRight } Kube.Label { text: qsTr("Timestamp:") } Kube.Label { text: Qt.formatDateTime(timestamp, " hh:mm:ss dd MMM yyyy") Layout.fillWidth: true elide: Text.ElideRight } Kube.Label { text: qsTr("Message:") Layout.alignment: Qt.AlignTop } Kube.Label { text: message Layout.fillWidth: true wrapMode: Text.Wrap } Item { Layout.columnSpan: 2 Layout.fillHeight: true Layout.fillWidth: true } } Kube.SelectableItem { layout: gridLayout } } } Component { id: loginErrorComponent Item { Column { anchors { top: parent.top left: parent.left right: parent.right } spacing: Kube.Units.largeSpacing Column { Kube.Heading { id: heading text: qsTr("Failed to login") color: Kube.Colors.warningColor } Kube.Label { id: subHeadline text: accountName + ": " + qsTr("Please check your credentials.") color: Kube.Colors.disabledTextColor wrapMode: Text.Wrap } } Kube.Button { text: qsTr("Change Password") onClicked: { Kube.Fabric.postMessage(Kube.Messages.componentDone, {}) Kube.Fabric.postMessage(Kube.Messages.requestLogin, {accountId: accountId}) } } } } } Component { id: hostNotFoundErrorComponent Item { Column { anchors { top: parent.top left: parent.left right: parent.right } spacing: Kube.Units.largeSpacing Column { Kube.Heading { id: heading text: qsTr("Host not found") color: Kube.Colors.warningColor } Kube.Label { id: subHeadline text: accountName + ": " + qsTr("Please check your network connection and settings.") color: Kube.Colors.disabledTextColor wrapMode: Text.Wrap } } Kube.Button { text: qsTr("Account settings") onClicked: { Kube.Fabric.postMessage(Kube.Messages.componentDone, {}) Kube.Fabric.postMessage(Kube.Messages.requestAccountsConfiguration, {}) } } } } } Component { id: transmissionErrorComponent Item { Column { anchors { top: parent.top left: parent.left right: parent.right } spacing: Kube.Units.largeSpacing Kube.Heading { id: heading text: qsTr("Failed to send the message.") color: Kube.Colors.warningColor } Column { spacing: Kube.Units.largeSpacing Repeater { model: Kube.MailListModel { entityId: entities.elements[0] } delegate: Column { id: subHeadline Kube.Label { text: qsTr("Account") + ": " + accountName color: Kube.Colors.disabledTextColor wrapMode: Text.Wrap } Kube.Label { text: qsTr("Subject") + ": " + model.subject color: Kube.Colors.disabledTextColor wrapMode: Text.Wrap } Kube.Label { text: qsTr("To") + ": " + model.to color: Kube.Colors.disabledTextColor wrapMode: Text.Wrap } Kube.Label { visible: !!model.cc text: qsTr("Cc") + ": " + model.cc; color: Kube.Colors.disabledTextColor wrapMode: Text.Wrap } } } } Kube.Button { text: qsTr("Try again") onClicked: { Kube.Fabric.postMessage(Kube.Messages.sendOutbox, {}) } } } } } }