Skip to content
Snippets Groups Projects
Verified Commit d3471a10 authored by Nicolas Werner's avatar Nicolas Werner
Browse files

Move uploads to InputBar

parent a42335ae
No related branches found
No related tags found
No related merge requests found
...@@ -311,7 +311,6 @@ set(SRC_FILES ...@@ -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 ...@@ -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 { ...@@ -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 {
} }
......
// 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);
}
}
// 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 @@ ...@@ -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 @@ ...@@ -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) ...@@ -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) ...@@ -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() ...@@ -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,123 +628,204 @@ InputBar::command(const QString &command, QString args) ...@@ -661,123 +628,204 @@ 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);
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));
}
}
connect( void
previewDialog_, MediaUpload::startUpload()
&dialogs::PreviewUploadOverlay::confirmUpload, {
this, auto payload = std::string(data.data(), data.size());
[this](const QByteArray &data, const QString &mime, const QString &fn) { if (encrypt_) {
if (!data.size()) { mtx::crypto::BinaryBuf buf;
nhlog::ui()->warn("Attempted to upload zero-byte file?! Mimetype {}, filename {}", std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(std::move(payload));
mime.toStdString(), payload = mtx::crypto::to_string(buf);
fn.toStdString()); }
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; void
QString blurhash; InputBar::finalizeUpload(MediaUpload *upload, QString url)
auto mimeClass = mime.left(mime.indexOf(u'/')); {
nhlog::ui()->debug("Mime: {}", mime.toStdString()); auto mime = upload->mimetype();
if (mimeClass == u"image") { auto filename = upload->filename();
QImage img = utils::readImage(data); auto mimeClass = upload->mimeClass();
auto size = upload->size();
dimensions = img.size(); auto encryptedFile = upload->encryptedFile_();
if (img.height() > 200 && img.width() > 360) if (mimeClass == u"image")
img = img.scaled(360, 200, Qt::KeepAspectRatioByExpanding); image(filename, encryptedFile, url, mime, size, upload->dimensions(), upload->blurhash());
std::vector<unsigned char> data_; else if (mimeClass == u"audio")
for (int y = 0; y < img.height(); y++) { audio(filename, encryptedFile, url, mime, size);
for (int x = 0; x < img.width(); x++) { else if (mimeClass == u"video")
auto p = img.pixel(x, y); video(filename, encryptedFile, url, mime, size);
data_.push_back(static_cast<unsigned char>(qRed(p))); else
data_.push_back(static_cast<unsigned char>(qGreen(p))); file(filename, encryptedFile, url, mime, size);
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( removeRunUpload(upload);
payload, }
encryptedFile ? "application/octet-stream" : mime.toStdString(),
QFileInfo(fn).fileName().toStdString(), void
[this, InputBar::removeRunUpload(MediaUpload *upload)
filename = fn, {
encryptedFile = std::move(encryptedFile), auto it = std::find_if(runningUploads.begin(),
mimeClass, runningUploads.end(),
mime, [upload](const UploadHandle &h) { return h.get() == upload; });
size = payload.size(), if (it != runningUploads.end())
dimensions, runningUploads.erase(it);
blurhash](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) mutable {
if (err) { if (runningUploads.empty())
emit ChatPage::instance()->showNotification( setUploading(false);
tr("Failed to upload media. Please try again.")); else
nhlog::net()->warn("failed to upload media: {} {} ({})", runningUploads.front()->startUpload();
err->matrix_error.error, }
to_string(err->matrix_error.errcode),
static_cast<int>(err->status_code)); void
setUploading(false); InputBar::startUploadFromPath(const QString &path)
return; {
} if (path.isEmpty())
return;
auto url = QString::fromStdString(res.content_uri);
if (encryptedFile) auto file = std::make_unique<QFile>(path);
encryptedFile->url = res.content_uri;
if (!file->open(QIODevice::ReadOnly)) {
if (mimeClass == u"image") nhlog::ui()->warn(
image(filename, encryptedFile, url, mime, size, dimensions, blurhash); "Failed to open file ({}): {}", path.toStdString(), file->errorString().toStdString());
else if (mimeClass == u"audio") return;
audio(filename, encryptedFile, url, mime, size); }
else if (mimeClass == u"video")
video(filename, encryptedFile, url, mime, size); QMimeDatabase db;
else auto mime = db.mimeTypeForFileNameAndData(path, file.get());
file(filename, encryptedFile, url, mime, size);
startUpload(std::move(file), path, mime.name());
setUploading(false); }
});
}); 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
......
...@@ -5,10 +5,14 @@ ...@@ -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 ...@@ -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: ...@@ -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: ...@@ -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: ...@@ -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: ...@@ -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;
}; };
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment