Merge branch 'dev' into 'master'
Context Menu Related Improvement Closes #6 See merge request b0/matrique!9
This commit is contained in:
commit
d8fbd8f507
|
@ -22,7 +22,6 @@ Item {
|
||||||
selectFolder: true
|
selectFolder: true
|
||||||
|
|
||||||
onAccepted: currentRoom.downloadFile(eventId, folder + "/" + currentRoom.fileNameToDownload(eventId))
|
onAccepted: currentRoom.downloadFile(eventId, folder + "/" + currentRoom.fileNameToDownload(eventId))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDownloadedChanged: downloaded && openOnFinished ? openSavedFile() : {}
|
onDownloadedChanged: downloaded && openOnFinished ? openSavedFile() : {}
|
||||||
|
|
|
@ -3,6 +3,9 @@ import QtQuick.Controls 2.2
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.2
|
||||||
|
|
||||||
AvatarContainer {
|
AvatarContainer {
|
||||||
|
readonly property var downloadAndOpen: downloadable.downloadAndOpen
|
||||||
|
readonly property var saveFileAs: downloadable.saveFileAs
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: messageRect
|
id: messageRect
|
||||||
|
|
||||||
|
@ -33,20 +36,6 @@ AvatarContainer {
|
||||||
ToolTip.text: content.body
|
ToolTip.text: content.body
|
||||||
|
|
||||||
onClicked: downloadable.downloadAndOpen()
|
onClicked: downloadable.downloadAndOpen()
|
||||||
onPressAndHold: messageImageMenu.popup()
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
id: messageImageMenu
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: "View"
|
|
||||||
onTriggered: downloadable.downloadAndOpen()
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Save as..."
|
|
||||||
onTriggered: downloadable.saveFileAs()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,13 +24,41 @@ Item {
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onPressAndHold: messageContextMenu.popup()
|
onPressAndHold: menuComponent.createObject(this)
|
||||||
|
|
||||||
Menu {
|
Component {
|
||||||
id: messageContextMenu
|
id: menuComponent
|
||||||
MenuItem {
|
Menu {
|
||||||
text: "Redact"
|
id: messageContextMenu
|
||||||
onTriggered: currentRoom.redactEvent(eventId)
|
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,6 @@ Item {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: listView
|
id: listView
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
@ -125,6 +124,7 @@ Item {
|
||||||
opacity: 0.2
|
opacity: 0.2
|
||||||
}
|
}
|
||||||
highlightMoveDuration: 250
|
highlightMoveDuration: 250
|
||||||
|
highlightResizeDuration: 0
|
||||||
|
|
||||||
currentIndex: -1
|
currentIndex: -1
|
||||||
|
|
||||||
|
@ -135,16 +135,13 @@ Item {
|
||||||
delegate: ItemDelegate {
|
delegate: ItemDelegate {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 80
|
height: 80
|
||||||
onClicked: listView.currentIndex = index
|
onPressed: listView.currentIndex = index
|
||||||
onPressAndHold: {
|
onPressAndHold: menuComponent.createObject(this)
|
||||||
roomListMenu.roomIndex = index
|
|
||||||
roomListMenu.popup()
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolTip.visible: mini && hovered
|
ToolTip.visible: mini && hovered
|
||||||
ToolTip.text: name
|
ToolTip.text: name
|
||||||
|
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 16
|
anchors.margins: 16
|
||||||
spacing: 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"
|
section.property: "category"
|
||||||
|
@ -202,27 +226,6 @@ Item {
|
||||||
color: Material.theme == Material.Light ? "#dbdbdb" : "#363636"
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "csapi/joining.h"
|
#include "csapi/joining.h"
|
||||||
#include "csapi/leaving.h"
|
#include "csapi/leaving.h"
|
||||||
|
|
||||||
|
#include <QClipboard>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
|
@ -120,3 +121,7 @@ void Controller::createRoom(const QString& name, const QString& topic) {
|
||||||
void Controller::createDirectChat(const QString& userID) {
|
void Controller::createDirectChat(const QString& userID) {
|
||||||
m_connection->requestDirectChat(userID);
|
m_connection->requestDirectChat(userID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Controller::copyToClipboard(const QString& text) {
|
||||||
|
m_clipboard->setText(text);
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
#ifndef CONTROLLER_H
|
#ifndef CONTROLLER_H
|
||||||
#define CONTROLLER_H
|
#define CONTROLLER_H
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
#include "roomlistmodel.h"
|
#include "roomlistmodel.h"
|
||||||
#include "user.h"
|
#include "user.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
using namespace QMatrixClient;
|
using namespace QMatrixClient;
|
||||||
|
|
||||||
class Controller : public QObject {
|
class Controller : public QObject {
|
||||||
|
@ -81,6 +83,8 @@ class Controller : public QObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QClipboard* m_clipboard = QApplication::clipboard();
|
||||||
|
|
||||||
void connected();
|
void connected();
|
||||||
void resync();
|
void resync();
|
||||||
void reconnect();
|
void reconnect();
|
||||||
|
@ -101,6 +105,7 @@ class Controller : public QObject {
|
||||||
void joinRoom(const QString& alias);
|
void joinRoom(const QString& alias);
|
||||||
void createRoom(const QString& name, const QString& topic);
|
void createRoom(const QString& name, const QString& topic);
|
||||||
void createDirectChat(const QString& userID);
|
void createDirectChat(const QString& userID);
|
||||||
|
void copyToClipboard(const QString& text);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONTROLLER_H
|
#endif // CONTROLLER_H
|
||||||
|
|
|
@ -28,6 +28,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const {
|
||||||
roles[SpecialMarksRole] = "marks";
|
roles[SpecialMarksRole] = "marks";
|
||||||
roles[LongOperationRole] = "progressInfo";
|
roles[LongOperationRole] = "progressInfo";
|
||||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||||
|
roles[PlainTextRole] = "plainText";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,6 +299,106 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const {
|
||||||
tr("Unknown Event"));
|
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) {
|
if (role == Qt::ToolTipRole) {
|
||||||
return evt.originalJson();
|
return evt.originalJson();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ class MessageEventModel : public QAbstractListModel {
|
||||||
ReadMarkerRole,
|
ReadMarkerRole,
|
||||||
SpecialMarksRole,
|
SpecialMarksRole,
|
||||||
LongOperationRole,
|
LongOperationRole,
|
||||||
|
PlainTextRole,
|
||||||
// For debugging
|
// For debugging
|
||||||
EventResolvedTypeRole,
|
EventResolvedTypeRole,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue