From a57a15a2e07da8cc07bc12e828b7c636efe36cbc Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 6 Aug 2021 01:45:47 +0200 Subject: Basic sticker pack editor --- src/SingleImagePackModel.cpp | 181 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) (limited to 'src/SingleImagePackModel.cpp') diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index 6c508da0..d3cc8014 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -5,12 +5,18 @@ #include "SingleImagePackModel.h" #include "Cache_p.h" +#include "ChatPage.h" #include "MatrixClient.h" +#include "timeline/Permissions.h" +#include "timeline/TimelineModel.h" + +#include "Logging.h" SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) : QAbstractListModel(parent) , roomid_(std::move(pack_.source_room)) , statekey_(std::move(pack_.state_key)) + , old_statekey_(statekey_) , pack(std::move(pack_.pack)) { if (!pack.pack) @@ -61,6 +67,73 @@ SingleImagePackModel::data(const QModelIndex &index, int role) const return {}; } +bool +SingleImagePackModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + using mtx::events::msc2545::PackUsage; + + if (hasIndex(index.row(), index.column(), index.parent())) { + auto &img = pack.images.at(shortcodes.at(index.row())); + switch (role) { + case ShortCode: { + auto newCode = value.toString().toStdString(); + + // otherwise we delete this by accident + if (pack.images.count(newCode)) + return false; + + auto tmp = img; + auto oldCode = shortcodes.at(index.row()); + pack.images.erase(oldCode); + shortcodes[index.row()] = newCode; + pack.images.insert({newCode, tmp}); + + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::ShortCode}); + return true; + } + case Body: + img.body = value.toString().toStdString(); + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::Body}); + return true; + case IsEmote: { + bool isEmote = value.toBool(); + bool isSticker = + img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); + + img.usage.set(PackUsage::Emoji, isEmote); + img.usage.set(PackUsage::Sticker, isSticker); + + if (img.usage == pack.pack->usage) + img.usage.reset(); + + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::IsEmote}); + + return true; + } + case IsSticker: { + bool isEmote = + img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); + bool isSticker = value.toBool(); + + img.usage.set(PackUsage::Emoji, isEmote); + img.usage.set(PackUsage::Sticker, isSticker); + + if (img.usage == pack.pack->usage) + img.usage.reset(); + + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::IsSticker}); + + return true; + } + } + } + return false; +} + bool SingleImagePackModel::isGloballyEnabled() const { @@ -98,3 +171,111 @@ SingleImagePackModel::setGloballyEnabled(bool enabled) // emit this->globallyEnabledChanged(); }); } + +bool +SingleImagePackModel::canEdit() const +{ + if (roomid_.empty()) + return true; + else + return Permissions(QString::fromStdString(roomid_)) + .canChange(qml_mtx_events::ImagePackInRoom); +} + +void +SingleImagePackModel::setPackname(QString val) +{ + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->display_name) { + this->pack.pack->display_name = val_; + emit packnameChanged(); + } +} + +void +SingleImagePackModel::setAttribution(QString val) +{ + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->attribution) { + this->pack.pack->attribution = val_; + emit attributionChanged(); + } +} + +void +SingleImagePackModel::setAvatarUrl(QString val) +{ + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->avatar_url) { + this->pack.pack->avatar_url = val_; + emit avatarUrlChanged(); + } +} + +void +SingleImagePackModel::setStatekey(QString val) +{ + auto val_ = val.toStdString(); + if (val_ != statekey_) { + statekey_ = val_; + emit statekeyChanged(); + } +} + +void +SingleImagePackModel::setIsStickerPack(bool val) +{ + using mtx::events::msc2545::PackUsage; + if (val != pack.pack->is_sticker()) { + pack.pack->usage.set(PackUsage::Sticker, val); + emit isStickerPackChanged(); + } +} + +void +SingleImagePackModel::setIsEmotePack(bool val) +{ + using mtx::events::msc2545::PackUsage; + if (val != pack.pack->is_emoji()) { + pack.pack->usage.set(PackUsage::Emoji, val); + emit isEmotePackChanged(); + } +} + +void +SingleImagePackModel::save() +{ + if (roomid_.empty()) { + http::client()->put_account_data(pack, [this](mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to update image pack: {}") + .arg(QString::fromStdString(e->matrix_error.error))); + }); + } else { + if (old_statekey_ != statekey_) { + http::client()->send_state_event( + roomid_, + to_string(mtx::events::EventType::ImagePackInRoom), + old_statekey_, + nlohmann::json::object(), + [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to delete old image pack: {}") + .arg(QString::fromStdString(e->matrix_error.error))); + }); + } + + http::client()->send_state_event( + roomid_, + statekey_, + pack, + [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to update image pack: {}") + .arg(QString::fromStdString(e->matrix_error.error))); + }); + } +} -- cgit 1.5.1 From 16d0190f4e1ee79025ec47f3dcfa1fb701a63ff1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 6 Aug 2021 03:28:56 +0200 Subject: Allow uploading additional stickers --- resources/qml/ScrollHelper.qml | 7 ++- resources/qml/dialogs/ImagePackEditorDialog.qml | 18 +++++++ src/SingleImagePackModel.cpp | 69 ++++++++++++++++++++++++- src/SingleImagePackModel.h | 8 +++ 4 files changed, 97 insertions(+), 5 deletions(-) (limited to 'src/SingleImagePackModel.cpp') diff --git a/resources/qml/ScrollHelper.qml b/resources/qml/ScrollHelper.qml index e584ae3d..e84e67fd 100644 --- a/resources/qml/ScrollHelper.qml +++ b/resources/qml/ScrollHelper.qml @@ -23,6 +23,9 @@ MouseArea { // console.warn("Delta: ", wheel.pixelDelta.y); // console.warn("Old position: ", flickable.contentY); // console.warn("New position: ", newPos); + // breaks ListView's with headers... + //if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem) + // minYExtent += flickableItem.headerItem.height; id: root @@ -30,10 +33,6 @@ MouseArea { property alias enabled: root.enabled function calculateNewPosition(flickableItem, wheel) { - // breaks ListView's with headers... - //if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem) - // minYExtent += flickableItem.headerItem.height; - //Nothing to scroll if (flickableItem.contentHeight < flickableItem.height) return flickableItem.contentY; diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index 0049d3b4..89301215 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -4,6 +4,7 @@ import ".." import "../components" +import Qt.labs.platform 1.1 import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 @@ -78,6 +79,23 @@ ApplicationWindow { } + footer: Button { + palette: Nheko.colors + onClicked: addFilesDialog.open() + width: ListView.view.width + text: qsTr("Add images") + + FileDialog { + id: addFilesDialog + + folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + fileMode: FileDialog.OpenFiles + nameFilters: [qsTr("Stickers (*.png *.webp)")] + onAccepted: imagePack.addStickers(files) + } + + } + delegate: AvatarListTile { id: packItem diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index d3cc8014..ddecf1ad 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -4,13 +4,18 @@ #include "SingleImagePackModel.h" +#include +#include + #include "Cache_p.h" #include "ChatPage.h" +#include "Logging.h" #include "MatrixClient.h" +#include "Utils.h" #include "timeline/Permissions.h" #include "timeline/TimelineModel.h" -#include "Logging.h" +Q_DECLARE_METATYPE(mtx::common::ImageInfo); SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) : QAbstractListModel(parent) @@ -19,11 +24,15 @@ SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) , old_statekey_(statekey_) , pack(std::move(pack_.pack)) { + [[maybe_unused]] static auto imageInfoType = qRegisterMetaType(); + if (!pack.pack) pack.pack = mtx::events::msc2545::ImagePack::PackDescription{}; for (const auto &e : pack.images) shortcodes.push_back(e.first); + + connect(this, &SingleImagePackModel::addImage, this, &SingleImagePackModel::addImageCb); } int @@ -279,3 +288,61 @@ SingleImagePackModel::save() }); } } + +void +SingleImagePackModel::addStickers(QList files) +{ + for (const auto &f : files) { + auto file = QFile(f.toLocalFile()); + if (!file.open(QFile::ReadOnly)) { + ChatPage::instance()->showNotification( + tr("Failed to open image: {}").arg(f.toLocalFile())); + return; + } + + auto bytes = file.readAll(); + auto img = utils::readImage(bytes); + + mtx::common::ImageInfo info{}; + + auto sz = img.size() / 2; + if (sz.width() > 512 || sz.height() > 512) { + sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio); + } + + info.h = sz.height(); + info.w = sz.width(); + info.size = bytes.size(); + + auto filename = f.fileName().toStdString(); + http::client()->upload( + bytes.toStdString(), + QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(), + filename, + [this, filename, info](const mtx::responses::ContentURI &uri, + mtx::http::RequestErr e) { + if (e) { + ChatPage::instance()->showNotification( + tr("Failed to upload image: {}") + .arg(QString::fromStdString(e->matrix_error.error))); + return; + } + + emit addImage(uri.content_uri, filename, info); + }); + } +} +void +SingleImagePackModel::addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info) +{ + mtx::events::msc2545::PackImage img{}; + img.url = uri; + img.info = info; + beginInsertRows( + QModelIndex(), static_cast(shortcodes.size()), static_cast(shortcodes.size())); + + pack.images[filename] = img; + shortcodes.push_back(filename); + + endInsertRows(); +} diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h index 44f413c6..cd38b3b6 100644 --- a/src/SingleImagePackModel.h +++ b/src/SingleImagePackModel.h @@ -5,6 +5,8 @@ #pragma once #include +#include +#include #include @@ -66,6 +68,7 @@ public: void setIsEmotePack(bool val); Q_INVOKABLE void save(); + Q_INVOKABLE void addStickers(QList files); signals: void globallyEnabledChanged(); @@ -76,6 +79,11 @@ signals: void isEmotePackChanged(); void isStickerPackChanged(); + void addImage(std::string uri, std::string filename, mtx::common::ImageInfo info); + +private slots: + void addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info); + private: std::string roomid_; std::string statekey_, old_statekey_; -- cgit 1.5.1 From e5a6b2b6efcb56072146aad5996132070f9c2078 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 6 Aug 2021 04:31:30 +0200 Subject: Allow creating new packs --- resources/qml/dialogs/ImagePackEditorDialog.qml | 8 +++---- resources/qml/dialogs/ImagePackSettingsDialog.qml | 28 +++++++++++++++++++++++ src/Cache.cpp | 2 +- src/ImagePackListModel.cpp | 18 +++++++++++++++ src/ImagePackListModel.h | 4 ++++ src/SingleImagePackModel.cpp | 4 +++- 6 files changed, 58 insertions(+), 6 deletions(-) (limited to 'src/SingleImagePackModel.cpp') diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index 89301215..b839c9e3 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -186,7 +186,7 @@ ApplicationWindow { ToggleButton { checked: imagePack.isEmotePack - onToggled: imagePack.isEmotePack = checked + onClicked: imagePack.isEmotePack = checked Layout.alignment: Qt.AlignRight } @@ -196,7 +196,7 @@ ApplicationWindow { ToggleButton { checked: imagePack.isStickerPack - onToggled: imagePack.isStickerPack = checked + onClicked: imagePack.isStickerPack = checked Layout.alignment: Qt.AlignRight } @@ -251,7 +251,7 @@ ApplicationWindow { ToggleButton { checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote) - onToggled: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.IsEmote) + onClicked: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsEmote) Layout.alignment: Qt.AlignRight } @@ -261,7 +261,7 @@ ApplicationWindow { ToggleButton { checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker) - onToggled: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.IsSticker) + onClicked: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsSticker) Layout.alignment: Qt.AlignRight } diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml index c57867fd..5181619c 100644 --- a/resources/qml/dialogs/ImagePackSettingsDialog.qml +++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml @@ -62,6 +62,34 @@ ApplicationWindow { enabled: !Settings.mobileMode } + footer: ColumnLayout { + Button { + palette: Nheko.colors + onClicked: { + var dialog = packEditor.createObject(timelineRoot, { + "imagePack": packlist.newPack(false) + }); + dialog.show(); + } + width: packlist.width + visible: !packlist.containsAccountPack + text: qsTr("Create account pack") + } + + Button { + palette: Nheko.colors + onClicked: { + var dialog = packEditor.createObject(timelineRoot, { + "imagePack": packlist.newPack(true) + }); + dialog.show(); + } + width: packlist.width + text: qsTr("New room pack") + } + + } + delegate: AvatarListTile { id: packItem diff --git a/src/Cache.cpp b/src/Cache.cpp index f3f3dbb6..6650334a 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3401,7 +3401,7 @@ Cache::getImagePacks(const std::string &room_id, std::optional stickers) info.pack.pack = pack.pack; for (const auto &img : pack.images) { - if (img.second.overrides_usage() && + if (stickers.has_value() && img.second.overrides_usage() && (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) continue; diff --git a/src/ImagePackListModel.cpp b/src/ImagePackListModel.cpp index 89f1f68e..6392de22 100644 --- a/src/ImagePackListModel.cpp +++ b/src/ImagePackListModel.cpp @@ -74,3 +74,21 @@ ImagePackListModel::packAt(int row) QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership); return e; } + +SingleImagePackModel * +ImagePackListModel::newPack(bool inRoom) +{ + ImagePackInfo info{}; + if (inRoom) + info.source_room = room_id; + return new SingleImagePackModel(info); +} + +bool +ImagePackListModel::containsAccountPack() const +{ + for (const auto &p : packs) + if (p->roomid().isEmpty()) + return true; + return false; +} diff --git a/src/ImagePackListModel.h b/src/ImagePackListModel.h index 0a044690..2aa5abb2 100644 --- a/src/ImagePackListModel.h +++ b/src/ImagePackListModel.h @@ -12,6 +12,7 @@ class SingleImagePackModel; class ImagePackListModel : public QAbstractListModel { Q_OBJECT + Q_PROPERTY(bool containsAccountPack READ containsAccountPack CONSTANT) public: enum Roles { @@ -29,6 +30,9 @@ public: QVariant data(const QModelIndex &index, int role) const override; Q_INVOKABLE SingleImagePackModel *packAt(int row); + Q_INVOKABLE SingleImagePackModel *newPack(bool inRoom); + + bool containsAccountPack() const; private: std::string room_id; diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index ddecf1ad..dea25264 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -15,7 +15,7 @@ #include "timeline/Permissions.h" #include "timeline/TimelineModel.h" -Q_DECLARE_METATYPE(mtx::common::ImageInfo); +Q_DECLARE_METATYPE(mtx::common::ImageInfo) SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) : QAbstractListModel(parent) @@ -285,6 +285,8 @@ SingleImagePackModel::save() ChatPage::instance()->showNotification( tr("Failed to update image pack: {}") .arg(QString::fromStdString(e->matrix_error.error))); + + nhlog::net()->info("Uploaded image pack: {}", statekey_); }); } } -- cgit 1.5.1 From cc22309c5b549d068e4cb1f4da91d346bb76562f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 6 Aug 2021 04:43:56 +0200 Subject: this is not needed for translations --- src/SingleImagePackModel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/SingleImagePackModel.cpp') diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index dea25264..7bf55617 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -255,7 +255,7 @@ void SingleImagePackModel::save() { if (roomid_.empty()) { - http::client()->put_account_data(pack, [this](mtx::http::RequestErr e) { + http::client()->put_account_data(pack, [](mtx::http::RequestErr e) { if (e) ChatPage::instance()->showNotification( tr("Failed to update image pack: {}") @@ -268,7 +268,7 @@ SingleImagePackModel::save() to_string(mtx::events::EventType::ImagePackInRoom), old_statekey_, nlohmann::json::object(), - [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { + [](const mtx::responses::EventId &, mtx::http::RequestErr e) { if (e) ChatPage::instance()->showNotification( tr("Failed to delete old image pack: {}") -- cgit 1.5.1