From bfbd2af45dd394993065183c089f3ec1367bdf60 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sat, 4 Aug 2018 20:35:31 +0000 Subject: [PATCH] Context Menu Related Improvement --- qml/component/DownloadableContent.qml | 1 - qml/component/ImageBubble.qml | 17 +---- qml/component/MessageDelegate.qml | 40 ++++++++-- qml/form/RoomListForm.qml | 59 ++++++++------- src/controller.cpp | 5 ++ src/controller.h | 7 +- src/messageeventmodel.cpp | 101 ++++++++++++++++++++++++++ src/messageeventmodel.h | 1 + 8 files changed, 181 insertions(+), 50 deletions(-) diff --git a/qml/component/DownloadableContent.qml b/qml/component/DownloadableContent.qml index 71200ec..495f22b 100644 --- a/qml/component/DownloadableContent.qml +++ b/qml/component/DownloadableContent.qml @@ -22,7 +22,6 @@ Item { selectFolder: true onAccepted: currentRoom.downloadFile(eventId, folder + "/" + currentRoom.fileNameToDownload(eventId)) - } onDownloadedChanged: downloaded && openOnFinished ? openSavedFile() : {} diff --git a/qml/component/ImageBubble.qml b/qml/component/ImageBubble.qml index 9b0b4ec..5fd8868 100644 --- a/qml/component/ImageBubble.qml +++ b/qml/component/ImageBubble.qml @@ -3,6 +3,9 @@ import QtQuick.Controls 2.2 import QtQuick.Controls.Material 2.2 AvatarContainer { + readonly property var downloadAndOpen: downloadable.downloadAndOpen + readonly property var saveFileAs: downloadable.saveFileAs + Rectangle { id: messageRect @@ -33,20 +36,6 @@ AvatarContainer { ToolTip.text: content.body onClicked: downloadable.downloadAndOpen() - onPressAndHold: messageImageMenu.popup() - } - - Menu { - id: messageImageMenu - - MenuItem { - text: "View" - onTriggered: downloadable.downloadAndOpen() - } - MenuItem { - text: "Save as..." - onTriggered: downloadable.saveFileAs() - } } } } diff --git a/qml/component/MessageDelegate.qml b/qml/component/MessageDelegate.qml index e77210a..defc0e5 100644 --- a/qml/component/MessageDelegate.qml +++ b/qml/component/MessageDelegate.qml @@ -24,13 +24,41 @@ Item { MouseArea { anchors.fill: parent - onPressAndHold: messageContextMenu.popup() + onPressAndHold: menuComponent.createObject(this) - Menu { - id: messageContextMenu - MenuItem { - text: "Redact" - onTriggered: currentRoom.redactEvent(eventId) + Component { + id: menuComponent + Menu { + id: messageContextMenu + + MenuItem { + text: "Copy" + onTriggered: matriqueController.copyToClipboard(plainText) + } + MenuItem { + text: "Copy Source" + onTriggered: matriqueController.copyToClipboard(toolTip) + } + MenuItem { + visible: isFile + height: visible ? undefined : 0 + text: "Open Externally" + onTriggered: delegateLoader.item.downloadAndOpen() + } + MenuItem { + visible: isFile + height: visible ? undefined : 0 + text: "Save As" + onTriggered: delegateLoader.item.saveFileAs() + } + MenuItem { + visible: sentByMe + height: visible ? undefined : 0 + text: "Redact" + onTriggered: currentRoom.redactEvent(eventId) + } + + Component.onCompleted: popup() } } } diff --git a/qml/form/RoomListForm.qml b/qml/form/RoomListForm.qml index bb4b76f..7426b14 100644 --- a/qml/form/RoomListForm.qml +++ b/qml/form/RoomListForm.qml @@ -112,7 +112,6 @@ Item { ] } - ListView { id: listView width: parent.width @@ -125,6 +124,7 @@ Item { opacity: 0.2 } highlightMoveDuration: 250 + highlightResizeDuration: 0 currentIndex: -1 @@ -135,16 +135,13 @@ Item { delegate: ItemDelegate { width: parent.width height: 80 - onClicked: listView.currentIndex = index - onPressAndHold: { - roomListMenu.roomIndex = index - roomListMenu.popup() - } + onPressed: listView.currentIndex = index + onPressAndHold: menuComponent.createObject(this) ToolTip.visible: mini && hovered ToolTip.text: name - contentItem: RowLayout { + contentItem: RowLayout { anchors.fill: parent anchors.margins: 16 spacing: 16 @@ -184,6 +181,33 @@ Item { } } } + + Component { + id: menuComponent + Menu { + id: roomListMenu + + MenuItem { + text: "Favourite" + checkable: true + checked: currentRoom.isFavourite + onTriggered: currentRoom.isFavourite ? currentRoom.removeTag("m.favourite") : currentRoom.addTag("m.favourite", "1") + } + MenuItem { + text: "Deprioritize" + checkable: true + checked: currentRoom.isLowPriority + onTriggered: currentRoom.isLowPriority ? currentRoom.removeTag("m.lowpriority") : currentRoom.addTag("m.lowpriority", "1") + } + MenuSeparator {} + MenuItem { + text: "Leave Room" + onTriggered: matriqueController.forgetRoom(currentRoom.id) + } + + Component.onCompleted: popup() + } + } } section.property: "category" @@ -202,27 +226,6 @@ Item { color: Material.theme == Material.Light ? "#dbdbdb" : "#363636" } } - - Menu { - property int roomIndex: -1 - readonly property int roomProxyIndex: roomListProxyModel.mapToSource(roomIndex) - readonly property var room: roomProxyIndex != -1 ? listModel.roomAt(roomProxyIndex) : null - - id: roomListMenu - - MenuItem { - text: "Favourite" + (roomListMenu.room && roomListMenu.room.isFavourite ? " \u2713" : "") - onTriggered: roomListMenu.room.isFavourite ? roomListMenu.room.removeTag("m.favourite") : roomListMenu.room.addTag("m.favourite", "1") - } - MenuItem { - text: "Deprioritize" + (roomListMenu.room && roomListMenu.room.isLowPriority ? " \u2713" : "") - onTriggered: roomListMenu.room.isLowPriority ? roomListMenu.room.removeTag("m.lowpriority") : roomListMenu.room.addTag("m.lowpriority", "1") - } - MenuItem { - text: "Leave Room" - onTriggered: matriqueController.forgetRoom(roomListMenu.room.id) - } - } } } } diff --git a/src/controller.cpp b/src/controller.cpp index 87aab58..7e0b472 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -8,6 +8,7 @@ #include "csapi/joining.h" #include "csapi/leaving.h" +#include #include #include #include @@ -120,3 +121,7 @@ void Controller::createRoom(const QString& name, const QString& topic) { void Controller::createDirectChat(const QString& userID) { m_connection->requestDirectChat(userID); } + +void Controller::copyToClipboard(const QString& text) { + m_clipboard->setText(text); +} diff --git a/src/controller.h b/src/controller.h index 563c010..1d78006 100644 --- a/src/controller.h +++ b/src/controller.h @@ -1,11 +1,13 @@ #ifndef CONTROLLER_H #define CONTROLLER_H -#include #include "connection.h" #include "roomlistmodel.h" #include "user.h" +#include +#include + using namespace QMatrixClient; class Controller : public QObject { @@ -81,6 +83,8 @@ class Controller : public QObject { } private: + QClipboard* m_clipboard = QApplication::clipboard(); + void connected(); void resync(); void reconnect(); @@ -101,6 +105,7 @@ class Controller : public QObject { void joinRoom(const QString& alias); void createRoom(const QString& name, const QString& topic); void createDirectChat(const QString& userID); + void copyToClipboard(const QString& text); }; #endif // CONTROLLER_H diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 8316cd0..22f813c 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -28,6 +28,7 @@ QHash MessageEventModel::roleNames() const { roles[SpecialMarksRole] = "marks"; roles[LongOperationRole] = "progressInfo"; roles[EventResolvedTypeRole] = "eventResolvedType"; + roles[PlainTextRole] = "plainText"; return roles; } @@ -298,6 +299,106 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const { tr("Unknown Event")); } + if (role == PlainTextRole) { + if (evt.isRedacted()) { + auto reason = evt.redactedBecause()->reason(); + if (reason.isEmpty()) return tr("Redacted"); + + return tr("Redacted: %1").arg(evt.redactedBecause()->reason()); + } + + return visit( + evt, + [this](const RoomMessageEvent& e) { + using namespace MessageEventContent; + + if (e.hasFileContent()) { + auto fileCaption = e.content()->fileInfo()->originalName; + if (fileCaption.isEmpty()) + fileCaption = e.plainBody(); + if (fileCaption.isEmpty()) return tr("a file"); + } + return e.plainBody(); + }, + [this](const RoomMemberEvent& e) { + // FIXME: Rewind to the name that was at the time of this event + QString subjectName = m_currentRoom->roomMembername(e.userId()); + // The below code assumes senderName output in AuthorRole + switch (e.membership()) { + case MembershipType::Invite: + if (e.repeatsState()) + return tr("reinvited %1 to the room").arg(subjectName); + FALLTHROUGH; + case MembershipType::Join: { + if (e.repeatsState()) return tr("joined the room (repeated)"); + if (!e.prevContent() || + e.membership() != e.prevContent()->membership) { + return e.membership() == MembershipType::Invite + ? tr("invited %1 to the room").arg(subjectName) + : tr("joined the room"); + } + QString text{}; + if (e.displayName() != e.prevContent()->displayName) { + if (e.displayName().isEmpty()) + text = tr("cleared the display name"); + else + text = + tr("changed the display name to %1").arg(e.displayName()); + } + if (e.avatarUrl() != e.prevContent()->avatarUrl) { + if (!text.isEmpty()) text += " and "; + if (e.avatarUrl().isEmpty()) + text += tr("cleared the avatar"); + else + text += tr("updated the avatar"); + } + return text; + } + case MembershipType::Leave: + if (e.prevContent() && + e.prevContent()->membership == MembershipType::Ban) { + return (e.senderId() != e.userId()) + ? tr("unbanned %1").arg(subjectName) + : tr("self-unbanned"); + } + return (e.senderId() != e.userId()) + ? tr("has put %1 out of the room").arg(subjectName) + : tr("left the room"); + case MembershipType::Ban: + return (e.senderId() != e.userId()) + ? tr("banned %1 from the room").arg(subjectName) + : tr("self-banned from the room"); + case MembershipType::Knock: + return tr("knocked"); + default:; + } + return tr("made something unknown"); + }, + [](const RoomAliasesEvent& e) { + return tr("set aliases to: %1").arg(e.aliases().join(", ")); + }, + [](const RoomCanonicalAliasEvent& e) { + return (e.alias().isEmpty()) + ? tr("cleared the room main alias") + : tr("set the room main alias to: %1").arg(e.alias()); + }, + [](const RoomNameEvent& e) { + return (e.name().isEmpty()) + ? tr("cleared the room name") + : tr("set the room name to: %1").arg(e.name()); + }, + [](const RoomTopicEvent& e) { + return (e.topic().isEmpty()) + ? tr("cleared the topic") + : tr("set the topic to: %1").arg(e.topic()); + }, + [](const RoomAvatarEvent&) { return tr("changed the room avatar"); }, + [](const EncryptionEvent&) { + return tr("activated End-to-End Encryption"); + }, + tr("Unknown Event")); + } + if (role == Qt::ToolTipRole) { return evt.originalJson(); } diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index 1e83fb4..c79a6c8 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -25,6 +25,7 @@ class MessageEventModel : public QAbstractListModel { ReadMarkerRole, SpecialMarksRole, LongOperationRole, + PlainTextRole, // For debugging EventResolvedTypeRole, };