From b7d8f70a5ff5064f879d7b50f911eca2042b03d1 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Fri, 17 May 2019 15:01:01 +0800 Subject: [PATCH 1/7] More changes in MessageDelegate. --- imports/Spectral/Component/AutoRectangle.qml | 71 +++++++++ imports/Spectral/Component/Avatar.qml | 28 ++-- .../Component/Timeline/MessageDelegate.qml | 144 +++++++----------- imports/Spectral/Component/qmldir | 1 + imports/Spectral/Dialog/UserDetailDialog.qml | 17 ++- include/libqmatrixclient | 2 +- res.qrc | 1 + src/spectraluser.cpp | 4 + src/spectraluser.h | 3 + 9 files changed, 168 insertions(+), 103 deletions(-) create mode 100644 imports/Spectral/Component/AutoRectangle.qml diff --git a/imports/Spectral/Component/AutoRectangle.qml b/imports/Spectral/Component/AutoRectangle.qml new file mode 100644 index 0000000..975e491 --- /dev/null +++ b/imports/Spectral/Component/AutoRectangle.qml @@ -0,0 +1,71 @@ +import QtQuick 2.12 + +Rectangle { + property alias topLeftRadius: topLeftRect.radius + property alias topRightRadius: topRightRect.radius + property alias bottomLeftRadius: bottomLeftRect.radius + property alias bottomRightRadius: bottomRightRect.radius + + property alias topLeftVisible: topLeftRect.visible + property alias topRightVisible: topRightRect.visible + property alias bottomLeftVisible: bottomLeftRect.visible + property alias bottomRightVisible: bottomRightRect.visible + + antialiasing: true + + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + + width: parent.width / 2 + height: parent.height / 2 + + id: topLeftRect + + antialiasing: true + + color: parent.color + } + + Rectangle { + anchors.top: parent.top + anchors.right: parent.right + + width: parent.width / 2 + height: parent.height / 2 + + id: topRightRect + + antialiasing: true + + color: parent.color + } + + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + + width: parent.width / 2 + height: parent.height / 2 + + id: bottomLeftRect + + antialiasing: true + + color: parent.color + } + + Rectangle { + anchors.bottom: parent.bottom + anchors.right: parent.right + + width: parent.width / 2 + height: parent.height / 2 + + id: bottomRightRect + + antialiasing: true + + color: parent.color + } +} diff --git a/imports/Spectral/Component/Avatar.qml b/imports/Spectral/Component/Avatar.qml index b9c2c8d..caf0f85 100644 --- a/imports/Spectral/Component/Avatar.qml +++ b/imports/Spectral/Component/Avatar.qml @@ -2,6 +2,8 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtGraphicalEffects 1.0 +import Spectral.Setting 0.1 + Item { property string hint: "H" property string source: "" @@ -35,7 +37,7 @@ Item { visible: !realSource || image.status != Image.Ready radius: height / 2 - color: stringToColor(hint) + color: MPalette.accent antialiasing: true Label { @@ -50,16 +52,16 @@ Item { } } - function stringToColor(str) { - var hash = 0; - for (var i = 0; i < str.length; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); - } - var colour = '#'; - for (var j = 0; j < 3; j++) { - var value = (hash >> (j * 8)) & 0xFF; - colour += ('00' + value.toString(16)).substr(-2); - } - return colour; - } +// function stringToColor(str) { +// var hash = 0; +// for (var i = 0; i < str.length; i++) { +// hash = str.charCodeAt(i) + ((hash << 5) - hash); +// } +// var colour = '#'; +// for (var j = 0; j < 3; j++) { +// var value = (hash >> (j * 8)) & 0xFF; +// colour += ('00' + value.toString(16)).substr(-2); +// } +// return colour; +// } } diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index 35e3361..670505f 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -67,65 +67,25 @@ ColumnLayout { Control { Layout.maximumWidth: messageListView.width - (!sentByMe ? 36 + messageRow.spacing : 0) - 48 - verticalPadding: 8 - horizontalPadding: 16 + padding: 0 + + background: AutoRectangle { + readonly property int minorRadius: 2 + + id: bubbleBackground - background: Rectangle { color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent radius: 18 - antialiasing: true - Rectangle { - anchors.top: parent.top - anchors.left: parent.left + topLeftVisible: !sentByMe && (bubbleShape == 3 || bubbleShape == 2) + topRightVisible: sentByMe && (bubbleShape == 3 || bubbleShape == 2) + bottomLeftVisible: !sentByMe && (bubbleShape == 1 || bubbleShape == 2) + bottomRightVisible: sentByMe && (bubbleShape == 1 || bubbleShape == 2) - width: parent.width / 2 - height: parent.height / 2 - - visible: !sentByMe && (bubbleShape == 3 || bubbleShape == 2) - - color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent - radius: 2 - } - - Rectangle { - anchors.top: parent.top - anchors.right: parent.right - - width: parent.width / 2 - height: parent.height / 2 - - visible: sentByMe && (bubbleShape == 3 || bubbleShape == 2) - - color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent - radius: 2 - } - - Rectangle { - anchors.bottom: parent.bottom - anchors.left: parent.left - - width: parent.width / 2 - height: parent.height / 2 - - visible: !sentByMe && (bubbleShape == 1 || bubbleShape == 2) - - color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent - radius: 2 - } - - Rectangle { - anchors.bottom: parent.bottom - anchors.right: parent.right - - width: parent.width / 2 - height: parent.height / 2 - - visible: sentByMe && (bubbleShape == 1 || bubbleShape == 2) - - color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent - radius: 2 - } + topLeftRadius: minorRadius + topRightRadius: minorRadius + bottomLeftRadius: minorRadius + bottomRightRadius: minorRadius AutoMouseArea { anchors.fill: parent @@ -166,60 +126,74 @@ ColumnLayout { } contentItem: ColumnLayout { - RowLayout { + spacing: 0 + + Control { Layout.fillWidth: true + padding: 8 + visible: replyVisible - Avatar { - Layout.preferredWidth: 28 - Layout.preferredHeight: 28 - Layout.alignment: Qt.AlignTop + contentItem: RowLayout { + Avatar { + Layout.preferredWidth: 28 + Layout.preferredHeight: 28 + Layout.alignment: Qt.AlignTop - source: replyVisible ? replyAuthor.avatarMediaId : "" - hint: replyVisible ? replyAuthor.displayName : "H" + source: replyVisible ? replyAuthor.avatarMediaId : "" + hint: replyVisible ? replyAuthor.displayName : "H" - RippleEffect { - anchors.fill: parent + RippleEffect { + anchors.fill: parent - circular: true + circular: true - onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": replyAuthor}).open() - } - } - - Control { - Layout.fillWidth: true - - visible: replyVisible - - padding: 0 - - background: RippleEffect { - onClicked: goToEvent(replyEventId) + onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": replyAuthor}).open() + } } - contentItem: Label { - color: darkBackground ? "white" : MPalette.lighter + Label { + Layout.fillWidth: true + + color: !sentByMe ? MPalette.foreground : "white" text: "" + (replyDisplay || "") wrapMode: Label.Wrap textFormat: Label.RichText } } - } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 + background: AutoRectangle { + color: sentByMe ? MPalette.accent : MPalette.background + radius: 16 - visible: replyVisible - color: darkBackground ? "white" : MPalette.lighter + topLeftRadius: 2 + topRightRadius: 2 + bottomLeftRadius: 0 + bottomRightRadius: 0 + + topLeftVisible: bubbleBackground.topLeftVisible + topRightVisible: bubbleBackground.topRightVisible + bottomLeftVisible: true + bottomRightVisible: true + + AutoMouseArea { + anchors.fill: parent + + onClicked: goToEvent(replyEventId) + } + } } TextEdit { Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 8 + Layout.bottomMargin: 8 + id: contentLabel text: "" + display diff --git a/imports/Spectral/Component/qmldir b/imports/Spectral/Component/qmldir index 4cbc3dd..dd821c5 100644 --- a/imports/Spectral/Component/qmldir +++ b/imports/Spectral/Component/qmldir @@ -7,3 +7,4 @@ AutoListView 2.0 AutoListView.qml AutoTextField 2.0 AutoTextField.qml Avatar 2.0 Avatar.qml FullScreenImage 2.0 FullScreenImage.qml +AutoRectangle 2.0 AutoRectangle.qml diff --git a/imports/Spectral/Dialog/UserDetailDialog.qml b/imports/Spectral/Dialog/UserDetailDialog.qml index 373b1a1..786fc33 100644 --- a/imports/Spectral/Dialog/UserDetailDialog.qml +++ b/imports/Spectral/Dialog/UserDetailDialog.qml @@ -27,8 +27,8 @@ Dialog { Layout.preferredWidth: 72 Layout.preferredHeight: 72 - hint: user ? user.displayName : "No name" - source: user ? user.avatarMediaId : null + hint: user.displayName + source: user.avatarMediaId RippleEffect { anchors.fill: parent @@ -50,10 +50,19 @@ Dialog { elide: Text.ElideRight wrapMode: Text.NoWrap - text: user ? user.displayName : "No Name" + text: user.displayName color: MPalette.foreground } + Label { + Layout.fillWidth: true + + visible: user.bridgeName + + text: user.bridgeName + color: MPalette.lighter + } + Label { Layout.fillWidth: true @@ -89,7 +98,7 @@ Dialog { elide: Text.ElideRight wrapMode: Text.NoWrap - text: user ? user.id : "No ID" + text: user.id color: MPalette.accent } diff --git a/include/libqmatrixclient b/include/libqmatrixclient index 52a81df..d6f39dc 160000 --- a/include/libqmatrixclient +++ b/include/libqmatrixclient @@ -1 +1 @@ -Subproject commit 52a81dfa8a5415be369d819837f445479b833cde +Subproject commit d6f39dcb0de69322479f287514a8c36afcb3fe7b diff --git a/res.qrc b/res.qrc index a07c611..8ab4cf4 100644 --- a/res.qrc +++ b/res.qrc @@ -58,5 +58,6 @@ imports/Spectral/Dialog/OpenFileDialog.qml imports/Spectral/Dialog/OpenFolderDialog.qml imports/Spectral/Component/Timeline/VideoDelegate.qml + imports/Spectral/Component/AutoRectangle.qml diff --git a/src/spectraluser.cpp b/src/spectraluser.cpp index cf0e67d..613aebf 100644 --- a/src/spectraluser.cpp +++ b/src/spectraluser.cpp @@ -1 +1,5 @@ #include "spectraluser.h" + +QColor SpectralUser::color() { + return QColor::fromHslF(hueF(), 0.7, 0.5, 1); +} diff --git a/src/spectraluser.h b/src/spectraluser.h index e62e51e..3cb7734 100644 --- a/src/spectraluser.h +++ b/src/spectraluser.h @@ -10,9 +10,12 @@ using namespace QMatrixClient; class SpectralUser : public User { Q_OBJECT + Q_PROPERTY(QColor color READ color CONSTANT) public: SpectralUser(QString userId, Connection* connection) : User(userId, connection) {} + + QColor color(); }; #endif // SpectralUser_H From 75c5c718555c4e99b7b401522d934f0c7cda75f3 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Fri, 17 May 2019 19:29:41 +0800 Subject: [PATCH 2/7] Make reply delegate's border round. --- .../Component/Timeline/MessageDelegate.qml | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index 670505f..e48f2c7 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -131,7 +131,12 @@ ColumnLayout { Control { Layout.fillWidth: true - padding: 8 + Layout.topMargin: 8 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + + padding: 4 + rightPadding: 12 visible: replyVisible @@ -164,19 +169,9 @@ ColumnLayout { } } - background: AutoRectangle { + background: Rectangle { color: sentByMe ? MPalette.accent : MPalette.background - radius: 16 - - topLeftRadius: 2 - topRightRadius: 2 - bottomLeftRadius: 0 - bottomRightRadius: 0 - - topLeftVisible: bubbleBackground.topLeftVisible - topRightVisible: bubbleBackground.topRightVisible - bottomLeftVisible: true - bottomRightVisible: true + radius: 18 AutoMouseArea { anchors.fill: parent From b4281896baf36ac5d582ea9d86aab9f300e0ba46 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Fri, 17 May 2019 19:46:59 +0800 Subject: [PATCH 3/7] Add drag and drop support. --- imports/Spectral/Panel/RoomPanel.qml | 12 +++++++ src/spectralroom.cpp | 53 ++++++++++++++++------------ src/spectralroom.h | 1 + 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/imports/Spectral/Panel/RoomPanel.qml b/imports/Spectral/Panel/RoomPanel.qml index 4573481..185e4bd 100644 --- a/imports/Spectral/Panel/RoomPanel.qml +++ b/imports/Spectral/Panel/RoomPanel.qml @@ -23,6 +23,18 @@ Item { room: currentRoom } + DropArea { + anchors.fill: parent + + enabled: currentRoom + + onDropped: { + if (!drop.hasUrls) return + + currentRoom.uploadFile(drop.urls[0]) + } + } + Column { anchors.centerIn: parent diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index ca5e207..63b3f33 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -47,32 +47,39 @@ inline QSize getImageSize(const QUrl& imageUrl) { void SpectralRoom::chooseAndUploadFile() { auto localFile = QFileDialog::getOpenFileUrl(Q_NULLPTR, tr("Save File as")); if (!localFile.isEmpty()) { - QString txnID = postFile(localFile.fileName(), localFile, false); - setHasFileUploading(true); - connect(this, &Room::fileTransferCompleted, - [=](QString id, QUrl localFile, QUrl mxcUrl) { - if (id == txnID) { - setFileUploadingProgress(0); - setHasFileUploading(false); - } - }); - connect(this, &Room::fileTransferFailed, [=](QString id, QString error) { - if (id == txnID) { - setFileUploadingProgress(0); - setHasFileUploading(false); - } - }); - connect( - this, &Room::fileTransferProgress, - [=](QString id, qint64 progress, qint64 total) { - if (id == txnID) { - qDebug() << "Progress:" << progress << total; - setFileUploadingProgress(int(float(progress) / float(total) * 100)); - } - }); + uploadFile(localFile); } } +void SpectralRoom::uploadFile(const QUrl& url) { + if (url.isEmpty()) + return; + + QString txnID = postFile(url.fileName(), url, false); + setHasFileUploading(true); + connect(this, &Room::fileTransferCompleted, + [=](QString id, QUrl localFile, QUrl mxcUrl) { + if (id == txnID) { + setFileUploadingProgress(0); + setHasFileUploading(false); + } + }); + connect(this, &Room::fileTransferFailed, [=](QString id, QString error) { + if (id == txnID) { + setFileUploadingProgress(0); + setHasFileUploading(false); + } + }); + connect( + this, &Room::fileTransferProgress, + [=](QString id, qint64 progress, qint64 total) { + if (id == txnID) { + qDebug() << "Progress:" << progress << total; + setFileUploadingProgress(int(float(progress) / float(total) * 100)); + } + }); +} + void SpectralRoom::acceptInvitation() { connection()->joinRoom(id()); } diff --git a/src/spectralroom.h b/src/spectralroom.h index 205c3f6..72cc487 100644 --- a/src/spectralroom.h +++ b/src/spectralroom.h @@ -255,6 +255,7 @@ class SpectralRoom : public Room { public slots: void chooseAndUploadFile(); + void uploadFile(const QUrl& url); void acceptInvitation(); void forget(); void sendTypingNotification(bool isTyping); From 5fd5d0d9ea767bdb9e3070e0f496ce3a1424cc8f Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sat, 18 May 2019 16:27:10 +0800 Subject: [PATCH 4/7] Sort room list based on notification count and highlight count. --- imports/Spectral/Panel/RoomListPanel.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/imports/Spectral/Panel/RoomListPanel.qml b/imports/Spectral/Panel/RoomListPanel.qml index 00cf0b1..a6125e7 100644 --- a/imports/Spectral/Panel/RoomListPanel.qml +++ b/imports/Spectral/Panel/RoomListPanel.qml @@ -54,6 +54,16 @@ Item { sorters: [ RoleSorter { roleName: "category" }, + ExpressionSorter { + expression: { + return modelLeft.highlightCount > 0; + } + }, + ExpressionSorter { + expression: { + return modelLeft.notificationCount > 0; + } + }, RoleSorter { roleName: "lastActiveTime" sortOrder: Qt.DescendingOrder From ae5154fd35bfec8a029f2872695a6c176d083d91 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sat, 18 May 2019 16:32:36 +0800 Subject: [PATCH 5/7] Fix link color in reply delegate. --- imports/Spectral/Component/Timeline/MessageDelegate.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index e48f2c7..8671a61 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -162,7 +162,7 @@ ColumnLayout { Layout.fillWidth: true color: !sentByMe ? MPalette.foreground : "white" - text: "" + (replyDisplay || "") + text: "" + (replyDisplay || "") wrapMode: Label.Wrap textFormat: Label.RichText @@ -191,7 +191,7 @@ ColumnLayout { id: contentLabel - text: "" + display + text: "" + display color: darkBackground ? "white" : MPalette.foreground From 603cb330428146c8303426bb6ef5a19162f15372 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 19 May 2019 21:58:54 +0800 Subject: [PATCH 6/7] New attachment mechanism. Also add image from clipboard. --- imports/Spectral/Dialog/qmldir | 1 + imports/Spectral/Panel/RoomListPanel.qml | 2 +- imports/Spectral/Panel/RoomPanel.qml | 107 +++++++++++++++++- imports/Spectral/Panel/RoomPanelInput.qml | 73 +++++++++++- qml/main.qml | 7 +- spectral.pro | 10 +- src/controller.cpp | 20 +--- src/controller.h | 9 -- src/imageclipboard.cpp | 26 +++++ src/imageclipboard.h | 29 +++++ src/main.cpp | 14 ++- ...geprovider.cpp => matriximageprovider.cpp} | 4 +- ...{imageprovider.h => matriximageprovider.h} | 10 +- src/notifications/manager.h | 20 ++-- src/spectralroom.cpp | 11 +- src/spectralroom.h | 3 +- 16 files changed, 279 insertions(+), 67 deletions(-) create mode 100644 src/imageclipboard.cpp create mode 100644 src/imageclipboard.h rename src/{imageprovider.cpp => matriximageprovider.cpp} (97%) rename src/{imageprovider.h => matriximageprovider.h} (87%) diff --git a/imports/Spectral/Dialog/qmldir b/imports/Spectral/Dialog/qmldir index df7f062..51a130a 100644 --- a/imports/Spectral/Dialog/qmldir +++ b/imports/Spectral/Dialog/qmldir @@ -11,3 +11,4 @@ FontFamilyDialog 2.0 FontFamilyDialog.qml AccountDetailDialog 2.0 AccountDetailDialog.qml OpenFileDialog 2.0 OpenFileDialog.qml OpenFolderDialog 2.0 OpenFolderDialog.qml +ImageClipboardDialog 2.0 ImageClipboardDialog.qml diff --git a/imports/Spectral/Panel/RoomListPanel.qml b/imports/Spectral/Panel/RoomListPanel.qml index a6125e7..254078a 100644 --- a/imports/Spectral/Panel/RoomListPanel.qml +++ b/imports/Spectral/Panel/RoomListPanel.qml @@ -31,7 +31,7 @@ Item { connection: root.connection - onNewMessage: if (!window.active && MSettings.showNotification) spectralController.postNotification(roomId, eventId, roomName, senderName, text, icon) + onNewMessage: if (!window.active && MSettings.showNotification) notificationsManager.postNotification(roomId, eventId, roomName, senderName, text, icon) } SortFilterProxyModel { diff --git a/imports/Spectral/Panel/RoomPanel.qml b/imports/Spectral/Panel/RoomPanel.qml index 185e4bd..7cb1c48 100644 --- a/imports/Spectral/Panel/RoomPanel.qml +++ b/imports/Spectral/Panel/RoomPanel.qml @@ -3,10 +3,12 @@ import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls.Material 2.12 import Qt.labs.qmlmodels 1.0 +import Qt.labs.platform 1.0 import Spectral.Component 2.0 import Spectral.Component.Emoji 2.0 import Spectral.Component.Timeline 2.0 +import Spectral.Dialog 2.0 import Spectral.Effect 2.0 import Spectral 0.1 @@ -31,10 +33,113 @@ Item { onDropped: { if (!drop.hasUrls) return - currentRoom.uploadFile(drop.urls[0]) + roomPanelInput.attach(drop.urls[0]) } } + ImageClipboard { + id: imageClipboard + } + + Popup { + anchors.centerIn: parent + + id: attachDialog + + padding: 16 + + contentItem: RowLayout { + Control { + Layout.preferredWidth: 160 + Layout.fillHeight: true + + padding: 16 + + contentItem: ColumnLayout { + spacing: 16 + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + + icon: "\ue2c8" + font.pixelSize: 64 + color: MPalette.lighter + } + + Label { + Layout.alignment: Qt.AlignHCenter + + text: "Choose local file" + } + } + + background: RippleEffect { + onClicked: { + attachDialog.close() + + var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay) + + fileDialog.chosen.connect(function(path) { + if (!path) return + + roomPanelInput.attach(path) + }) + + fileDialog.open() + } + } + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.fillHeight: true + + color: MPalette.banner + } + + Control { + Layout.preferredWidth: 160 + Layout.fillHeight: true + + padding: 16 + + contentItem: ColumnLayout { + spacing: 16 + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + + icon: "\ue410" + font.pixelSize: 64 + color: MPalette.lighter + } + + Label { + Layout.alignment: Qt.AlignHCenter + + text: "Clipboard image" + color: MPalette.foreground + } + } + + background: RippleEffect { + onClicked: { + var localPath = StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png" + imageClipboard.saveImage(localPath) + roomPanelInput.attach(localPath) + attachDialog.close() + } + } + } + } + } + + Component { + id: openFileDialog + + OpenFileDialog {} + } + Column { anchors.centerIn: parent diff --git a/imports/Spectral/Panel/RoomPanelInput.qml b/imports/Spectral/Panel/RoomPanelInput.qml index 6dc897d..41bbd72 100644 --- a/imports/Spectral/Panel/RoomPanelInput.qml +++ b/imports/Spectral/Panel/RoomPanelInput.qml @@ -5,6 +5,7 @@ import QtQuick.Controls.Material 2.12 import Spectral.Component 2.0 import Spectral.Component.Emoji 2.0 +import Spectral.Dialog 2.0 import Spectral.Effect 2.0 import Spectral.Setting 0.1 @@ -21,6 +22,9 @@ Control { property int autoCompleteBeginPosition property int autoCompleteEndPosition + property bool hasAttachment: false + property url attachmentPath + id: root padding: 0 @@ -171,13 +175,13 @@ Control { Layout.alignment: Qt.AlignBottom id: uploadButton - visible: !isReply + visible: !isReply && !hasAttachment contentItem: MaterialIcon { icon: "\ue226" } - onClicked: currentRoom.chooseAndUploadFile() + onClicked: attachDialog.open() BusyIndicator { anchors.fill: parent @@ -202,6 +206,51 @@ Control { onClicked: clearReply() } + Control { + Layout.margins: 6 + Layout.preferredHeight: 36 + Layout.alignment: Qt.AlignVCenter + + visible: hasAttachment + + rightPadding: 8 + + background: Rectangle { + color: MPalette.accent + radius: height / 2 + antialiasing: true + } + + contentItem: RowLayout { + spacing: 0 + + ToolButton { + Layout.preferredWidth: height + Layout.fillHeight: true + + id: cancelAttachmentButton + + contentItem: MaterialIcon { + icon: "\ue5cd" + color: "white" + font.pixelSize: 18 + } + + onClicked: { + hasAttachment = false + attachmentPath = "" + } + } + + Label { + Layout.alignment: Qt.AlignVCenter + + text: attachmentPath != "" ? attachmentPath.toString().substring(attachmentPath.toString().lastIndexOf('/') + 1, attachmentPath.length) : "" + color: "white" + } + } + } + TextArea { property real progress: 0 @@ -307,6 +356,12 @@ Control { if (text.trim().length === 0) { return } if(!currentRoom) { return } + if (hasAttachment) { + currentRoom.uploadFile(attachmentPath, text) + clearAttachment() + return + } + var PREFIX_ME = '/me ' var PREFIX_NOTICE = '/notice ' var PREFIX_RAINBOW = '/rainbow ' @@ -372,6 +427,10 @@ Control { } } + ImageClipboard { + id: imageClipboard + } + function insert(str) { inputField.insert(inputField.cursorPosition, str) } @@ -396,4 +455,14 @@ Control { autoCompleteListView.visible = false emojiPicker.visible = false } + + function attach(localPath) { + hasAttachment = true + attachmentPath = localPath + } + + function clearAttachment() { + hasAttachment = false + attachmentPath = "" + } } diff --git a/qml/main.qml b/qml/main.qml index 350b241..e28743d 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -56,12 +56,17 @@ ApplicationWindow { quitOnLastWindowClosed: !MSettings.showTray + onErrorOccured: errorControl.show(error + ": " + detail, 3000) + } + + NotificationsManager { + id: notificationsManager + onNotificationClicked: { roomListForm.enteredRoom = spectralController.connection.room(roomId) roomForm.goToEvent(eventId) showWindow() } - onErrorOccured: errorControl.show(error + ": " + detail, 3000) } Shortcut { diff --git a/spectral.pro b/spectral.pro index fe7103f..91e9392 100644 --- a/spectral.pro +++ b/spectral.pro @@ -42,7 +42,9 @@ HEADERS += \ include/hoedown/escape.h \ include/hoedown/html.h \ include/hoedown/stack.h \ - include/hoedown/version.h + include/hoedown/version.h \ + src/imageclipboard.h \ + src/matriximageprovider.h SOURCES += \ include/hoedown/autolink.c \ @@ -53,7 +55,9 @@ SOURCES += \ include/hoedown/html_blocks.c \ include/hoedown/html_smartypants.c \ include/hoedown/stack.c \ - include/hoedown/version.c + include/hoedown/version.c \ + src/imageclipboard.cpp \ + src/matriximageprovider.cpp # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked deprecated (the exact warnings @@ -117,7 +121,6 @@ mac { HEADERS += \ src/controller.h \ src/roomlistmodel.h \ - src/imageprovider.h \ src/messageeventmodel.h \ src/emojimodel.h \ src/spectralroom.h \ @@ -130,7 +133,6 @@ HEADERS += \ SOURCES += src/main.cpp \ src/controller.cpp \ src/roomlistmodel.cpp \ - src/imageprovider.cpp \ src/messageeventmodel.cpp \ src/emojimodel.cpp \ src/spectralroom.cpp \ diff --git a/src/controller.cpp b/src/controller.cpp index d305f38..4f52ebd 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -31,13 +31,9 @@ #include #include -Controller::Controller(QObject* parent) - : QObject(parent), notificationsManager(this) { +Controller::Controller(QObject* parent) : QObject(parent) { QApplication::setQuitOnLastWindowClosed(false); - connect(¬ificationsManager, &NotificationsManager::notificationClicked, - this, &Controller::notificationClicked); - Connection::setRoomType(); Connection::setUserType(); @@ -232,10 +228,6 @@ void Controller::createDirectChat(Connection* c, const QString& userID) { }); } -void Controller::copyToClipboard(const QString& text) { - m_clipboard->setText(text); -} - void Controller::playAudio(QUrl localFile) { QMediaPlayer* player = new QMediaPlayer; player->setMedia(localFile); @@ -243,16 +235,6 @@ void Controller::playAudio(QUrl localFile) { connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); }); } -void Controller::postNotification(const QString& roomId, - const QString& eventId, - const QString& roomName, - const QString& senderName, - const QString& text, - const QImage& icon) { - notificationsManager.postNotification(roomId, eventId, roomName, senderName, - text, icon); -} - int Controller::dpi() { return SettingsGroup("Interface").value("dpi", 100).toInt(); } diff --git a/src/controller.h b/src/controller.h index 2e73c21..cb25b1b 100644 --- a/src/controller.h +++ b/src/controller.h @@ -67,8 +67,6 @@ class Controller : public QObject { } private: - QClipboard* m_clipboard = QApplication::clipboard(); - NotificationsManager notificationsManager; QVector m_connections; QPointer m_connection; @@ -99,14 +97,7 @@ class Controller : public QObject { void joinRoom(Connection* c, const QString& alias); void createRoom(Connection* c, const QString& name, const QString& topic); void createDirectChat(Connection* c, const QString& userID); - void copyToClipboard(const QString& text); void playAudio(QUrl localFile); - void postNotification(const QString& roomId, - const QString& eventId, - const QString& roomName, - const QString& senderName, - const QString& text, - const QImage& icon); }; #endif // CONTROLLER_H diff --git a/src/imageclipboard.cpp b/src/imageclipboard.cpp new file mode 100644 index 0000000..deb00ed --- /dev/null +++ b/src/imageclipboard.cpp @@ -0,0 +1,26 @@ +#include "imageclipboard.h" + +#include +#include + +ImageClipboard::ImageClipboard(QObject* parent) + : QObject(parent), m_clipboard(QGuiApplication::clipboard()) { + connect(m_clipboard, &QClipboard::changed, this, + &ImageClipboard::imageChanged); +} + +bool ImageClipboard::hasImage() { + return !image().isNull(); +} + +QImage ImageClipboard::image() { + return m_clipboard->image(); +} + +void ImageClipboard::saveImage(const QUrl& localPath) { + auto i = image(); + + if (i.isNull()) return; + + i.save(localPath.toString()); +} diff --git a/src/imageclipboard.h b/src/imageclipboard.h new file mode 100644 index 0000000..d548c7e --- /dev/null +++ b/src/imageclipboard.h @@ -0,0 +1,29 @@ +#ifndef IMAGECLIPBOARD_H +#define IMAGECLIPBOARD_H + +#include +#include +#include + +class ImageClipboard : public QObject { + Q_OBJECT + Q_PROPERTY(bool hasImage READ hasImage NOTIFY imageChanged) + Q_PROPERTY(QImage image READ image NOTIFY imageChanged) + + public: + explicit ImageClipboard(QObject* parent = nullptr); + + bool hasImage(); + QImage image(); + + private: + QClipboard* m_clipboard; + + signals: + void imageChanged(); + + public slots: + void saveImage(const QUrl& localPath); +}; + +#endif // IMAGECLIPBOARD_H diff --git a/src/main.cpp b/src/main.cpp index 60727f3..b6ee77f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,8 +8,10 @@ #include "accountlistmodel.h" #include "controller.h" #include "emojimodel.h" -#include "imageprovider.h" +#include "imageclipboard.h" +#include "matriximageprovider.h" #include "messageeventmodel.h" +#include "notifications/manager.h" #include "room.h" #include "roomlistmodel.h" #include "spectralroom.h" @@ -55,6 +57,9 @@ int main(int argc, char* argv[]) { qmlRegisterType("Spectral", 0, 1, "UserListModel"); qmlRegisterType("Spectral", 0, 1, "MessageEventModel"); qmlRegisterType("Spectral", 0, 1, "EmojiModel"); + qmlRegisterType("Spectral", 0, 1, + "NotificationsManager"); + qmlRegisterType("Spectral", 0, 1, "ImageClipboard"); qmlRegisterUncreatableType("Spectral", 0, 1, "RoomMessageEvent", "ENUM"); qmlRegisterUncreatableType("Spectral", 0, 1, "RoomType", "ENUM"); @@ -72,9 +77,10 @@ int main(int argc, char* argv[]) { QQmlApplicationEngine engine; engine.addImportPath("qrc:/imports"); - ImageProvider* m_provider = new ImageProvider(); - engine.rootContext()->setContextProperty("imageProvider", m_provider); - engine.addImageProvider(QLatin1String("mxc"), m_provider); + MatrixImageProvider* matrixImageProvider = new MatrixImageProvider(); + engine.rootContext()->setContextProperty("imageProvider", + matrixImageProvider); + engine.addImageProvider(QLatin1String("mxc"), matrixImageProvider); engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); if (engine.rootObjects().isEmpty()) diff --git a/src/imageprovider.cpp b/src/matriximageprovider.cpp similarity index 97% rename from src/imageprovider.cpp rename to src/matriximageprovider.cpp index be98de2..13c4ca5 100644 --- a/src/imageprovider.cpp +++ b/src/matriximageprovider.cpp @@ -1,4 +1,4 @@ -#include "imageprovider.h" +#include "matriximageprovider.h" #include #include @@ -108,7 +108,7 @@ void ThumbnailResponse::cancel() { Qt::QueuedConnection); } -QQuickImageResponse* ImageProvider::requestImageResponse( +QQuickImageResponse* MatrixImageProvider::requestImageResponse( const QString& id, const QSize& requestedSize) { return new ThumbnailResponse(m_connection.load(), id, requestedSize); diff --git a/src/imageprovider.h b/src/matriximageprovider.h similarity index 87% rename from src/imageprovider.h rename to src/matriximageprovider.h index ed31e94..980ed3f 100644 --- a/src/imageprovider.h +++ b/src/matriximageprovider.h @@ -1,5 +1,5 @@ -#ifndef IMAGEPROVIDER_H -#define IMAGEPROVIDER_H +#ifndef MatrixImageProvider_H +#define MatrixImageProvider_H #pragma once #include @@ -43,12 +43,12 @@ class ThumbnailResponse : public QQuickImageResponse { void cancel() override; }; -class ImageProvider : public QObject, public QQuickAsyncImageProvider { +class MatrixImageProvider : public QObject, public QQuickAsyncImageProvider { Q_OBJECT Q_PROPERTY(QMatrixClient::Connection* connection READ connection WRITE setConnection NOTIFY connectionChanged) public: - explicit ImageProvider() = default; + explicit MatrixImageProvider() = default; QQuickImageResponse* requestImageResponse( const QString& id, @@ -67,4 +67,4 @@ class ImageProvider : public QObject, public QQuickAsyncImageProvider { QAtomicPointer m_connection; }; -#endif // IMAGEPROVIDER_H +#endif // MatrixImageProvider_H diff --git a/src/notifications/manager.h b/src/notifications/manager.h index 0115713..5e7888f 100644 --- a/src/notifications/manager.h +++ b/src/notifications/manager.h @@ -19,11 +19,7 @@ struct roomEventId { class NotificationsManager : public QObject { Q_OBJECT public: - NotificationsManager(QObject *parent = nullptr); - - void postNotification(const QString &roomId, const QString &eventId, - const QString &roomName, const QString &senderName, - const QString &text, const QImage &icon); + NotificationsManager(QObject* parent = nullptr); signals: void notificationClicked(const QString roomId, const QString eventId); @@ -31,7 +27,8 @@ class NotificationsManager : public QObject { private: #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) QDBusInterface dbus; - uint showNotification(const QString summary, const QString text, + uint showNotification(const QString summary, + const QString text, const QImage image); #endif @@ -43,9 +40,16 @@ class NotificationsManager : public QObject { public slots: void actionInvoked(uint id, QString action); void notificationClosed(uint id, uint reason); + + void postNotification(const QString& roomId, + const QString& eventId, + const QString& roomName, + const QString& senderName, + const QString& text, + const QImage& icon); }; #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) -QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image); -const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &); +QDBusArgument& operator<<(QDBusArgument& arg, const QImage& image); +const QDBusArgument& operator>>(const QDBusArgument& arg, QImage&); #endif diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index 63b3f33..ddbdb95 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -44,18 +44,11 @@ inline QSize getImageSize(const QUrl& imageUrl) { return reader.size(); } -void SpectralRoom::chooseAndUploadFile() { - auto localFile = QFileDialog::getOpenFileUrl(Q_NULLPTR, tr("Save File as")); - if (!localFile.isEmpty()) { - uploadFile(localFile); - } -} - -void SpectralRoom::uploadFile(const QUrl& url) { +void SpectralRoom::uploadFile(const QUrl& url, const QString& body) { if (url.isEmpty()) return; - QString txnID = postFile(url.fileName(), url, false); + QString txnID = postFile(body.isEmpty() ? url.fileName() : body, url, false); setHasFileUploading(true); connect(this, &Room::fileTransferCompleted, [=](QString id, QUrl localFile, QUrl mxcUrl) { diff --git a/src/spectralroom.h b/src/spectralroom.h index 72cc487..10d7d37 100644 --- a/src/spectralroom.h +++ b/src/spectralroom.h @@ -254,8 +254,7 @@ class SpectralRoom : public Room { void fileUploadingProgressChanged(); public slots: - void chooseAndUploadFile(); - void uploadFile(const QUrl& url); + void uploadFile(const QUrl& url, const QString& body = ""); void acceptInvitation(); void forget(); void sendTypingNotification(bool isTyping); From 6bf7e7e0c94808223a715307e47408ea5b0b04e6 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 19 May 2019 22:35:08 +0800 Subject: [PATCH 7/7] Fix imageclipboard saveImage(). --- imports/Spectral/Panel/RoomPanel.qml | 2 +- imports/Spectral/Panel/RoomPanelInput.qml | 5 +++-- spectral.pro | 16 ++++++++-------- src/imageclipboard.cpp | 22 ++++++++++++++++++---- src/imageclipboard.h | 5 ++--- 5 files changed, 32 insertions(+), 18 deletions(-) diff --git a/imports/Spectral/Panel/RoomPanel.qml b/imports/Spectral/Panel/RoomPanel.qml index 7cb1c48..df05a5f 100644 --- a/imports/Spectral/Panel/RoomPanel.qml +++ b/imports/Spectral/Panel/RoomPanel.qml @@ -125,7 +125,7 @@ Item { background: RippleEffect { onClicked: { var localPath = StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png" - imageClipboard.saveImage(localPath) + if (!imageClipboard.saveImage(localPath)) return roomPanelInput.attach(localPath) attachDialog.close() } diff --git a/imports/Spectral/Panel/RoomPanelInput.qml b/imports/Spectral/Panel/RoomPanelInput.qml index 41bbd72..6930290 100644 --- a/imports/Spectral/Panel/RoomPanelInput.qml +++ b/imports/Spectral/Panel/RoomPanelInput.qml @@ -301,7 +301,7 @@ Control { Keys.onReturnPressed: { if (event.modifiers & Qt.ShiftModifier) { insert(cursorPosition, "\n") - } else if (text) { + } else { postMessage(text) text = "" closeAll() @@ -353,7 +353,6 @@ Control { } function postMessage(text) { - if (text.trim().length === 0) { return } if(!currentRoom) { return } if (hasAttachment) { @@ -362,6 +361,8 @@ Control { return } + if (text.trim().length === 0) { return } + var PREFIX_ME = '/me ' var PREFIX_NOTICE = '/notice ' var PREFIX_RAINBOW = '/rainbow ' diff --git a/spectral.pro b/spectral.pro index 91e9392..dfcab20 100644 --- a/spectral.pro +++ b/spectral.pro @@ -42,9 +42,7 @@ HEADERS += \ include/hoedown/escape.h \ include/hoedown/html.h \ include/hoedown/stack.h \ - include/hoedown/version.h \ - src/imageclipboard.h \ - src/matriximageprovider.h + include/hoedown/version.h SOURCES += \ include/hoedown/autolink.c \ @@ -55,9 +53,7 @@ SOURCES += \ include/hoedown/html_blocks.c \ include/hoedown/html_smartypants.c \ include/hoedown/stack.c \ - include/hoedown/version.c \ - src/imageclipboard.cpp \ - src/matriximageprovider.cpp + include/hoedown/version.c # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked deprecated (the exact warnings @@ -128,7 +124,9 @@ HEADERS += \ src/accountlistmodel.h \ src/spectraluser.h \ src/notifications/manager.h \ - src/utils.h + src/utils.h \ + src/imageclipboard.h \ + src/matriximageprovider.h SOURCES += src/main.cpp \ src/controller.cpp \ @@ -139,7 +137,9 @@ SOURCES += src/main.cpp \ src/userlistmodel.cpp \ src/accountlistmodel.cpp \ src/spectraluser.cpp \ - src/utils.cpp + src/utils.cpp \ + src/imageclipboard.cpp \ + src/matriximageprovider.cpp unix:!mac { SOURCES += src/notifications/managerlinux.cpp diff --git a/src/imageclipboard.cpp b/src/imageclipboard.cpp index deb00ed..d825835 100644 --- a/src/imageclipboard.cpp +++ b/src/imageclipboard.cpp @@ -1,7 +1,10 @@ #include "imageclipboard.h" +#include +#include #include #include +#include ImageClipboard::ImageClipboard(QObject* parent) : QObject(parent), m_clipboard(QGuiApplication::clipboard()) { @@ -17,10 +20,21 @@ QImage ImageClipboard::image() { return m_clipboard->image(); } -void ImageClipboard::saveImage(const QUrl& localPath) { - auto i = image(); +bool ImageClipboard::saveImage(const QUrl& localPath) { + if (!localPath.isLocalFile()) + return false; - if (i.isNull()) return; + auto i = image(); - i.save(localPath.toString()); + if (i.isNull()) + return false; + + QString path = QFileInfo(localPath.toString()).absolutePath(); + QDir dir; + if (!dir.exists(path)) + dir.mkpath(path); + + i.save(localPath.toLocalFile()); + + return true; } diff --git a/src/imageclipboard.h b/src/imageclipboard.h index d548c7e..a8cc181 100644 --- a/src/imageclipboard.h +++ b/src/imageclipboard.h @@ -16,14 +16,13 @@ class ImageClipboard : public QObject { bool hasImage(); QImage image(); + Q_INVOKABLE bool saveImage(const QUrl& localPath); + private: QClipboard* m_clipboard; signals: void imageChanged(); - - public slots: - void saveImage(const QUrl& localPath); }; #endif // IMAGECLIPBOARD_H