From 6d726bb10386b3d7f5481d41b735ec06cb2163ad Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Tue, 9 Jan 2018 09:35:59 +0100 Subject: Install composer/converations/people as separate views and load them dynamically. --- CMakeLists.txt | 1 + components/kube/qml/AccountsView.qml | 143 ------ components/kube/qml/AddresseeListEditor.qml | 138 ------ components/kube/qml/ComposerView.qml | 533 ---------------------- components/kube/qml/Kube.qml | 228 +++------ components/kube/qml/LogView.qml | 329 ------------- components/kube/qml/LoginView.qml | 52 --- components/kube/qml/MailView.qml | 120 ----- components/kube/qml/PeopleView.qml | 38 -- components/kube/qml/ViewManager.qml | 84 ++++ components/kube/tests/tst_accountsview.qml | 37 -- components/kube/tests/tst_composerview.qml | 97 ---- components/kube/tests/tst_logview.qml | 65 --- components/kube/tests/tst_mailview.qml | 37 -- components/kube/tests/tst_peopleview.qml | 37 -- framework/qml/LoginAccount.qml | 4 +- framework/src/extensionmodel.cpp | 63 ++- framework/src/extensionmodel.h | 14 +- views/CMakeLists.txt | 11 + views/accounts/metadata.json | 4 + views/accounts/qml/View.qml | 143 ++++++ views/accounts/tests/tst_accountsview.qml | 37 ++ views/composer/metadata.json | 4 + views/composer/qml/AddresseeListEditor.qml | 138 ++++++ views/composer/qml/View.qml | 533 ++++++++++++++++++++++ views/composer/tests/tst_composerview.qml | 97 ++++ views/conversation/metadata.json | 4 + views/conversation/qml/View.qml | 120 +++++ views/conversation/tests/tst_conversationview.qml | 37 ++ views/log/metadata.json | 4 + views/log/qml/View.qml | 329 +++++++++++++ views/log/tests/tst_logview.qml | 65 +++ views/people/metadata.json | 4 + views/people/qml/View.qml | 38 ++ views/people/tests/tst_peopleview.qml | 37 ++ 35 files changed, 1823 insertions(+), 1802 deletions(-) delete mode 100644 components/kube/qml/AccountsView.qml delete mode 100644 components/kube/qml/AddresseeListEditor.qml delete mode 100644 components/kube/qml/ComposerView.qml delete mode 100644 components/kube/qml/LogView.qml delete mode 100644 components/kube/qml/LoginView.qml delete mode 100644 components/kube/qml/MailView.qml delete mode 100644 components/kube/qml/PeopleView.qml create mode 100644 components/kube/qml/ViewManager.qml delete mode 100644 components/kube/tests/tst_accountsview.qml delete mode 100644 components/kube/tests/tst_composerview.qml delete mode 100644 components/kube/tests/tst_logview.qml delete mode 100644 components/kube/tests/tst_mailview.qml delete mode 100644 components/kube/tests/tst_peopleview.qml create mode 100644 views/CMakeLists.txt create mode 100644 views/accounts/metadata.json create mode 100644 views/accounts/qml/View.qml create mode 100644 views/accounts/tests/tst_accountsview.qml create mode 100644 views/composer/metadata.json create mode 100644 views/composer/qml/AddresseeListEditor.qml create mode 100644 views/composer/qml/View.qml create mode 100644 views/composer/tests/tst_composerview.qml create mode 100644 views/conversation/metadata.json create mode 100644 views/conversation/qml/View.qml create mode 100644 views/conversation/tests/tst_conversationview.qml create mode 100644 views/log/metadata.json create mode 100644 views/log/qml/View.qml create mode 100644 views/log/tests/tst_logview.qml create mode 100644 views/people/metadata.json create mode 100644 views/people/qml/View.qml create mode 100644 views/people/tests/tst_peopleview.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 7933a9e6..48120389 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,5 +36,6 @@ add_subdirectory(framework) add_subdirectory(components) add_subdirectory(icons) add_subdirectory(applications) +add_subdirectory(views) add_subdirectory(accounts) add_subdirectory(tests) diff --git a/components/kube/qml/AccountsView.qml b/components/kube/qml/AccountsView.qml deleted file mode 100644 index 9b774907..00000000 --- a/components/kube/qml/AccountsView.qml +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2017 Michael Bohlender, - * - * 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 -import org.kube.framework 1.0 as Kube -import org.kube.components.accounts 1.0 as KubeAccounts - -FocusScope { - id: root - //Defines whether more than one account is supported. - property bool singleAccountMode: false - //Defines available account types. - property var availableAccountPlugins: ["kolabnow", "imap", "maildir", "gmail"] - - Controls.SplitView { - height: parent.height - width: parent.width - - Item { - id: accountList - width: Kube.Units.gridUnit * 12 - Layout.fillHeight: true - visible: !root.singleAccountMode - - Kube.PositiveButton { - id: newAccountButton - anchors { - top: parent.top - left: parent.left - right: parent.right - margins: Kube.Units.largeSpacing - } - text: qsTr("New Account") - - onClicked: accountWizard.open() - } - - Kube.ListView { - id: listView - - anchors { - top: newAccountButton.bottom - left: parent.left - right: parent.right - bottom: parent.bottom - topMargin: Kube.Units.largeSpacing - } - - clip: true - - model: Kube.AccountsModel {} - - onCurrentItemChanged: { - if (currentItem) { - edit.accountId = currentItem.currentData.accountId - } - } - - delegate: Kube.ListDelegate { - id: delegateRoot - - Kube.Label { - anchors { - verticalCenter: parent.verticalCenter - left: parent.left - leftMargin: Kube.Units.largeSpacing - } - width: parent.width - Kube.Units.largeSpacing * 2 - - text: model.name - color: delegateRoot.textColor - elide: Text.ElideRight - } - } - } - } - - Item { - height: parent.height - width: Kube.Units.gridUnit * 20 - Layout.fillWidth: true - - Kube.EditAccount { - id: edit - anchors { - fill: parent - bottomMargin: Kube.Units.largeSpacing - } - - canRemove: !root.singleAccountMode - - Component.onCompleted: { - //We don't have any accounts setup if accountId is empty, so we trigger the accountWizard - //FIXME: this assumes we load accounts synchronously, which we do right now. - if (accountId == "") { - //Require the setup to be completed since it's the first account - accountWizard.requireSetup = true - //Launch account wizard - accountWizard.open() - } - } - } - } - } - - onActiveFocusChanged: { - if (activeFocus && accountWizard.visible) { - accountWizard.forceActiveFocus() - } - } - - //BEGIN AccountWizard - KubeAccounts.AccountWizard { - id: accountWizard - - parent: ApplicationWindow.overlay - height: app.height - width: app.width - app.sidebarWidth - x: app.sidebarWidth - y: 0 - - availableAccountPlugins: root.availableAccountPlugins - } - //END AccountWizard -} diff --git a/components/kube/qml/AddresseeListEditor.qml b/components/kube/qml/AddresseeListEditor.qml deleted file mode 100644 index 8f9862e7..00000000 --- a/components/kube/qml/AddresseeListEditor.qml +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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.Controls 1.3 -import QtQuick.Layouts 1.1 - -import org.kube.framework 1.0 as Kube - -FocusScope { - id: root - property variant controller - property variant completer - property bool encrypt: false - - implicitHeight: listView.height + lineEdit.height - height: implicitHeight - - Column { - anchors.fill: parent - - spacing: Kube.Units.smallSpacing - - ListView { - id: listView - anchors { - left: parent.left - right: parent.right - } - height: contentHeight - spacing: Kube.Units.smallSpacing - model: controller.model - delegate: Rectangle { - height: Kube.Units.gridUnit + Kube.Units.smallSpacing * 2 //smallSpacing for padding - width: parent.width - color: Kube.Colors.buttonColor - Row { - anchors { - top: parent.top - bottom: parent.bottom - left: parent.left - right: removeButton.left - margins: Kube.Units.smallSpacing - } - spacing: Kube.Units.smallSpacing - Kube.Label { - id: label - anchors { - top: parent.top - } - text: model.name - elide: Text.ElideRight - } - Kube.Icon { - anchors { - top: parent.top - } - height: Kube.Units.gridUnit - width: height - visible: root.encrypt - iconName: model.keyFound ? Kube.Icons.secure: Kube.Icons.insecure - } - } - Kube.IconButton { - id: removeButton - anchors { - right: parent.right - verticalCenter: parent.verticalCenter - margins: Kube.Units.smallSpacing - } - height: Kube.Units.gridUnit - width: height - onClicked: root.controller.remove(model.id) - padding: 0 - iconName: Kube.Icons.remove - } - } - } - - FocusScope { - height: Kube.Units.gridUnit * Kube.Units.smallSpacing * 2 - width: parent.width - focus: true - - Kube.TextButton { - id: button - text: "+ " + qsTr("Add recipient") - textColor: Kube.Colors.highlightColor - focus: true - onClicked: { - lineEdit.visible = true - lineEdit.forceActiveFocus() - } - } - - Kube.AutocompleteLineEdit { - id: lineEdit - anchors { - left: parent.left - right: parent.right - } - visible: false - - placeholderText: "+ " + qsTr("Add recipient") - model: root.completer.model - onSearchTermChanged: root.completer.searchString = searchTerm - onAccepted: { - root.controller.add({name: text}); - clear() - visible = false - button.forceActiveFocus() - } - onAborted: { - clear() - visible = false - button.forceActiveFocus() - } - } - } - } -} diff --git a/components/kube/qml/ComposerView.qml b/components/kube/qml/ComposerView.qml deleted file mode 100644 index 1eb88bb4..00000000 --- a/components/kube/qml/ComposerView.qml +++ /dev/null @@ -1,533 +0,0 @@ -/* - * 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.Controls 1.3 -import QtQuick.Controls 2.0 as Controls2 -import QtQuick.Layouts 1.1 -import QtQuick.Dialogs 1.0 as Dialogs - -import org.kube.framework 1.0 as Kube - -Kube.View { - id: root - - property bool newMessage: false - property bool loadAsDraft: false - property variant message: {} - property variant recipients: [] - - resources: [ - Kube.ComposerController { - id: composerController - htmlBody: html.checked - sign: signCheckbox.checked - encrypt: encryptCheckbox.checked - onDone: Kube.Fabric.postMessage(Kube.Messages.componentDone, {}) - - property bool foundAllKeys: to.foundAllKeys && cc.foundAllKeys && bcc.foundAllKeys - - sendAction.enabled: composerController.accountId && composerController.subject && (!composerController.encrypt || composerController.foundAllKeys) && (!composerController.sign && !composerController.encrypt || composerController.foundPersonalKeys) - saveAsDraftAction.enabled: composerController.accountId - } - ] - - Component.onCompleted: loadMessage(root.message, root.loadAsDraft) - - Controls2.StackView.onActivated: { - Kube.Fabric.postMessage(Kube.Messages.synchronize, {"type": "mail", "specialPurpose": "drafts"}) - //For autocompletion - Kube.Fabric.postMessage(Kube.Messages.synchronize, {"type": "contacts"}) - } - - function loadMessage(message, loadAsDraft) { - if (message) { - composerController.loadMessage(message, loadAsDraft) - //Forward focus for replies directly - if (!loadAsDraft) { - subject.forceActiveFocus() - } - } else if (newMessage) { - composerController.clear() - if (root.recipients) { - for (var i = 0; i < root.recipients.length; ++i) { - composerController.to.add({name: root.recipients[i]}) - } - } - subject.forceActiveFocus() - } - } - - function closeFirstSplitIfNecessary() { - //Move the view forward - if (root.currentIndex == 0) { - root.incrementCurrentIndex() - } - } - - //Drafts - Rectangle { - - anchors { - top: parent.top - bottom: parent.bottom - } - - width: Kube.Units.gridUnit * 10 - Layout.minimumWidth: Kube.Units.gridUnit * 5 - - color: Kube.Colors.textColor - - ColumnLayout { - - anchors { - fill: parent - margins: Kube.Units.largeSpacing - } - - spacing: Kube.Units.largeSpacing - - Kube.PositiveButton { - objectName: "newMailButton" - anchors { - left: parent.left - right: parent.right - } - focus: true - text: qsTr("New Email") - onClicked: { - listView.currentIndex = -1 - composerController.clear() - subject.forceActiveFocus() - } - } - - Kube.Label{ - text: qsTr("Drafts") - color: Kube.Colors.highlightedTextColor - } - - Kube.ListView { - id: listView - activeFocusOnTab: true - - anchors { - left: parent.left - right: parent.right - } - - Layout.fillHeight: true - clip: true - currentIndex: -1 - highlightFollowsCurrentItem: false - - //BEGIN keyboard nav - onActiveFocusChanged: { - if (activeFocus && currentIndex < 0) { - currentIndex = 0 - } - } - Keys.onDownPressed: { - listView.incrementCurrentIndex() - } - Keys.onUpPressed: { - listView.decrementCurrentIndex() - } - //END keyboard nav - - onCurrentItemChanged: { - if (currentItem) { - root.loadMessage(currentItem.currentData.domainObject, true) - } - } - - model: Kube.MailListModel { - id: mailListModel - showDrafts: true - } - - delegate: Kube.ListDelegate { - id: delegateRoot - - color: Kube.Colors.textColor - border.width: 0 - - Item { - id: content - - anchors { - fill: parent - margins: Kube.Units.smallSpacing - } - - Kube.Label { - width: content.width - text: model.subject == "" ? "no subject" : model.subject - color: Kube.Colors.highlightedTextColor - maximumLineCount: 2 - wrapMode: Text.WrapAnywhere - elide: Text.ElideRight - } - - Kube.Label { - anchors { - right: parent.right - bottom: parent.bottom - } - text: Qt.formatDateTime(model.date, "dd MMM yyyy") - font.italic: true - color: Kube.Colors.disabledTextColor - font.pointSize: Kube.Units.smallFontSize - visible: !delegateRoot.hovered - } - } - Row { - id: buttons - - anchors { - right: parent.right - bottom: parent.bottom - margins: Kube.Units.smallSpacing - } - - visible: delegateRoot.hovered - spacing: Kube.Units.smallSpacing - opacity: 0.7 - - Kube.IconButton { - id: deleteButton - activeFocusOnTab: true - iconName: Kube.Icons.moveToTrash - visible: enabled - enabled: !!model.mail - onClicked: Kube.Fabric.postMessage(Kube.Messages.moveToTrash, {"mail": model.mail}) - } - } - } - } - } - } - - //Content - Rectangle { - Layout.fillWidth: true - Layout.minimumWidth: Kube.Units.gridUnit * 5 - anchors { - top: parent.top - bottom: parent.bottom - } - color: Kube.Colors.backgroundColor - - ColumnLayout { - anchors { - fill: parent - margins: Kube.Units.largeSpacing - leftMargin: Kube.Units.largeSpacing + Kube.Units.gridUnit * 2 - rightMargin: Kube.Units.largeSpacing + Kube.Units.gridUnit * 2 - } - - spacing: Kube.Units.smallSpacing - - Kube.TextField { - id: subject - Layout.fillWidth: true - activeFocusOnTab: true - - placeholderText: qsTr("Enter Subject...") - text: composerController.subject - onTextChanged: composerController.subject = text; - onActiveFocusChanged: { - if (activeFocus) { - closeFirstSplitIfNecessary() - } - } - } - - Flow { - id: attachments - - Layout.fillWidth: true - layoutDirection: Qt.RightToLeft - spacing: Kube.Units.smallSpacing - clip: true - - Repeater { - model: composerController.attachments.model - delegate: Kube.AttachmentDelegate { - name: model.filename - icon: model.iconname - clip: true - actionIcon: Kube.Icons.remove - onExecute: composerController.attachments.remove(model.id) - } - } - } - - RowLayout { - - spacing: Kube.Units.largeSpacing - - Kube.Switch { - id: html - text: checked ? qsTr("plain") : qsTr("html") - focusPolicy: Qt.TabFocus - focus: false - checked: composerController.htmlBody - } - - Row { - visible: html.checked - spacing: 1 - - Kube.IconButton { - iconName: Kube.Icons.bold - checkable: true - checked: textEditor.bold - onClicked: textEditor.bold = !textEditor.bold - focusPolicy: Qt.TabFocus - focus: false - } - Kube.IconButton { - iconName: Kube.Icons.italic - checkable: true - checked: textEditor.italic - onClicked: textEditor.italic = !textEditor.italic - focusPolicy: Qt.TabFocus - focus: false - } - Kube.IconButton { - iconName: Kube.Icons.underline - checkable: true - checked: textEditor.underline - onClicked: textEditor.underline = !textEditor.underline - focusPolicy: Qt.TabFocus - focus: false - } - } - - Item { - height: 1 - Layout.fillWidth: true - } - - Kube.Button { - text: qsTr("Attach file") - - onClicked: { - fileDialogComponent.createObject(parent) - } - - Component { - id: fileDialogComponent - Dialogs.FileDialog { - id: fileDialog - visible: true - title: "Choose a file to attach" - selectFolder: false - onAccepted: { - composerController.attachments.add({url: fileDialog.fileUrl}) - } - } - } - } - } - - Kube.TextEditor { - id: textEditor - - Layout.fillWidth: true - Layout.fillHeight: true - htmlEnabled: html.checked - - onActiveFocusChanged: closeFirstSplitIfNecessary() - Keys.onEscapePressed: recipients.forceActiveFocus() - initialText: composerController.body - onTextChanged: composerController.body = text; - } - } - } - - //Recepients - FocusScope { - id: recipients - anchors { - top: parent.top - bottom: parent.bottom - } - width: Kube.Units.gridUnit * 15 - activeFocusOnTab: true - - //background - Rectangle { - anchors.fill: parent - color: Kube.Colors.backgroundColor - - Rectangle { - height: parent.height - width: 1 - color: Kube.Colors.buttonColor - } - } - - //Content - ColumnLayout { - anchors { - fill: parent - margins: Kube.Units.largeSpacing - } - - spacing: Kube.Units.largeSpacing - ColumnLayout { - Layout.maximumWidth: parent.width - Layout.fillWidth: true - Layout.fillHeight: true - - Kube.Label { - text: qsTr("Sending Email to:") - } - - AddresseeListEditor { - Layout.preferredHeight: implicitHeight - Layout.fillWidth: true - focus: true - activeFocusOnTab: true - encrypt: composerController.encrypt - controller: composerController.to - completer: composerController.recipientCompleter - } - - Kube.Label { - text: qsTr("Sending Copy to (CC):") - } - AddresseeListEditor { - id: cc - Layout.preferredHeight: cc.implicitHeight - Layout.fillWidth: true - activeFocusOnTab: true - encrypt: composerController.encrypt - controller: composerController.cc - completer: composerController.recipientCompleter - } - - Kube.Label { - text: qsTr("Sending Secret Copy to (Bcc):") - } - AddresseeListEditor { - id: bcc - Layout.preferredHeight: bcc.implicitHeight - Layout.fillWidth: true - activeFocusOnTab: true - encrypt: composerController.encrypt - controller: composerController.bcc - completer: composerController.recipientCompleter - } - Item { - width: parent.width - Layout.fillHeight: true - } - } - - RowLayout { - enabled: composerController.foundPersonalKeys - Kube.CheckBox { - id: encryptCheckbox - checked: composerController.encrypt - } - Kube.Label { - text: qsTr("encrypt") - } - } - - RowLayout { - enabled: composerController.foundPersonalKeys - Kube.CheckBox { - id: signCheckbox - checked: composerController.sign - } - Kube.Label { - text: qsTr("sign") - } - } - Kube.Label { - visible: !composerController.foundPersonalKeys - Layout.maximumWidth: parent.width - text: qsTr("Encryption is not available because your personal key has not been found.") - wrapMode: Text.Wrap - } - - RowLayout { - Layout.maximumWidth: parent.width - width: parent.width - height: Kube.Units.gridUnit - - Kube.Button { - width: saveDraftButton.width - text: qsTr("Discard") - onClicked: Kube.Fabric.postMessage(Kube.Messages.componentDone, {}) - } - - Kube.Button { - id: saveDraftButton - - text: qsTr("Save as Draft") - enabled: composerController.saveAsDraftAction.enabled - onClicked: { - composerController.saveAsDraftAction.execute() - } - } - } - - ColumnLayout { - Layout.maximumWidth: parent.width - Layout.fillWidth: true - Kube.Label { - id: fromLabel - text: qsTr("You are sending this from:") - } - - Kube.ComboBox { - id: identityCombo - - width: parent.width - Kube.Units.largeSpacing * 2 - - model: composerController.identitySelector.model - textRole: "address" - Layout.fillWidth: true - onCurrentIndexChanged: { - composerController.identitySelector.currentIndex = currentIndex - } - } - } - - Kube.PositiveButton { - objectName: "sendButton" - id: sendButton - - width: parent.width - - text: qsTr("Send") - enabled: composerController.sendAction.enabled - onClicked: { - composerController.sendAction.execute() - } - } - } - }//FocusScope -} diff --git a/components/kube/qml/Kube.qml b/components/kube/qml/Kube.qml index 9a31ba58..be3f7e34 100644 --- a/components/kube/qml/Kube.qml +++ b/components/kube/qml/Kube.qml @@ -86,35 +86,6 @@ Controls2.ApplicationWindow { } } - Kube.Listener { - filter: Kube.Messages.reply - onMessageReceived: { - kubeViews.openComposerWithMail(message.mail, false) - } - } - - Kube.Listener { - filter: Kube.Messages.edit - onMessageReceived: { - kubeViews.openComposerWithMail(message.mail, true) - } - } - - Kube.Listener { - filter: Kube.Messages.compose - onMessageReceived: kubeViews.openComposer(true, message.recipients) - } - - Kube.Listener { - filter: Kube.Messages.requestLogin - onMessageReceived: kubeViews.setLoginView(message.accountId) - } - - Kube.Listener { - filter: Kube.Messages.requestAccountsConfiguration - onMessageReceived: kubeViews.setAccountsView() - } - //BEGIN Shortcuts Shortcut { sequence: StandardKey.Quit @@ -174,48 +145,20 @@ Controls2.ApplicationWindow { spacing: Kube.Units.largeSpacing - Kube.Units.smallSpacing - Kube.IconButton { - id: composerButton - iconName: Kube.Icons.edit_inverted - onClicked: kubeViews.openComposer(false, []) - activeFocusOnTab: true - checkable: true - Controls2.ButtonGroup.group: viewButtonGroup - tooltip: qsTr("composer") - } - - Kube.IconButton { - id: mailButton - iconName: Kube.Icons.mail_inverted - onClicked: kubeViews.setMailView() - activeFocusOnTab: true - checkable: true - checked: true - Controls2.ButtonGroup.group: viewButtonGroup - tooltip: qsTr("mails") - } - - Kube.IconButton { - id: peopleButton - iconName: Kube.Icons.user_inverted - onClicked: kubeViews.setPeopleView() - activeFocusOnTab: true - checkable: true - Controls2.ButtonGroup.group: viewButtonGroup - tooltip: qsTr("people") - } Repeater { - model: Kube.ExtensionModel {} + model: Kube.ExtensionModel { + id: extensionModel + sortOrder: ["composer", "conversation", "people"] + } Kube.IconButton { + id: button iconName: model.icon - onClicked: { - var component = Qt.createComponent(model.source) - kubeViews.pushView(component, {}) - } + onClicked: kubeViews.showView(model.name) activeFocusOnTab: true checkable: true Controls2.ButtonGroup.group: viewButtonGroup tooltip: model.tooltip + checked: kubeViews.currentViewName == model.name } } } @@ -240,10 +183,11 @@ Controls2.ApplicationWindow { Kube.IconButton { id: logButton iconName: Kube.Icons.info_inverted - onClicked: kubeViews.setLogView() + onClicked: kubeViews.showView("log") activeFocusOnTab: true checkable: true - alert: logView.pendingError + alert: kubeViews.getView("log").pendingError + checked: kubeViews.currentViewName == "log" Controls2.ButtonGroup.group: viewButtonGroup tooltip: qsTr("logview") } @@ -251,141 +195,95 @@ Controls2.ApplicationWindow { Kube.IconButton { id: accountsButton iconName: Kube.Icons.menu_inverted - onClicked: kubeViews.setAccountsView() + onClicked: kubeViews.showView("accounts") activeFocusOnTab: true checkable: true + checked: kubeViews.currentViewName == "accounts" Controls2.ButtonGroup.group: viewButtonGroup tooltip: qsTr("settings") } } } - Controls2.StackView { + ViewManager { id: kubeViews - anchors { top: mainContent.top bottom: mainContent.bottom } Layout.fillWidth: true - Kube.Listener { - filter: Kube.Messages.componentDone - onMessageReceived: { - //Return to the mailview if we try to pop everything off - if (kubeViews.depth == 1) { - kubeViews.setMailView() - } else { - kubeViews.pop(Controls2.StackView.Immediate) - } - } - } - - onCurrentItemChanged: { - if (currentItem) { - currentItem.forceActiveFocus() - } - } + extensionModel: extensionModel Component.onCompleted: { - //Setup the initial item stack - if (!currentItem) { - setMailView() - if (startupCheck.noAccount) { - setAccountsView() - } + dontFocus = true + showView("conversation") + if (startupCheck.noAccount) { + showView("accounts") } + dontFocus = false } - ///Replace the current view (we can't go back to the old view, and we destroy the old view) - function replaceView(view) { - if (currentItem != view) { - kubeViews.replace(null, view, {}, Controls2.StackView.Immediate) - } - } - - ///Push a new view on the stack (the old view remains, and we can go back once done) - function pushView(view, properties) { - kubeViews.push(view, properties, Controls2.StackView.Immediate) - } - - //TODO replacing here while a composer is open is destructive - function setPeopleView() { - replaceView(peopleView) + Kube.Listener { + filter: Kube.Messages.reply + onMessageReceived: kubeViews.replaceView("composer", {message: message.mail, loadAsDraft: false}) } - function setMailView() { - replaceView(mailView) + Kube.Listener { + filter: Kube.Messages.edit + onMessageReceived: kubeViews.replaceView("composer", {message: message.mail, loadAsDraft: true}) } - function setAccountsView() { - pushView(accountsView, {}) + Kube.Listener { + filter: Kube.Messages.compose + onMessageReceived: kubeViews.replaceView("composer", {newMessage: true, recipients: message.recipients}) } - function setLogView() { - replaceView(logView) + Kube.Listener { + filter: Kube.Messages.requestAccountsConfiguration + onMessageReceived: kubeViews.showView("accounts") } - function setLoginView(account) { - if (currentItem != loginView) { - pushView(loginView, {accountId: account}) + Kube.Listener { + filter: Kube.Messages.componentDone + onMessageReceived: { + kubeViews.closeView() } } - function openComposer(newMessage, recipients) { - pushView(composerView, {newMessage: newMessage, recipients: recipients}) - } - - function openComposerWithMail(mail, openAsDraft) { - pushView(composerView, {message: mail, loadAsDraft: openAsDraft}) - } - - - //These items are not visible until pushed onto the stack, so we keep them in resources instead of items - resources: [ - //Not components so we maintain state - MailView { - id: mailView - anchors.fill: parent - Controls2.StackView.onActivated: mailButton.checked = true - Controls2.StackView.onDeactivated: mailButton.checked = false - }, - PeopleView { - id: peopleView - anchors.fill: parent - Controls2.StackView.onActivated: peopleButton.checked = true - Controls2.StackView.onDeactivated: peopleButton.checked = false - }, - //Not a component because otherwise we can't log stuff - LogView { - id: logView - anchors.fill: parent - Controls2.StackView.onActivated: logButton.checked = true - Controls2.StackView.onDeactivated: logButton.checked = false - } - ] - //A component so it's always destroyed when we're done - Component { - id: composerView - ComposerView { - anchors.fill: parent - Controls2.StackView.onActivated: composerButton.checked = true - Controls2.StackView.onDeactivated: composerButton.checked = false - } - } - Component { - id: accountsView - AccountsView { - anchors.fill: parent - Controls2.StackView.onActivated: accountsButton.checked = true - Controls2.StackView.onDeactivated: accountsButton.checked = false + Kube.Listener { + filter: Kube.Messages.requestLogin + onMessageReceived: { + loginView.createObject(kubeViews, {accountId: message.accountId}) } } + Component { id: loginView - LoginView { - anchors.fill: parent + 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: { + popup.close() + kubeViews.currentItem.forceActiveFocus() + } + } } } + } } //END Main content diff --git a/components/kube/qml/LogView.qml b/components/kube/qml/LogView.qml deleted file mode 100644 index 4ae1a67c..00000000 --- a/components/kube/qml/LogView.qml +++ /dev/null @@ -1,329 +0,0 @@ -/* - * 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: { - if (message.type == Kube.Notifications.error) { - root.pendingError = true - } - var error = {timestamp: new Date(), message: message.message, details: message.details, resource: message.resource} - 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 = "" - } - } - - 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: "" - - 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 : "" - - function getComponent(subtype) { - if (subtype == Kube.Notifications.loginError) { - return loginErrorComponent - } - if (subtype == Kube.Notifications.hostNotFoundError) { - return hostNotFoundErrorComponent - } - if (subtype == Kube.Notifications.connectionError) { - return hostNotFoundErrorComponent - } - 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, {}) - } - } - } - } - } -} diff --git a/components/kube/qml/LoginView.qml b/components/kube/qml/LoginView.qml deleted file mode 100644 index dbbed11c..00000000 --- a/components/kube/qml/LoginView.qml +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 2.0 -import org.kube.framework 1.0 as Kube - -FocusScope { - id: root - property alias accountId: login.accountId - onActiveFocusChanged: { - if (activeFocus) { - popup.forceActiveFocus() - } - } - - Kube.Popup { - id: popup - visible: true - parent: ApplicationWindow.overlay - height: app.height - width: app.width - app.sidebarWidth - x: app.sidebarWidth - y: 0 - modal: true - closePolicy: Popup.NoAutoClose - Kube.LoginAccount { - id: login - anchors { - fill: parent - bottomMargin: Kube.Units.largeSpacing - } - } - } -} diff --git a/components/kube/qml/MailView.qml b/components/kube/qml/MailView.qml deleted file mode 100644 index 8b2b0caf..00000000 --- a/components/kube/qml/MailView.qml +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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.Controls 1.3 -import QtQuick.Controls 2.0 as Controls2 -import QtQuick.Layouts 1.1 - -import org.kube.framework 1.0 as Kube - -FocusScope { - SplitView { - anchors.fill: parent - Rectangle { - width: Kube.Units.gridUnit * 10 - Layout.fillHeight: parent.height - color: Kube.Colors.textColor - - Kube.PositiveButton { - id: newMailButton - - anchors { - top: parent.top - left: parent.left - right: parent.right - margins: Kube.Units.largeSpacing - } - focus: true - text: qsTr("New Email") - onClicked: Kube.Fabric.postMessage(Kube.Messages.compose, {}) - } - - Kube.InlineAccountSwitcher { - id: accountFolderview - activeFocusOnTab: true - anchors { - top: newMailButton.bottom - topMargin: Kube.Units.largeSpacing - bottom: statusBarContainer.top - left: newMailButton.left - right: parent.right - } - } - - Item { - id: statusBarContainer - anchors { - topMargin: Kube.Units.smallSpacing - bottom: parent.bottom - left: parent.left - right: parent.right - } - height: childrenRect.height - - Rectangle { - id: border - visible: statusBar.visible - anchors { - right: parent.right - left: parent.left - margins: Kube.Units.smallSpacing - } - height: 1 - color: Kube.Colors.viewBackgroundColor - opacity: 0.3 - } - Kube.StatusBar { - id: statusBar - accountId: accountFolderview.currentAccount - height: Kube.Units.gridUnit * 2 - anchors { - top: border.bottom - left: statusBarContainer.left - right: statusBarContainer.right - } - } - } - } - - Rectangle { - width: Kube.Units.gridUnit * 18 - Layout.fillHeight: parent.height - - color: "transparent" - border.width: 1 - border.color: Kube.Colors.buttonColor - - Kube.MailListView { - id: mailListView - anchors.fill: parent - activeFocusOnTab: true - Layout.minimumWidth: Kube.Units.gridUnit * 10 - } - } - - Kube.ConversationView { - id: mailView - Layout.fillWidth: true - Layout.fillHeight: parent.height - activeFocusOnTab: true - } - } -} diff --git a/components/kube/qml/PeopleView.qml b/components/kube/qml/PeopleView.qml deleted file mode 100644 index 3f1b9261..00000000 --- a/components/kube/qml/PeopleView.qml +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.Controls 2.1 -import org.kube.framework 1.0 as Kube - -Item { - - StackView.onActivated: { - Kube.Fabric.postMessage(Kube.Messages.synchronize, {"type": "contacts"}) - } - - Kube.People { - id: people - anchors { - fill: parent - margins: Kube.Units.smallSpacing - } - } -} diff --git a/components/kube/qml/ViewManager.qml b/components/kube/qml/ViewManager.qml new file mode 100644 index 00000000..6874107a --- /dev/null +++ b/components/kube/qml/ViewManager.qml @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 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.Controls 2.0 +/* +* TODO +* * Only replace composer if necessary (on reply, edit draft, ...) +* * Shutdown procedure to save draft before destruction +* * Separate logging from view and and make accessible to log (initialize()) call? +*/ +StackView { + id: root + property string currentViewName: currentItem ? currentItem.objectName : "" + property variant extensionModel: null + property bool dontFocus: false + + property var viewDict: new Object + function getView(name, replaceView) { + if (!replaceView && name in viewDict) { + var item = viewDict[name] + if (item) { + return item + } + } + var v = Qt.createComponent(extensionModel.findSource(name, "View.qml")) + v = v.createObject(root) + viewDict[name] = v + return v; + } + + onCurrentItemChanged: { + if (currentItem && !dontFocus) { + currentItem.forceActiveFocus() + } + } + + function showOrReplaceView(name, properties, replace) { + if (currentItem && currentItem.objectName == name) { + return + } + if (root.depth > 0) { + root.pop(StackView.Immediate) + } + var view = getView(name, replace) + var item = push(view, properties, StackView.Immediate) + item.parent = root + item.anchors.fill = root + item.objectName = name + } + + function showView(name, properties) { + showOrReplaceView(name, properties, false) + } + + function replaceView(name, properties) { + showOrReplaceView(name, properties, true) + } + + function closeView() { + //The initial view remains + if (kubeViews.depth > 1) { + var item = kubeViews.pop(StackView.Immediate) + viewDict[item.objectName] = null + item.destroy() + } + } +} diff --git a/components/kube/tests/tst_accountsview.qml b/components/kube/tests/tst_accountsview.qml deleted file mode 100644 index 428bb4cc..00000000 --- a/components/kube/tests/tst_accountsview.qml +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2017 Christian Mollekopf - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details - * - * You should have received a copy of the GNU Library 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 QtTest 1.0 -import "../qml" - -TestCase { - id: testCase - width: 400 - height: 400 - name: "AccountsView" - - AccountsView { - id: accountsView - } - - function test_start() { - verify(accountsView) - } -} diff --git a/components/kube/tests/tst_composerview.qml b/components/kube/tests/tst_composerview.qml deleted file mode 100644 index 67db6ef6..00000000 --- a/components/kube/tests/tst_composerview.qml +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2017 Christian Mollekopf - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details - * - * You should have received a copy of the GNU Library 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 QtTest 1.0 -import "../qml" -import org.kube.framework 1.0 as Kube -import org.kube.test 1.0 - -TestCase { - id: testCase - width: 400 - height: 400 - name: "ComposerView" - when: windowShown - - Component { - id:composerComponent - ComposerView { - focus: true - } - } - - function test_1start() { - var composer = createTemporaryObject(composerComponent, testCase, {}) - verify(composer) - } - - function test_2verifyInitialFocus() { - var composer = createTemporaryObject(composerComponent, testCase, {}) - var newMailButton = findChild(composer, "newMailButton"); - verify(newMailButton) - verify(newMailButton.activeFocus) - } - - function test_3sendMessage() { - var initialState = { - accounts: [{ - id: "account1", - }], - identities: [{ - account: "account1", - name: "Test Identity", - address: "identity@example.org" - }], - resources: [{ - id: "resource1", - account: "account1", - type: "dummy" - }, - { - id: "resource2", - account: "account1", - type: "mailtransport" - }], - mails:[{ - resource: "resource1", - subject: "subject", - body: "body", - to: ["to@example.org"], - cc: ["cc@example.org"], - bcc: ["bcc@example.org"], - draft: true - }] - } - TestStore.setup(initialState) - var composer = createTemporaryObject(composerComponent, testCase, {}) - - var createdMail = TestStore.load("mail", {resource: "resource1"}) - - var loadAsDraft = true - composer.loadMessage(createdMail, loadAsDraft) - var sendMailButton = findChild(composer, "sendButton") - verify(sendMailButton) - tryVerify(function(){ return sendMailButton.enabled }) - sendMailButton.clicked() - - tryVerify(function(){ return TestStore.load("mail", {resource: "resource2"}) }) - tryVerify(function(){ return !TestStore.load("mail", {resource: "resource1"}) }) - } -} diff --git a/components/kube/tests/tst_logview.qml b/components/kube/tests/tst_logview.qml deleted file mode 100644 index d3326db1..00000000 --- a/components/kube/tests/tst_logview.qml +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2017 Christian Mollekopf - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details - * - * You should have received a copy of the GNU Library 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.Controls 2.0 -import QtQuick.Window 2.1 -import QtTest 1.0 -import org.kube.framework 1.0 as Kube -import "../qml" - -TestCase { - id: logviewTestcase - width: 400 - height: 400 - name: "LogView" - - LogView { - id: logView - } - - function test_logview() { - var listModel = findChild(logView, "logModel"); - verify(listModel) - compare(listModel.count, 0) - //ignore progress - Kube.Fabric.postMessage(Kube.Messages.progressNotification, {}) - compare(listModel.count, 0) - - Kube.Fabric.postMessage(Kube.Messages.notification, {type: Kube.Notifications.info, message: "foobar", resource: "resource"}) - compare(listModel.count, 1) - compare(logView.pendingError, false) - - Kube.Fabric.postMessage(Kube.Messages.notification, {"type": Kube.Notifications.error, message: "foobar", resource: "resource"}) - compare(listModel.count, 2) - compare(logView.pendingError, true) - compare(listModel.get(0).type, Kube.Notifications.error) - compare(listModel.get(0).errors.count, 1) - compare(listModel.get(0).errors.get(0).message, "foobar") - compare(listModel.get(0).errors.get(0).resource, "resource") - - Kube.Fabric.postMessage(Kube.Messages.notification, {"type": Kube.Notifications.error, "subtype": "merge", message: "merge1", resource: "resource1"}) - compare(listModel.count, 3) - Kube.Fabric.postMessage(Kube.Messages.notification, {"type": Kube.Notifications.error, "subtype": "merge", message: "merge2", resource: "resource2"}) - compare(listModel.count, 3) - compare(listModel.get(0).errors.count, 2) - compare(listModel.get(0).errors.get(0).message, "merge2") - compare(listModel.get(0).errors.get(0).resource, "resource2") - } -} diff --git a/components/kube/tests/tst_mailview.qml b/components/kube/tests/tst_mailview.qml deleted file mode 100644 index f31d574d..00000000 --- a/components/kube/tests/tst_mailview.qml +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2017 Christian Mollekopf - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details - * - * You should have received a copy of the GNU Library 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 QtTest 1.0 -import "../qml" - -TestCase { - id: testCase - width: 400 - height: 400 - name: "MailView" - - MailView { - id: mailView - } - - function test_start() { - verify(mailView) - } -} diff --git a/components/kube/tests/tst_peopleview.qml b/components/kube/tests/tst_peopleview.qml deleted file mode 100644 index e224b527..00000000 --- a/components/kube/tests/tst_peopleview.qml +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2017 Christian Mollekopf - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details - * - * You should have received a copy of the GNU Library 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 QtTest 1.0 -import "../qml" - -TestCase { - id: testCase - width: 400 - height: 400 - name: "PeopleView" - - PeopleView { - id: peopleView - } - - function test_start() { - verify(peopleView) - } -} diff --git a/framework/qml/LoginAccount.qml b/framework/qml/LoginAccount.qml index f02050ae..261746d5 100644 --- a/framework/qml/LoginAccount.qml +++ b/framework/qml/LoginAccount.qml @@ -26,10 +26,12 @@ Item { property string accountId property bool canRemove: true + signal done() + function login() { loader.item.login() Kube.Fabric.postMessage(Kube.Messages.synchronize, {"accountId": loader.item.accountIdentifier}); - Kube.Fabric.postMessage(Kube.Messages.componentDone, {}) + root.done() } Kube.AccountFactory { diff --git a/framework/src/extensionmodel.cpp b/framework/src/extensionmodel.cpp index f64a0e17..e3fab7d8 100644 --- a/framework/src/extensionmodel.cpp +++ b/framework/src/extensionmodel.cpp @@ -22,12 +22,17 @@ #include #include #include +#include +#include using namespace Kube; ExtensionModel::ExtensionModel(QObject *parent) : QSortFilterProxyModel(parent) { + setDynamicSortFilter(true); + sort(0, Qt::DescendingOrder); + setFilterCaseSensitivity(Qt::CaseInsensitive); QTimer::singleShot(0, this, &ExtensionModel::load); } @@ -36,8 +41,7 @@ QHash ExtensionModel::roleNames() const return { {Name, "name"}, {Tooltip, "tooltip"}, - {Icon, "icon"}, - {Source, "source"} + {Icon, "icon"} }; } @@ -48,21 +52,55 @@ void ExtensionModel::load() auto engine = qmlEngine(this); Q_ASSERT(engine); for (const auto &path : engine->importPathList()) { - QDir dir{path + "/org/kube/viewextensions"}; + QDir dir{path + "/org/kube/views"}; for (const auto &pluginName : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { - auto viewPath = dir.path() + pluginName + "/View.qml"; - qWarning() << "Plugin path: " << dir.path() + pluginName + "/View.qml"; + const auto pluginPath = dir.path() + "/" + pluginName; + mPaths.insert(pluginName, pluginPath); auto item = new QStandardItem; - item->setData(viewPath, Source); item->setData(pluginName, Name); item->setData(pluginName, Tooltip); - item->setData("document-decrypt", Icon); + item->setData("kdocumentinfo-inverted", Icon); + + if (QFileInfo::exists(pluginPath + "/metadata.json")) { + QFile file{pluginPath + "/metadata.json"}; + file.open(QIODevice::ReadOnly); + auto json = QJsonDocument::fromJson(file.readAll()); + auto map = json.object().toVariantMap(); + item->setData(map.value("icon").toString(), Icon); + item->setData(map.value("tooltip").toString(), Tooltip); + if (map.value("hidden", false).toBool()) { + delete item; + continue; + } + } + model->appendRow(item); } } setSourceModel(model); } +QString ExtensionModel::findSource(const QString &extensionName, const QString &sourceName) +{ + if (mPaths.isEmpty()) { + load(); + } + return mPaths.value(extensionName) + "/" + sourceName; +} + +void ExtensionModel::setSortOrder(const QVariantList &order) +{ + mSortOrder.clear(); + for (const auto &e : order) { + mSortOrder << e.toString(); + } +} + +QVariantList ExtensionModel::sortOrder() const +{ + return {}; +} + QVariant ExtensionModel::data(const QModelIndex &idx, int role) const { return QSortFilterProxyModel::data(idx, role); @@ -70,5 +108,14 @@ QVariant ExtensionModel::data(const QModelIndex &idx, int role) const bool ExtensionModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - return QSortFilterProxyModel::lessThan(left, right); + auto leftIndex = mSortOrder.indexOf(left.data(Name).toString()); + auto rightIndex = mSortOrder.indexOf(right.data(Name).toString()); + if (leftIndex >= 0 && rightIndex >= 0) { + //Higher index is less than + return leftIndex > rightIndex; + } + if (leftIndex < 0 && rightIndex < 0) { + return QSortFilterProxyModel::lessThan(left, right); + } + return leftIndex < rightIndex; } diff --git a/framework/src/extensionmodel.h b/framework/src/extensionmodel.h index 6e984005..8e5580f7 100644 --- a/framework/src/extensionmodel.h +++ b/framework/src/extensionmodel.h @@ -27,6 +27,8 @@ namespace Kube { class ExtensionModel : public QSortFilterProxyModel { Q_OBJECT + + Q_PROPERTY(QVariantList sortOrder WRITE setSortOrder READ sortOrder CONSTANT) public: ExtensionModel(QObject *parent = Q_NULLPTR); @@ -39,14 +41,22 @@ public: enum Roles { Name = Qt::UserRole + 1, Tooltip, - Icon, - Source + Icon }; QHash roleNames() const Q_DECL_OVERRIDE; + void setSortOrder(const QVariantList &order); + QVariantList sortOrder() const; + + Q_INVOKABLE QString findSource(const QString &extensionName, const QString &sourceName); + private slots: void load(); + +private: + QStringList mSortOrder; + QHash mPaths; }; } diff --git a/views/CMakeLists.txt b/views/CMakeLists.txt new file mode 100644 index 00000000..4004e310 --- /dev/null +++ b/views/CMakeLists.txt @@ -0,0 +1,11 @@ +macro(install_view name) + install(DIRECTORY ${name}/qml/ DESTINATION ${QML_INSTALL_DIR}/org/kube/views/${name}) + install(FILES ${name}/metadata.json DESTINATION ${QML_INSTALL_DIR}/org/kube/views/${name}) + add_test(NAME viewtest-${name} COMMAND kubetestrunner WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${name}/tests) +endmacro() + +install_view(composer) +install_view(conversation) +install_view(people) +install_view(log) +install_view(accounts) diff --git a/views/accounts/metadata.json b/views/accounts/metadata.json new file mode 100644 index 00000000..55091f97 --- /dev/null +++ b/views/accounts/metadata.json @@ -0,0 +1,4 @@ +{ + "tooltip": "Account configuration.", + "hidden": true +} diff --git a/views/accounts/qml/View.qml b/views/accounts/qml/View.qml new file mode 100644 index 00000000..9b774907 --- /dev/null +++ b/views/accounts/qml/View.qml @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 Michael Bohlender, + * + * 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 +import org.kube.framework 1.0 as Kube +import org.kube.components.accounts 1.0 as KubeAccounts + +FocusScope { + id: root + //Defines whether more than one account is supported. + property bool singleAccountMode: false + //Defines available account types. + property var availableAccountPlugins: ["kolabnow", "imap", "maildir", "gmail"] + + Controls.SplitView { + height: parent.height + width: parent.width + + Item { + id: accountList + width: Kube.Units.gridUnit * 12 + Layout.fillHeight: true + visible: !root.singleAccountMode + + Kube.PositiveButton { + id: newAccountButton + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: Kube.Units.largeSpacing + } + text: qsTr("New Account") + + onClicked: accountWizard.open() + } + + Kube.ListView { + id: listView + + anchors { + top: newAccountButton.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + topMargin: Kube.Units.largeSpacing + } + + clip: true + + model: Kube.AccountsModel {} + + onCurrentItemChanged: { + if (currentItem) { + edit.accountId = currentItem.currentData.accountId + } + } + + delegate: Kube.ListDelegate { + id: delegateRoot + + Kube.Label { + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: Kube.Units.largeSpacing + } + width: parent.width - Kube.Units.largeSpacing * 2 + + text: model.name + color: delegateRoot.textColor + elide: Text.ElideRight + } + } + } + } + + Item { + height: parent.height + width: Kube.Units.gridUnit * 20 + Layout.fillWidth: true + + Kube.EditAccount { + id: edit + anchors { + fill: parent + bottomMargin: Kube.Units.largeSpacing + } + + canRemove: !root.singleAccountMode + + Component.onCompleted: { + //We don't have any accounts setup if accountId is empty, so we trigger the accountWizard + //FIXME: this assumes we load accounts synchronously, which we do right now. + if (accountId == "") { + //Require the setup to be completed since it's the first account + accountWizard.requireSetup = true + //Launch account wizard + accountWizard.open() + } + } + } + } + } + + onActiveFocusChanged: { + if (activeFocus && accountWizard.visible) { + accountWizard.forceActiveFocus() + } + } + + //BEGIN AccountWizard + KubeAccounts.AccountWizard { + id: accountWizard + + parent: ApplicationWindow.overlay + height: app.height + width: app.width - app.sidebarWidth + x: app.sidebarWidth + y: 0 + + availableAccountPlugins: root.availableAccountPlugins + } + //END AccountWizard +} diff --git a/views/accounts/tests/tst_accountsview.qml b/views/accounts/tests/tst_accountsview.qml new file mode 100644 index 00000000..096ca9eb --- /dev/null +++ b/views/accounts/tests/tst_accountsview.qml @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details + * + * You should have received a copy of the GNU Library 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 QtTest 1.0 +import "../qml" + +TestCase { + id: testCase + width: 400 + height: 400 + name: "AccountsView" + + View { + id: accountsView + } + + function test_start() { + verify(accountsView) + } +} diff --git a/views/composer/metadata.json b/views/composer/metadata.json new file mode 100644 index 00000000..c4654fba --- /dev/null +++ b/views/composer/metadata.json @@ -0,0 +1,4 @@ +{ + "icon": "document-edit-inverted", + "tooltip": "Compose new messages." +} diff --git a/views/composer/qml/AddresseeListEditor.qml b/views/composer/qml/AddresseeListEditor.qml new file mode 100644 index 00000000..8f9862e7 --- /dev/null +++ b/views/composer/qml/AddresseeListEditor.qml @@ -0,0 +1,138 @@ +/* + * 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.Controls 1.3 +import QtQuick.Layouts 1.1 + +import org.kube.framework 1.0 as Kube + +FocusScope { + id: root + property variant controller + property variant completer + property bool encrypt: false + + implicitHeight: listView.height + lineEdit.height + height: implicitHeight + + Column { + anchors.fill: parent + + spacing: Kube.Units.smallSpacing + + ListView { + id: listView + anchors { + left: parent.left + right: parent.right + } + height: contentHeight + spacing: Kube.Units.smallSpacing + model: controller.model + delegate: Rectangle { + height: Kube.Units.gridUnit + Kube.Units.smallSpacing * 2 //smallSpacing for padding + width: parent.width + color: Kube.Colors.buttonColor + Row { + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + right: removeButton.left + margins: Kube.Units.smallSpacing + } + spacing: Kube.Units.smallSpacing + Kube.Label { + id: label + anchors { + top: parent.top + } + text: model.name + elide: Text.ElideRight + } + Kube.Icon { + anchors { + top: parent.top + } + height: Kube.Units.gridUnit + width: height + visible: root.encrypt + iconName: model.keyFound ? Kube.Icons.secure: Kube.Icons.insecure + } + } + Kube.IconButton { + id: removeButton + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + margins: Kube.Units.smallSpacing + } + height: Kube.Units.gridUnit + width: height + onClicked: root.controller.remove(model.id) + padding: 0 + iconName: Kube.Icons.remove + } + } + } + + FocusScope { + height: Kube.Units.gridUnit * Kube.Units.smallSpacing * 2 + width: parent.width + focus: true + + Kube.TextButton { + id: button + text: "+ " + qsTr("Add recipient") + textColor: Kube.Colors.highlightColor + focus: true + onClicked: { + lineEdit.visible = true + lineEdit.forceActiveFocus() + } + } + + Kube.AutocompleteLineEdit { + id: lineEdit + anchors { + left: parent.left + right: parent.right + } + visible: false + + placeholderText: "+ " + qsTr("Add recipient") + model: root.completer.model + onSearchTermChanged: root.completer.searchString = searchTerm + onAccepted: { + root.controller.add({name: text}); + clear() + visible = false + button.forceActiveFocus() + } + onAborted: { + clear() + visible = false + button.forceActiveFocus() + } + } + } + } +} diff --git a/views/composer/qml/View.qml b/views/composer/qml/View.qml new file mode 100644 index 00000000..1eb88bb4 --- /dev/null +++ b/views/composer/qml/View.qml @@ -0,0 +1,533 @@ +/* + * 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.Controls 1.3 +import QtQuick.Controls 2.0 as Controls2 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.0 as Dialogs + +import org.kube.framework 1.0 as Kube + +Kube.View { + id: root + + property bool newMessage: false + property bool loadAsDraft: false + property variant message: {} + property variant recipients: [] + + resources: [ + Kube.ComposerController { + id: composerController + htmlBody: html.checked + sign: signCheckbox.checked + encrypt: encryptCheckbox.checked + onDone: Kube.Fabric.postMessage(Kube.Messages.componentDone, {}) + + property bool foundAllKeys: to.foundAllKeys && cc.foundAllKeys && bcc.foundAllKeys + + sendAction.enabled: composerController.accountId && composerController.subject && (!composerController.encrypt || composerController.foundAllKeys) && (!composerController.sign && !composerController.encrypt || composerController.foundPersonalKeys) + saveAsDraftAction.enabled: composerController.accountId + } + ] + + Component.onCompleted: loadMessage(root.message, root.loadAsDraft) + + Controls2.StackView.onActivated: { + Kube.Fabric.postMessage(Kube.Messages.synchronize, {"type": "mail", "specialPurpose": "drafts"}) + //For autocompletion + Kube.Fabric.postMessage(Kube.Messages.synchronize, {"type": "contacts"}) + } + + function loadMessage(message, loadAsDraft) { + if (message) { + composerController.loadMessage(message, loadAsDraft) + //Forward focus for replies directly + if (!loadAsDraft) { + subject.forceActiveFocus() + } + } else if (newMessage) { + composerController.clear() + if (root.recipients) { + for (var i = 0; i < root.recipients.length; ++i) { + composerController.to.add({name: root.recipients[i]}) + } + } + subject.forceActiveFocus() + } + } + + function closeFirstSplitIfNecessary() { + //Move the view forward + if (root.currentIndex == 0) { + root.incrementCurrentIndex() + } + } + + //Drafts + Rectangle { + + anchors { + top: parent.top + bottom: parent.bottom + } + + width: Kube.Units.gridUnit * 10 + Layout.minimumWidth: Kube.Units.gridUnit * 5 + + color: Kube.Colors.textColor + + ColumnLayout { + + anchors { + fill: parent + margins: Kube.Units.largeSpacing + } + + spacing: Kube.Units.largeSpacing + + Kube.PositiveButton { + objectName: "newMailButton" + anchors { + left: parent.left + right: parent.right + } + focus: true + text: qsTr("New Email") + onClicked: { + listView.currentIndex = -1 + composerController.clear() + subject.forceActiveFocus() + } + } + + Kube.Label{ + text: qsTr("Drafts") + color: Kube.Colors.highlightedTextColor + } + + Kube.ListView { + id: listView + activeFocusOnTab: true + + anchors { + left: parent.left + right: parent.right + } + + Layout.fillHeight: true + clip: true + currentIndex: -1 + highlightFollowsCurrentItem: false + + //BEGIN keyboard nav + onActiveFocusChanged: { + if (activeFocus && currentIndex < 0) { + currentIndex = 0 + } + } + Keys.onDownPressed: { + listView.incrementCurrentIndex() + } + Keys.onUpPressed: { + listView.decrementCurrentIndex() + } + //END keyboard nav + + onCurrentItemChanged: { + if (currentItem) { + root.loadMessage(currentItem.currentData.domainObject, true) + } + } + + model: Kube.MailListModel { + id: mailListModel + showDrafts: true + } + + delegate: Kube.ListDelegate { + id: delegateRoot + + color: Kube.Colors.textColor + border.width: 0 + + Item { + id: content + + anchors { + fill: parent + margins: Kube.Units.smallSpacing + } + + Kube.Label { + width: content.width + text: model.subject == "" ? "no subject" : model.subject + color: Kube.Colors.highlightedTextColor + maximumLineCount: 2 + wrapMode: Text.WrapAnywhere + elide: Text.ElideRight + } + + Kube.Label { + anchors { + right: parent.right + bottom: parent.bottom + } + text: Qt.formatDateTime(model.date, "dd MMM yyyy") + font.italic: true + color: Kube.Colors.disabledTextColor + font.pointSize: Kube.Units.smallFontSize + visible: !delegateRoot.hovered + } + } + Row { + id: buttons + + anchors { + right: parent.right + bottom: parent.bottom + margins: Kube.Units.smallSpacing + } + + visible: delegateRoot.hovered + spacing: Kube.Units.smallSpacing + opacity: 0.7 + + Kube.IconButton { + id: deleteButton + activeFocusOnTab: true + iconName: Kube.Icons.moveToTrash + visible: enabled + enabled: !!model.mail + onClicked: Kube.Fabric.postMessage(Kube.Messages.moveToTrash, {"mail": model.mail}) + } + } + } + } + } + } + + //Content + Rectangle { + Layout.fillWidth: true + Layout.minimumWidth: Kube.Units.gridUnit * 5 + anchors { + top: parent.top + bottom: parent.bottom + } + color: Kube.Colors.backgroundColor + + ColumnLayout { + anchors { + fill: parent + margins: Kube.Units.largeSpacing + leftMargin: Kube.Units.largeSpacing + Kube.Units.gridUnit * 2 + rightMargin: Kube.Units.largeSpacing + Kube.Units.gridUnit * 2 + } + + spacing: Kube.Units.smallSpacing + + Kube.TextField { + id: subject + Layout.fillWidth: true + activeFocusOnTab: true + + placeholderText: qsTr("Enter Subject...") + text: composerController.subject + onTextChanged: composerController.subject = text; + onActiveFocusChanged: { + if (activeFocus) { + closeFirstSplitIfNecessary() + } + } + } + + Flow { + id: attachments + + Layout.fillWidth: true + layoutDirection: Qt.RightToLeft + spacing: Kube.Units.smallSpacing + clip: true + + Repeater { + model: composerController.attachments.model + delegate: Kube.AttachmentDelegate { + name: model.filename + icon: model.iconname + clip: true + actionIcon: Kube.Icons.remove + onExecute: composerController.attachments.remove(model.id) + } + } + } + + RowLayout { + + spacing: Kube.Units.largeSpacing + + Kube.Switch { + id: html + text: checked ? qsTr("plain") : qsTr("html") + focusPolicy: Qt.TabFocus + focus: false + checked: composerController.htmlBody + } + + Row { + visible: html.checked + spacing: 1 + + Kube.IconButton { + iconName: Kube.Icons.bold + checkable: true + checked: textEditor.bold + onClicked: textEditor.bold = !textEditor.bold + focusPolicy: Qt.TabFocus + focus: false + } + Kube.IconButton { + iconName: Kube.Icons.italic + checkable: true + checked: textEditor.italic + onClicked: textEditor.italic = !textEditor.italic + focusPolicy: Qt.TabFocus + focus: false + } + Kube.IconButton { + iconName: Kube.Icons.underline + checkable: true + checked: textEditor.underline + onClicked: textEditor.underline = !textEditor.underline + focusPolicy: Qt.TabFocus + focus: false + } + } + + Item { + height: 1 + Layout.fillWidth: true + } + + Kube.Button { + text: qsTr("Attach file") + + onClicked: { + fileDialogComponent.createObject(parent) + } + + Component { + id: fileDialogComponent + Dialogs.FileDialog { + id: fileDialog + visible: true + title: "Choose a file to attach" + selectFolder: false + onAccepted: { + composerController.attachments.add({url: fileDialog.fileUrl}) + } + } + } + } + } + + Kube.TextEditor { + id: textEditor + + Layout.fillWidth: true + Layout.fillHeight: true + htmlEnabled: html.checked + + onActiveFocusChanged: closeFirstSplitIfNecessary() + Keys.onEscapePressed: recipients.forceActiveFocus() + initialText: composerController.body + onTextChanged: composerController.body = text; + } + } + } + + //Recepients + FocusScope { + id: recipients + anchors { + top: parent.top + bottom: parent.bottom + } + width: Kube.Units.gridUnit * 15 + activeFocusOnTab: true + + //background + Rectangle { + anchors.fill: parent + color: Kube.Colors.backgroundColor + + Rectangle { + height: parent.height + width: 1 + color: Kube.Colors.buttonColor + } + } + + //Content + ColumnLayout { + anchors { + fill: parent + margins: Kube.Units.largeSpacing + } + + spacing: Kube.Units.largeSpacing + ColumnLayout { + Layout.maximumWidth: parent.width + Layout.fillWidth: true + Layout.fillHeight: true + + Kube.Label { + text: qsTr("Sending Email to:") + } + + AddresseeListEditor { + Layout.preferredHeight: implicitHeight + Layout.fillWidth: true + focus: true + activeFocusOnTab: true + encrypt: composerController.encrypt + controller: composerController.to + completer: composerController.recipientCompleter + } + + Kube.Label { + text: qsTr("Sending Copy to (CC):") + } + AddresseeListEditor { + id: cc + Layout.preferredHeight: cc.implicitHeight + Layout.fillWidth: true + activeFocusOnTab: true + encrypt: composerController.encrypt + controller: composerController.cc + completer: composerController.recipientCompleter + } + + Kube.Label { + text: qsTr("Sending Secret Copy to (Bcc):") + } + AddresseeListEditor { + id: bcc + Layout.preferredHeight: bcc.implicitHeight + Layout.fillWidth: true + activeFocusOnTab: true + encrypt: composerController.encrypt + controller: composerController.bcc + completer: composerController.recipientCompleter + } + Item { + width: parent.width + Layout.fillHeight: true + } + } + + RowLayout { + enabled: composerController.foundPersonalKeys + Kube.CheckBox { + id: encryptCheckbox + checked: composerController.encrypt + } + Kube.Label { + text: qsTr("encrypt") + } + } + + RowLayout { + enabled: composerController.foundPersonalKeys + Kube.CheckBox { + id: signCheckbox + checked: composerController.sign + } + Kube.Label { + text: qsTr("sign") + } + } + Kube.Label { + visible: !composerController.foundPersonalKeys + Layout.maximumWidth: parent.width + text: qsTr("Encryption is not available because your personal key has not been found.") + wrapMode: Text.Wrap + } + + RowLayout { + Layout.maximumWidth: parent.width + width: parent.width + height: Kube.Units.gridUnit + + Kube.Button { + width: saveDraftButton.width + text: qsTr("Discard") + onClicked: Kube.Fabric.postMessage(Kube.Messages.componentDone, {}) + } + + Kube.Button { + id: saveDraftButton + + text: qsTr("Save as Draft") + enabled: composerController.saveAsDraftAction.enabled + onClicked: { + composerController.saveAsDraftAction.execute() + } + } + } + + ColumnLayout { + Layout.maximumWidth: parent.width + Layout.fillWidth: true + Kube.Label { + id: fromLabel + text: qsTr("You are sending this from:") + } + + Kube.ComboBox { + id: identityCombo + + width: parent.width - Kube.Units.largeSpacing * 2 + + model: composerController.identitySelector.model + textRole: "address" + Layout.fillWidth: true + onCurrentIndexChanged: { + composerController.identitySelector.currentIndex = currentIndex + } + } + } + + Kube.PositiveButton { + objectName: "sendButton" + id: sendButton + + width: parent.width + + text: qsTr("Send") + enabled: composerController.sendAction.enabled + onClicked: { + composerController.sendAction.execute() + } + } + } + }//FocusScope +} diff --git a/views/composer/tests/tst_composerview.qml b/views/composer/tests/tst_composerview.qml new file mode 100644 index 00000000..eac391c1 --- /dev/null +++ b/views/composer/tests/tst_composerview.qml @@ -0,0 +1,97 @@ +/* + * Copyright 2017 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details + * + * You should have received a copy of the GNU Library 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 QtTest 1.0 +import "../qml" +import org.kube.framework 1.0 as Kube +import org.kube.test 1.0 + +TestCase { + id: testCase + width: 400 + height: 400 + name: "ComposerView" + when: windowShown + + Component { + id:composerComponent + View { + focus: true + } + } + + function test_1start() { + var composer = createTemporaryObject(composerComponent, testCase, {}) + verify(composer) + } + + function test_2verifyInitialFocus() { + var composer = createTemporaryObject(composerComponent, testCase, {}) + var newMailButton = findChild(composer, "newMailButton"); + verify(newMailButton) + verify(newMailButton.activeFocus) + } + + function test_3sendMessage() { + var initialState = { + accounts: [{ + id: "account1", + }], + identities: [{ + account: "account1", + name: "Test Identity", + address: "identity@example.org" + }], + resources: [{ + id: "resource1", + account: "account1", + type: "dummy" + }, + { + id: "resource2", + account: "account1", + type: "mailtransport" + }], + mails:[{ + resource: "resource1", + subject: "subject", + body: "body", + to: ["to@example.org"], + cc: ["cc@example.org"], + bcc: ["bcc@example.org"], + draft: true + }] + } + TestStore.setup(initialState) + var composer = createTemporaryObject(composerComponent, testCase, {}) + + var createdMail = TestStore.load("mail", {resource: "resource1"}) + + var loadAsDraft = true + composer.loadMessage(createdMail, loadAsDraft) + var sendMailButton = findChild(composer, "sendButton") + verify(sendMailButton) + tryVerify(function(){ return sendMailButton.enabled }) + sendMailButton.clicked() + + tryVerify(function(){ return TestStore.load("mail", {resource: "resource2"}) }) + tryVerify(function(){ return !TestStore.load("mail", {resource: "resource1"}) }) + } +} diff --git a/views/conversation/metadata.json b/views/conversation/metadata.json new file mode 100644 index 00000000..870ff2aa --- /dev/null +++ b/views/conversation/metadata.json @@ -0,0 +1,4 @@ +{ + "icon": "mail-message-inverted", + "tooltip": "Follow conversations." +} diff --git a/views/conversation/qml/View.qml b/views/conversation/qml/View.qml new file mode 100644 index 00000000..8b2b0caf --- /dev/null +++ b/views/conversation/qml/View.qml @@ -0,0 +1,120 @@ +/* + * 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.Controls 1.3 +import QtQuick.Controls 2.0 as Controls2 +import QtQuick.Layouts 1.1 + +import org.kube.framework 1.0 as Kube + +FocusScope { + SplitView { + anchors.fill: parent + Rectangle { + width: Kube.Units.gridUnit * 10 + Layout.fillHeight: parent.height + color: Kube.Colors.textColor + + Kube.PositiveButton { + id: newMailButton + + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: Kube.Units.largeSpacing + } + focus: true + text: qsTr("New Email") + onClicked: Kube.Fabric.postMessage(Kube.Messages.compose, {}) + } + + Kube.InlineAccountSwitcher { + id: accountFolderview + activeFocusOnTab: true + anchors { + top: newMailButton.bottom + topMargin: Kube.Units.largeSpacing + bottom: statusBarContainer.top + left: newMailButton.left + right: parent.right + } + } + + Item { + id: statusBarContainer + anchors { + topMargin: Kube.Units.smallSpacing + bottom: parent.bottom + left: parent.left + right: parent.right + } + height: childrenRect.height + + Rectangle { + id: border + visible: statusBar.visible + anchors { + right: parent.right + left: parent.left + margins: Kube.Units.smallSpacing + } + height: 1 + color: Kube.Colors.viewBackgroundColor + opacity: 0.3 + } + Kube.StatusBar { + id: statusBar + accountId: accountFolderview.currentAccount + height: Kube.Units.gridUnit * 2 + anchors { + top: border.bottom + left: statusBarContainer.left + right: statusBarContainer.right + } + } + } + } + + Rectangle { + width: Kube.Units.gridUnit * 18 + Layout.fillHeight: parent.height + + color: "transparent" + border.width: 1 + border.color: Kube.Colors.buttonColor + + Kube.MailListView { + id: mailListView + anchors.fill: parent + activeFocusOnTab: true + Layout.minimumWidth: Kube.Units.gridUnit * 10 + } + } + + Kube.ConversationView { + id: mailView + Layout.fillWidth: true + Layout.fillHeight: parent.height + activeFocusOnTab: true + } + } +} diff --git a/views/conversation/tests/tst_conversationview.qml b/views/conversation/tests/tst_conversationview.qml new file mode 100644 index 00000000..467c049a --- /dev/null +++ b/views/conversation/tests/tst_conversationview.qml @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details + * + * You should have received a copy of the GNU Library 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 QtTest 1.0 +import "../qml" + +TestCase { + id: testCase + width: 400 + height: 400 + name: "MailView" + + View { + id: mailView + } + + function test_start() { + verify(mailView) + } +} diff --git a/views/log/metadata.json b/views/log/metadata.json new file mode 100644 index 00000000..1bcba1fb --- /dev/null +++ b/views/log/metadata.json @@ -0,0 +1,4 @@ +{ + "tooltip": "Log messages.", + "hidden": true +} diff --git a/views/log/qml/View.qml b/views/log/qml/View.qml new file mode 100644 index 00000000..4ae1a67c --- /dev/null +++ b/views/log/qml/View.qml @@ -0,0 +1,329 @@ +/* + * 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: { + if (message.type == Kube.Notifications.error) { + root.pendingError = true + } + var error = {timestamp: new Date(), message: message.message, details: message.details, resource: message.resource} + 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 = "" + } + } + + 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: "" + + 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 : "" + + function getComponent(subtype) { + if (subtype == Kube.Notifications.loginError) { + return loginErrorComponent + } + if (subtype == Kube.Notifications.hostNotFoundError) { + return hostNotFoundErrorComponent + } + if (subtype == Kube.Notifications.connectionError) { + return hostNotFoundErrorComponent + } + 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, {}) + } + } + } + } + } +} diff --git a/views/log/tests/tst_logview.qml b/views/log/tests/tst_logview.qml new file mode 100644 index 00000000..a78d71cb --- /dev/null +++ b/views/log/tests/tst_logview.qml @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details + * + * You should have received a copy of the GNU Library 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.Controls 2.0 +import QtQuick.Window 2.1 +import QtTest 1.0 +import org.kube.framework 1.0 as Kube +import "../qml" + +TestCase { + id: logviewTestcase + width: 400 + height: 400 + name: "LogView" + + View { + id: logView + } + + function test_logview() { + var listModel = findChild(logView, "logModel"); + verify(listModel) + compare(listModel.count, 0) + //ignore progress + Kube.Fabric.postMessage(Kube.Messages.progressNotification, {}) + compare(listModel.count, 0) + + Kube.Fabric.postMessage(Kube.Messages.notification, {type: Kube.Notifications.info, message: "foobar", resource: "resource"}) + compare(listModel.count, 1) + compare(logView.pendingError, false) + + Kube.Fabric.postMessage(Kube.Messages.notification, {"type": Kube.Notifications.error, message: "foobar", resource: "resource"}) + compare(listModel.count, 2) + compare(logView.pendingError, true) + compare(listModel.get(0).type, Kube.Notifications.error) + compare(listModel.get(0).errors.count, 1) + compare(listModel.get(0).errors.get(0).message, "foobar") + compare(listModel.get(0).errors.get(0).resource, "resource") + + Kube.Fabric.postMessage(Kube.Messages.notification, {"type": Kube.Notifications.error, "subtype": "merge", message: "merge1", resource: "resource1"}) + compare(listModel.count, 3) + Kube.Fabric.postMessage(Kube.Messages.notification, {"type": Kube.Notifications.error, "subtype": "merge", message: "merge2", resource: "resource2"}) + compare(listModel.count, 3) + compare(listModel.get(0).errors.count, 2) + compare(listModel.get(0).errors.get(0).message, "merge2") + compare(listModel.get(0).errors.get(0).resource, "resource2") + } +} diff --git a/views/people/metadata.json b/views/people/metadata.json new file mode 100644 index 00000000..44296a7e --- /dev/null +++ b/views/people/metadata.json @@ -0,0 +1,4 @@ +{ + "icon": "im-user-inverted", + "tooltip": "Your contacts." +} diff --git a/views/people/qml/View.qml b/views/people/qml/View.qml new file mode 100644 index 00000000..3f1b9261 --- /dev/null +++ b/views/people/qml/View.qml @@ -0,0 +1,38 @@ +/* + * 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.Controls 2.1 +import org.kube.framework 1.0 as Kube + +Item { + + StackView.onActivated: { + Kube.Fabric.postMessage(Kube.Messages.synchronize, {"type": "contacts"}) + } + + Kube.People { + id: people + anchors { + fill: parent + margins: Kube.Units.smallSpacing + } + } +} diff --git a/views/people/tests/tst_peopleview.qml b/views/people/tests/tst_peopleview.qml new file mode 100644 index 00000000..263247ad --- /dev/null +++ b/views/people/tests/tst_peopleview.qml @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Christian Mollekopf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details + * + * You should have received a copy of the GNU Library 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 QtTest 1.0 +import "../qml" + +TestCase { + id: testCase + width: 400 + height: 400 + name: "PeopleView" + + View { + id: peopleView + } + + function test_start() { + verify(peopleView) + } +} -- cgit v1.2.3