New attachment mechanism. Also add image from clipboard.

square-messages
Black Hat 2019-05-19 21:58:54 +08:00
parent ae5154fd35
commit 603cb33042
16 changed files with 279 additions and 67 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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 = ""
}
}

View File

@ -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 {

View File

@ -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 \

View File

@ -31,13 +31,9 @@
#include <QtNetwork/QAuthenticator>
#include <QtNetwork/QNetworkReply>
Controller::Controller(QObject* parent)
: QObject(parent), notificationsManager(this) {
Controller::Controller(QObject* parent) : QObject(parent) {
QApplication::setQuitOnLastWindowClosed(false);
connect(&notificationsManager, &NotificationsManager::notificationClicked,
this, &Controller::notificationClicked);
Connection::setRoomType<SpectralRoom>();
Connection::setUserType<SpectralUser>();
@ -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();
}

View File

@ -67,8 +67,6 @@ class Controller : public QObject {
}
private:
QClipboard* m_clipboard = QApplication::clipboard();
NotificationsManager notificationsManager;
QVector<Connection*> m_connections;
QPointer<Connection> 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

26
src/imageclipboard.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "imageclipboard.h"
#include <QGuiApplication>
#include <QUrl>
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());
}

29
src/imageclipboard.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef IMAGECLIPBOARD_H
#define IMAGECLIPBOARD_H
#include <QClipboard>
#include <QImage>
#include <QObject>
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

View File

@ -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<UserListModel>("Spectral", 0, 1, "UserListModel");
qmlRegisterType<MessageEventModel>("Spectral", 0, 1, "MessageEventModel");
qmlRegisterType<EmojiModel>("Spectral", 0, 1, "EmojiModel");
qmlRegisterType<NotificationsManager>("Spectral", 0, 1,
"NotificationsManager");
qmlRegisterType<ImageClipboard>("Spectral", 0, 1, "ImageClipboard");
qmlRegisterUncreatableType<RoomMessageEvent>("Spectral", 0, 1,
"RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<RoomType>("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())

View File

@ -1,4 +1,4 @@
#include "imageprovider.h"
#include "matriximageprovider.h"
#include <QDir>
#include <QFileInfo>
@ -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);

View File

@ -1,5 +1,5 @@
#ifndef IMAGEPROVIDER_H
#define IMAGEPROVIDER_H
#ifndef MatrixImageProvider_H
#define MatrixImageProvider_H
#pragma once
#include <QtQuick/QQuickAsyncImageProvider>
@ -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<QMatrixClient::Connection> m_connection;
};
#endif // IMAGEPROVIDER_H
#endif // MatrixImageProvider_H

View File

@ -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

View File

@ -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) {

View File

@ -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);