summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/GridImagePackModel.cpp88
-rw-r--r--src/GridImagePackModel.h72
-rw-r--r--src/MainWindow.cpp2
-rw-r--r--src/timeline/InputBar.cpp42
-rw-r--r--src/timeline/InputBar.h2
-rw-r--r--src/timeline/TimelineViewManager.cpp4
6 files changed, 194 insertions, 16 deletions
diff --git a/src/GridImagePackModel.cpp b/src/GridImagePackModel.cpp
new file mode 100644
index 00000000..4fee086a
--- /dev/null
+++ b/src/GridImagePackModel.cpp
@@ -0,0 +1,88 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "GridImagePackModel.h"
+
+#include "Cache_p.h"
+#include "CompletionModelRoles.h"
+
+#include <algorithm>
+
+Q_DECLARE_METATYPE(StickerImage)
+
+GridImagePackModel::GridImagePackModel(const std::string &roomId, bool stickers, QObject *parent)
+  : QAbstractListModel(parent)
+  , room_id(roomId)
+{
+    [[maybe_unused]] static auto id = qRegisterMetaType<StickerImage>();
+
+    auto originalPacks = cache::client()->getImagePacks(room_id, stickers);
+
+    for (auto &pack : originalPacks) {
+        PackDesc newPack{};
+        newPack.packname =
+          pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : QString();
+        newPack.room_id   = pack.source_room;
+        newPack.state_key = pack.state_key;
+
+        newPack.images.resize(pack.pack.images.size());
+        std::ranges::transform(std::move(pack.pack.images), newPack.images.begin(), [](auto &&img) {
+            return std::pair(std::move(img.second), QString::fromStdString(img.first));
+        });
+
+        size_t packRowCount =
+          (newPack.images.size() / columns) + (newPack.images.size() % columns ? 1 : 0);
+        newPack.firstRow = rowToPack.size();
+        for (size_t i = 0; i < packRowCount; i++)
+            rowToPack.push_back(packs.size());
+        packs.push_back(std::move(newPack));
+    }
+}
+
+int
+GridImagePackModel::rowCount(const QModelIndex &) const
+{
+    return (int)rowToPack.size();
+}
+
+QHash<int, QByteArray>
+GridImagePackModel::roleNames() const
+{
+    return {
+      {Roles::PackName, "packname"},
+      {Roles::Row, "row"},
+    };
+}
+
+QVariant
+GridImagePackModel::data(const QModelIndex &index, int role) const
+{
+    if (index.row() < rowCount() && index.row() >= 0) {
+        const auto &pack = packs[rowToPack[index.row()]];
+        switch (role) {
+        case Roles::PackName:
+            return pack.packname;
+        case Roles::Row: {
+            std::size_t offset = static_cast<std::size_t>(index.row()) - pack.firstRow;
+            QList<StickerImage> imgs;
+            auto endOffset = std::min((offset + 1) * 3, pack.images.size());
+            for (std::size_t img = offset * 3; img < endOffset; img++) {
+                const auto &data = pack.images.at(img);
+                imgs.push_back({.url         = QString::fromStdString(data.first.url),
+                                .shortcode   = data.second,
+                                .body        = QString::fromStdString(data.first.body),
+                                .descriptor_ = std::vector{
+                                  pack.room_id,
+                                  pack.state_key,
+                                  data.second.toStdString(),
+                                }});
+            }
+            return QVariant::fromValue(imgs);
+        }
+        default:
+            return {};
+        }
+    }
+    return {};
+}
diff --git a/src/GridImagePackModel.h b/src/GridImagePackModel.h
new file mode 100644
index 00000000..1345b103
--- /dev/null
+++ b/src/GridImagePackModel.h
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <QAbstractListModel>
+#include <QObject>
+#include <QString>
+
+#include <mtx/events/mscs/image_packs.hpp>
+
+struct StickerImage
+{
+    Q_GADGET
+    Q_PROPERTY(QString url MEMBER url CONSTANT)
+    Q_PROPERTY(QString shortcode MEMBER shortcode CONSTANT)
+    Q_PROPERTY(QString body MEMBER body CONSTANT)
+    Q_PROPERTY(QStringList descriptor READ descriptor CONSTANT)
+
+public:
+    QStringList descriptor() const
+    {
+        if (descriptor_.size() == 3)
+            return QStringList{
+              QString::fromStdString(descriptor_[0]),
+              QString::fromStdString(descriptor_[1]),
+              QString::fromStdString(descriptor_[2]),
+            };
+        else
+            return {};
+    }
+
+    QString url;
+    QString shortcode;
+    QString body;
+
+    std::vector<std::string> descriptor_; // roomid, statekey, shortcode
+};
+
+class GridImagePackModel final : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    enum Roles
+    {
+        PackName = Qt::UserRole,
+        Row,
+    };
+
+    GridImagePackModel(const std::string &roomId, bool stickers, 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;
+
+private:
+    std::string room_id;
+
+    struct PackDesc
+    {
+        QString packname;
+        QString packavatar;
+        std::string room_id, state_key;
+
+        std::vector<std::pair<mtx::events::msc2545::PackImage, QString>> images;
+        std::size_t firstRow;
+    };
+
+    std::vector<PackDesc> packs;
+    std::vector<size_t> rowToPack;
+    int columns = 3;
+};
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 3ba40bb6..d9a03346 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -19,6 +19,7 @@
 #include "CompletionProxyModel.h"
 #include "Config.h"
 #include "EventAccessors.h"
