Add support for pasting images into a room (#180)

fixes #132
This commit is contained in:
christarazi 2018-01-09 23:52:59 -08:00 committed by mujx
parent 53f670096c
commit ddfce136ed
22 changed files with 388 additions and 73 deletions

View file

@ -144,6 +144,7 @@ set(SRC_FILES
# Dialogs # Dialogs
src/dialogs/CreateRoom.cc src/dialogs/CreateRoom.cc
src/dialogs/ImageOverlay.cc src/dialogs/ImageOverlay.cc
src/dialogs/PreviewImageOverlay.cc
src/dialogs/InviteUsers.cc src/dialogs/InviteUsers.cc
src/dialogs/JoinRoom.cc src/dialogs/JoinRoom.cc
src/dialogs/LeaveRoom.cc src/dialogs/LeaveRoom.cc
@ -229,6 +230,7 @@ qt5_wrap_cpp(MOC_HEADERS
# Dialogs # Dialogs
include/dialogs/CreateRoom.h include/dialogs/CreateRoom.h
include/dialogs/ImageOverlay.h include/dialogs/ImageOverlay.h
include/dialogs/PreviewImageOverlay.h
include/dialogs/InviteUsers.h include/dialogs/InviteUsers.h
include/dialogs/JoinRoom.h include/dialogs/JoinRoom.h
include/dialogs/LeaveRoom.h include/dialogs/LeaveRoom.h

View file

@ -55,9 +55,15 @@ public:
void downloadImage(const QString &event_id, const QUrl &url); void downloadImage(const QString &event_id, const QUrl &url);
void downloadFile(const QString &event_id, const QUrl &url); void downloadFile(const QString &event_id, const QUrl &url);
void messages(const QString &room_id, const QString &from_token, int limit = 30) noexcept; void messages(const QString &room_id, const QString &from_token, int limit = 30) noexcept;
void uploadImage(const QString &roomid, const QString &filename); void uploadImage(const QString &roomid,
void uploadFile(const QString &roomid, const QString &filename); const QSharedPointer<QIODevice> data,
void uploadAudio(const QString &roomid, const QString &filename); const QString &filename);
void uploadFile(const QString &roomid,
const QSharedPointer<QIODevice> data,
const QString &filename);
void uploadAudio(const QString &roomid,
const QSharedPointer<QIODevice> data,
const QString &filename);
void joinRoom(const QString &roomIdOrAlias); void joinRoom(const QString &roomIdOrAlias);
void leaveRoom(const QString &roomId); void leaveRoom(const QString &roomId);
void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000); void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000);
@ -98,7 +104,10 @@ signals:
const QString &homeserver, const QString &homeserver,
const QString &token); const QString &token);
void versionSuccess(); void versionSuccess();
void imageUploaded(const QString &roomid, const QString &filename, const QString &url); void imageUploaded(const QString &roomid,
const QSharedPointer<QIODevice> data,
const QString &filename,
const QString &url);
void fileUploaded(const QString &roomid, const QString &filename, const QString &url); void fileUploaded(const QString &roomid, const QString &filename, const QString &url);
void audioUploaded(const QString &roomid, const QString &filename, const QString &url); void audioUploaded(const QString &roomid, const QString &filename, const QString &url);
@ -131,7 +140,7 @@ signals:
void roomCreationFailed(const QString &msg); void roomCreationFailed(const QString &msg);
private: private:
QNetworkReply *makeUploadRequest(const QString &filename); QNetworkReply *makeUploadRequest(QSharedPointer<QIODevice> iodev);
// Client API prefix. // Client API prefix.
QString clientApiUrl_; QString clientApiUrl_;

View file

