diff --git a/framework/qml/ConversationView.qml b/framework/qml/ConversationView.qml index fc239d40..4df7dfa0 100644 --- a/framework/qml/ConversationView.qml +++ b/framework/qml/ConversationView.qml @@ -1,149 +1,151 @@ /* * Copyright (C) 2016 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.9 import QtQuick.Controls 2 import QtQuick.Layouts 1.1 import org.kube.framework 1.0 as Kube import QtQml 2.2 as QtQml FocusScope { id: root property variant mail; property bool hideTrash: true; property bool hideNonTrash: false; property string searchString: "" Kube.Listener { filter: Kube.Messages.searchString onMessageReceived: root.searchString = message.searchString } Kube.Listener { filter: Kube.Messages.selectNextMessage onMessageReceived: { listView.incrementCurrentIndex() listView.forceActiveFocus() } } Kube.Listener { filter: Kube.Messages.selectPreviousMessage onMessageReceived: { listView.decrementCurrentIndex() listView.forceActiveFocus() } } Kube.Listener { filter: Kube.Messages.scrollConversationDown onMessageReceived: listView.scrollDown() } Kube.Listener { filter: Kube.Messages.scrollConversationUp onMessageReceived: listView.scrollUp() } Rectangle { anchors.fill: parent color: Kube.Colors.backgroundColor Kube.ConversationListView { id: listView objectName: "listView" focus: true anchors { top: parent.top left: parent.left right: parent.right } //Shrink the listview if the content doesn't fill the full height, so the email appears on top instead of on the bottom. height: Math.min(contentHeight, parent.height) model: Kube.MailListModel { mail: root.mail } Keys.onPressed: { + //Not implemented as a shortcut because we want it only to apply if we have the focus if (event.text == "d") { - //Not implemented as a shortcut because we want it only to apply if we have the focus Kube.Fabric.postMessage(Kube.Messages.moveToTrash, {"mail": listView.currentItem.currentData.mail}) + } else if (event.text == "r") { + Kube.Fabric.postMessage(Kube.Messages.reply, {"mail": listView.currentItem.currentData.mail}) } } delegate: FocusScope { id: delegateRoot property var currentData: model property bool isCurrentItem: false property int index: -1 focus: true activeFocusOnTab: false onActiveFocusChanged: { if (activeFocus) { listView.currentIndex = delegateRoot.index } } height: sheet.height + Kube.Units.gridUnit width: listView.width //FIXME breaks keyboard navigation because we don't jump over invisible items visible: !((root.hideTrash && model.trash) || (root.hideNonTrash && !model.trash)) MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton hoverEnabled: true onEntered: delegateRoot.forceActiveFocus(Qt.MouseFocusReason) } MailViewer { id: sheet anchors.centerIn: parent width: parent.width - Kube.Units.gridUnit * 2 message: model.mimeMessage subject: model.subject sender: model.sender senderName: model.senderName to: model.to cc: model.cc bcc: model.bcc date: model.date unread: model.unread trash: model.trash draft: model.draft sent: model.sent incomplete: model.incomplete current: delegateRoot.isCurrentItem searchString: root.searchString } } } } } diff --git a/framework/qml/MailListView.qml b/framework/qml/MailListView.qml index a8322f15..46d3146a 100644 --- a/framework/qml/MailListView.qml +++ b/framework/qml/MailListView.qml @@ -1,322 +1,324 @@ /* Copyright (C) 2016 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.9 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.1 import org.kube.framework 1.0 as Kube FocusScope { id: root //Private properties property variant parentFolder: null property bool isDraft : false property bool isImportant : false property bool isTrash : false property bool isUnread : false property variant currentMail: null property bool showFilter: false property string filter: null onFilterChanged: { Kube.Fabric.postMessage(Kube.Messages.searchString, {"searchString": filter}) } onParentFolderChanged: { currentMail = null filterField.clearSearch() } onShowFilterChanged: { find.forceActiveFocus() } Kube.Listener { filter: Kube.Messages.selectNextConversation onMessageReceived: { listView.incrementCurrentIndex() listView.forceActiveFocus() } } Kube.Listener { filter: Kube.Messages.selectPreviousConversation onMessageReceived: { listView.decrementCurrentIndex() listView.forceActiveFocus() } } Kube.Label { anchors.centerIn: parent visible: listView.count === 0 //TODO depending on whether we synchronized already or not the label should change. text: qsTr("Nothing here...") } ColumnLayout { anchors.fill: parent spacing: 0 Rectangle { id: filterField Layout.fillWidth: true height: Kube.Units.gridUnit * 2 color: Kube.Colors.buttonColor visible: root.showFilter function clearSearch() { root.showFilter = false find.text = "" root.filter = "" } RowLayout { anchors { verticalCenter: parent.verticalCenter } width: parent.width - Kube.Units.smallSpacing spacing: 0 Kube.IconButton { iconName: Kube.Icons.remove activeFocusOnTab: visible onClicked: filterField.clearSearch() } Kube.TextField { id: find Layout.fillWidth: true placeholderText: qsTr("Filter...") onTextChanged: root.filter = text activeFocusOnTab: visible focus: visible Keys.onEscapePressed: filterField.clearSearch() } } } Kube.ListView { id: listView objectName: "listView" Layout.fillWidth: true Layout.fillHeight: true clip: true focus: true onActiveFocusChanged: { if (activeFocus && currentIndex < 0) { currentIndex = 0 } } Keys.onPressed: { + //Not implemented as a shortcut because we want it only to apply if we have the focus if (event.text == "d") { - //Not implemented as a shortcut because we want it only to apply if we have the focus Kube.Fabric.postMessage(Kube.Messages.moveToTrash, {"mail": root.currentMail}) + } else if (event.text == "r") { + Kube.Fabric.postMessage(Kube.Messages.reply, {"mail": root.currentMail}) } } onCurrentItemChanged: { if (currentItem) { var currentData = currentItem.currentData; root.currentMail = currentData.mail; root.isDraft = currentData.draft; root.isTrash = currentData.trash; root.isImportant = currentData.important; root.isUnread = currentData.unread; if (currentData.mail && currentData.unread) { Kube.Fabric.postMessage(Kube.Messages.markAsRead, {"mail": currentData.mail}) } } } model: Kube.MailListModel { id: mailListModel parentFolder: root.parentFolder filter: root.filter } delegate: Kube.ListDelegate { id: delegateRoot //Required for D&D property var mail: model.mail width: listView.availableWidth height: Kube.Units.gridUnit * 5 color: Kube.Colors.viewBackgroundColor border.color: Kube.Colors.backgroundColor border.width: 1 states: [ State { name: "dnd" when: mouseArea.drag.active PropertyChanges {target: mouseArea; cursorShape: Qt.ClosedHandCursor} PropertyChanges {target: delegateRoot; x: x; y: y} PropertyChanges {target: delegateRoot; parent: root} PropertyChanges {target: delegateRoot; opacity: 0.7} PropertyChanges {target: delegateRoot; highlighted: true} } ] Drag.active: mouseArea.drag.active Drag.hotSpot.x: mouseArea.mouseX Drag.hotSpot.y: mouseArea.mouseY Drag.source: delegateRoot MouseArea { id: mouseArea anchors.fill: parent drag.target: parent onReleased: parent.Drag.drop() onClicked: delegateRoot.clicked() } Item { id: content anchors { fill: parent margins: Kube.Units.smallSpacing } property color unreadColor: (model.unread && !delegateRoot.highlighted) ? Kube.Colors.highlightColor : delegateRoot.textColor //TODO batch editing // Kube.CheckBox { // id: checkBox // // anchors.verticalCenter: parent.verticalCenter // visible: (checked || delegateRoot.hovered) && !mouseArea.drag.active // opacity: 0.9 // } Column { anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: Kube.Units.largeSpacing // + checkBox.width } Kube.Label{ id: subject width: content.width - Kube.Units.gridUnit * 3 text: model.subject color: content.unreadColor maximumLineCount: 2 wrapMode: Text.WordWrap elide: Text.ElideRight } Kube.Label { id: sender text: model.senderName color: delegateRoot.textColor font.italic: true width: delegateRoot.width - Kube.Units.gridUnit * 3 elide: Text.ElideRight } } Kube.Label { id: date anchors { right: parent.right bottom: parent.bottom } visible: !delegateRoot.hovered text: Qt.formatDateTime(model.date, "dd MMM yyyy") font.italic: true color: Kube.Colors.disabledTextColor font.pointSize: Kube.Units.tinyFontSize } Kube.Label { id: threadCounter anchors.right: parent.right text: model.threadSize color: content.unreadColor visible: model.threadSize > 1 } } Row { id: buttons anchors { right: parent.right bottom: parent.bottom margins: Kube.Units.smallSpacing } visible: delegateRoot.hovered && !mouseArea.drag.active spacing: Kube.Units.smallSpacing opacity: 0.7 Kube.IconButton { id: readButton iconName: Kube.Icons.markAsRead visible: model.unread onClicked: Kube.Fabric.postMessage(Kube.Messages.markAsRead, {"mail": model.mail}) activeFocusOnTab: false } Kube.IconButton { id: unreadButton iconName: Kube.Icons.markAsUnread visible: !model.unread onClicked: Kube.Fabric.postMessage(Kube.Messages.markAsUnread, {"mail": model.mail}) activeFocusOnTab: false } Kube.IconButton { id: importantButton iconName: Kube.Icons.markImportant visible: !!model.mail onClicked: Kube.Fabric.postMessage(Kube.Messages.toggleImportant, {"mail": model.mail, "important": model.important}) activeFocusOnTab: false } Kube.IconButton { id: deleteButton objectName: "deleteButton" iconName: Kube.Icons.moveToTrash visible: !!model.mail onClicked: Kube.Fabric.postMessage(Kube.Messages.moveToTrash, {"mail": model.mail}) activeFocusOnTab: false } Kube.IconButton { id: restoreButton iconName: Kube.Icons.undo visible: !!model.trash onClicked: Kube.Fabric.postMessage(Kube.Messages.restoreFromTrash, {"mail": model.mail}) activeFocusOnTab: false } } } } } } diff --git a/views/conversation/qml/View.qml b/views/conversation/qml/View.qml index 088b7ae5..33f4868f 100644 --- a/views/conversation/qml/View.qml +++ b/views/conversation/qml/View.qml @@ -1,212 +1,214 @@ /* * 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.9 import QtQuick.Controls 1.3 as Controls1 import QtQuick.Controls 2 import QtQuick.Layouts 1.1 import org.kube.framework 1.0 as Kube FocusScope { id: root property alias currentAccount: accountFolderview.currentAccount Shortcut { sequences: ['j'] onActivated: Kube.Fabric.postMessage(Kube.Messages.selectNextConversation, {}) } Shortcut { sequences: ['k'] onActivated: Kube.Fabric.postMessage(Kube.Messages.selectPreviousConversation, {}) } Shortcut { sequences: ['Shift+J'] onActivated: Kube.Fabric.postMessage(Kube.Messages.scrollConversationDown, {}) } Shortcut { sequences: ['Shift+K'] onActivated: Kube.Fabric.postMessage(Kube.Messages.scrollConversationUp, {}) } Shortcut { sequences: ['n'] onActivated: Kube.Fabric.postMessage(Kube.Messages.selectNextMessage, {}) } Shortcut { sequences: ['p'] onActivated: Kube.Fabric.postMessage(Kube.Messages.selectPreviousMessage, {}) } Shortcut { sequences: ['f,n'] onActivated: Kube.Fabric.postMessage(Kube.Messages.selectNextFolder, {}) } Shortcut { sequences: ['f,p'] onActivated: Kube.Fabric.postMessage(Kube.Messages.selectPreviousFolder, {}) } Shortcut { sequences: ['c'] onActivated: Kube.Fabric.postMessage(Kube.Messages.compose, {}) } Shortcut { sequence: "?" onActivated: helpViewComponent.createObject(root).open() } Controls1.SplitView { anchors.fill: parent Rectangle { width: Kube.Units.gridUnit * 10 Layout.fillHeight: parent.height color: Kube.Colors.textColor Kube.PositiveButton { id: newMailButton objectName: "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 objectName: "mailListView" anchors.fill: parent activeFocusOnTab: true Layout.minimumWidth: Kube.Units.gridUnit * 10 Kube.Listener { filter: Kube.Messages.folderSelection onMessageReceived: mailListView.parentFolder = message.folder } Kube.Listener { filter: Kube.Messages.search onMessageReceived: mailListView.showFilter = true } onCurrentMailChanged: { Kube.Fabric.postMessage(Kube.Messages.mailSelection, {"mail": currentMail}) } } } Kube.ConversationView { id: mailView objectName: "mailView" Layout.fillWidth: true Layout.fillHeight: parent.height activeFocusOnTab: true Kube.Listener { filter: Kube.Messages.mailSelection onMessageReceived: { mailView.mail = message.mail } } Kube.Listener { filter: Kube.Messages.folderSelection onMessageReceived: { mailView.hideTrash = !message.trash mailView.hideNonTrash = message.trash } } } } Component { id: helpViewComponent Kube.HelpPopup { ListModel { ListElement { description: qsTr("Jump to next thread:"); shortcut: "j" } ListElement { description: qsTr("Jump to previous thread:"); shortcut: "k" } ListElement { description: qsTr("Jump to next message:"); shortcut: "n" } ListElement { description: qsTr("Jump to previous message:"); shortcut: "p" } ListElement { description: qsTr("Jump to next folder:"); shortcut: "f,n" } ListElement { description: qsTr("Jump to previous previous folder:"); shortcut: "f,p" } ListElement { description: qsTr("Compose new message:"); shortcut: "c" } + ListElement { description: qsTr("Reply to the currently focused message:"); shortcut: "r" } + ListElement { description: qsTr("Delete the currently focused message:"); shortcut: "d" } ListElement { description: qsTr("Show this help text:"); shortcut: "?" } } } } }