+#include "GridImagePackModel.h"
 #include "ImagePackListModel.h"
 #include "InviteesModel.h"
 #include "JdenticonProvider.h"
@@ -150,6 +151,7 @@ MainWindow::registerQmlTypes()
     qRegisterMetaType<mtx::responses::User>();
     qRegisterMetaType<mtx::responses::Profile>();
     qRegisterMetaType<CombinedImagePackModel *>();
+    qRegisterMetaType<GridImagePackModel *>();
     qRegisterMetaType<RoomSettingsAllowedRoomsModel *>();
     qRegisterMetaType<mtx::events::collections::TimelineEvents>();
     qRegisterMetaType<std::vector<DeviceInfo>>();
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index dd6813c2..0c2e3752 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -836,28 +836,40 @@ InputBar::getCommandAndArgs(const QString &currentText) const
 }
 
 void
-InputBar::sticker(CombinedImagePackModel *model, int row)
+InputBar::sticker(QStringList descriptor)
 {
-    if (!model || row < 0)
+    if (descriptor.size() != 3)
         return;
 
-    auto img = model->imageAt(row);
+    auto originalPacks = cache::client()->getImagePacks(room->roomId().toStdString(), true);
 
-    mtx::events::msg::StickerImage sticker{};
-    sticker.info = img.info.value_or(mtx::common::ImageInfo{});
-    sticker.url  = img.url;
-    sticker.body = img.body.empty() ? model->shortcodeAt(row).toStdString() : img.body;
+    auto source_room = descriptor[0].toStdString();
+    auto state_key   = descriptor[1].toStdString();
+    auto short_code  = descriptor[2].toStdString();
 
-    // workaround for https://github.com/vector-im/element-ios/issues/2353
-    sticker.info.thumbnail_url           = sticker.url;
-    sticker.info.thumbnail_info.mimetype = sticker.info.mimetype;
-    sticker.info.thumbnail_info.size     = sticker.info.size;
-    sticker.info.thumbnail_info.h        = sticker.info.h;
-    sticker.info.thumbnail_info.w        = sticker.info.w;
+    for (auto &pack : originalPacks) {
+        if (pack.source_room == source_room && pack.state_key == state_key &&
+            pack.pack.images.contains(short_code)) {
+            auto img = pack.pack.images.at(short_code);
 
-    sticker.relations = generateRelations();
+            mtx::events::msg::StickerImage sticker{};
+            sticker.info = img.info.value_or(mtx::common::ImageInfo{});
+            sticker.url  = img.url;
+            sticker.body = img.body.empty() ? short_code : img.body;
 
-    room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
+            // workaround for https://github.com/vector-im/element-ios/issues/2353
+            sticker.info.thumbnail_url           = sticker.url;
+            sticker.info.thumbnail_info.mimetype = sticker.info.mimetype;
+            sticker.info.thumbnail_info.size     = sticker.info.size;
+            sticker.info.thumbnail_info.h        = sticker.info.h;
+            sticker.info.thumbnail_info.w        = sticker.info.w;
+
+            sticker.relations = generateRelations();
+
+            room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
+            break;
+        }
+    }
 }
 
 bool
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index b2db377f..1f1d6fe1 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -217,7 +217,7 @@ public slots:
                  MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
                  bool rainbowify              = false);
     void reaction(const QString &reactedEvent, const QString &reactionKey);
-    void sticker(CombinedImagePackModel *model, int row);
+    void sticker(QStringList descriptor);
 
     void acceptUploads();
     void declineUploads();
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 44f288c6..4b171dc4 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -17,6 +17,7 @@
 #include "CommandCompleter.h"
 #include "CompletionProxyModel.h"
 #include "EventAccessors.h"
+#include "GridImagePackModel.h"
 #include "ImagePackListModel.h"
 #include "InviteesModel.h"
 #include "Logging.h"
@@ -477,6 +478,9 @@ TimelineViewManager::completerFor(const QString &completerName, const QString &r
         auto proxy        = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4);
         stickerModel->setParent(proxy);
         return proxy;
+    } else if (completerName == QLatin1String("stickergrid")) {
+        auto stickerModel = new GridImagePackModel(roomId.toStdString(), true);
+        return stickerModel;
     } else if (completerName == QLatin1String("customEmoji")) {
         auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), false);
         auto proxy        = new CompletionProxyModel(stickerModel);