diff options
author | Nicolas Werner <nicolas.werner@hotmail.de> | 2021-08-06 01:45:47 +0200 |
---|---|---|
committer | Nicolas Werner <nicolas.werner@hotmail.de> | 2021-08-06 04:31:53 +0200 |
commit | a57a15a2e07da8cc07bc12e828b7c636efe36cbc (patch) | |
tree | a33d6375ab1c24ded5cdf3f628af29bfe8370a68 /src | |
parent | Merge pull request #666 from LorenDB/qml-all-the-things (diff) | |
download | nheko-a57a15a2e07da8cc07bc12e828b7c636efe36cbc.tar.xz |
Basic sticker pack editor
Diffstat (limited to 'src')
-rw-r--r-- | src/Cache.cpp | 2 | ||||
-rw-r--r-- | src/Cache_p.h | 34 | ||||
-rw-r--r-- | src/MxcImageProvider.cpp | 26 | ||||
-rw-r--r-- | src/MxcImageProvider.h | 7 | ||||
-rw-r--r-- | src/SingleImagePackModel.cpp | 181 | ||||
-rw-r--r-- | src/SingleImagePackModel.h | 38 | ||||
-rw-r--r-- | src/timeline/TimelineModel.cpp | 9 | ||||
-rw-r--r-- | src/timeline/TimelineModel.h | 8 |
8 files changed, 275 insertions, 30 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp index 291df053..f3f3dbb6 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -125,7 +125,7 @@ template<class T> bool containsStateUpdates(const T &e) { - return std::visit([](const auto &ev) { return Cache::isStateEvent(ev); }, e); + return std::visit([](const auto &ev) { return Cache::isStateEvent_<decltype(ev)>; }, e); } bool diff --git a/src/Cache_p.h b/src/Cache_p.h index 5d700658..30c365a6 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -291,15 +291,9 @@ public: std::optional<std::string> secret(const std::string name); template<class T> - static constexpr bool isStateEvent(const mtx::events::StateEvent<T> &) - { - return true; - } - template<class T> - static constexpr bool isStateEvent(const mtx::events::Event<T> &) - { - return false; - } + constexpr static bool isStateEvent_ = + std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, + mtx::events::StateEvent<decltype(std::declval<T>().content)>>; static int compare_state_key(const MDB_val *a, const MDB_val *b) { @@ -416,11 +410,27 @@ private: } std::visit( - [&txn, &statesdb, &stateskeydb, &eventsDb](auto e) { - if constexpr (isStateEvent(e)) { + [&txn, &statesdb, &stateskeydb, &eventsDb, &membersdb](const auto &e) { + if constexpr (isStateEvent_<decltype(e)>) { eventsDb.put(txn, e.event_id, json(e).dump()); - if (e.type != EventType::Unsupported) { + if (std::is_same_v< + std::remove_cv_t<std::remove_reference_t<decltype(e)>>, + StateEvent<mtx::events::msg::Redacted>>) { + if (e.type == EventType::RoomMember) + membersdb.del(txn, e.state_key, ""); + else if (e.state_key.empty()) + statesdb.del(txn, to_string(e.type)); + else + stateskeydb.del( + txn, + to_string(e.type), + json::object({ + {"key", e.state_key}, + {"id", e.event_id}, + }) + .dump()); + } else if (e.type != EventType::Unsupported) { if (e.state_key.empty()) statesdb.put( txn, to_string(e.type), json(e).dump()); diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index ab0f8152..b8648269 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -22,7 +22,14 @@ QHash<QString, mtx::crypto::EncryptedFile> infos; QQuickImageResponse * MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize) { - MxcImageResponse *response = new MxcImageResponse(id, requestedSize); + auto id_ = id; + bool crop = true; + if (id.endsWith("?scale")) { + crop = false; + id_.remove("?scale"); + } + + MxcImageResponse *response = new MxcImageResponse(id_, crop, requestedSize); pool.start(response); return response; } @@ -36,20 +43,24 @@ void MxcImageResponse::run() { MxcImageProvider::download( - m_id, m_requestedSize, [this](QString, QSize, QImage image, QString) { + m_id, + m_requestedSize, + [this](QString, QSize, QImage image, QString) { if (image.isNull()) { m_error = "Failed to download image."; } else { m_image = image; } emit finished(); - }); + }, + m_crop); } void MxcImageProvider::download(const QString &id, const QSize &requestedSize, - std::function<void(QString, QSize, QImage, QString)> then) + std::function<void(QString, QSize, QImage, QString)> then, + bool crop) { std::optional<mtx::crypto::EncryptedFile> encryptionInfo; auto temp = infos.find("mxc://" + id); @@ -58,11 +69,12 @@ MxcImageProvider::download(const QString &id, if (requestedSize.isValid() && !encryptionInfo) { QString fileName = - QString("%1_%2x%3_crop") + QString("%1_%2x%3_%4") .arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) .arg(requestedSize.width()) - .arg(requestedSize.height()); + .arg(requestedSize.height()) + .arg(crop ? "crop" : "scale"); QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/media_cache", fileName); @@ -85,7 +97,7 @@ MxcImageProvider::download(const QString &id, opts.mxc_url = "mxc://" + id.toStdString(); opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1; opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1; - opts.method = "crop"; + opts.method = crop ? "crop" : "scale"; http::client()->get_thumbnail( opts, [fileInfo, requestedSize, then, id](const std::string &res, diff --git a/src/MxcImageProvider.h b/src/MxcImageProvider.h index 7b960836..61d82852 100644 --- a/src/MxcImageProvider.h +++ b/src/MxcImageProvider.h @@ -19,9 +19,10 @@ class MxcImageResponse , public QRunnable { public: - MxcImageResponse(const QString &id, const QSize &requestedSize) + MxcImageResponse(const QString &id, bool crop, const QSize &requestedSize) : m_id(id) , m_requestedSize(requestedSize) + , m_crop(crop) { setAutoDelete(false); } @@ -37,6 +38,7 @@ public: QString m_id, m_error; QSize m_requestedSize; QImage m_image; + bool m_crop; }; class MxcImageProvider @@ -51,7 +53,8 @@ public slots: static void addEncryptionInfo(mtx::crypto::EncryptedFile info); static void download(const QString &id, const QSize &requestedSize, - std::function<void(QString, QSize, QImage, QString)> then); + std::function<void(QString, QSize, QImage, QString)> then, + bool crop = true); private: QThreadPool pool; 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) @@ -62,6 +68,73 @@ SingleImagePackModel::data(const QModelIndex &index, int role) const } 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 { if (auto roomPacks = @@ -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))); + }); + } +} diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h index e0c791ba..44f413c6 100644 --- a/src/SingleImagePackModel.h +++ b/src/SingleImagePackModel.h @@ -15,14 +15,18 @@ class SingleImagePackModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(QString roomid READ roomid CONSTANT) - Q_PROPERTY(QString statekey READ statekey CONSTANT) - Q_PROPERTY(QString attribution READ statekey CONSTANT) - Q_PROPERTY(QString packname READ packname CONSTANT) - Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT) - Q_PROPERTY(bool isStickerPack READ isStickerPack CONSTANT) - Q_PROPERTY(bool isEmotePack READ isEmotePack CONSTANT) + Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged) + Q_PROPERTY( + QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged) + Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged) + Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged) + Q_PROPERTY( + bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged) + Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged) Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY globallyEnabledChanged) + Q_PROPERTY(bool canEdit READ canEdit CONSTANT) + public: enum Roles { @@ -32,11 +36,15 @@ public: IsEmote, IsSticker, }; + Q_ENUM(Roles); SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr); QHash<int, QByteArray> roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, + const QVariant &value, + int role = Qt::EditRole) override; QString roomid() const { return QString::fromStdString(roomid_); } QString statekey() const { return QString::fromStdString(statekey_); } @@ -47,14 +55,30 @@ public: bool isEmotePack() const { return pack.pack->is_emoji(); } bool isGloballyEnabled() const; + bool canEdit() const; void setGloballyEnabled(bool enabled); + void setPackname(QString val); + void setAttribution(QString val); + void setAvatarUrl(QString val); + void setStatekey(QString val); + void setIsStickerPack(bool val); + void setIsEmotePack(bool val); + + Q_INVOKABLE void save(); + signals: void globallyEnabledChanged(); + void statekeyChanged(); + void attributionChanged(); + void packnameChanged(); + void avatarUrlChanged(); + void isEmotePackChanged(); + void isStickerPackChanged(); private: std::string roomid_; - std::string statekey_; + std::string statekey_, old_statekey_; mtx::events::msc2545::ImagePack pack; std::vector<std::string> shortcodes; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index a8adf05b..10d9788d 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -308,6 +308,15 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t) case qml_mtx_events::KeyVerificationDone: case qml_mtx_events::KeyVerificationReady: return mtx::events::EventType::RoomMessage; + //! m.image_pack, currently im.ponies.room_emotes + case qml_mtx_events::ImagePackInRoom: + return mtx::events::EventType::ImagePackRooms; + //! m.image_pack, currently im.ponies.user_emotes + case qml_mtx_events::ImagePackInAccountData: + return mtx::events::EventType::ImagePackInAccountData; + //! m.image_pack.rooms, currently im.ponies.emote_rooms + case qml_mtx_events::ImagePackRooms: + return mtx::events::EventType::ImagePackRooms; default: return mtx::events::EventType::Unsupported; }; diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index f62c5360..b5c8ca37 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -107,7 +107,13 @@ enum EventType KeyVerificationCancel, KeyVerificationKey, KeyVerificationDone, - KeyVerificationReady + KeyVerificationReady, + //! m.image_pack, currently im.ponies.room_emotes + ImagePackInRoom, + //! m.image_pack, currently im.ponies.user_emotes + ImagePackInAccountData, + //! m.image_pack.rooms, currently im.ponies.emote_rooms + ImagePackRooms, }; Q_ENUM_NS(EventType) mtx::events::EventType fromRoomEventType(qml_mtx_events::EventType); |