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);
|