@ -27,8 +27,14 @@
#include "FlatButton.h" #include "FlatButton.h"
#include "LoadingIndicator.h" #include "LoadingIndicator.h"
#include "dialogs/PreviewImageOverlay.h"
#include "emoji/PickButton.h" #include "emoji/PickButton.h"
namespace dialogs {
class PreviewImageOverlay;
}
class FilteredTextEdit : public QTextEdit class FilteredTextEdit : public QTextEdit
{ {
Q_OBJECT Q_OBJECT
@ -48,16 +54,22 @@ signals:
void stoppedTyping(); void stoppedTyping();
void message(QString); void message(QString);
void command(QString name, QString args); void command(QString name, QString args);
void image(const QSharedPointer<QIODevice> iodev, const QString &img_name);
protected: protected:
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;
bool canInsertFromMimeData(const QMimeData *source) const override;
void insertFromMimeData(const QMimeData *source) override;
private: private:
std::deque<QString> true_history_, working_history_; std::deque<QString> true_history_, working_history_;
size_t history_index_; size_t history_index_;
QTimer *typingTimer_; QTimer *typingTimer_;
dialogs::PreviewImageOverlay previewDialog_;
void textChanged(); void textChanged();
void receiveImage(const QByteArray img, const QString &img_name);
void afterCompletion(int); void afterCompletion(int);
}; };
@ -83,9 +95,9 @@ signals:
void sendTextMessage(QString msg); void sendTextMessage(QString msg);
void sendEmoteMessage(QString msg); void sendEmoteMessage(QString msg);
void uploadImage(QString filename); void uploadImage(QSharedPointer<QIODevice> data, const QString &filename);
void uploadFile(QString filename); void uploadFile(QSharedPointer<QIODevice> data, const QString &filename);
void uploadAudio(QString filename); void uploadAudio(QSharedPointer<QIODevice> data, const QString &filename);
void sendJoinRoomRequest(const QString &room); void sendJoinRoomRequest(const QString &room);

View file

@ -0,0 +1,57 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QLabel>
#include <QLineEdit>
#include <QPixmap>
#include <QWidget>
#include "FlatButton.h"
class QMimeData;
namespace dialogs {
class PreviewImageOverlay : public QWidget
{
Q_OBJECT
public:
PreviewImageOverlay(QWidget *parent = nullptr);
void setImageAndCreate(const QByteArray data, const QString &type);
void setImageAndCreate(const QString &path);
signals:
void confirmImageUpload(const QByteArray data, const QString &img_name);
private:
void init();
QPixmap image_;
QByteArray imageData_;
QString imagePath_;
QLabel titleLabel_;
QLabel imageLabel_;
QLineEdit imageName_;
FlatButton upload_;
FlatButton cancel_;
};
} // dialogs

View file

