mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Move uploads to InputBar
This commit is contained in:
parent
a42335aed2
commit
d3471a1097
6 changed files with 304 additions and 428 deletions
|
@ -311,7 +311,6 @@ set(SRC_FILES
|
||||||
# Dialogs
|
# Dialogs
|
||||||
src/dialogs/CreateRoom.cpp
|
src/dialogs/CreateRoom.cpp
|
||||||
src/dialogs/FallbackAuth.cpp
|
src/dialogs/FallbackAuth.cpp
|
||||||
src/dialogs/PreviewUploadOverlay.cpp
|
|
||||||
src/dialogs/ReCaptcha.cpp
|
src/dialogs/ReCaptcha.cpp
|
||||||
|
|
||||||
# Emoji
|
# Emoji
|
||||||
|
@ -509,7 +508,6 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||||
# Dialogs
|
# Dialogs
|
||||||
src/dialogs/CreateRoom.h
|
src/dialogs/CreateRoom.h
|
||||||
src/dialogs/FallbackAuth.h
|
src/dialogs/FallbackAuth.h
|
||||||
src/dialogs/PreviewUploadOverlay.h
|
|
||||||
src/dialogs/ReCaptcha.h
|
src/dialogs/ReCaptcha.h
|
||||||
|
|
||||||
# Emoji
|
# Emoji
|
||||||
|
|
|
@ -124,6 +124,12 @@ Item {
|
||||||
color: Nheko.theme.separator
|
color: Nheko.theme.separator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Send files " + (room ? room.input.uploads.length : 0)
|
||||||
|
visible: room && room.input.uploads.length > 0
|
||||||
|
onClicked: room.input.acceptUploads()
|
||||||
|
}
|
||||||
|
|
||||||
NotificationWarning {
|
NotificationWarning {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,223 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
|
||||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
|
||||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#include <QBuffer>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QMimeDatabase>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
#include "dialogs/PreviewUploadOverlay.h"
|
|
||||||
|
|
||||||
#include "Config.h"
|
|
||||||
#include "Logging.h"
|
|
||||||
#include "MainWindow.h"
|
|
||||||
#include "Utils.h"
|
|
||||||
|
|
||||||
using namespace dialogs;
|
|
||||||
|
|
||||||
constexpr const char *DEFAULT = "Upload %1?";
|
|
||||||
constexpr const char *ERR_MSG = "Failed to load image type '%1'. Continue upload?";
|
|
||||||
|
|
||||||
PreviewUploadOverlay::PreviewUploadOverlay(QWidget *parent)
|
|
||||||
: QWidget{parent}
|
|
||||||
, titleLabel_{this}
|
|
||||||
, fileName_{this}
|
|
||||||
, upload_{tr("Upload"), this}
|
|
||||||
, cancel_{tr("Cancel"), this}
|
|
||||||
{
|
|
||||||
auto hlayout = new QHBoxLayout;
|
|
||||||
hlayout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
hlayout->addStretch(1);
|
|
||||||
hlayout->addWidget(&cancel_);
|
|
||||||
hlayout->addWidget(&upload_);
|
|
||||||
|
|
||||||
auto vlayout = new QVBoxLayout{this};
|
|
||||||
vlayout->addWidget(&titleLabel_);
|
|
||||||
vlayout->addWidget(&infoLabel_);
|
|
||||||
vlayout->addWidget(&fileName_);
|
|
||||||
vlayout->addLayout(hlayout);
|
|
||||||
vlayout->setSpacing(conf::modals::WIDGET_SPACING);
|
|
||||||
vlayout->setContentsMargins(conf::modals::WIDGET_MARGIN,
|
|
||||||
conf::modals::WIDGET_MARGIN,
|
|
||||||
conf::modals::WIDGET_MARGIN,
|
|
||||||
conf::modals::WIDGET_MARGIN);
|
|
||||||
|
|
||||||
upload_.setDefault(true);
|
|
||||||
connect(&upload_, &QPushButton::clicked, this, [this]() {
|
|
||||||
emit confirmUpload(data_, mediaType_, fileName_.text());
|
|
||||||
close();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(&fileName_, &QLineEdit::returnPressed, this, [this]() {
|
|
||||||
emit confirmUpload(data_, mediaType_, fileName_.text());
|
|
||||||
close();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(&cancel_, &QPushButton::clicked, this, [this]() {
|
|
||||||
emit aborted();
|
|
||||||
close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
PreviewUploadOverlay::init()
|
|
||||||
{
|
|
||||||
QSize winsize;
|
|
||||||
QPoint center;
|
|
||||||
|
|
||||||
auto window = MainWindow::instance();
|
|
||||||
if (window) {
|
|
||||||
winsize = window->frameGeometry().size();
|
|
||||||
center = window->frameGeometry().center();
|
|
||||||
} else {
|
|
||||||
nhlog::ui()->warn("unable to retrieve MainWindow's size");
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName_.setText(QFileInfo{filePath_}.fileName());
|
|
||||||
|
|
||||||
setAutoFillBackground(true);
|
|
||||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
|
||||||
setWindowModality(Qt::WindowModal);
|
|
||||||
|
|
||||||
QFont font;
|
|
||||||
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
|
|
||||||
|
|
||||||
titleLabel_.setFont(font);
|
|
||||||
titleLabel_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
||||||
titleLabel_.setAlignment(Qt::AlignCenter);
|
|
||||||
infoLabel_.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
|
||||||
fileName_.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
|
||||||
fileName_.setAlignment(Qt::AlignCenter);
|
|
||||||
upload_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
cancel_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
|
|
||||||
if (isImage_) {
|
|
||||||
infoLabel_.setAlignment(Qt::AlignCenter);
|
|
||||||
|
|
||||||
const auto maxWidth = winsize.width() * 0.8;
|
|
||||||
const auto maxHeight = winsize.height() * 0.8;
|
|
||||||
|
|
||||||
// Scale image preview to fit into the application window.
|
|
||||||
infoLabel_.setPixmap(utils::scaleDown(maxWidth, maxHeight, image_));
|
|
||||||
move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
|
|
||||||
} else {
|
|
||||||
infoLabel_.setAlignment(Qt::AlignLeft);
|
|
||||||
}
|
|
||||||
infoLabel_.setScaledContents(false);
|
|
||||||
|
|
||||||
show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
PreviewUploadOverlay::setLabels(const QString &type, const QString &mime, uint64_t upload_size)
|
|
||||||
{
|
|
||||||
if (mediaType_.split('/')[0] == QLatin1String("image")) {
|
|
||||||
if (!image_.loadFromData(data_)) {
|
|
||||||
titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
|
|
||||||
} else {
|
|
||||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg(mediaType_));
|
|
||||||
}
|
|
||||||
isImage_ = true;
|
|
||||||
} else {
|
|
||||||
auto const info = QString{tr("Media type: %1\n"
|
|
||||||
"Media size: %2\n")}
|
|
||||||
.arg(mime, utils::humanReadableFileSize(upload_size));
|
|
||||||
|
|
||||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg(QStringLiteral("file")));
|
|
||||||
infoLabel_.setText(info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
PreviewUploadOverlay::setPreview(const QImage &src, const QString &mime)
|
|
||||||
{
|
|
||||||
nhlog::ui()->info(
|
|
||||||
"Pasting image with size: {}x{}, format: {}", src.height(), src.width(), mime.toStdString());
|
|
||||||
|
|
||||||
auto const &split = mime.split('/');
|
|
||||||
auto const &type = split[1];
|
|
||||||
|
|
||||||
QBuffer buffer(&data_);
|
|
||||||
buffer.open(QIODevice::WriteOnly);
|
|
||||||
if (src.save(&buffer, type.toStdString().c_str()))
|
|
||||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg(QStringLiteral("image")));
|
|
||||||
else
|
|
||||||
titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
|
|
||||||
|
|
||||||
mediaType_ = mime;
|
|
||||||
filePath_ = "clipboard." + type;
|
|
||||||
image_.convertFromImage(src);
|
|
||||||
isImage_ = true;
|
|
||||||
|
|
||||||
titleLabel_.setText(QString{tr(DEFAULT)}.arg(QStringLiteral("image")));
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
PreviewUploadOverlay::setPreview(const QByteArray data, const QString &mime)
|
|
||||||
{
|
|
||||||
nhlog::ui()->info("Pasting {} bytes of data, mimetype {}", data.size(), mime.toStdString());
|
|
||||||
|
|
||||||
auto const &split = mime.split('/');
|
|
||||||
auto const &type = split[1];
|
|
||||||
|
|
||||||
data_ = data;
|
|
||||||
mediaType_ = mime;
|
|
||||||
filePath_ = "clipboard." + type;
|
|
||||||
isImage_ = false;
|
|
||||||
|
|
||||||
if (mime == QLatin1String("image/svg+xml")) {
|
|
||||||
isImage_ = true;
|
|
||||||
image_.loadFromData(data_, mediaType_.toStdString().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
setLabels(type, mime, data_.size());
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
PreviewUploadOverlay::setPreview(const QString &path)
|
|
||||||
{
|
|
||||||
QFile file{path};
|
|
||||||
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
nhlog::ui()->warn(
|
|
||||||
"Failed to open file ({}): {}", path.toStdString(), file.errorString().toStdString());
|
|
||||||
close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QMimeDatabase db;
|
|
||||||
auto mime = db.mimeTypeForFileNameAndData(path, &file);
|
|
||||||
|
|
||||||
if ((data_ = file.readAll()).isEmpty()) {
|
|
||||||
nhlog::ui()->warn("Failed to read media: {}", file.errorString().toStdString());
|
|
||||||
close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto const &split = mime.name().split('/');
|
|
||||||
|
|
||||||
mediaType_ = mime.name();
|
|
||||||
filePath_ = file.fileName();
|
|
||||||
isImage_ = false;
|
|
||||||
|
|
||||||
setLabels(split[1], mime.name(), data_.size());
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
PreviewUploadOverlay::keyPressEvent(QKeyEvent *event)
|
|
||||||
{
|
|
||||||
if (event->matches(QKeySequence::Cancel)) {
|
|
||||||
emit aborted();
|
|
||||||
close();
|
|
||||||
} else {
|
|
||||||
QWidget::keyPressEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
|
||||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
|
||||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QImage>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QLineEdit>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
class QMimeData;
|
|
||||||
|
|
||||||
namespace dialogs {
|
|
||||||
|
|
||||||
class PreviewUploadOverlay : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
PreviewUploadOverlay(QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
void setPreview(const QImage &src, const QString &mime);
|
|
||||||
void setPreview(const QByteArray data, const QString &mime);
|
|
||||||
void setPreview(const QString &path);
|
|
||||||
void keyPressEvent(QKeyEvent *event);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void confirmUpload(const QByteArray data, const QString &media, const QString &filename);
|
|
||||||
void aborted();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void init();
|
|
||||||
void setLabels(const QString &type, const QString &mime, uint64_t upload_size);
|
|
||||||
|
|
||||||
bool isImage_;
|
|
||||||
QPixmap image_;
|
|
||||||
|
|
||||||
QByteArray data_;
|
|
||||||
QString filePath_;
|
|
||||||
QString mediaType_;
|
|
||||||
|
|
||||||
QLabel titleLabel_;
|
|
||||||
QLabel infoLabel_;
|
|
||||||
QLineEdit fileName_;
|
|
||||||
|
|
||||||
QPushButton upload_;
|
|
||||||
QPushButton cancel_;
|
|
||||||
};
|
|
||||||
} // dialogs
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "InputBar.h"
|
#include "InputBar.h"
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QDropEvent>
|
#include <QDropEvent>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
@ -31,7 +32,6 @@
|
||||||
#include "TimelineViewManager.h"
|
#include "TimelineViewManager.h"
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "dialogs/PreviewUploadOverlay.h"
|
|
||||||
|
|
||||||
#include "blurhash.hpp"
|
#include "blurhash.hpp"
|
||||||
|
|
||||||
|
@ -67,29 +67,23 @@ InputBar::insertMimeData(const QMimeData *md)
|
||||||
|
|
||||||
if (md->hasImage()) {
|
if (md->hasImage()) {
|
||||||
if (formats.contains(QStringLiteral("image/svg+xml"), Qt::CaseInsensitive)) {
|
if (formats.contains(QStringLiteral("image/svg+xml"), Qt::CaseInsensitive)) {
|
||||||
showPreview(*md, QLatin1String(""), QStringList(QStringLiteral("image/svg+xml")));
|
startUploadFromMimeData(*md, QStringLiteral("image/svg+xml"));
|
||||||
|
} else if (formats.contains(QStringLiteral("image/png"), Qt::CaseInsensitive)) {
|
||||||
|
startUploadFromMimeData(*md, QStringLiteral("image/png"));
|
||||||
} else {
|
} else {
|
||||||
showPreview(*md, QLatin1String(""), image);
|
startUploadFromMimeData(*md, image.first());
|
||||||
}
|
}
|
||||||
} else if (!audio.empty()) {
|
} else if (!audio.empty()) {
|
||||||
showPreview(*md, QLatin1String(""), audio);
|
startUploadFromMimeData(*md, audio.first());
|
||||||
} else if (!video.empty()) {
|
} else if (!video.empty()) {
|
||||||
showPreview(*md, QLatin1String(""), video);
|
startUploadFromMimeData(*md, video.first());
|
||||||
} else if (md->hasUrls()) {
|
} else if (md->hasUrls()) {
|
||||||
// Generic file path for any platform.
|
// Generic file path for any platform.
|
||||||
QString path;
|
|
||||||
for (auto &&u : md->urls()) {
|
for (auto &&u : md->urls()) {
|
||||||
if (u.isLocalFile()) {
|
if (u.isLocalFile()) {
|
||||||
path = u.toLocalFile();
|
startUploadFromPath(u.toLocalFile());
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!path.isEmpty() && QFileInfo::exists(path)) {
|
|
||||||
showPreview(*md, path, formats);
|
|
||||||
} else {
|
|
||||||
nhlog::ui()->warn("Clipboard does not contain any valid file paths.");
|
|
||||||
}
|
|
||||||
} else if (md->hasFormat(QStringLiteral("x-special/gnome-copied-files"))) {
|
} else if (md->hasFormat(QStringLiteral("x-special/gnome-copied-files"))) {
|
||||||
// Special case for X11 users. See "Notes for X11 Users" in md.
|
// Special case for X11 users. See "Notes for X11 Users" in md.
|
||||||
// Source: http://doc.qt.io/qt-5/qclipboard.html
|
// Source: http://doc.qt.io/qt-5/qclipboard.html
|
||||||
|
@ -108,21 +102,12 @@ InputBar::insertMimeData(const QMimeData *md)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString path;
|
|
||||||
for (int i = 1; i < data.size(); ++i) {
|
for (int i = 1; i < data.size(); ++i) {
|
||||||
QUrl url{data[i]};
|
QUrl url{data[i]};
|
||||||
if (url.isLocalFile()) {
|
if (url.isLocalFile()) {
|
||||||
path = url.toLocalFile();
|
startUploadFromPath(url.toLocalFile());
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!path.isEmpty()) {
|
|
||||||
showPreview(*md, path, formats);
|
|
||||||
} else {
|
|
||||||
nhlog::ui()->warn("Clipboard does not contain any valid file paths: {}",
|
|
||||||
data.join(", ").toStdString());
|
|
||||||
}
|
|
||||||
} else if (md->hasText()) {
|
} else if (md->hasText()) {
|
||||||
emit insertText(md->text());
|
emit insertText(md->text());
|
||||||
} else {
|
} else {
|
||||||
|
@ -275,25 +260,7 @@ InputBar::openFileSelection()
|
||||||
if (fileName.isEmpty())
|
if (fileName.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QMimeDatabase db;
|
startUploadFromPath(fileName);
|
||||||
QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
|
|
||||||
|
|
||||||
QFile file{fileName};
|
|
||||||
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
emit ChatPage::instance()->showNotification(
|
|
||||||
QStringLiteral("Error while reading media: %1").arg(file.errorString()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setUploading(true);
|
|
||||||
|
|
||||||
auto bin = file.readAll();
|
|
||||||
|
|
||||||
QMimeData data;
|
|
||||||
data.setData(mime.name(), bin);
|
|
||||||
|
|
||||||
showPreview(data, fileName, QStringList{mime.name()});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -661,125 +628,206 @@ InputBar::command(const QString &command, QString args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
MediaUpload::MediaUpload(std::unique_ptr<QIODevice> source_,
|
||||||
InputBar::showPreview(const QMimeData &source, const QString &path, const QStringList &formats)
|
QString mimetype,
|
||||||
|
QString originalFilename,
|
||||||
|
bool encrypt,
|
||||||
|
QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, source(std::move(source_))
|
||||||
|
, mimetype_(std::move(mimetype))
|
||||||
|
, originalFilename_(QFileInfo(originalFilename).fileName())
|
||||||
|
, encrypt_(encrypt)
|
||||||
{
|
{
|
||||||
auto *previewDialog_ = new dialogs::PreviewUploadOverlay(nullptr);
|
mimeClass_ = mimetype_.left(mimetype_.indexOf(u'/'));
|
||||||
previewDialog_->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
|
|
||||||
// Force SVG to _not_ be handled as an image, but as raw data
|
if (!source->isOpen())
|
||||||
if (source.hasImage() &&
|
source->open(QIODevice::ReadOnly);
|
||||||
(formats.empty() || formats.front() != QLatin1String("image/svg+xml"))) {
|
|
||||||
if (!formats.empty() && formats.front().startsWith(QLatin1String("image/"))) {
|
data = source->readAll();
|
||||||
// known format, keep as-is
|
|
||||||
previewDialog_->setPreview(qvariant_cast<QImage>(source.imageData()), formats.front());
|
if (!data.size()) {
|
||||||
} else {
|
nhlog::ui()->warn("Attempted to upload zero-byte file?! Mimetype {}, filename {}",
|
||||||
// unknown image format, default to image/png
|
mimetype_.toStdString(),
|
||||||
previewDialog_->setPreview(qvariant_cast<QImage>(source.imageData()),
|
originalFilename_.toStdString());
|
||||||
QStringLiteral("image/png"));
|
emit uploadFailed(this);
|
||||||
}
|
|
||||||
} else if (!path.isEmpty())
|
|
||||||
previewDialog_->setPreview(path);
|
|
||||||
else if (!formats.isEmpty()) {
|
|
||||||
const auto &mime = formats.first();
|
|
||||||
previewDialog_->setPreview(source.data(mime), mime);
|
|
||||||
} else {
|
|
||||||
setUploading(false);
|
|
||||||
previewDialog_->deleteLater();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(previewDialog_, &dialogs::PreviewUploadOverlay::aborted, this, [this]() {
|
nhlog::ui()->debug("Mime: {}", mimetype_.toStdString());
|
||||||
setUploading(false);
|
if (mimeClass_ == u"image") {
|
||||||
});
|
QImage img = utils::readImage(data);
|
||||||
|
|
||||||
connect(
|
dimensions_ = img.size();
|
||||||
previewDialog_,
|
if (img.height() > 200 && img.width() > 360)
|
||||||
&dialogs::PreviewUploadOverlay::confirmUpload,
|
img = img.scaled(360, 200, Qt::KeepAspectRatioByExpanding);
|
||||||
this,
|
std::vector<unsigned char> data_;
|
||||||
[this](const QByteArray &data, const QString &mime, const QString &fn) {
|
for (int y = 0; y < img.height(); y++) {
|
||||||
if (!data.size()) {
|
for (int x = 0; x < img.width(); x++) {
|
||||||
nhlog::ui()->warn("Attempted to upload zero-byte file?! Mimetype {}, filename {}",
|
auto p = img.pixel(x, y);
|
||||||
mime.toStdString(),
|
data_.push_back(static_cast<unsigned char>(qRed(p)));
|
||||||
fn.toStdString());
|
data_.push_back(static_cast<unsigned char>(qGreen(p)));
|
||||||
|
data_.push_back(static_cast<unsigned char>(qBlue(p)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
blurhash_ =
|
||||||
|
QString::fromStdString(blurhash::encode(data_.data(), img.width(), img.height(), 4, 3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MediaUpload::startUpload()
|
||||||
|
{
|
||||||
|
auto payload = std::string(data.data(), data.size());
|
||||||
|
if (encrypt_) {
|
||||||
|
mtx::crypto::BinaryBuf buf;
|
||||||
|
std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(std::move(payload));
|
||||||
|
payload = mtx::crypto::to_string(buf);
|
||||||
|
}
|
||||||
|
size_ = payload.size();
|
||||||
|
|
||||||
|
http::client()->upload(
|
||||||
|
payload,
|
||||||
|
encryptedFile ? "application/octet-stream" : mimetype_.toStdString(),
|
||||||
|
originalFilename_.toStdString(),
|
||||||
|
[this](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) mutable {
|
||||||
|
if (err) {
|
||||||
|
emit ChatPage::instance()->showNotification(
|
||||||
|
tr("Failed to upload media. Please try again."));
|
||||||
|
nhlog::net()->warn("failed to upload media: {} {} ({})",
|
||||||
|
err->matrix_error.error,
|
||||||
|
to_string(err->matrix_error.errcode),
|
||||||
|
static_cast<int>(err->status_code));
|
||||||
|
emit uploadFailed(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setUploading(true);
|
|
||||||
|
|
||||||
setText(QLatin1String(""));
|
auto url = QString::fromStdString(res.content_uri);
|
||||||
|
if (encryptedFile)
|
||||||
|
encryptedFile->url = res.content_uri;
|
||||||
|
|
||||||
auto payload = std::string(data.data(), data.size());
|
emit uploadComplete(this, std::move(url));
|
||||||
std::optional<mtx::crypto::EncryptedFile> encryptedFile;
|
|
||||||
if (cache::isRoomEncrypted(room->roomId().toStdString())) {
|
|
||||||
mtx::crypto::BinaryBuf buf;
|
|
||||||
std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(payload);
|
|
||||||
payload = mtx::crypto::to_string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize dimensions;
|
|
||||||
QString blurhash;
|
|
||||||
auto mimeClass = mime.left(mime.indexOf(u'/'));
|
|
||||||
nhlog::ui()->debug("Mime: {}", mime.toStdString());
|
|
||||||
if (mimeClass == u"image") {
|
|
||||||
QImage img = utils::readImage(data);
|
|
||||||
|
|
||||||
dimensions = img.size();
|
|
||||||
if (img.height() > 200 && img.width() > 360)
|
|
||||||
img = img.scaled(360, 200, Qt::KeepAspectRatioByExpanding);
|
|
||||||
std::vector<unsigned char> data_;
|
|
||||||
for (int y = 0; y < img.height(); y++) {
|
|
||||||
for (int x = 0; x < img.width(); x++) {
|
|
||||||
auto p = img.pixel(x, y);
|
|
||||||
data_.push_back(static_cast<unsigned char>(qRed(p)));
|
|
||||||
data_.push_back(static_cast<unsigned char>(qGreen(p)));
|
|
||||||
data_.push_back(static_cast<unsigned char>(qBlue(p)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
blurhash = QString::fromStdString(
|
|
||||||
blurhash::encode(data_.data(), img.width(), img.height(), 4, 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
http::client()->upload(
|
|
||||||
payload,
|
|
||||||
encryptedFile ? "application/octet-stream" : mime.toStdString(),
|
|
||||||
QFileInfo(fn).fileName().toStdString(),
|
|
||||||
[this,
|
|
||||||
filename = fn,
|
|
||||||
encryptedFile = std::move(encryptedFile),
|
|
||||||
mimeClass,
|
|
||||||
mime,
|
|
||||||
size = payload.size(),
|
|
||||||
dimensions,
|
|
||||||
blurhash](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) mutable {
|
|
||||||
if (err) {
|
|
||||||
emit ChatPage::instance()->showNotification(
|
|
||||||
tr("Failed to upload media. Please try again."));
|
|
||||||
nhlog::net()->warn("failed to upload media: {} {} ({})",
|
|
||||||
err->matrix_error.error,
|
|
||||||
to_string(err->matrix_error.errcode),
|
|
||||||
static_cast<int>(err->status_code));
|
|
||||||
setUploading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto url = QString::fromStdString(res.content_uri);
|
|
||||||
if (encryptedFile)
|
|
||||||
encryptedFile->url = res.content_uri;
|
|
||||||
|
|
||||||
if (mimeClass == u"image")
|
|
||||||
image(filename, encryptedFile, url, mime, size, dimensions, blurhash);
|
|
||||||
else if (mimeClass == u"audio")
|
|
||||||
audio(filename, encryptedFile, url, mime, size);
|
|
||||||
else if (mimeClass == u"video")
|
|
||||||
video(filename, encryptedFile, url, mime, size);
|
|
||||||
else
|
|
||||||
file(filename, encryptedFile, url, mime, size);
|
|
||||||
|
|
||||||
setUploading(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputBar::finalizeUpload(MediaUpload *upload, QString url)
|
||||||
|
{
|
||||||
|
auto mime = upload->mimetype();
|
||||||
|
auto filename = upload->filename();
|
||||||
|
auto mimeClass = upload->mimeClass();
|
||||||
|
auto size = upload->size();
|
||||||
|
auto encryptedFile = upload->encryptedFile_();
|
||||||
|
if (mimeClass == u"image")
|
||||||
|
image(filename, encryptedFile, url, mime, size, upload->dimensions(), upload->blurhash());
|
||||||
|
else if (mimeClass == u"audio")
|
||||||
|
audio(filename, encryptedFile, url, mime, size);
|
||||||
|
else if (mimeClass == u"video")
|
||||||
|
video(filename, encryptedFile, url, mime, size);
|
||||||
|
else
|
||||||
|
file(filename, encryptedFile, url, mime, size);
|
||||||
|
|
||||||
|
removeRunUpload(upload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputBar::removeRunUpload(MediaUpload *upload)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(runningUploads.begin(),
|
||||||
|
runningUploads.end(),
|
||||||
|
[upload](const UploadHandle &h) { return h.get() == upload; });
|
||||||
|
if (it != runningUploads.end())
|
||||||
|
runningUploads.erase(it);
|
||||||
|
|
||||||
|
if (runningUploads.empty())
|
||||||
|
setUploading(false);
|
||||||
|
else
|
||||||
|
runningUploads.front()->startUpload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputBar::startUploadFromPath(const QString &path)
|
||||||
|
{
|
||||||
|
if (path.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto file = std::make_unique<QFile>(path);
|
||||||
|
|
||||||
|
if (!file->open(QIODevice::ReadOnly)) {
|
||||||
|
nhlog::ui()->warn(
|
||||||
|
"Failed to open file ({}): {}", path.toStdString(), file->errorString().toStdString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMimeDatabase db;
|
||||||
|
auto mime = db.mimeTypeForFileNameAndData(path, file.get());
|
||||||
|
|
||||||
|
startUpload(std::move(file), path, mime.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputBar::startUploadFromMimeData(const QMimeData &source, const QString &format)
|
||||||
|
{
|
||||||
|
auto file = std::make_unique<QBuffer>();
|
||||||
|
file->setData(source.data(format));
|
||||||
|
|
||||||
|
if (!file->open(QIODevice::ReadOnly)) {
|
||||||
|
nhlog::ui()->warn("Failed to open buffer: {}", file->errorString().toStdString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startUpload(std::move(file), QStringLiteral(""), format);
|
||||||
|
}
|
||||||
|
void
|
||||||
|
InputBar::startUpload(std::unique_ptr<QIODevice> dev, const QString &orgPath, const QString &format)
|
||||||
|
{
|
||||||
|
auto upload =
|
||||||
|
UploadHandle(new MediaUpload(std::move(dev), format, orgPath, room->isEncrypted(), this));
|
||||||
|
connect(upload.get(), &MediaUpload::uploadComplete, this, &InputBar::finalizeUpload);
|
||||||
|
|
||||||
|
unconfirmedUploads.push_back(std::move(upload));
|
||||||
|
|
||||||
|
nhlog::ui()->info("Uploads {}", unconfirmedUploads.size());
|
||||||
|
emit uploadsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputBar::acceptUploads()
|
||||||
|
{
|
||||||
|
if (unconfirmedUploads.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool wasntRunning = runningUploads.empty();
|
||||||
|
runningUploads.insert(runningUploads.end(),
|
||||||
|
std::make_move_iterator(unconfirmedUploads.begin()),
|
||||||
|
std::make_move_iterator(unconfirmedUploads.end()));
|
||||||
|
unconfirmedUploads.clear();
|
||||||
|
emit uploadsChanged();
|
||||||
|
|
||||||
|
if (wasntRunning) {
|
||||||
|
setUploading(true);
|
||||||
|
runningUploads.front()->startUpload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputBar::declineUploads()
|
||||||
|
{
|
||||||
|
unconfirmedUploads.clear();
|
||||||
|
emit uploadsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList
|
||||||
|
InputBar::uploads() const
|
||||||
|
{
|
||||||
|
QVariantList l;
|
||||||
|
l.reserve((int)unconfirmedUploads.size());
|
||||||
|
|
||||||
|
for (auto &e : unconfirmedUploads)
|
||||||
|
l.push_back(QVariant::fromValue(e.get()));
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InputBar::startTyping()
|
InputBar::startTyping()
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,10 +5,14 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QIODevice>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QSize>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QVariantList>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <mtx/common.hpp>
|
#include <mtx/common.hpp>
|
||||||
#include <mtx/responses/messages.hpp>
|
#include <mtx/responses/messages.hpp>
|
||||||
|
@ -25,12 +29,85 @@ enum class MarkdownOverride
|
||||||
OFF,
|
OFF,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MediaUpload : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
// Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
|
||||||
|
// Q_PROPERTY(MediaType mediaType READ type NOTIFY mediaTypeChanged)
|
||||||
|
// // https://stackoverflow.com/questions/33422265/pass-qimage-to-qml/68554646#68554646
|
||||||
|
// Q_PROPERTY(QUrl thumbnail READ thumbnail NOTIFY thumbnailChanged)
|
||||||
|
// Q_PROPERTY(QString humanSize READ humanSize NOTIFY huSizeChanged)
|
||||||
|
// Q_PROPERTY(QString filename READ filename NOTIFY filenameChanged)
|
||||||
|
// Q_PROPERTY(QString mimetype READ mimetype NOTIFY mimetypeChanged)
|
||||||
|
// Q_PROPERTY(int height READ height NOTIFY heightChanged)
|
||||||
|
// Q_PROPERTY(int width READ width NOTIFY widthChanged)
|
||||||
|
|
||||||
|
// thumbnail video
|
||||||
|
// https://stackoverflow.com/questions/26229633/display-on-screen-using-qabstractvideosurface
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum MediaType
|
||||||
|
{
|
||||||
|
File,
|
||||||
|
Image,
|
||||||
|
Video,
|
||||||
|
Audio,
|
||||||
|
};
|
||||||
|
Q_ENUM(MediaType);
|
||||||
|
|
||||||
|
explicit MediaUpload(std::unique_ptr<QIODevice> data,
|
||||||
|
QString mimetype,
|
||||||
|
QString originalFilename,
|
||||||
|
bool encrypt,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] QString url() const { return url_; }
|
||||||
|
[[nodiscard]] QString mimetype() const { return mimetype_; }
|
||||||
|
[[nodiscard]] QString mimeClass() const { return mimeClass_; }
|
||||||
|
[[nodiscard]] QString filename() const { return originalFilename_; }
|
||||||
|
[[nodiscard]] QString blurhash() const { return blurhash_; }
|
||||||
|
[[nodiscard]] uint64_t size() const { return size_; }
|
||||||
|
[[nodiscard]] std::optional<mtx::crypto::EncryptedFile> encryptedFile_()
|
||||||
|
{
|
||||||
|
return encryptedFile;
|
||||||
|
}
|
||||||
|
[[nodiscard]] QSize dimensions() const { return dimensions_; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void uploadComplete(MediaUpload *self, QString url);
|
||||||
|
void uploadFailed(MediaUpload *self);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void startUpload();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void updateThumbnailUrl(QString url) { this->thumbnailUrl_ = std::move(url); }
|
||||||
|
|
||||||
|
public:
|
||||||
|
// void uploadThumbnail(QImage img);
|
||||||
|
|
||||||
|
std::unique_ptr<QIODevice> source;
|
||||||
|
QByteArray data;
|
||||||
|
QString mimetype_;
|
||||||
|
QString mimeClass_;
|
||||||
|
QString originalFilename_;
|
||||||
|
QString blurhash_;
|
||||||
|
QString thumbnailUrl_;
|
||||||
|
QString url_;
|
||||||
|
std::optional<mtx::crypto::EncryptedFile> encryptedFile;
|
||||||
|
|
||||||
|
QSize dimensions_;
|
||||||
|
uint64_t size_ = 0;
|
||||||
|
bool encrypt_;
|
||||||
|
};
|
||||||
|
|
||||||
class InputBar : public QObject
|
class InputBar : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
|
Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
|
||||||
Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged)
|
Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged)
|
||||||
Q_PROPERTY(QString text READ text NOTIFY textChanged)
|
Q_PROPERTY(QString text READ text NOTIFY textChanged)
|
||||||
|
Q_PROPERTY(QVariantList uploads READ uploads NOTIFY uploadsChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit InputBar(TimelineModel *parent)
|
explicit InputBar(TimelineModel *parent)
|
||||||
|
@ -45,6 +122,8 @@ public:
|
||||||
connect(&typingTimeout_, &QTimer::timeout, this, &InputBar::stopTyping);
|
connect(&typingTimeout_, &QTimer::timeout, this, &InputBar::stopTyping);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantList uploads() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
[[nodiscard]] QString text() const;
|
[[nodiscard]] QString text() const;
|
||||||
QString previousText();
|
QString previousText();
|
||||||
|
@ -65,15 +144,22 @@ public slots:
|
||||||
void reaction(const QString &reactedEvent, const QString &reactionKey);
|
void reaction(const QString &reactedEvent, const QString &reactionKey);
|
||||||
void sticker(CombinedImagePackModel *model, int row);
|
void sticker(CombinedImagePackModel *model, int row);
|
||||||
|
|
||||||
|
void acceptUploads();
|
||||||
|
void declineUploads();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void startTyping();
|
void startTyping();
|
||||||
void stopTyping();
|
void stopTyping();
|
||||||
|
|
||||||
|
void finalizeUpload(MediaUpload *upload, QString url);
|
||||||
|
void removeRunUpload(MediaUpload *upload);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void insertText(QString text);
|
void insertText(QString text);
|
||||||
void textChanged(QString newText);
|
void textChanged(QString newText);
|
||||||
void uploadingChanged(bool value);
|
void uploadingChanged(bool value);
|
||||||
void containsAtRoomChanged();
|
void containsAtRoomChanged();
|
||||||
|
void uploadsChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void emote(const QString &body, bool rainbowify);
|
void emote(const QString &body, bool rainbowify);
|
||||||
|
@ -102,7 +188,9 @@ private:
|
||||||
const QString &mime,
|
const QString &mime,
|
||||||
uint64_t dsize);
|
uint64_t dsize);
|
||||||
|
|
||||||
void showPreview(const QMimeData &source, const QString &path, const QStringList &formats);
|
void startUploadFromPath(const QString &path);
|
||||||
|
void startUploadFromMimeData(const QMimeData &source, const QString &format);
|
||||||
|
void startUpload(std::unique_ptr<QIODevice> dev, const QString &orgPath, const QString &format);
|
||||||
void setUploading(bool value)
|
void setUploading(bool value)
|
||||||
{
|
{
|
||||||
if (value != uploading_) {
|
if (value != uploading_) {
|
||||||
|
@ -121,4 +209,16 @@ private:
|
||||||
int selectionStart = 0, selectionEnd = 0, cursorPosition = 0;
|
int selectionStart = 0, selectionEnd = 0, cursorPosition = 0;
|
||||||
bool uploading_ = false;
|
bool uploading_ = false;
|
||||||
bool containsAtRoom_ = false;
|
bool containsAtRoom_ = false;
|
||||||
|
|
||||||
|
struct DeleteLaterDeleter
|
||||||
|
{
|
||||||
|
void operator()(QObject *p)
|
||||||
|
{
|
||||||
|
if (p)
|
||||||
|
p->deleteLater();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using UploadHandle = std::unique_ptr<MediaUpload, DeleteLaterDeleter>;
|
||||||
|
std::vector<UploadHandle> unconfirmedUploads;
|
||||||
|
std::vector<UploadHandle> runningUploads;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue