diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index ebdec835..f49c0a08 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -228,17 +228,26 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
client_.data(),
&MatrixClient::joinRoom);
- connect(text_input_, &TextInputWidget::uploadImage, this, [=](QString filename) {
- client_->uploadImage(current_room_, filename);
- });
+ connect(text_input_,
+ &TextInputWidget::uploadImage,
+ this,
+ [=](QSharedPointer<QIODevice> data, const QString &fn) {
+ client_->uploadImage(current_room_, data, fn);
+ });
- connect(text_input_, &TextInputWidget::uploadFile, this, [=](QString filename) {
- client_->uploadFile(current_room_, filename);
- });
+ connect(text_input_,
+ &TextInputWidget::uploadFile,
+ this,
+ [=](QSharedPointer<QIODevice> data, const QString &fn) {
+ client_->uploadFile(current_room_, data, fn);
+ });
- connect(text_input_, &TextInputWidget::uploadAudio, this, [=](QString filename) {
- client_->uploadAudio(current_room_, filename);
- });
+ connect(text_input_,
+ &TextInputWidget::uploadAudio,
+ this,
+ [=](QSharedPointer<QIODevice> data, const QString &fn) {
+ client_->uploadAudio(current_room_, data, fn);
+ });
connect(
client_.data(), &MatrixClient::roomCreationFailed, this, &ChatPage::showNotification);
@@ -246,9 +255,9 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
connect(client_.data(),
&MatrixClient::imageUploaded,
this,
- [=](QString roomid, QString filename, QString url) {
+ [=](QString roomid, QSharedPointer<QIODevice> data, QString filename, QString url) {
text_input_->hideUploadSpinner();
- view_manager_->queueImageMessage(roomid, filename, url);
+ view_manager_->queueImageMessage(roomid, data, filename, url);
});
connect(client_.data(),
&MatrixClient::fileUploaded,
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index 72467385..67203d59 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -821,14 +821,16 @@ MatrixClient::messages(const QString &roomid, const QString &from_token, int lim
}
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)
return;
- connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() {
+ connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, data, filename]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@@ -838,12 +840,12 @@ MatrixClient::uploadImage(const QString &roomid, const QString &filename)
return;
}
- auto data = reply->readAll();
+ auto res_data = reply->readAll();
- if (data.isEmpty())
+ if (res_data.isEmpty())
return;
- auto json = QJsonDocument::fromJson(data);
+ auto json = QJsonDocument::fromJson(res_data);
if (!json.isObject()) {
qDebug() << "Media upload: Response is not a json object.";
@@ -857,16 +859,18 @@ MatrixClient::uploadImage(const QString &roomid, const QString &filename)
return;
}
- emit imageUploaded(roomid, filename, object.value("content_uri").toString());
+ emit imageUploaded(roomid, data, filename, object.value("content_uri").toString());
});
}
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();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@@ -900,11 +904,13 @@ MatrixClient::uploadFile(const QString &roomid, const QString &filename)
}
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();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@@ -1158,7 +1164,7 @@ MatrixClient::readEvent(const QString &room_id, const QString &event_id)
}
QNetworkReply *
-MatrixClient::makeUploadRequest(const QString &filename)
+MatrixClient::makeUploadRequest(QSharedPointer<QIODevice> iodev)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
@@ -1167,20 +1173,18 @@ MatrixClient::makeUploadRequest(const QString &filename)
endpoint.setPath(mediaApiUrl_ + "/upload");
endpoint.setQuery(query);
- QFile file(filename);
- if (!file.open(QIODevice::ReadWrite)) {
- qDebug() << "Error while reading" << filename;
+ if (!iodev->open(QIODevice::ReadOnly)) {
+ qWarning() << "Error while reading device:" << iodev->errorString();
return nullptr;
}
QMimeDatabase db;
- QMimeType mime = db.mimeTypeForFile(filename, QMimeDatabase::MatchContent);
+ QMimeType mime = db.mimeTypeForData(iodev.data());
QNetworkRequest request(QString(endpoint.toEncoded()));
- request.setHeader(QNetworkRequest::ContentLengthHeader, file.size());
request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name());
- auto reply = post(request, file.readAll());
+ auto reply = post(request, iodev.data());
return reply;
}
diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc
index dc2bebe7..f9198c78 100644
--- a/src/TextInputWidget.cc
+++ b/src/TextInputWidget.cc
@@ -16,10 +16,13 @@
*/
#include <QAbstractTextDocumentLayout>
+#include <QApplication>
+#include <QBuffer>
+#include <QClipboard>
#include <QDebug>
-#include <QFile>
#include <QFileDialog>
#include <QImageReader>
+#include <QMimeData>
#include <QMimeDatabase>
#include <QMimeType>
#include <QPainter>
@@ -33,6 +36,7 @@ static constexpr size_t INPUT_HISTORY_SIZE = 127;
FilteredTextEdit::FilteredTextEdit(QWidget *parent)
: QTextEdit{parent}
, history_index_{0}
+ , previewDialog_{parent}
{
connect(document()->documentLayout(),
&QAbstractTextDocumentLayout::documentSizeChanged,
@@ -50,6 +54,12 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
typingTimer_->setSingleShot(true);
connect(typingTimer_, &QTimer::timeout, this, &FilteredTextEdit::stopTyping);
+ connect(&previewDialog_,
+ &dialogs::PreviewImageOverlay::confirmImageUpload,
+ this,
+ &FilteredTextEdit::receiveImage);
+
+ previewDialog_.hide();
}
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
FilteredTextEdit::stopTyping()
{
@@ -146,6 +192,7 @@ FilteredTextEdit::submit()
history_index_ = 0;
QString text = toPlainText();
+
if (text.startsWith('/')) {
int command_end = text.indexOf(' ');
if (command_end == -1)
@@ -170,6 +217,14 @@ FilteredTextEdit::textChanged()
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)
: QFrame(parent)
{
@@ -231,6 +286,7 @@ TextInputWidget::TextInputWidget(QWidget *parent)
connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection()));
connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage);
connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command);
+ connect(input_, &FilteredTextEdit::image, this, &TextInputWidget::uploadImage);
connect(emojiBtn_,
SIGNAL(emojiSelected(const QString &)),
this,
@@ -289,12 +345,13 @@ TextInputWidget::openFileSelection()
const auto format = mime.name().split("/")[0];
+ QSharedPointer<QFile> file{new QFile{fileName, this}};
if (format == "image")
- emit uploadImage(fileName);
+ emit uploadImage(file, fileName);
else if (format == "audio")
- emit uploadAudio(fileName);
+ emit uploadAudio(file, fileName);
else
- emit uploadFile(fileName);
+ emit uploadFile(file, fileName);
showUploadSpinner();
}
diff --git a/src/dialogs/PreviewImageOverlay.cc b/src/dialogs/PreviewImageOverlay.cc
new file mode 100644
index 00000000..31ef00ed
--- /dev/null
+++ b/src/dialogs/PreviewImageOverlay.cc
@@ -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();
+}
diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index d21f30f0..75ce8141 100644
--- a/src/timeline/TimelineView.cc
+++ b/src/timeline/TimelineView.cc
@@ -510,12 +510,8 @@ TimelineView::sendNextPendingMessage()
case mtx::events::MessageType::Image:
case mtx::events::MessageType::File:
// FIXME: Improve the API
- client_->sendRoomMessage(m.ty,
- m.txn_id,
- room_id_,
- QFileInfo(m.filename).fileName(),
- QFileInfo(m.filename),
- m.body);
+ client_->sendRoomMessage(
+ m.ty, m.txn_id, room_id_, m.filename, QFileInfo(m.filename), m.body);
break;
default:
client_->sendRoomMessage(m.ty, m.txn_id, room_id_, m.body, QFileInfo());
diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc
index de1e1e32..65c9ac83 100644
--- a/src/timeline/TimelineViewManager.cc
+++ b/src/timeline/TimelineViewManager.cc
@@ -85,6 +85,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
void
TimelineViewManager::queueImageMessage(const QString &roomid,
+ const QSharedPointer<QIODevice> data,
const QString &filename,
const QString &url)
{
@@ -95,7 +96,7 @@ TimelineViewManager::queueImageMessage(const QString &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
@@ -110,7 +111,7 @@ TimelineViewManager::queueFileMessage(const QString &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
@@ -125,7 +126,7 @@ TimelineViewManager::queueAudioMessage(const QString &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
diff --git a/src/timeline/widgets/AudioItem.cc b/src/timeline/widgets/AudioItem.cc
index 5d9dd77b..e84cbb3a 100644
--- a/src/timeline/widgets/AudioItem.cc
+++ b/src/timeline/widgets/AudioItem.cc
@@ -89,14 +89,16 @@ AudioItem::AudioItem(QSharedPointer<MatrixClient> client,
AudioItem::AudioItem(QSharedPointer<MatrixClient> client,
const QString &url,
+ const QSharedPointer<QIODevice> data,
const QString &filename,
QWidget *parent)
: QWidget(parent)
, url_{url}
- , text_{QFileInfo(filename).fileName()}
+ , text_{QFileInfo{filename}.fileName()}
, client_{client}
{
- readableFileSize_ = calculateFileSize(QFileInfo(filename).size());
+ Q_UNUSED(data);
+ readableFileSize_ = calculateFileSize(QFileInfo{filename}.size());
init();
}
diff --git a/src/timeline/widgets/FileItem.cc b/src/timeline/widgets/FileItem.cc
index 3c38dc31..a6159309 100644
--- a/src/timeline/widgets/FileItem.cc
+++ b/src/timeline/widgets/FileItem.cc
@@ -76,14 +76,16 @@ FileItem::FileItem(QSharedPointer<MatrixClient> client,
FileItem::FileItem(QSharedPointer<MatrixClient> client,
const QString &url,
+ const QSharedPointer<QIODevice> data,
const QString &filename,
QWidget *parent)
: QWidget(parent)
, url_{url}
- , text_{QFileInfo(filename).fileName()}
+ , text_{QFileInfo{filename}.fileName()}
, client_{client}
{
- readableFileSize_ = calculateFileSize(QFileInfo(filename).size());
+ Q_UNUSED(data);
+ readableFileSize_ = calculateFileSize(QFileInfo{filename}.size());
init();
}
diff --git a/src/timeline/widgets/ImageItem.cc b/src/timeline/widgets/ImageItem.cc
index 9038456d..48a4c1eb 100644
--- a/src/timeline/widgets/ImageItem.cc
+++ b/src/timeline/widgets/ImageItem.cc
@@ -61,11 +61,12 @@ ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
const QString &url,
+ const QSharedPointer<QIODevice> data,
const QString &filename,
QWidget *parent)
: QWidget(parent)
, url_{url}
- , text_{QFileInfo(filename).fileName()}
+ , text_{filename}
, client_{client}
{
setMouseTracking(true);
@@ -83,7 +84,19 @@ ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
url_ = QString("%1/_matrix/media/r0/download/%2")
.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
diff --git a/src/timeline/widgets/VideoItem.cc b/src/timeline/widgets/VideoItem.cc
index b3987b83..b46dff7b 100644
--- a/src/timeline/widgets/VideoItem.cc
+++ b/src/timeline/widgets/VideoItem.cc
@@ -66,6 +66,7 @@ VideoItem::VideoItem(QSharedPointer<MatrixClient> client,
VideoItem::VideoItem(QSharedPointer<MatrixClient> client,
const QString &url,
+ const QSharedPointer<QIODevice> data,
const QString &filename,
QWidget *parent)
: QWidget(parent)
@@ -73,6 +74,7 @@ VideoItem::VideoItem(QSharedPointer<MatrixClient> client,
, text_{QFileInfo(filename).fileName()}
, client_{client}
{
+ Q_UNUSED(data);
readableFileSize_ = calculateFileSize(QFileInfo(filename).size());
init();
|