@ -87,7 +87,9 @@ public:
void addUserMessage(mtx::events::MessageType ty, const QString &msg); void addUserMessage(mtx::events::MessageType ty, const QString &msg);
template<class Widget, mtx::events::MessageType MsgType> template<class Widget, mtx::events::MessageType MsgType>
void addUserMessage(const QString &url, const QString &filename); void addUserMessage(const QString &url,
const QSharedPointer<QIODevice> data,
const QString &filename);
void updatePendingMessage(int txn_id, QString event_id); void updatePendingMessage(int txn_id, QString event_id);
void scrollDown(); void scrollDown();
void addDateSeparator(QDateTime datetime, int position); void addDateSeparator(QDateTime datetime, int position);
@ -216,11 +218,13 @@ private:
template<class Widget, mtx::events::MessageType MsgType> template<class Widget, mtx::events::MessageType MsgType>
void void
TimelineView::addUserMessage(const QString &url, const QString &filename) TimelineView::addUserMessage(const QString &url,
const QSharedPointer<QIODevice> data,
const QString &filename)
{ {
auto with_sender = lastSender_ != local_user_; auto with_sender = lastSender_ != local_user_;
auto widget = new Widget(client_, url, filename, this); auto widget = new Widget(client_, url, data, filename, this);
TimelineItem *view_item = TimelineItem *view_item =
new TimelineItem(widget, local_user_, with_sender, scroll_widget_); new TimelineItem(widget, local_user_, with_sender, scroll_widget_);

View file

@ -23,6 +23,8 @@
#include <mtx.hpp> #include <mtx.hpp>
class QFile;
class MatrixClient; class MatrixClient;
class RoomInfoListItem; class RoomInfoListItem;
class TimelineView; class TimelineView;
@ -64,7 +66,10 @@ public slots:
void setHistoryView(const QString &room_id); void setHistoryView(const QString &room_id);
void queueTextMessage(const QString &msg); void queueTextMessage(const QString &msg);
void queueEmoteMessage(const QString &msg); void queueEmoteMessage(const QString &msg);
void queueImageMessage(const QString &roomid, const QString &filename, const QString &url); void queueImageMessage(const QString &roomid,
const QSharedPointer<QIODevice> data,
const QString &filename,
const QString &url);
void queueFileMessage(const QString &roomid, const QString &filename, const QString &url); void queueFileMessage(const QString &roomid, const QString &filename, const QString &url);
void queueAudioMessage(const QString &roomid, const QString &filename, const QString &url); void queueAudioMessage(const QString &roomid, const QString &filename, const QString &url);

View file

@ -48,6 +48,7 @@ public:
AudioItem(QSharedPointer<MatrixClient> client, AudioItem(QSharedPointer<MatrixClient> client,
const QString &url, const QString &url,
const QSharedPointer<QIODevice> data,
const QString &filename, const QString &filename,
QWidget *parent = nullptr); QWidget *parent = nullptr);

View file

@ -42,6 +42,7 @@ public:
FileItem(QSharedPointer<MatrixClient> client, FileItem(QSharedPointer<MatrixClient> client,
const QString &url, const QString &url,
const QSharedPointer<QIODevice> data,
const QString &filename, const QString &filename,
QWidget *parent = nullptr); QWidget *parent = nullptr);

View file

@ -36,6 +36,7 @@ public:
ImageItem(QSharedPointer<MatrixClient> client, ImageItem(QSharedPointer<MatrixClient> client,
const QString &url, const QString &url,
const QSharedPointer<QIODevice> data,
const QString &filename, const QString &filename,
QWidget *parent = nullptr); QWidget *parent = nullptr);

View file

@ -37,6 +37,7 @@ public:
VideoItem(QSharedPointer<MatrixClient> client, VideoItem(QSharedPointer<MatrixClient> client,
const QString &url, const QString &url,
const QSharedPointer<QIODevice> data,
const QString &filename, const QString &filename,
QWidget *parent = nullptr); QWidget *parent = nullptr);

View file

@ -95,16 +95,13 @@ dialogs--LeaveRoom,
dialogs--CreateRoom, dialogs--CreateRoom,
dialogs--InviteUsers, dialogs--InviteUsers,
dialogs--ReadReceipts, dialogs--ReadReceipts,
dialogs--JoinRoom { dialogs--JoinRoom,
background-color: #383c4a; dialogs--PreviewImageOverlay {
color: #caccd1;
}
QListWidget {
background-color: #383c4a; background-color: #383c4a;
color: #caccd1; color: #caccd1;
} }
QListWidget,
WelcomePage, WelcomePage,
LoginPage, LoginPage,
RegisterPage { RegisterPage {

View file

@ -98,11 +98,8 @@ dialogs--LeaveRoom,
dialogs--CreateRoom, dialogs--CreateRoom,
dialogs--InviteUsers, dialogs--InviteUsers,
dialogs--ReadReceipts, dialogs--ReadReceipts,
dialogs--JoinRoom { dialogs--JoinRoom,
background-color: white; dialogs--PreviewImageOverlay,
color: #333;
}
QListWidget { QListWidget {
background-color: white; background-color: white;
color: #333; color: #333;

View file

@ -228,17 +228,26 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
client_.data(), client_.data(),
&MatrixClient::joinRoom); &MatrixClient::joinRoom);
connect(text_input_, &TextInputWidget::uploadImage, this, [=](QString filename) { connect(text_input_,
client_->uploadImage(current_room_, filename); &TextInputWidget::uploadImage,
}); this,
[=](QSharedPointer<QIODevice> data, const QString &fn) {
client_->uploadImage(current_room_, data, fn);
});
connect(text_input_, &TextInputWidget::uploadFile, this, [=](QString filename) { connect(text_input_,
client_->uploadFile(current_room_, filename); &TextInputWidget::uploadFile,
}); this,
[=](QSharedPointer<QIODevice> data, const QString &fn) {
client_->uploadFile(current_room_, data, fn);
});
connect(text_input_, &TextInputWidget::uploadAudio, this, [=](QString filename) { connect(text_input_,
client_->uploadAudio(current_room_, filename); &TextInputWidget::uploadAudio,
}); this,
[=](QSharedPointer<QIODevice> data, const QString &fn) {
client_->uploadAudio(current_room_, data, fn);
});
connect( connect(
client_.data(), &MatrixClient::roomCreationFailed, this, &ChatPage::showNotification); client_.data(), &MatrixClient::roomCreationFailed, this, &ChatPage::showNotification);
@ -246,9 +255,9 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
connect(client_.data(), connect(client_.data(),
&MatrixClient::imageUploaded, &MatrixClient::imageUploaded,
this, this,
[=](QString roomid, QString filename, QString url) { [=](QString roomid, QSharedPointer<QIODevice> data, QString filename, QString url) {
text_input_->hideUploadSpinner(); text_input_->hideUploadSpinner();
view_manager_->queueImageMessage(roomid, filename, url); view_manager_->queueImageMessage(roomid, data, filename, url);
}); });
connect(client_.data(), connect(client_.data(),
&MatrixClient::fileUploaded, &MatrixClient::fileUploaded,

View file

@ -821,14 +821,16 @@ MatrixClient::messages(const QString &roomid, const QString &from_token, int lim
} }
void void
MatrixClient::uploadImage(const QString &roomid, const QString &filename) MatrixClient::uploadImage(const QString &roomid,
const QSharedPointer<QIODevice> data,
const QString &filename)
{ {
auto reply = makeUploadRequest(filename); auto reply = makeUploadRequest(data);
if (reply == nullptr) if (reply == nullptr)
return; return;
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, data, filename]() {
reply->deleteLater(); reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@ -838,12 +840,12 @@ MatrixClient::uploadImage(const QString &roomid, const QString &filename)
return; return;
} }
auto data = reply->readAll(); auto res_data = reply->readAll();
if (data.isEmpty()) if (res_data.isEmpty())
return; return;
auto json = QJsonDocument::fromJson(data); auto json = QJsonDocument::fromJson(res_data);
if (!json.isObject()) { if (!json.isObject()) {
qDebug() << "Media upload: Response is not a json object."; qDebug() << "Media upload: Response is not a json object.";
@ -857,16 +859,18 @@ MatrixClient::uploadImage(const QString &roomid, const QString &filename)
return; return;
} }
emit imageUploaded(roomid, filename, object.value("content_uri").toString()); emit imageUploaded(roomid, data, filename, object.value("content_uri").toString());
}); });
} }
void void
MatrixClient::uploadFile(const QString &roomid, const QString &filename) MatrixClient::uploadFile(const QString &roomid,
const QSharedPointer<QIODevice> data,
const QString &filename)
{ {
auto reply = makeUploadRequest(filename); auto reply = makeUploadRequest(data);
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, data, filename]() {
reply->deleteLater(); reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@ -900,11 +904,13 @@ MatrixClient::uploadFile(const QString &roomid, const QString &filename)
} }
void void
MatrixClient::uploadAudio(const QString &roomid, const QString &filename) MatrixClient::uploadAudio(const QString &roomid,
const QSharedPointer<QIODevice> data,
const QString &filename)
{ {
auto reply = makeUploadRequest(filename); auto reply = makeUploadRequest(data);
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, data, filename]() {
reply->deleteLater(); reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@ -1158,7 +1164,7 @@ MatrixClient::readEvent(const QString &room_id, const QString &event_id)
} }
QNetworkReply * QNetworkReply *
MatrixClient::makeUploadRequest(const QString &filename) MatrixClient::makeUploadRequest(QSharedPointer<QIODevice> iodev)
{ {
QUrlQuery query; QUrlQuery query;
query.addQueryItem("access_token", token_); query.addQueryItem("access_token", token_);
@ -1167,20 +1173,18 @@ MatrixClient::makeUploadRequest(const QString &filename)
endpoint.setPath(mediaApiUrl_ + "/upload"); endpoint.setPath(mediaApiUrl_ + "/upload");
endpoint.setQuery(query); endpoint.setQuery(query);
QFile file(filename); if (!iodev->open(QIODevice::ReadOnly)) {
if (!file.open(QIODevice::ReadWrite)) { qWarning() << "Error while reading device:" << iodev->errorString();
qDebug() << "Error while reading" << filename;
return nullptr; return nullptr;
} }
QMimeDatabase db; QMimeDatabase db;
QMimeType mime = db.mimeTypeForFile(filename, QMimeDatabase::MatchContent); QMimeType mime = db.mimeTypeForData(iodev.data());
QNetworkRequest request(QString(endpoint.toEncoded())); QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentLengthHeader, file.size());
request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name()); request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name());
auto reply = post(request, file.readAll()); auto reply = post(request, iodev.data());
return reply; return reply;
} }

View file

@ -16,10 +16,13 @@
*/ */
#include <QAbstractTextDocumentLayout> #include <QAbstractTextDocumentLayout>
#include <QApplication>
#include <QBuffer>
#include <QClipboard>
#include <QDebug> #include <QDebug>
#include <QFile>
#include <QFileDialog> #include <QFileDialog>
#include <QImageReader> #include <QImageReader>
#include <QMimeData>
#include <QMimeDatabase> #include <QMimeDatabase>
#include <QMimeType> #include <QMimeType>
#include <QPainter> #include <QPainter>
@ -33,6 +36,7 @@ static constexpr size_t INPUT_HISTORY_SIZE = 127;
FilteredTextEdit::FilteredTextEdit(QWidget *parent) FilteredTextEdit::FilteredTextEdit(QWidget *parent)
: QTextEdit{parent} : QTextEdit{parent}
, history_index_{0} , history_index_{0}
, previewDialog_{parent}
{ {
connect(document()->documentLayout(), connect(document()->documentLayout(),
&QAbstractTextDocumentLayout::documentSizeChanged, &QAbstractTextDocumentLayout::documentSizeChanged,
@ -50,6 +54,12 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
typingTimer_->setSingleShot(true); typingTimer_->setSingleShot(true);
connect(typingTimer_, &QTimer::timeout, this, &FilteredTextEdit::stopTyping); connect(typingTimer_, &QTimer::timeout, this, &FilteredTextEdit::stopTyping);
connect(&previewDialog_,
&dialogs::PreviewImageOverlay::confirmImageUpload,
this,
&FilteredTextEdit::receiveImage);
previewDialog_.hide();
} }
void void
@ -101,6 +111,42 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event)
} }
} }
bool
FilteredTextEdit::canInsertFromMimeData(const QMimeData *source) const
{
return (source->hasImage() || QTextEdit::canInsertFromMimeData(source));
}
void
FilteredTextEdit::insertFromMimeData(const QMimeData *source)
{
if (source->hasImage()) {
const auto formats = source->formats();
const auto idx = formats.indexOf(
QRegularExpression{"image/.+", QRegularExpression::CaseInsensitiveOption});
// Note: in the future we may want to look into what the best choice is from the
// formats list. For now we will default to PNG format.
QString type = "png";
if (idx != -1) {
type = formats.at(idx).split('/')[1];
}
// Encode raw pixel data of image.
QByteArray data = source->data("image/" + type);
previewDialog_.setImageAndCreate(data, type);
previewDialog_.show();
} else if (source->hasFormat("x-special/gnome-copied-files") &&
QImageReader{source->text()}.canRead()) {
// Special case for X11 users. See "Notes for X11 Users" in source.
// Source: http://doc.qt.io/qt-5/qclipboard.html
previewDialog_.setImageAndCreate(source->text());
previewDialog_.show();
} else {
QTextEdit::insertFromMimeData(source);
}
}
void void
FilteredTextEdit::stopTyping() FilteredTextEdit::stopTyping()
{ {
@ -146,6 +192,7 @@ FilteredTextEdit::submit()
history_index_ = 0; history_index_ = 0;
QString text = toPlainText(); QString text = toPlainText();
if (text.startsWith('/')) { if (text.startsWith('/')) {
int command_end = text.indexOf(' '); int command_end = text.indexOf(' ');
if (command_end == -1) if (command_end == -1)
@ -170,6 +217,14 @@ FilteredTextEdit::textChanged()
working_history_[history_index_] = toPlainText(); working_history_[history_index_] = toPlainText();
} }
void
FilteredTextEdit::receiveImage(const QByteArray img, const QString &img_name)
{
QSharedPointer<QBuffer> buffer{new QBuffer{this}};
buffer->setData(img);
emit image(buffer, img_name);
}
TextInputWidget::TextInputWidget(QWidget *parent) TextInputWidget::TextInputWidget(QWidget *parent)
: QFrame(parent) : QFrame(parent)
{ {
@ -231,6 +286,7 @@ TextInputWidget::TextInputWidget(QWidget *parent)
connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection())); connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection()));
connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage); connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage);
connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command); connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command);
connect(input_, &FilteredTextEdit::image, this, &TextInputWidget::uploadImage);
connect(emojiBtn_, connect(emojiBtn_,
SIGNAL(emojiSelected(const QString &)), SIGNAL(emojiSelected(const QString &)),
this, this,
@ -289,12 +345,13 @@ TextInputWidget::openFileSelection()
const auto format = mime.name().split("/")[0]; const auto format = mime.name().split("/")[0];
QSharedPointer<QFile> file{new QFile{fileName, this}};
if (format == "image") if (format == "image")
emit uploadImage(fileName); emit uploadImage(file, fileName);
else if (format == "audio") else if (format == "audio")
emit uploadAudio(fileName); emit uploadAudio(file, fileName);
else else
emit uploadFile(fileName); emit uploadFile(file, fileName);
showUploadSpinner(); showUploadSpinner();
} }

