summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2021-08-06 01:45:47 +0200
committerNicolas Werner <nicolas.werner@hotmail.de>2021-08-06 04:31:53 +0200
commita57a15a2e07da8cc07bc12e828b7c636efe36cbc (patch)
treea33d6375ab1c24ded5cdf3f628af29bfe8370a68 /src
parentMerge pull request #666 from LorenDB/qml-all-the-things (diff)
downloadnheko-a57a15a2e07da8cc07bc12e828b7c636efe36cbc.tar.xz
Basic sticker pack editor
Diffstat (limited to 'src')
-rw-r--r--src/Cache.cpp2
-rw-r--r--src/Cache_p.h34
-rw-r--r--src/MxcImageProvider.cpp26
-rw-r--r--src/MxcImageProvider.h7
-rw-r--r--src/SingleImagePackModel.cpp181
-rw-r--r--src/SingleImagePackModel.h38
-rw-r--r--src/timeline/TimelineModel.cpp9
-rw-r--r--src/timeline/TimelineModel.h8
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);