View file

@ -0,0 +1,142 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <QApplication>
#include <QBuffer>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include "Config.h"
#include "dialogs/PreviewImageOverlay.h"
using namespace dialogs;
static constexpr const char *DEFAULT = "Upload image?";
static constexpr const char *ERROR = "Failed to load image type '%1'. Continue upload?";
PreviewImageOverlay::PreviewImageOverlay(QWidget *parent)
: QWidget{parent}
, titleLabel_{tr(DEFAULT), this}
, imageLabel_{this}
, imageName_{tr("clipboard"), this}
, upload_{tr("Upload"), this}
, cancel_{tr("Cancel"), this}
{
auto hlayout = new QHBoxLayout;
hlayout->addWidget(&upload_);
hlayout->addWidget(&cancel_);
auto vlayout = new QVBoxLayout{this};
vlayout->addWidget(&titleLabel_);
vlayout->addWidget(&imageLabel_);
vlayout->addWidget(&imageName_);
vlayout->addLayout(hlayout);
connect(&upload_, &QPushButton::clicked, [&]() {
emit confirmImageUpload(imageData_, imageName_.text());
close();
});
connect(&cancel_, &QPushButton::clicked, [&]() { close(); });
}
void
PreviewImageOverlay::init()
{
auto window = QApplication::activeWindow();
auto winsize = window->frameGeometry().size();
auto center = window->frameGeometry().center();
auto img_size = image_.size();
imageName_.setText(QFileInfo{imagePath_}.fileName());
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
titleLabel_.setStyleSheet(
QString{"font-weight: bold; font-size: %1px;"}.arg(conf::headerFontSize));
titleLabel_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
titleLabel_.setAlignment(Qt::AlignCenter);
imageLabel_.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
imageLabel_.setAlignment(Qt::AlignCenter);
imageName_.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
imageName_.setAlignment(Qt::AlignCenter);
upload_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
cancel_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
upload_.setFontSize(conf::btn::fontSize);
cancel_.setFontSize(conf::btn::fontSize);
// Scale image preview to the size of the current window if it is larger.
if ((img_size.height() * img_size.width()) > (winsize.height() * winsize.width())) {
imageLabel_.setPixmap(image_.scaled(winsize, Qt::KeepAspectRatio));
} else {
imageLabel_.setPixmap(image_);
move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
}
imageLabel_.setScaledContents(false);
raise();
}
void
PreviewImageOverlay::setImageAndCreate(const QByteArray data, const QString &type)
{
imageData_ = data;
imagePath_ = "clipboard." + type;
auto loaded = image_.loadFromData(imageData_);
if (!loaded) {
titleLabel_.setText(QString{tr(ERROR)}.arg(type));
} else {
titleLabel_.setText(tr(DEFAULT));
}
init();
}
void
PreviewImageOverlay::setImageAndCreate(const QString &path)
{
QFile file{path};
imagePath_ = path;
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "Failed to open image from:" << path;
qWarning() << "Reason:" << file.errorString();
close();
return;
}
if ((imageData_ = file.readAll()).isEmpty()) {
qWarning() << "Failed to read image:" << file.errorString();
close();
return;
}
auto loaded = image_.loadFromData(imageData_);
if (!loaded) {
auto t = QFileInfo{path}.suffix();
titleLabel_.setText(QString{tr(ERROR)}.arg(t));
} else {
titleLabel_.setText(tr(DEFAULT));
}
init();
}

View file

@ -510,12 +510,8 @@ TimelineView::sendNextPendingMessage()
case mtx::events::MessageType::Image: case mtx::events::MessageType::Image:
case mtx::events::MessageType::File: case mtx::events::MessageType::File:
// FIXME: Improve the API // FIXME: Improve the API
client_->sendRoomMessage(m.ty, client_->sendRoomMessage(
m.txn_id, m.ty, m.txn_id, room_id_, m.filename, QFileInfo(m.filename), m.body);
room_id_,
QFileInfo(m.filename).fileName(),
QFileInfo(m.filename),
m.body);
break; break;
default: default:
client_->sendRoomMessage(m.ty, m.txn_id, room_id_, m.body, QFileInfo()); client_->sendRoomMessage(m.ty, m.txn_id, room_id_, m.body, QFileInfo());

View file

@ -85,6 +85,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
void void
TimelineViewManager::queueImageMessage(const QString &roomid, TimelineViewManager::queueImageMessage(const QString &roomid,
const QSharedPointer<QIODevice> data,
const QString &filename, const QString &filename,
const QString &url) const QString &url)
{ {
@ -95,7 +96,7 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
auto view = views_[roomid]; auto view = views_[roomid];
view->addUserMessage<ImageItem, mtx::events::MessageType::Image>(url, filename); view->addUserMessage<ImageItem, mtx::events::MessageType::Image>(url, data, filename);
} }
void void
@ -110,7 +111,7 @@ TimelineViewManager::queueFileMessage(const QString &roomid,
auto view = views_[roomid]; auto view = views_[roomid];
view->addUserMessage<FileItem, mtx::events::MessageType::File>(url, filename); view->addUserMessage<FileItem, mtx::events::MessageType::File>(url, nullptr, filename);
} }
void void
@ -125,7 +126,7 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
auto view = views_[roomid]; auto view = views_[roomid];
view->addUserMessage<AudioItem, mtx::events::MessageType::Audio>(url, filename); view->addUserMessage<AudioItem, mtx::events::MessageType::Audio>(url, nullptr, filename);
} }
void void

View file

@ -89,14 +89,16 @@ AudioItem::AudioItem(QSharedPointer<MatrixClient> client,
AudioItem::AudioItem(QSharedPointer<MatrixClient> client, AudioItem::AudioItem(QSharedPointer<MatrixClient> client,
const QString &url, const QString &url,
const QSharedPointer<QIODevice> data,
const QString &filename, const QString &filename,
QWidget *parent) QWidget *parent)
: QWidget(parent) : QWidget(parent)
, url_{url} , url_{url}
, text_{QFileInfo(filename).fileName()} , text_{QFileInfo{filename}.fileName()}
, client_{client} , client_{client}
{ {
readableFileSize_ = calculateFileSize(QFileInfo(filename).size()); Q_UNUSED(data);
readableFileSize_ = calculateFileSize(QFileInfo{filename}.size());
init(); init();
} }

View file

@ -76,14 +76,16 @@ FileItem::FileItem(QSharedPointer<MatrixClient> client,
FileItem::FileItem(QSharedPointer<MatrixClient> client, FileItem::FileItem(QSharedPointer<MatrixClient> client,
const QString &url, const QString &url,
const QSharedPointer<QIODevice> data,
const QString &filename, const QString &filename,
QWidget *parent) QWidget *parent)
: QWidget(parent) : QWidget(parent)
, url_{url} , url_{url}
, text_{QFileInfo(filename).fileName()} , text_{QFileInfo{filename}.fileName()}
, client_{client} , client_{client}
{ {
readableFileSize_ = calculateFileSize(QFileInfo(filename).size()); Q_UNUSED(data);
readableFileSize_ = calculateFileSize(QFileInfo{filename}.size());
init(); init();
} }

View file

@ -61,11 +61,12 @@ ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
ImageItem::ImageItem(QSharedPointer<MatrixClient> client, ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
const QString &url, const QString &url,
const QSharedPointer<QIODevice> data,
const QString &filename, const QString &filename,
QWidget *parent) QWidget *parent)
: QWidget(parent) : QWidget(parent)
, url_{url} , url_{url}
, text_{QFileInfo(filename).fileName()} , text_{filename}
, client_{client} , client_{client}
{ {
setMouseTracking(true); setMouseTracking(true);
@ -83,7 +84,19 @@ ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
url_ = QString("%1/_matrix/media/r0/download/%2") url_ = QString("%1/_matrix/media/r0/download/%2")
.arg(client_.data()->getHomeServer().toString(), media_params); .arg(client_.data()->getHomeServer().toString(), media_params);
setImage(QPixmap(filename)); if (data.isNull()) {
qWarning() << "No image data to display";
return;
}
if (data->reset()) {
QPixmap p;
p.loadFromData(data->readAll());
setImage(p);
} else {
qWarning() << "Failed to seek to beginning of device:" << data->errorString();
return;
}
} }
void void

View file

@ -66,6 +66,7 @@ VideoItem::VideoItem(QSharedPointer<MatrixClient> client,
VideoItem::VideoItem(QSharedPointer<MatrixClient> client, VideoItem::VideoItem(QSharedPointer<MatrixClient> client,
const QString &url, const QString &url,
const QSharedPointer<QIODevice> data,
const QString &filename, const QString &filename,
QWidget *parent) QWidget *parent)
: QWidget(parent) : QWidget(parent)
@ -73,6 +74,7 @@ VideoItem::VideoItem(QSharedPointer<MatrixClient> client,
, text_{QFileInfo(filename).fileName()} , text_{QFileInfo(filename).fileName()}
, client_{client} , client_{client}
{ {
Q_UNUSED(data);
readableFileSize_ = calculateFileSize(QFileInfo(filename).size()); readableFileSize_ = calculateFileSize(QFileInfo(filename).size());
init(); init();