summary refs log tree commit diff
path: root/src/timeline
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2021-09-18 00:22:33 +0200
committerNicolas Werner <nicolas.werner@hotmail.de>2021-09-18 00:45:50 +0200
commitcfca7157b98c9dc8e0852fe6484bc3f75008af7d (patch)
tree32b92340908a9374214ec7b84c1fac7ea338f56d /src/timeline
parentMerge pull request #728 from Thulinma/goto (diff)
downloadnheko-cfca7157b98c9dc8e0852fe6484bc3f75008af7d.tar.xz
Change indentation to 4 spaces
Diffstat (limited to 'src/timeline')
-rw-r--r--src/timeline/CommunitiesModel.cpp363
-rw-r--r--src/timeline/CommunitiesModel.h102
-rw-r--r--src/timeline/DelegateChooser.cpp111
-rw-r--r--src/timeline/DelegateChooser.h88
-rw-r--r--src/timeline/EventStore.cpp1283
-rw-r--r--src/timeline/EventStore.h206
-rw-r--r--src/timeline/InputBar.cpp1116
-rw-r--r--src/timeline/InputBar.h162
-rw-r--r--src/timeline/Permissions.cpp34
-rw-r--r--src/timeline/Permissions.h24
-rw-r--r--src/timeline/Reaction.h26
-rw-r--r--src/timeline/RoomlistModel.cpp1515
-rw-r--r--src/timeline/RoomlistModel.h300
-rw-r--r--src/timeline/TimelineModel.cpp2947
-rw-r--r--src/timeline/TimelineModel.h691
-rw-r--r--src/timeline/TimelineViewManager.cpp925
-rw-r--r--src/timeline/TimelineViewManager.h164
17 files changed, 4950 insertions, 5107 deletions
diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp

index 97bfa76d..77bed387 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp
@@ -16,231 +16,230 @@ CommunitiesModel::CommunitiesModel(QObject *parent) QHash<int, QByteArray> CommunitiesModel::roleNames() const { - return { - {AvatarUrl, "avatarUrl"}, - {DisplayName, "displayName"}, - {Tooltip, "tooltip"}, - {ChildrenHidden, "childrenHidden"}, - {Hidden, "hidden"}, - {Id, "id"}, - }; + return { + {AvatarUrl, "avatarUrl"}, + {DisplayName, "displayName"}, + {Tooltip, "tooltip"}, + {ChildrenHidden, "childrenHidden"}, + {Hidden, "hidden"}, + {Id, "id"}, + }; } QVariant CommunitiesModel::data(const QModelIndex &index, int role) const { - if (index.row() == 0) { - switch (role) { - case CommunitiesModel::Roles::AvatarUrl: - return QString(":/icons/icons/ui/world.png"); - case CommunitiesModel::Roles::DisplayName: - return tr("All rooms"); - case CommunitiesModel::Roles::Tooltip: - return tr("Shows all rooms without filtering."); - case CommunitiesModel::Roles::ChildrenHidden: - return false; - case CommunitiesModel::Roles::Hidden: - return false; - case CommunitiesModel::Roles::Id: - return ""; - } - } else if (index.row() - 1 < spaceOrder_.size()) { - auto id = spaceOrder_.at(index.row() - 1); - switch (role) { - case CommunitiesModel::Roles::AvatarUrl: - return QString::fromStdString(spaces_.at(id).avatar_url); - case CommunitiesModel::Roles::DisplayName: - case CommunitiesModel::Roles::Tooltip: - return QString::fromStdString(spaces_.at(id).name); - case CommunitiesModel::Roles::ChildrenHidden: - return true; - case CommunitiesModel::Roles::Hidden: - return hiddentTagIds_.contains("space:" + id); - case CommunitiesModel::Roles::Id: - return "space:" + id; - } - } else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) { - auto tag = tags_.at(index.row() - 1 - spaceOrder_.size()); - if (tag == "m.favourite") { - switch (role) { - case CommunitiesModel::Roles::AvatarUrl: - return QString(":/icons/icons/ui/star.png"); - case CommunitiesModel::Roles::DisplayName: - return tr("Favourites"); - case CommunitiesModel::Roles::Tooltip: - return tr("Rooms you have favourited."); - } - } else if (tag == "m.lowpriority") { - switch (role) { - case CommunitiesModel::Roles::AvatarUrl: - return QString(":/icons/icons/ui/lowprio.png"); - case CommunitiesModel::Roles::DisplayName: - return tr("Low Priority"); - case CommunitiesModel::Roles::Tooltip: - return tr("Rooms with low priority."); - } - } else if (tag == "m.server_notice") { - switch (role) { - case CommunitiesModel::Roles::AvatarUrl: - return QString(":/icons/icons/ui/tag.png"); - case CommunitiesModel::Roles::DisplayName: - return tr("Server Notices"); - case CommunitiesModel::Roles::Tooltip: - return tr("Messages from your server or administrator."); - } - } else { - switch (role) { - case CommunitiesModel::Roles::AvatarUrl: - return QString(":/icons/icons/ui/tag.png"); - case CommunitiesModel::Roles::DisplayName: - case CommunitiesModel::Roles::Tooltip: - return tag.mid(2); - } - } + if (index.row() == 0) { + switch (role) { + case CommunitiesModel::Roles::AvatarUrl: + return QString(":/icons/icons/ui/world.png"); + case CommunitiesModel::Roles::DisplayName: + return tr("All rooms"); + case CommunitiesModel::Roles::Tooltip: + return tr("Shows all rooms without filtering."); + case CommunitiesModel::Roles::ChildrenHidden: + return false; + case CommunitiesModel::Roles::Hidden: + return false; + case CommunitiesModel::Roles::Id: + return ""; + } + } else if (index.row() - 1 < spaceOrder_.size()) { + auto id = spaceOrder_.at(index.row() - 1); + switch (role) { + case CommunitiesModel::Roles::AvatarUrl: + return QString::fromStdString(spaces_.at(id).avatar_url); + case CommunitiesModel::Roles::DisplayName: + case CommunitiesModel::Roles::Tooltip: + return QString::fromStdString(spaces_.at(id).name); + case CommunitiesModel::Roles::ChildrenHidden: + return true; + case CommunitiesModel::Roles::Hidden: + return hiddentTagIds_.contains("space:" + id); + case CommunitiesModel::Roles::Id: + return "space:" + id; + } + } else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) { + auto tag = tags_.at(index.row() - 1 - spaceOrder_.size()); + if (tag == "m.favourite") { + switch (role) { + case CommunitiesModel::Roles::AvatarUrl: + return QString(":/icons/icons/ui/star.png"); + case CommunitiesModel::Roles::DisplayName: + return tr("Favourites"); + case CommunitiesModel::Roles::Tooltip: + return tr("Rooms you have favourited."); + } + } else if (tag == "m.lowpriority") { + switch (role) { + case CommunitiesModel::Roles::AvatarUrl: + return QString(":/icons/icons/ui/lowprio.png"); + case CommunitiesModel::Roles::DisplayName: + return tr("Low Priority"); + case CommunitiesModel::Roles::Tooltip: + return tr("Rooms with low priority."); + } + } else if (tag == "m.server_notice") { + switch (role) { + case CommunitiesModel::Roles::AvatarUrl: + return QString(":/icons/icons/ui/tag.png"); + case CommunitiesModel::Roles::DisplayName: + return tr("Server Notices"); + case CommunitiesModel::Roles::Tooltip: + return tr("Messages from your server or administrator."); + } + } else { + switch (role) { + case CommunitiesModel::Roles::AvatarUrl: + return QString(":/icons/icons/ui/tag.png"); + case CommunitiesModel::Roles::DisplayName: + case CommunitiesModel::Roles::Tooltip: + return tag.mid(2); + } + } - switch (role) { - case CommunitiesModel::Roles::Hidden: - return hiddentTagIds_.contains("tag:" + tag); - case CommunitiesModel::Roles::ChildrenHidden: - return true; - case CommunitiesModel::Roles::Id: - return "tag:" + tag; - } + switch (role) { + case CommunitiesModel::Roles::Hidden: + return hiddentTagIds_.contains("tag:" + tag); + case CommunitiesModel::Roles::ChildrenHidden: + return true; + case CommunitiesModel::Roles::Id: + return "tag:" + tag; } - return QVariant(); + } + return QVariant(); } void CommunitiesModel::initializeSidebar() { - beginResetModel(); - tags_.clear(); - spaceOrder_.clear(); - spaces_.clear(); + beginResetModel(); + tags_.clear(); + spaceOrder_.clear(); + spaces_.clear(); - std::set<std::string> ts; - std::vector<RoomInfo> tempSpaces; - auto infos = cache::roomInfo(); - for (auto it = infos.begin(); it != infos.end(); it++) { - if (it.value().is_space) { - spaceOrder_.push_back(it.key()); - spaces_[it.key()] = it.value(); - } else { - for (const auto &t : it.value().tags) { - if (t.find("u.") == 0 || t.find("m." == 0)) { - ts.insert(t); - } - } + std::set<std::string> ts; + std::vector<RoomInfo> tempSpaces; + auto infos = cache::roomInfo(); + for (auto it = infos.begin(); it != infos.end(); it++) { + if (it.value().is_space) { + spaceOrder_.push_back(it.key()); + spaces_[it.key()] = it.value(); + } else { + for (const auto &t : it.value().tags) { + if (t.find("u.") == 0 || t.find("m." == 0)) { + ts.insert(t); } + } } + } - for (const auto &t : ts) - tags_.push_back(QString::fromStdString(t)); + for (const auto &t : ts) + tags_.push_back(QString::fromStdString(t)); - hiddentTagIds_ = UserSettings::instance()->hiddenTags(); - endResetModel(); + hiddentTagIds_ = UserSettings::instance()->hiddenTags(); + endResetModel(); - emit tagsChanged(); - emit hiddenTagsChanged(); + emit tagsChanged(); + emit hiddenTagsChanged(); } void CommunitiesModel::clear() { - beginResetModel(); - tags_.clear(); - endResetModel(); - resetCurrentTagId(); + beginResetModel(); + tags_.clear(); + endResetModel(); + resetCurrentTagId(); - emit tagsChanged(); + emit tagsChanged(); } void CommunitiesModel::sync(const mtx::responses::Rooms &rooms) { - bool tagsUpdated = false; + bool tagsUpdated = false; - for (const auto &[roomid, room] : rooms.join) { - (void)roomid; - for (const auto &e : room.account_data.events) - if (std::holds_alternative< - mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) { - tagsUpdated = true; - } - for (const auto &e : room.state.events) - if (std::holds_alternative< - mtx::events::StateEvent<mtx::events::state::space::Child>>(e) || - std::holds_alternative< - mtx::events::StateEvent<mtx::events::state::space::Parent>>(e)) { - tagsUpdated = true; - } - for (const auto &e : room.timeline.events) - if (std::holds_alternative< - mtx::events::StateEvent<mtx::events::state::space::Child>>(e) || - std::holds_alternative< - mtx::events::StateEvent<mtx::events::state::space::Parent>>(e)) { - tagsUpdated = true; - } - } - for (const auto &[roomid, room] : rooms.leave) { - (void)room; - if (spaceOrder_.contains(QString::fromStdString(roomid))) - tagsUpdated = true; - } + for (const auto &[roomid, room] : rooms.join) { + (void)roomid; + for (const auto &e : room.account_data.events) + if (std::holds_alternative< + mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) { + tagsUpdated = true; + } + for (const auto &e : room.state.events) + if (std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Child>>( + e) || + std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Parent>>( + e)) { + tagsUpdated = true; + } + for (const auto &e : room.timeline.events) + if (std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Child>>( + e) || + std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Parent>>( + e)) { + tagsUpdated = true; + } + } + for (const auto &[roomid, room] : rooms.leave) { + (void)room; + if (spaceOrder_.contains(QString::fromStdString(roomid))) + tagsUpdated = true; + } - if (tagsUpdated) - initializeSidebar(); + if (tagsUpdated) + initializeSidebar(); } void CommunitiesModel::setCurrentTagId(QString tagId) { - if (tagId.startsWith("tag:")) { - auto tag = tagId.mid(4); - for (const auto &t : tags_) { - if (t == tag) { - this->currentTagId_ = tagId; - emit currentTagIdChanged(currentTagId_); - return; - } - } - } else if (tagId.startsWith("space:")) { - auto tag = tagId.mid(6); - for (const auto &t : spaceOrder_) { - if (t == tag) { - this->currentTagId_ = tagId; - emit currentTagIdChanged(currentTagId_); - return; - } - } + if (tagId.startsWith("tag:")) { + auto tag = tagId.mid(4); + for (const auto &t : tags_) { + if (t == tag) { + this->currentTagId_ = tagId; + emit currentTagIdChanged(currentTagId_); + return; + } } + } else if (tagId.startsWith("space:")) { + auto tag = tagId.mid(6); + for (const auto &t : spaceOrder_) { + if (t == tag) { + this->currentTagId_ = tagId; + emit currentTagIdChanged(currentTagId_); + return; + } + } + } - this->currentTagId_ = ""; - emit currentTagIdChanged(currentTagId_); + this->currentTagId_ = ""; + emit currentTagIdChanged(currentTagId_); } void CommunitiesModel::toggleTagId(QString tagId) { - if (hiddentTagIds_.contains(tagId)) { - hiddentTagIds_.removeOne(tagId); - UserSettings::instance()->setHiddenTags(hiddentTagIds_); - } else { - hiddentTagIds_.push_back(tagId); - UserSettings::instance()->setHiddenTags(hiddentTagIds_); - } + if (hiddentTagIds_.contains(tagId)) { + hiddentTagIds_.removeOne(tagId); + UserSettings::instance()->setHiddenTags(hiddentTagIds_); + } else { + hiddentTagIds_.push_back(tagId); + UserSettings::instance()->setHiddenTags(hiddentTagIds_); + } - if (tagId.startsWith("tag:")) { - auto idx = tags_.indexOf(tagId.mid(4)); - if (idx != -1) - emit dataChanged(index(idx + 1 + spaceOrder_.size()), - index(idx + 1 + spaceOrder_.size()), - {Hidden}); - } else if (tagId.startsWith("space:")) { - auto idx = spaceOrder_.indexOf(tagId.mid(6)); - if (idx != -1) - emit dataChanged(index(idx + 1), index(idx + 1), {Hidden}); - } + if (tagId.startsWith("tag:")) { + auto idx = tags_.indexOf(tagId.mid(4)); + if (idx != -1) + emit dataChanged( + index(idx + 1 + spaceOrder_.size()), index(idx + 1 + spaceOrder_.size()), {Hidden}); + } else if (tagId.startsWith("space:")) { + auto idx = spaceOrder_.indexOf(tagId.mid(6)); + if (idx != -1) + emit dataChanged(index(idx + 1), index(idx + 1), {Hidden}); + } - emit hiddenTagsChanged(); + emit hiddenTagsChanged(); } diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h
index 677581dc..0440d17f 100644 --- a/src/timeline/CommunitiesModel.h +++ b/src/timeline/CommunitiesModel.h
@@ -15,64 +15,64 @@ class CommunitiesModel : public QAbstractListModel { - Q_OBJECT - Q_PROPERTY(QString currentTagId READ currentTagId WRITE setCurrentTagId NOTIFY - currentTagIdChanged RESET resetCurrentTagId) - Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged) - Q_PROPERTY(QStringList tagsWithDefault READ tagsWithDefault NOTIFY tagsChanged) + Q_OBJECT + Q_PROPERTY(QString currentTagId READ currentTagId WRITE setCurrentTagId NOTIFY + currentTagIdChanged RESET resetCurrentTagId) + Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged) + Q_PROPERTY(QStringList tagsWithDefault READ tagsWithDefault NOTIFY tagsChanged) public: - enum Roles - { - AvatarUrl = Qt::UserRole, - DisplayName, - Tooltip, - ChildrenHidden, - Hidden, - Id, - }; + enum Roles + { + AvatarUrl = Qt::UserRole, + DisplayName, + Tooltip, + ChildrenHidden, + Hidden, + Id, + }; - CommunitiesModel(QObject *parent = nullptr); - QHash<int, QByteArray> roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - (void)parent; - return 1 + tags_.size() + spaceOrder_.size(); - } - QVariant data(const QModelIndex &index, int role) const override; + CommunitiesModel(QObject *parent = nullptr); + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return 1 + tags_.size() + spaceOrder_.size(); + } + QVariant data(const QModelIndex &index, int role) const override; public slots: - void initializeSidebar(); - void sync(const mtx::responses::Rooms &rooms); - void clear(); - QString currentTagId() const { return currentTagId_; } - void setCurrentTagId(QString tagId); - void resetCurrentTagId() - { - currentTagId_.clear(); - emit currentTagIdChanged(currentTagId_); - } - QStringList tags() const { return tags_; } - QStringList tagsWithDefault() const - { - QStringList tagsWD = tags_; - tagsWD.prepend("m.lowpriority"); - tagsWD.prepend("m.favourite"); - tagsWD.removeOne("m.server_notice"); - tagsWD.removeDuplicates(); - return tagsWD; - } - void toggleTagId(QString tagId); + void initializeSidebar(); + void sync(const mtx::responses::Rooms &rooms); + void clear(); + QString currentTagId() const { return currentTagId_; } + void setCurrentTagId(QString tagId); + void resetCurrentTagId() + { + currentTagId_.clear(); + emit currentTagIdChanged(currentTagId_); + } + QStringList tags() const { return tags_; } + QStringList tagsWithDefault() const + { + QStringList tagsWD = tags_; + tagsWD.prepend("m.lowpriority"); + tagsWD.prepend("m.favourite"); + tagsWD.removeOne("m.server_notice"); + tagsWD.removeDuplicates(); + return tagsWD; + } + void toggleTagId(QString tagId); signals: - void currentTagIdChanged(QString tagId); - void hiddenTagsChanged(); - void tagsChanged(); + void currentTagIdChanged(QString tagId); + void hiddenTagsChanged(); + void tagsChanged(); private: - QStringList tags_; - QString currentTagId_; - QStringList hiddentTagIds_; - QStringList spaceOrder_; - std::map<QString, RoomInfo> spaces_; + QStringList tags_; + QString currentTagId_; + QStringList hiddentTagIds_; + QStringList spaceOrder_; + std::map<QString, RoomInfo> spaces_; }; diff --git a/src/timeline/DelegateChooser.cpp b/src/timeline/DelegateChooser.cpp
index 39c8fa17..682077ae 100644 --- a/src/timeline/DelegateChooser.cpp +++ b/src/timeline/DelegateChooser.cpp
@@ -13,127 +13,126 @@ QQmlComponent * DelegateChoice::delegate() const { - return delegate_; + return delegate_; } void DelegateChoice::setDelegate(QQmlComponent *delegate) { - if (delegate != delegate_) { - delegate_ = delegate; - emit delegateChanged(); - emit changed(); - } + if (delegate != delegate_) { + delegate_ = delegate; + emit delegateChanged(); + emit changed(); + } } QVariant DelegateChoice::roleValue() const { - return roleValue_; + return roleValue_; } void DelegateChoice::setRoleValue(const QVariant &value) { - if (value != roleValue_) { - roleValue_ = value; - emit roleValueChanged(); - emit changed(); - } + if (value != roleValue_) { + roleValue_ = value; + emit roleValueChanged(); + emit changed(); + } } QVariant DelegateChooser::roleValue() const { - return roleValue_; + return roleValue_; } void DelegateChooser::setRoleValue(const QVariant &value) { - if (value != roleValue_) { - roleValue_ = value; - recalcChild(); - emit roleValueChanged(); - } + if (value != roleValue_) { + roleValue_ = value; + recalcChild(); + emit roleValueChanged(); + } } QQmlListProperty<DelegateChoice> DelegateChooser::choices() { - return QQmlListProperty<DelegateChoice>(this, - this, - &DelegateChooser::appendChoice, - &DelegateChooser::choiceCount, - &DelegateChooser::choice, - &DelegateChooser::clearChoices); + return QQmlListProperty<DelegateChoice>(this, + this, + &DelegateChooser::appendChoice, + &DelegateChooser::choiceCount, + &DelegateChooser::choice, + &DelegateChooser::clearChoices); } void DelegateChooser::appendChoice(QQmlListProperty<DelegateChoice> *p, DelegateChoice *c) { - DelegateChooser *dc = static_cast<DelegateChooser *>(p->object); - dc->choices_.append(c); + DelegateChooser *dc = static_cast<DelegateChooser *>(p->object); + dc->choices_.append(c); } int DelegateChooser::choiceCount(QQmlListProperty<DelegateChoice> *p) { - return static_cast<DelegateChooser *>(p->object)->choices_.count(); + return static_cast<DelegateChooser *>(p->object)->choices_.count(); } DelegateChoice * DelegateChooser::choice(QQmlListProperty<DelegateChoice> *p, int index) { - return static_cast<DelegateChooser *>(p->object)->choices_.at(index); + return static_cast<DelegateChooser *>(p->object)->choices_.at(index); } void DelegateChooser::clearChoices(QQmlListProperty<DelegateChoice> *p) { - static_cast<DelegateChooser *>(p->object)->choices_.clear(); + static_cast<DelegateChooser *>(p->object)->choices_.clear(); } void DelegateChooser::recalcChild() { - for (const auto choice : qAsConst(choices_)) { - auto choiceValue = choice->roleValue(); - if (!roleValue_.isValid() || !choiceValue.isValid() || choiceValue == roleValue_) { - if (child_) { - child_->setParentItem(nullptr); - child_ = nullptr; - } + for (const auto choice : qAsConst(choices_)) { + auto choiceValue = choice->roleValue(); + if (!roleValue_.isValid() || !choiceValue.isValid() || choiceValue == roleValue_) { + if (child_) { + child_->setParentItem(nullptr); + child_ = nullptr; + } - choice->delegate()->create(incubator, QQmlEngine::contextForObject(this)); - return; - } + choice->delegate()->create(incubator, QQmlEngine::contextForObject(this)); + return; } + } } void DelegateChooser::componentComplete() { - QQuickItem::componentComplete(); - recalcChild(); + QQuickItem::componentComplete(); + recalcChild(); } void DelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status status) { - if (status == QQmlIncubator::Ready) { - chooser.child_ = dynamic_cast<QQuickItem *>(object()); - if (chooser.child_ == nullptr) { - nhlog::ui()->error("Delegate has to be derived of Item!"); - return; - } + if (status == QQmlIncubator::Ready) { + chooser.child_ = dynamic_cast<QQuickItem *>(object()); + if (chooser.child_ == nullptr) { + nhlog::ui()->error("Delegate has to be derived of Item!"); + return; + } - chooser.child_->setParentItem(&chooser); - QQmlEngine::setObjectOwnership(chooser.child_, - QQmlEngine::ObjectOwnership::JavaScriptOwnership); - emit chooser.childChanged(); + chooser.child_->setParentItem(&chooser); + QQmlEngine::setObjectOwnership(chooser.child_, + QQmlEngine::ObjectOwnership::JavaScriptOwnership); + emit chooser.childChanged(); - } else if (status == QQmlIncubator::Error) { - for (const auto &e : errors()) - nhlog::ui()->error("Error instantiating delegate: {}", - e.toString().toStdString()); - } + } else if (status == QQmlIncubator::Error) { + for (const auto &e : errors()) + nhlog::ui()->error("Error instantiating delegate: {}", e.toString().toStdString()); + } } diff --git a/src/timeline/DelegateChooser.h b/src/timeline/DelegateChooser.h
index 22e423a2..3e4b16d7 100644 --- a/src/timeline/DelegateChooser.h +++ b/src/timeline/DelegateChooser.h
@@ -18,73 +18,73 @@ class QQmlAdaptorModel; class DelegateChoice : public QObject { - Q_OBJECT - Q_CLASSINFO("DefaultProperty", "delegate") + Q_OBJECT + Q_CLASSINFO("DefaultProperty", "delegate") public: - Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged) - Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) - QQmlComponent *delegate() const; - void setDelegate(QQmlComponent *delegate); + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); - QVariant roleValue() const; - void setRoleValue(const QVariant &value); + QVariant roleValue() const; + void setRoleValue(const QVariant &value); signals: - void delegateChanged(); - void roleValueChanged(); - void changed(); + void delegateChanged(); + void roleValueChanged(); + void changed(); private: - QVariant roleValue_; - QQmlComponent *delegate_ = nullptr; + QVariant roleValue_; + QQmlComponent *delegate_ = nullptr; }; class DelegateChooser : public QQuickItem { - Q_OBJECT - Q_CLASSINFO("DefaultProperty", "choices") + Q_OBJECT + Q_CLASSINFO("DefaultProperty", "choices") public: - Q_PROPERTY(QQmlListProperty<DelegateChoice> choices READ choices CONSTANT) - Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged) - Q_PROPERTY(QQuickItem *child READ child NOTIFY childChanged) + Q_PROPERTY(QQmlListProperty<DelegateChoice> choices READ choices CONSTANT) + Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged) + Q_PROPERTY(QQuickItem *child READ child NOTIFY childChanged) - QQmlListProperty<DelegateChoice> choices(); + QQmlListProperty<DelegateChoice> choices(); - QVariant roleValue() const; - void setRoleValue(const QVariant &value); + QVariant roleValue() const; + void setRoleValue(const QVariant &value); - QQuickItem *child() const { return child_; } + QQuickItem *child() const { return child_; } - void recalcChild(); - void componentComplete() override; + void recalcChild(); + void componentComplete() override; signals: - void roleChanged(); - void roleValueChanged(); - void childChanged(); + void roleChanged(); + void roleValueChanged(); + void childChanged(); private: - struct DelegateIncubator : public QQmlIncubator - { - DelegateIncubator(DelegateChooser &parent) - : QQmlIncubator(QQmlIncubator::AsynchronousIfNested) - , chooser(parent) - {} - void statusChanged(QQmlIncubator::Status status) override; + struct DelegateIncubator : public QQmlIncubator + { + DelegateIncubator(DelegateChooser &parent) + : QQmlIncubator(QQmlIncubator::AsynchronousIfNested) + , chooser(parent) + {} + void statusChanged(QQmlIncubator::Status status) override; - DelegateChooser &chooser; - }; + DelegateChooser &chooser; + }; - QVariant roleValue_; - QList<DelegateChoice *> choices_; - QQuickItem *child_ = nullptr; - DelegateIncubator incubator{*this}; + QVariant roleValue_; + QList<DelegateChoice *> choices_; + QQuickItem *child_ = nullptr; + DelegateIncubator incubator{*this}; - static void appendChoice(QQmlListProperty<DelegateChoice> *, DelegateChoice *); - static int choiceCount(QQmlListProperty<DelegateChoice> *); - static DelegateChoice *choice(QQmlListProperty<DelegateChoice> *, int index); - static void clearChoices(QQmlListProperty<DelegateChoice> *); + static void appendChoice(QQmlListProperty<DelegateChoice> *, DelegateChoice *); + static int choiceCount(QQmlListProperty<DelegateChoice> *); + static DelegateChoice *choice(QQmlListProperty<DelegateChoice> *, int index); + static void clearChoices(QQmlListProperty<DelegateChoice> *); }; diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 881fd5bb..7144424a 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp
@@ -28,393 +28,373 @@ QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore:: EventStore::EventStore(std::string room_id, QObject *) : room_id_(std::move(room_id)) { - static auto reactionType = qRegisterMetaType<Reaction>(); - (void)reactionType; + static auto reactionType = qRegisterMetaType<Reaction>(); + (void)reactionType; - auto range = cache::client()->getTimelineRange(room_id_); + auto range = cache::client()->getTimelineRange(room_id_); - if (range) { - this->first = range->first; - this->last = range->last; - } + if (range) { + this->first = range->first; + this->last = range->last; + } - connect( - this, - &EventStore::eventFetched, - this, - [this](std::string id, - std::string relatedTo, - mtx::events::collections::TimelineEvents timeline) { - cache::client()->storeEvent(room_id_, id, {timeline}); + connect( + this, + &EventStore::eventFetched, + this, + [this]( + std::string id, std::string relatedTo, mtx::events::collections::TimelineEvents timeline) { + cache::client()->storeEvent(room_id_, id, {timeline}); - if (!relatedTo.empty()) { - auto idx = idToIndex(relatedTo); - if (idx) - emit dataChanged(*idx, *idx); - } - }, - Qt::QueuedConnection); + if (!relatedTo.empty()) { + auto idx = idToIndex(relatedTo); + if (idx) + emit dataChanged(*idx, *idx); + } + }, + Qt::QueuedConnection); - connect( - this, - &EventStore::oldMessagesRetrieved, - this, - [this](const mtx::responses::Messages &res) { - if (res.end.empty() || cache::client()->previousBatchToken(room_id_) == res.end) { - noMoreMessages = true; - emit fetchedMore(); - return; - } + connect( + this, + &EventStore::oldMessagesRetrieved, + this, + [this](const mtx::responses::Messages &res) { + if (res.end.empty() || cache::client()->previousBatchToken(room_id_) == res.end) { + noMoreMessages = true; + emit fetchedMore(); + return; + } - uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); - if (newFirst == first) - fetchMore(); - else { - if (this->last != std::numeric_limits<uint64_t>::max()) { - auto oldFirst = this->first; - emit beginInsertRows(toExternalIdx(newFirst), - toExternalIdx(this->first - 1)); - this->first = newFirst; - emit endInsertRows(); - emit fetchedMore(); - emit dataChanged(toExternalIdx(oldFirst), - toExternalIdx(oldFirst)); - } else { - auto range = cache::client()->getTimelineRange(room_id_); + uint64_t newFirst = cache::client()->saveOldMessages(room_id_, res); + if (newFirst == first) + fetchMore(); + else { + if (this->last != std::numeric_limits<uint64_t>::max()) { + auto oldFirst = this->first; + emit beginInsertRows(toExternalIdx(newFirst), toExternalIdx(this->first - 1)); + this->first = newFirst; + emit endInsertRows(); + emit fetchedMore(); + emit dataChanged(toExternalIdx(oldFirst), toExternalIdx(oldFirst)); + } else { + auto range = cache::client()->getTimelineRange(room_id_); - if (range && range->last - range->first != 0) { - emit beginInsertRows(0, int(range->last - range->first)); - this->first = range->first; - this->last = range->last; - emit endInsertRows(); - emit fetchedMore(); - } else { - fetchMore(); - } - } + if (range && range->last - range->first != 0) { + emit beginInsertRows(0, int(range->last - range->first)); + this->first = range->first; + this->last = range->last; + emit endInsertRows(); + emit fetchedMore(); + } else { + fetchMore(); } - }, - Qt::QueuedConnection); - - connect(this, &EventStore::processPending, this, [this]() { - if (!current_txn.empty()) { - nhlog::ui()->debug("Already processing {}", current_txn); - return; - } + } + } + }, + Qt::QueuedConnection); - auto event = cache::client()->firstPendingMessage(room_id_); + connect(this, &EventStore::processPending, this, [this]() { + if (!current_txn.empty()) { + nhlog::ui()->debug("Already processing {}", current_txn); + return; + } - if (!event) { - nhlog::ui()->debug("No event to send"); - return; - } + auto event = cache::client()->firstPendingMessage(room_id_); - std::visit( - [this](auto e) { - auto txn_id = e.event_id; - this->current_txn = txn_id; + if (!event) { + nhlog::ui()->debug("No event to send"); + return; + } - if (txn_id.empty() || txn_id[0] != 'm') { - nhlog::ui()->debug("Invalid txn id '{}'", txn_id); - cache::client()->removePendingStatus(room_id_, txn_id); - return; - } + std::visit( + [this](auto e) { + auto txn_id = e.event_id; + this->current_txn = txn_id; - if constexpr (mtx::events::message_content_to_type<decltype(e.content)> != - mtx::events::EventType::Unsupported) - http::client()->send_room_message( - room_id_, - txn_id, - e.content, - [this, txn_id, e](const mtx::responses::EventId &event_id, - mtx::http::RequestErr err) { - if (err) { - const int status_code = - static_cast<int>(err->status_code); - nhlog::net()->warn( - "[{}] failed to send message: {} {}", - txn_id, - err->matrix_error.error, - status_code); - emit messageFailed(txn_id); - return; - } + if (txn_id.empty() || txn_id[0] != 'm') { + nhlog::ui()->debug("Invalid txn id '{}'", txn_id); + cache::client()->removePendingStatus(room_id_, txn_id); + return; + } - emit messageSent(txn_id, event_id.event_id.to_string()); - if constexpr (std::is_same_v< - decltype(e.content), - mtx::events::msg::Encrypted>) { - auto event = - decryptEvent({room_id_, e.event_id}, e); - if (event->event) { - if (auto dec = std::get_if< - mtx::events::RoomEvent< - mtx::events::msg:: - KeyVerificationRequest>>( - &event->event.value())) { - emit updateFlowEventId( - event_id.event_id - .to_string()); - } - } - } - }); - }, - event->data); - }); + if constexpr (mtx::events::message_content_to_type<decltype(e.content)> != + mtx::events::EventType::Unsupported) + http::client()->send_room_message( + room_id_, + txn_id, + e.content, + [this, txn_id, e](const mtx::responses::EventId &event_id, + mtx::http::RequestErr err) { + if (err) { + const int status_code = static_cast<int>(err->status_code); + nhlog::net()->warn("[{}] failed to send message: {} {}", + txn_id, + err->matrix_error.error, + status_code); + emit messageFailed(txn_id); + return; + } - connect( - this, - &EventStore::messageFailed, - this, - [this](std::string txn_id) { - if (current_txn == txn_id) { - current_txn_error_count++; - if (current_txn_error_count > 10) { - nhlog::ui()->debug("failing txn id '{}'", txn_id); - cache::client()->removePendingStatus(room_id_, txn_id); - current_txn_error_count = 0; - } - } - QTimer::singleShot(1000, this, [this]() { - nhlog::ui()->debug("timeout"); - this->current_txn = ""; - emit processPending(); - }); + emit messageSent(txn_id, event_id.event_id.to_string()); + if constexpr (std::is_same_v<decltype(e.content), + mtx::events::msg::Encrypted>) { + auto event = decryptEvent({room_id_, e.event_id}, e); + if (event->event) { + if (auto dec = std::get_if<mtx::events::RoomEvent< + mtx::events::msg::KeyVerificationRequest>>( + &event->event.value())) { + emit updateFlowEventId(event_id.event_id.to_string()); + } + } + } + }); }, - Qt::QueuedConnection); + event->data); + }); - connect( - this, - &EventStore::messageSent, - this, - [this](std::string txn_id, std::string event_id) { - nhlog::ui()->debug("sent {}", txn_id); + connect( + this, + &EventStore::messageFailed, + this, + [this](std::string txn_id) { + if (current_txn == txn_id) { + current_txn_error_count++; + if (current_txn_error_count > 10) { + nhlog::ui()->debug("failing txn id '{}'", txn_id); + cache::client()->removePendingStatus(room_id_, txn_id); + current_txn_error_count = 0; + } + } + QTimer::singleShot(1000, this, [this]() { + nhlog::ui()->debug("timeout"); + this->current_txn = ""; + emit processPending(); + }); + }, + Qt::QueuedConnection); - // Replace the event_id in pending edits/replies/redactions with the actual - // event_id of this event. This allows one to edit and reply to events that are - // currently pending. + connect( + this, + &EventStore::messageSent, + this, + [this](std::string txn_id, std::string event_id) { + nhlog::ui()->debug("sent {}", txn_id); - // FIXME (introduced by balsoft): this doesn't work for encrypted events, but - // allegedly it's hard to fix so I'll leave my first contribution at that - for (auto related_event_id : cache::client()->relatedEvents(room_id_, txn_id)) { - if (cache::client()->getEvent(room_id_, related_event_id)) { - auto related_event = - cache::client()->getEvent(room_id_, related_event_id).value(); - auto relations = mtx::accessors::relations(related_event.data); + // Replace the event_id in pending edits/replies/redactions with the actual + // event_id of this event. This allows one to edit and reply to events that are + // currently pending. - // Replace the blockquote in fallback reply - auto related_text = - std::get_if<mtx::events::RoomEvent<mtx::events::msg::Text>>( - &related_event.data); - if (related_text && relations.reply_to() == txn_id) { - size_t index = - related_text->content.formatted_body.find(txn_id); - if (index != std::string::npos) { - related_text->content.formatted_body.replace( - index, event_id.length(), event_id); - } - } + // FIXME (introduced by balsoft): this doesn't work for encrypted events, but + // allegedly it's hard to fix so I'll leave my first contribution at that + for (auto related_event_id : cache::client()->relatedEvents(room_id_, txn_id)) { + if (cache::client()->getEvent(room_id_, related_event_id)) { + auto related_event = + cache::client()->getEvent(room_id_, related_event_id).value(); + auto relations = mtx::accessors::relations(related_event.data); - for (mtx::common::Relation &rel : relations.relations) { - if (rel.event_id == txn_id) - rel.event_id = event_id; - } + // Replace the blockquote in fallback reply + auto related_text = std::get_if<mtx::events::RoomEvent<mtx::events::msg::Text>>( + &related_event.data); + if (related_text && relations.reply_to() == txn_id) { + size_t index = related_text->content.formatted_body.find(txn_id); + if (index != std::string::npos) { + related_text->content.formatted_body.replace( + index, event_id.length(), event_id); + } + } - mtx::accessors::set_relations(related_event.data, relations); + for (mtx::common::Relation &rel : relations.relations) { + if (rel.event_id == txn_id) + rel.event_id = event_id; + } - cache::client()->replaceEvent( - room_id_, related_event_id, related_event); + mtx::accessors::set_relations(related_event.data, relations); - auto idx = idToIndex(related_event_id); + cache::client()->replaceEvent(room_id_, related_event_id, related_event); - events_by_id_.remove({room_id_, related_event_id}); - events_.remove({room_id_, toInternalIdx(*idx)}); - } - } + auto idx = idToIndex(related_event_id); - http::client()->read_event( - room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to read_event ({}, {})", room_id_, event_id); - } - }); + events_by_id_.remove({room_id_, related_event_id}); + events_.remove({room_id_, toInternalIdx(*idx)}); + } + } - auto idx = idToIndex(event_id); + http::client()->read_event( + room_id_, event_id, [this, event_id](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to read_event ({}, {})", room_id_, event_id); + } + }); - if (idx) - emit dataChanged(*idx, *idx); + auto idx = idToIndex(event_id); - cache::client()->removePendingStatus(room_id_, txn_id); - this->current_txn = ""; - this->current_txn_error_count = 0; - emit processPending(); - }, - Qt::QueuedConnection); + if (idx) + emit dataChanged(*idx, *idx); + + cache::client()->removePendingStatus(room_id_, txn_id); + this->current_txn = ""; + this->current_txn_error_count = 0; + emit processPending(); + }, + Qt::QueuedConnection); } void EventStore::addPending(mtx::events::collections::TimelineEvents event) { - if (this->thread() != QThread::currentThread()) - nhlog::db()->warn("{} called from a different thread!", __func__); + if (this->thread() != QThread::currentThread()) + nhlog::db()->warn("{} called from a different thread!", __func__); - cache::client()->savePendingMessage(this->room_id_, {event}); - mtx::responses::Timeline events; - events.limited = false; - events.events.emplace_back(event); - handleSync(events); + cache::client()->savePendingMessage(this->room_id_, {event}); + mtx::responses::Timeline events; + events.limited = false; + events.events.emplace_back(event); + handleSync(events); - emit processPending(); + emit processPending(); } void EventStore::clearTimeline() { - emit beginResetModel(); + emit beginResetModel(); - cache::client()->clearTimeline(room_id_); - auto range = cache::client()->getTimelineRange(room_id_); - if (range) { - nhlog::db()->info("Range {} {}", range->last, range->first); - this->last = range->last; - this->first = range->first; - } else { - this->first = std::numeric_limits<uint64_t>::max(); - this->last = std::numeric_limits<uint64_t>::max(); - } - nhlog::ui()->info("Range {} {}", this->last, this->first); + cache::client()->clearTimeline(room_id_); + auto range = cache::client()->getTimelineRange(room_id_); + if (range) { + nhlog::db()->info("Range {} {}", range->last, range->first); + this->last = range->last; + this->first = range->first; + } else { + this->first = std::numeric_limits<uint64_t>::max(); + this->last = std::numeric_limits<uint64_t>::max(); + } + nhlog::ui()->info("Range {} {}", this->last, this->first); - decryptedEvents_.clear(); - events_.clear(); + decryptedEvents_.clear(); + events_.clear(); - emit endResetModel(); + emit endResetModel(); } void EventStore::receivedSessionKey(const std::string &session_id) { - if (!pending_key_requests.count(session_id)) - return; + if (!pending_key_requests.count(session_id)) + return; - auto request = pending_key_requests.at(session_id); + auto request = pending_key_requests.at(session_id); - // Don't request keys again until Nheko is restarted (for now) - pending_key_requests[session_id].events.clear(); + // Don't request keys again until Nheko is restarted (for now) + pending_key_requests[session_id].events.clear(); - if (!request.events.empty()) - olm::send_key_request_for(request.events.front(), request.request_id, true); + if (!request.events.empty()) + olm::send_key_request_for(request.events.front(), request.request_id, true); - for (const auto &e : request.events) { - auto idx = idToIndex(e.event_id); - if (idx) { - decryptedEvents_.remove({room_id_, e.event_id}); - events_by_id_.remove({room_id_, e.event_id}); - events_.remove({room_id_, toInternalIdx(*idx)}); - emit dataChanged(*idx, *idx); - } + for (const auto &e : request.events) { + auto idx = idToIndex(e.event_id); + if (idx) { + decryptedEvents_.remove({room_id_, e.event_id}); + events_by_id_.remove({room_id_, e.event_id}); + events_.remove({room_id_, toInternalIdx(*idx)}); + emit dataChanged(*idx, *idx); } + } } void EventStore::handleSync(const mtx::responses::Timeline &events) { - if (this->thread() != QThread::currentThread()) - nhlog::db()->warn("{} called from a different thread!", __func__); + if (this->thread() != QThread::currentThread()) + nhlog::db()->warn("{} called from a different thread!", __func__); - auto range = cache::client()->getTimelineRange(room_id_); - if (!range) { - emit beginResetModel(); - this->first = std::numeric_limits<uint64_t>::max(); - this->last = std::numeric_limits<uint64_t>::max(); - - decryptedEvents_.clear(); - events_.clear(); - emit endResetModel(); - return; - } + auto range = cache::client()->getTimelineRange(room_id_); + if (!range) { + emit beginResetModel(); + this->first = std::numeric_limits<uint64_t>::max(); + this->last = std::numeric_limits<uint64_t>::max(); - if (events.limited) { - emit beginResetModel(); - this->last = range->last; - this->first = range->first; + decryptedEvents_.clear(); + events_.clear(); + emit endResetModel(); + return; + } - decryptedEvents_.clear(); - events_.clear(); - emit endResetModel(); - } else if (range->last > this->last) { - emit beginInsertRows(toExternalIdx(this->last + 1), toExternalIdx(range->last)); - this->last = range->last; - emit endInsertRows(); - } + if (events.limited) { + emit beginResetModel(); + this->last = range->last; + this->first = range->first; - for (const auto &event : events.events) { - std::set<std::string> relates_to; - if (auto redaction = - std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>( - &event)) { - // fixup reactions - auto redacted = events_by_id_.object({room_id_, redaction->redacts}); - if (redacted) { - auto id = mtx::accessors::relations(*redacted); - if (id.annotates()) { - auto idx = idToIndex(id.annotates()->event_id); - if (idx) { - events_by_id_.remove( - {room_id_, redaction->redacts}); - events_.remove({room_id_, toInternalIdx(*idx)}); - emit dataChanged(*idx, *idx); - } - } - } + decryptedEvents_.clear(); + events_.clear(); + emit endResetModel(); + } else if (range->last > this->last) { + emit beginInsertRows(toExternalIdx(this->last + 1), toExternalIdx(range->last)); + this->last = range->last; + emit endInsertRows(); + } - relates_to.insert(redaction->redacts); - } else { - for (const auto &r : mtx::accessors::relations(event).relations) - relates_to.insert(r.event_id); + for (const auto &event : events.events) { + std::set<std::string> relates_to; + if (auto redaction = + std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(&event)) { + // fixup reactions + auto redacted = events_by_id_.object({room_id_, redaction->redacts}); + if (redacted) { + auto id = mtx::accessors::relations(*redacted); + if (id.annotates()) { + auto idx = idToIndex(id.annotates()->event_id); + if (idx) { + events_by_id_.remove({room_id_, redaction->redacts}); + events_.remove({room_id_, toInternalIdx(*idx)}); + emit dataChanged(*idx, *idx); + } } + } - for (const auto &relates_to_id : relates_to) { - auto idx = cache::client()->getTimelineIndex(room_id_, relates_to_id); - if (idx) { - events_by_id_.remove({room_id_, relates_to_id}); - decryptedEvents_.remove({room_id_, relates_to_id}); - events_.remove({room_id_, *idx}); - emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx)); - } - } + relates_to.insert(redaction->redacts); + } else { + for (const auto &r : mtx::accessors::relations(event).relations) + relates_to.insert(r.event_id); + } - if (auto txn_id = mtx::accessors::transaction_id(event); !txn_id.empty()) { - auto idx = cache::client()->getTimelineIndex( - room_id_, mtx::accessors::event_id(event)); - if (idx) { - Index index{room_id_, *idx}; - events_.remove(index); - emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx)); - } - } + for (const auto &relates_to_id : relates_to) { + auto idx = cache::client()->getTimelineIndex(room_id_, relates_to_id); + if (idx) { + events_by_id_.remove({room_id_, relates_to_id}); + decryptedEvents_.remove({room_id_, relates_to_id}); + events_.remove({room_id_, *idx}); + emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx)); + } + } - // decrypting and checking some encrypted messages - if (auto encrypted = - std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( - &event)) { - auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted); - if (d_event->event && - std::visit( - [](auto e) { return (e.sender != utils::localUser().toStdString()); }, - *d_event->event)) { - handle_room_verification(*d_event->event); - } - } + if (auto txn_id = mtx::accessors::transaction_id(event); !txn_id.empty()) { + auto idx = cache::client()->getTimelineIndex(room_id_, mtx::accessors::event_id(event)); + if (idx) { + Index index{room_id_, *idx}; + events_.remove(index); + emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx)); + } } + + // decrypting and checking some encrypted messages + if (auto encrypted = + std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) { + auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted); + if (d_event->event && + std::visit([](auto e) { return (e.sender != utils::localUser().toStdString()); }, + *d_event->event)) { + handle_room_verification(*d_event->event); + } + } + } } namespace { template<class... Ts> struct overloaded : Ts... { - using Ts::operator()...; + using Ts::operator()...; }; template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; @@ -423,462 +403,451 @@ overloaded(Ts...) -> overloaded<Ts...>; void EventStore::handle_room_verification(mtx::events::collections::TimelineEvents event) { - std::visit( - overloaded{ - [this](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg) { - nhlog::db()->debug("handle_room_verification: Request"); - emit startDMVerification(msg); - }, - [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel> &msg) { - nhlog::db()->debug("handle_room_verification: Cancel"); - ChatPage::instance()->receivedDeviceVerificationCancel(msg.content); - }, - [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept> &msg) { - nhlog::db()->debug("handle_room_verification: Accept"); - ChatPage::instance()->receivedDeviceVerificationAccept(msg.content); - }, - [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey> &msg) { - nhlog::db()->debug("handle_room_verification: Key"); - ChatPage::instance()->receivedDeviceVerificationKey(msg.content); - }, - [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac> &msg) { - nhlog::db()->debug("handle_room_verification: Mac"); - ChatPage::instance()->receivedDeviceVerificationMac(msg.content); - }, - [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady> &msg) { - nhlog::db()->debug("handle_room_verification: Ready"); - ChatPage::instance()->receivedDeviceVerificationReady(msg.content); - }, - [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone> &msg) { - nhlog::db()->debug("handle_room_verification: Done"); - ChatPage::instance()->receivedDeviceVerificationDone(msg.content); - }, - [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart> &msg) { - nhlog::db()->debug("handle_room_verification: Start"); - ChatPage::instance()->receivedDeviceVerificationStart(msg.content, msg.sender); - }, - [](const auto &) {}, - }, - event); + std::visit( + overloaded{ + [this](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg) { + nhlog::db()->debug("handle_room_verification: Request"); + emit startDMVerification(msg); + }, + [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel> &msg) { + nhlog::db()->debug("handle_room_verification: Cancel"); + ChatPage::instance()->receivedDeviceVerificationCancel(msg.content); + }, + [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept> &msg) { + nhlog::db()->debug("handle_room_verification: Accept"); + ChatPage::instance()->receivedDeviceVerificationAccept(msg.content); + }, + [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey> &msg) { + nhlog::db()->debug("handle_room_verification: Key"); + ChatPage::instance()->receivedDeviceVerificationKey(msg.content); + }, + [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac> &msg) { + nhlog::db()->debug("handle_room_verification: Mac"); + ChatPage::instance()->receivedDeviceVerificationMac(msg.content); + }, + [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady> &msg) { + nhlog::db()->debug("handle_room_verification: Ready"); + ChatPage::instance()->receivedDeviceVerificationReady(msg.content); + }, + [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone> &msg) { + nhlog::db()->debug("handle_room_verification: Done"); + ChatPage::instance()->receivedDeviceVerificationDone(msg.content); + }, + [](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart> &msg) { + nhlog::db()->debug("handle_room_verification: Start"); + ChatPage::instance()->receivedDeviceVerificationStart(msg.content, msg.sender); + }, + [](const auto &) {}, + }, + event); } std::vector<mtx::events::collections::TimelineEvents> EventStore::edits(const std::string &event_id) { - auto event_ids = cache::client()->relatedEvents(room_id_, event_id); + auto event_ids = cache::client()->relatedEvents(room_id_, event_id); - auto original_event = get(event_id, "", false, false); - if (!original_event) - return {}; + auto original_event = get(event_id, "", false, false); + if (!original_event) + return {}; - auto original_sender = mtx::accessors::sender(*original_event); - auto original_relations = mtx::accessors::relations(*original_event); + auto original_sender = mtx::accessors::sender(*original_event); + auto original_relations = mtx::accessors::relations(*original_event); - std::vector<mtx::events::collections::TimelineEvents> edits; - for (const auto &id : event_ids) { - auto related_event = get(id, event_id, false, false); - if (!related_event) - continue; + std::vector<mtx::events::collections::TimelineEvents> edits; + for (const auto &id : event_ids) { + auto related_event = get(id, event_id, false, false); + if (!related_event) + continue; - auto related_ev = *related_event; + auto related_ev = *related_event; - auto edit_rel = mtx::accessors::relations(related_ev); - if (edit_rel.replaces() == event_id && - original_sender == mtx::accessors::sender(related_ev)) { - if (edit_rel.synthesized && original_relations.reply_to() && - !edit_rel.reply_to()) { - edit_rel.relations.push_back( - {mtx::common::RelationType::InReplyTo, - original_relations.reply_to().value()}); - mtx::accessors::set_relations(related_ev, std::move(edit_rel)); - } - edits.push_back(std::move(related_ev)); - } + auto edit_rel = mtx::accessors::relations(related_ev); + if (edit_rel.replaces() == event_id && + original_sender == mtx::accessors::sender(related_ev)) { + if (edit_rel.synthesized && original_relations.reply_to() && !edit_rel.reply_to()) { + edit_rel.relations.push_back( + {mtx::common::RelationType::InReplyTo, original_relations.reply_to().value()}); + mtx::accessors::set_relations(related_ev, std::move(edit_rel)); + } + edits.push_back(std::move(related_ev)); } + } - auto c = cache::client(); - std::sort(edits.begin(), - edits.end(), - [this, c](const mtx::events::collections::TimelineEvents &a, - const mtx::events::collections::TimelineEvents &b) { - return c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(a)) < - c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(b)); - }); + auto c = cache::client(); + std::sort(edits.begin(), + edits.end(), + [this, c](const mtx::events::collections::TimelineEvents &a, + const mtx::events::collections::TimelineEvents &b) { + return c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(a)) < + c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(b)); + }); - return edits; + return edits; } QVariantList EventStore::reactions(const std::string &event_id) { - auto event_ids = cache::client()->relatedEvents(room_id_, event_id); + auto event_ids = cache::client()->relatedEvents(room_id_, event_id); - struct TempReaction - { - int count = 0; - std::vector<std::string> users; - std::string reactedBySelf; - }; - std::map<std::string, TempReaction> aggregation; - std::vector<Reaction> reactions; + struct TempReaction + { + int count = 0; + std::vector<std::string> users; + std::string reactedBySelf; + }; + std::map<std::string, TempReaction> aggregation; + std::vector<Reaction> reactions; - auto self = http::client()->user_id().to_string(); - for (const auto &id : event_ids) { - auto related_event = get(id, event_id); - if (!related_event) - continue; + auto self = http::client()->user_id().to_string(); + for (const auto &id : event_ids) { + auto related_event = get(id, event_id); + if (!related_event) + continue; - if (auto reaction = std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>( - related_event); - reaction && reaction->content.relations.annotates() && - reaction->content.relations.annotates()->key) { - auto key = reaction->content.relations.annotates()->key.value(); - auto &agg = aggregation[key]; + if (auto reaction = + std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(related_event); + reaction && reaction->content.relations.annotates() && + reaction->content.relations.annotates()->key) { + auto key = reaction->content.relations.annotates()->key.value(); + auto &agg = aggregation[key]; - if (agg.count == 0) { - Reaction temp{}; - temp.key_ = QString::fromStdString(key); - reactions.push_back(temp); - } + if (agg.count == 0) { + Reaction temp{}; + temp.key_ = QString::fromStdString(key); + reactions.push_back(temp); + } - agg.count++; - agg.users.push_back(cache::displayName(room_id_, reaction->sender)); - if (reaction->sender == self) - agg.reactedBySelf = reaction->event_id; - } + agg.count++; + agg.users.push_back(cache::displayName(room_id_, reaction->sender)); + if (reaction->sender == self) + agg.reactedBySelf = reaction->event_id; } + } - QVariantList temp; - for (auto &reaction : reactions) { - const auto &agg = aggregation[reaction.key_.toStdString()]; - reaction.count_ = agg.count; - reaction.selfReactedEvent_ = QString::fromStdString(agg.reactedBySelf); + QVariantList temp; + for (auto &reaction : reactions) { + const auto &agg = aggregation[reaction.key_.toStdString()]; + reaction.count_ = agg.count; + reaction.selfReactedEvent_ = QString::fromStdString(agg.reactedBySelf); - bool firstReaction = true; - for (const auto &user : agg.users) { - if (firstReaction) - firstReaction = false; - else - reaction.users_ += ", "; - - reaction.users_ += QString::fromStdString(user); - } + bool firstReaction = true; + for (const auto &user : agg.users) { + if (firstReaction) + firstReaction = false; + else + reaction.users_ += ", "; - nhlog::db()->debug("key: {}, count: {}, users: {}", - reaction.key_.toStdString(), - reaction.count_, - reaction.users_.toStdString()); - temp.append(QVariant::fromValue(reaction)); + reaction.users_ += QString::fromStdString(user); } - return temp; + nhlog::db()->debug("key: {}, count: {}, users: {}", + reaction.key_.toStdString(), + reaction.count_, + reaction.users_.toStdString()); + temp.append(QVariant::fromValue(reaction)); + } + + return temp; } mtx::events::collections::TimelineEvents * EventStore::get(int idx, bool decrypt) { - if (this->thread() != QThread::currentThread()) - nhlog::db()->warn("{} called from a different thread!", __func__); + if (this->thread() != QThread::currentThread()) + nhlog::db()->warn("{} called from a different thread!", __func__); - Index index{room_id_, toInternalIdx(idx)}; - if (index.idx > last || index.idx < first) - return nullptr; + Index index{room_id_, toInternalIdx(idx)}; + if (index.idx > last || index.idx < first) + return nullptr; - auto event_ptr = events_.object(index); - if (!event_ptr) { - auto event_id = cache::client()->getTimelineEventId(room_id_, index.idx); - if (!event_id) - return nullptr; + auto event_ptr = events_.object(index); + if (!event_ptr) { + auto event_id = cache::client()->getTimelineEventId(room_id_, index.idx); + if (!event_id) + return nullptr; - std::optional<mtx::events::collections::TimelineEvent> event; - auto edits_ = edits(*event_id); - if (edits_.empty()) - event = cache::client()->getEvent(room_id_, *event_id); - else - event = {edits_.back()}; + std::optional<mtx::events::collections::TimelineEvent> event; + auto edits_ = edits(*event_id); + if (edits_.empty()) + event = cache::client()->getEvent(room_id_, *event_id); + else + event = {edits_.back()}; - if (!event) - return nullptr; - else - event_ptr = - new mtx::events::collections::TimelineEvents(std::move(event->data)); - events_.insert(index, event_ptr); - } + if (!event) + return nullptr; + else + event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data)); + events_.insert(index, event_ptr); + } - if (decrypt) { - if (auto encrypted = - std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( - event_ptr)) { - auto decrypted = decryptEvent({room_id_, encrypted->event_id}, *encrypted); - if (decrypted->event) - return &*decrypted->event; - } + if (decrypt) { + if (auto encrypted = + std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) { + auto decrypted = decryptEvent({room_id_, encrypted->event_id}, *encrypted); + if (decrypted->event) + return &*decrypted->event; } + } - return event_ptr; + return event_ptr; } std::optional<int> EventStore::idToIndex(std::string_view id) const { - if (this->thread() != QThread::currentThread()) - nhlog::db()->warn("{} called from a different thread!", __func__); + if (this->thread() != QThread::currentThread()) + nhlog::db()->warn("{} called from a different thread!", __func__); - auto idx = cache::client()->getTimelineIndex(room_id_, id); - if (idx) - return toExternalIdx(*idx); - else - return std::nullopt; + auto idx = cache::client()->getTimelineIndex(room_id_, id); + if (idx) + return toExternalIdx(*idx); + else + return std::nullopt; } std::optional<std::string> EventStore::indexToId(int idx) const { - if (this->thread() != QThread::currentThread()) - nhlog::db()->warn("{} called from a different thread!", __func__); + if (this->thread() != QThread::currentThread()) + nhlog::db()->warn("{} called from a different thread!", __func__); - return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx)); + return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx)); } olm::DecryptionResult * EventStore::decryptEvent(const IdIndex &idx, const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e) { - if (auto cachedEvent = decryptedEvents_.object(idx)) - return cachedEvent; + if (auto cachedEvent = decryptedEvents_.object(idx)) + return cachedEvent; - MegolmSessionIndex index(room_id_, e.content); + MegolmSessionIndex index(room_id_, e.content); - auto asCacheEntry = [&idx](olm::DecryptionResult &&event) { - auto event_ptr = new olm::DecryptionResult(std::move(event)); - decryptedEvents_.insert(idx, event_ptr); - return event_ptr; - }; + auto asCacheEntry = [&idx](olm::DecryptionResult &&event) { + auto event_ptr = new olm::DecryptionResult(std::move(event)); + decryptedEvents_.insert(idx, event_ptr); + return event_ptr; + }; - auto decryptionResult = olm::decryptEvent(index, e); + auto decryptionResult = olm::decryptEvent(index, e); - if (decryptionResult.error) { - switch (decryptionResult.error) { - case olm::DecryptionErrorCode::MissingSession: - case olm::DecryptionErrorCode::MissingSessionIndex: { - nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})", - index.room_id, - index.session_id, - e.sender); + if (decryptionResult.error) { + switch (decryptionResult.error) { + case olm::DecryptionErrorCode::MissingSession: + case olm::DecryptionErrorCode::MissingSessionIndex: { + nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})", + index.room_id, + index.session_id, + e.sender); - requestSession(e, false); - break; - } - case olm::DecryptionErrorCode::DbError: - nhlog::db()->critical( - "failed to retrieve megolm session with index ({}, {}, {})", - index.room_id, - index.session_id, - index.sender_key, - decryptionResult.error_message.value_or("")); - break; - case olm::DecryptionErrorCode::DecryptionFailed: - nhlog::crypto()->critical( - "failed to decrypt message with index ({}, {}, {}): {}", - index.room_id, - index.session_id, - index.sender_key, - decryptionResult.error_message.value_or("")); - break; - case olm::DecryptionErrorCode::ParsingFailed: - break; - case olm::DecryptionErrorCode::ReplayAttack: - nhlog::crypto()->critical( - "Reply attack while decryptiong event {} in room {} from {}!", - e.event_id, - room_id_, - index.sender_key); - break; - case olm::DecryptionErrorCode::NoError: - // unreachable - break; - } - return asCacheEntry(std::move(decryptionResult)); + requestSession(e, false); + break; } + case olm::DecryptionErrorCode::DbError: + nhlog::db()->critical("failed to retrieve megolm session with index ({}, {}, {})", + index.room_id, + index.session_id, + index.sender_key, + decryptionResult.error_message.value_or("")); + break; + case olm::DecryptionErrorCode::DecryptionFailed: + nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}", + index.room_id, + index.session_id, + index.sender_key, + decryptionResult.error_message.value_or("")); + break; + case olm::DecryptionErrorCode::ParsingFailed: + break; + case olm::DecryptionErrorCode::ReplayAttack: + nhlog::crypto()->critical("Reply attack while decryptiong event {} in room {} from {}!", + e.event_id, + room_id_, + index.sender_key); + break; + case olm::DecryptionErrorCode::NoError: + // unreachable + break; + } + return asCacheEntry(std::move(decryptionResult)); + } - auto encInfo = mtx::accessors::file(decryptionResult.event.value()); - if (encInfo) - emit newEncryptedImage(encInfo.value()); + auto encInfo = mtx::accessors::file(decryptionResult.event.value()); + if (encInfo) + emit newEncryptedImage(encInfo.value()); - return asCacheEntry(std::move(decryptionResult)); + return asCacheEntry(std::move(decryptionResult)); } void EventStore::requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev, bool manual) { - // we may not want to request keys during initial sync and such - if (suppressKeyRequests) - return; + // we may not want to request keys during initial sync and such + if (suppressKeyRequests) + return; - // TODO: Look in key backup - auto copy = ev; - copy.room_id = room_id_; - if (pending_key_requests.count(ev.content.session_id)) { - auto &r = pending_key_requests.at(ev.content.session_id); - r.events.push_back(copy); + // TODO: Look in key backup + auto copy = ev; + copy.room_id = room_id_; + if (pending_key_requests.count(ev.content.session_id)) { + auto &r = pending_key_requests.at(ev.content.session_id); + r.events.push_back(copy); - // automatically request once every 10 min, manually every 1 min - qint64 delay = manual ? 60 : (60 * 10); - if (r.requested_at + delay < QDateTime::currentSecsSinceEpoch()) { - r.requested_at = QDateTime::currentSecsSinceEpoch(); - olm::lookup_keybackup(room_id_, ev.content.session_id); - olm::send_key_request_for(copy, r.request_id); - } - } else { - PendingKeyRequests request; - request.request_id = "key_request." + http::client()->generate_txn_id(); - request.requested_at = QDateTime::currentSecsSinceEpoch(); - request.events.push_back(copy); - olm::lookup_keybackup(room_id_, ev.content.session_id); - olm::send_key_request_for(copy, request.request_id); - pending_key_requests[ev.content.session_id] = request; + // automatically request once every 10 min, manually every 1 min + qint64 delay = manual ? 60 : (60 * 10); + if (r.requested_at + delay < QDateTime::currentSecsSinceEpoch()) { + r.requested_at = QDateTime::currentSecsSinceEpoch(); + olm::lookup_keybackup(room_id_, ev.content.session_id); + olm::send_key_request_for(copy, r.request_id); } + } else { + PendingKeyRequests request; + request.request_id = "key_request." + http::client()->generate_txn_id(); + request.requested_at = QDateTime::currentSecsSinceEpoch(); + request.events.push_back(copy); + olm::lookup_keybackup(room_id_, ev.content.session_id); + olm::send_key_request_for(copy, request.request_id); + pending_key_requests[ev.content.session_id] = request; + } } void EventStore::enableKeyRequests(bool suppressKeyRequests_) { - if (!suppressKeyRequests_) { - for (const auto &key : decryptedEvents_.keys()) - if (key.room == this->room_id_) - decryptedEvents_.remove(key); - suppressKeyRequests = false; - } else - suppressKeyRequests = true; + if (!suppressKeyRequests_) { + for (const auto &key : decryptedEvents_.keys()) + if (key.room == this->room_id_) + decryptedEvents_.remove(key); + suppressKeyRequests = false; + } else + suppressKeyRequests = true; } mtx::events::collections::TimelineEvents * EventStore::get(std::string id, std::string_view related_to, bool decrypt, bool resolve_edits) { - if (this->thread() != QThread::currentThread()) - nhlog::db()->warn("{} called from a different thread!", __func__); + if (this->thread() != QThread::currentThread()) + nhlog::db()->warn("{} called from a different thread!", __func__); - if (id.empty()) - return nullptr; + if (id.empty()) + return nullptr; - IdIndex index{room_id_, std::move(id)}; - if (resolve_edits) { - auto edits_ = edits(index.id); - if (!edits_.empty()) { - index.id = mtx::accessors::event_id(edits_.back()); - auto event_ptr = - new mtx::events::collections::TimelineEvents(std::move(edits_.back())); - events_by_id_.insert(index, event_ptr); - } + IdIndex index{room_id_, std::move(id)}; + if (resolve_edits) { + auto edits_ = edits(index.id); + if (!edits_.empty()) { + index.id = mtx::accessors::event_id(edits_.back()); + auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(edits_.back())); + events_by_id_.insert(index, event_ptr); } + } - auto event_ptr = events_by_id_.object(index); - if (!event_ptr) { - auto event = cache::client()->getEvent(room_id_, index.id); - if (!event) { - http::client()->get_event( - room_id_, - index.id, - [this, relatedTo = std::string(related_to), id = index.id]( - const mtx::events::collections::TimelineEvents &timeline, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->error( - "Failed to retrieve event with id {}, which was " - "requested to show the replyTo for event {}", - relatedTo, - id); - return; - } - emit eventFetched(id, relatedTo, timeline); - }); - return nullptr; - } - event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data)); - events_by_id_.insert(index, event_ptr); + auto event_ptr = events_by_id_.object(index); + if (!event_ptr) { + auto event = cache::client()->getEvent(room_id_, index.id); + if (!event) { + http::client()->get_event(room_id_, + index.id, + [this, relatedTo = std::string(related_to), id = index.id]( + const mtx::events::collections::TimelineEvents &timeline, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error( + "Failed to retrieve event with id {}, which was " + "requested to show the replyTo for event {}", + relatedTo, + id); + return; + } + emit eventFetched(id, relatedTo, timeline); + }); + return nullptr; } + event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data)); + events_by_id_.insert(index, event_ptr); + } - if (decrypt) { - if (auto encrypted = - std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( - event_ptr)) { - auto decrypted = decryptEvent(index, *encrypted); - if (decrypted->event) - return &*decrypted->event; - } + if (decrypt) { + if (auto encrypted = + std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) { + auto decrypted = decryptEvent(index, *encrypted); + if (decrypted->event) + return &*decrypted->event; } + } - return event_ptr; + return event_ptr; } olm::DecryptionErrorCode EventStore::decryptionError(std::string id) { - if (this->thread() != QThread::currentThread()) - nhlog::db()->warn("{} called from a different thread!", __func__); + if (this->thread() != QThread::currentThread()) + nhlog::db()->warn("{} called from a different thread!", __func__); - if (id.empty()) - return olm::DecryptionErrorCode::NoError; + if (id.empty()) + return olm::DecryptionErrorCode::NoError; - IdIndex index{room_id_, std::move(id)}; - auto edits_ = edits(index.id); - if (!edits_.empty()) { - index.id = mtx::accessors::event_id(edits_.back()); - auto event_ptr = - new mtx::events::collections::TimelineEvents(std::move(edits_.back())); - events_by_id_.insert(index, event_ptr); - } + IdIndex index{room_id_, std::move(id)}; + auto edits_ = edits(index.id); + if (!edits_.empty()) { + index.id = mtx::accessors::event_id(edits_.back()); + auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(edits_.back())); + events_by_id_.insert(index, event_ptr); + } - auto event_ptr = events_by_id_.object(index); - if (!event_ptr) { - auto event = cache::client()->getEvent(room_id_, index.id); - if (!event) { - return olm::DecryptionErrorCode::NoError; - } - event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data)); - events_by_id_.insert(index, event_ptr); + auto event_ptr = events_by_id_.object(index); + if (!event_ptr) { + auto event = cache::client()->getEvent(room_id_, index.id); + if (!event) { + return olm::DecryptionErrorCode::NoError; } + event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data)); + events_by_id_.insert(index, event_ptr); + } - if (auto encrypted = - std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) { - auto decrypted = decryptEvent(index, *encrypted); - return decrypted->error; - } + if (auto encrypted = + std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(event_ptr)) { + auto decrypted = decryptEvent(index, *encrypted); + return decrypted->error; + } - return olm::DecryptionErrorCode::NoError; + return olm::DecryptionErrorCode::NoError; } void EventStore::fetchMore() { - if (noMoreMessages) - return; + if (noMoreMessages) + return; - mtx::http::MessagesOpts opts; - opts.room_id = room_id_; - opts.from = cache::client()->previousBatchToken(room_id_); + mtx::http::MessagesOpts opts; + opts.room_id = room_id_; + opts.from = cache::client()->previousBatchToken(room_id_); - nhlog::ui()->debug("Paginating room {}, token {}", opts.room_id, opts.from); + nhlog::ui()->debug("Paginating room {}, token {}", opts.room_id, opts.from); - http::client()->messages( - opts, [this, opts](const mtx::responses::Messages &res, mtx::http::RequestErr err) { - if (cache::client()->previousBatchToken(room_id_) != opts.from) { - nhlog::net()->warn("Cache cleared while fetching more messages, dropping " - "/messages response"); - if (!opts.to.empty()) - emit fetchedMore(); - return; - } - if (err) { - nhlog::net()->error("failed to call /messages ({}): {} - {} - {}", - opts.room_id, - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error, - err->parse_error); - emit fetchedMore(); - return; - } + http::client()->messages( + opts, [this, opts](const mtx::responses::Messages &res, mtx::http::RequestErr err) { + if (cache::client()->previousBatchToken(room_id_) != opts.from) { + nhlog::net()->warn("Cache cleared while fetching more messages, dropping " + "/messages response"); + if (!opts.to.empty()) + emit fetchedMore(); + return; + } + if (err) { + nhlog::net()->error("failed to call /messages ({}): {} - {} - {}", + opts.room_id, + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error, + err->parse_error); + emit fetchedMore(); + return; + } - emit oldMessagesRetrieved(std::move(res)); - }); + emit oldMessagesRetrieved(std::move(res)); + }); } diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
index 59c1c7c0..53dbaff4 100644 --- a/src/timeline/EventStore.h +++ b/src/timeline/EventStore.h
@@ -20,133 +20,131 @@ class EventStore : public QObject { - Q_OBJECT + Q_OBJECT public: - EventStore(std::string room_id, QObject *parent); + EventStore(std::string room_id, QObject *parent); - // taken from QtPrivate::QHashCombine - static uint hashCombine(uint hash, uint seed) - { - return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2)); - }; - struct Index - { - std::string room; - uint64_t idx; + // taken from QtPrivate::QHashCombine + static uint hashCombine(uint hash, uint seed) + { + return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2)); + }; + struct Index + { + std::string room; + uint64_t idx; - friend uint qHash(const Index &i, uint seed = 0) noexcept - { - seed = - hashCombine(qHashBits(i.room.data(), (int)i.room.size(), seed), seed); - seed = hashCombine(qHash(i.idx, seed), seed); - return seed; - } + friend uint qHash(const Index &i, uint seed = 0) noexcept + { + seed = hashCombine(qHashBits(i.room.data(), (int)i.room.size(), seed), seed); + seed = hashCombine(qHash(i.idx, seed), seed); + return seed; + } - friend bool operator==(const Index &a, const Index &b) noexcept - { - return a.idx == b.idx && a.room == b.room; - } - }; - struct IdIndex + friend bool operator==(const Index &a, const Index &b) noexcept { - std::string room, id; + return a.idx == b.idx && a.room == b.room; + } + }; + struct IdIndex + { + std::string room, id; - friend uint qHash(const IdIndex &i, uint seed = 0) noexcept - { - seed = - hashCombine(qHashBits(i.room.data(), (int)i.room.size(), seed), seed); - seed = hashCombine(qHashBits(i.id.data(), (int)i.id.size(), seed), seed); - return seed; - } + friend uint qHash(const IdIndex &i, uint seed = 0) noexcept + { + seed = hashCombine(qHashBits(i.room.data(), (int)i.room.size(), seed), seed); + seed = hashCombine(qHashBits(i.id.data(), (int)i.id.size(), seed), seed); + return seed; + } - friend bool operator==(const IdIndex &a, const IdIndex &b) noexcept - { - return a.id == b.id && a.room == b.room; - } - }; + friend bool operator==(const IdIndex &a, const IdIndex &b) noexcept + { + return a.id == b.id && a.room == b.room; + } + }; - void fetchMore(); - void handleSync(const mtx::responses::Timeline &events); + void fetchMore(); + void handleSync(const mtx::responses::Timeline &events); - // optionally returns the event or nullptr and fetches it, after which it emits a - // relatedFetched event - mtx::events::collections::TimelineEvents *get(std::string id, - std::string_view related_to, - bool decrypt = true, - bool resolve_edits = true); - // always returns a proper event as long as the idx is valid - mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true); + // optionally returns the event or nullptr and fetches it, after which it emits a + // relatedFetched event + mtx::events::collections::TimelineEvents *get(std::string id, + std::string_view related_to, + bool decrypt = true, + bool resolve_edits = true); + // always returns a proper event as long as the idx is valid + mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true); - QVariantList reactions(const std::string &event_id); - olm::DecryptionErrorCode decryptionError(std::string id); - void requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev, - bool manual); + QVariantList reactions(const std::string &event_id); + olm::DecryptionErrorCode decryptionError(std::string id); + void requestSession(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &ev, + bool manual); - int size() const - { - return (last != std::numeric_limits<uint64_t>::max() && last >= first) - ? static_cast<int>(last - first) + 1 - : 0; - } - int toExternalIdx(uint64_t idx) const { return static_cast<int>(idx - first); } - uint64_t toInternalIdx(int idx) const { return first + idx; } + int size() const + { + return (last != std::numeric_limits<uint64_t>::max() && last >= first) + ? static_cast<int>(last - first) + 1 + : 0; + } + int toExternalIdx(uint64_t idx) const { return static_cast<int>(idx - first); } + uint64_t toInternalIdx(int idx) const { return first + idx; } - std::optional<int> idToIndex(std::string_view id) const; - std::optional<std::string> indexToId(int idx) const; + std::optional<int> idToIndex(std::string_view id) const; + std::optional<std::string> indexToId(int idx) const; signals: - void beginInsertRows(int from, int to); - void endInsertRows(); - void beginResetModel(); - void endResetModel(); - void dataChanged(int from, int to); - void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo); - void eventFetched(std::string id, - std::string relatedTo, - mtx::events::collections::TimelineEvents timeline); - void oldMessagesRetrieved(const mtx::responses::Messages &); - void fetchedMore(); + void beginInsertRows(int from, int to); + void endInsertRows(); + void beginResetModel(); + void endResetModel(); + void dataChanged(int from, int to); + void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo); + void eventFetched(std::string id, + std::string relatedTo, + mtx::events::collections::TimelineEvents timeline); + void oldMessagesRetrieved(const mtx::responses::Messages &); + void fetchedMore(); - void processPending(); - void messageSent(std::string txn_id, std::string event_id); - void messageFailed(std::string txn_id); - void startDMVerification( - const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg); - void updateFlowEventId(std::string event_id); + void processPending(); + void messageSent(std::string txn_id, std::string event_id); + void messageFailed(std::string txn_id); + void startDMVerification( + const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg); + void updateFlowEventId(std::string event_id); public slots: - void addPending(mtx::events::collections::TimelineEvents event); - void receivedSessionKey(const std::string &session_id); - void clearTimeline(); - void enableKeyRequests(bool suppressKeyRequests_); + void addPending(mtx::events::collections::TimelineEvents event); + void receivedSessionKey(const std::string &session_id); + void clearTimeline(); + void enableKeyRequests(bool suppressKeyRequests_); private: - std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id); - olm::DecryptionResult *decryptEvent( - const IdIndex &idx, - const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e); - void handle_room_verification(mtx::events::collections::TimelineEvents event); + std::vector<mtx::events::collections::TimelineEvents> edits(const std::string &event_id); + olm::DecryptionResult *decryptEvent( + const IdIndex &idx, + const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e); + void handle_room_verification(mtx::events::collections::TimelineEvents event); - std::string room_id_; + std::string room_id_; - uint64_t first = std::numeric_limits<uint64_t>::max(), - last = std::numeric_limits<uint64_t>::max(); + uint64_t first = std::numeric_limits<uint64_t>::max(), + last = std::numeric_limits<uint64_t>::max(); - static QCache<IdIndex, olm::DecryptionResult> decryptedEvents_; - static QCache<Index, mtx::events::collections::TimelineEvents> events_; - static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_; + static QCache<IdIndex, olm::DecryptionResult> decryptedEvents_; + static QCache<Index, mtx::events::collections::TimelineEvents> events_; + static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_; - struct PendingKeyRequests - { - std::string request_id; - std::vector<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>> events; - qint64 requested_at; - }; - std::map<std::string, PendingKeyRequests> pending_key_requests; + struct PendingKeyRequests + { + std::string request_id; + std::vector<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>> events; + qint64 requested_at; + }; + std::map<std::string, PendingKeyRequests> pending_key_requests; - std::string current_txn; - int current_txn_error_count = 0; - bool noMoreMessages = false; - bool suppressKeyRequests = true; + std::string current_txn; + int current_txn_error_count = 0; + bool noMoreMessages = false; + bool suppressKeyRequests = true; }; diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index a6fbab78..f0c38c84 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp
@@ -43,374 +43,370 @@ static constexpr size_t INPUT_HISTORY_SIZE = 10; void InputBar::paste(bool fromMouse) { - const QMimeData *md = nullptr; + const QMimeData *md = nullptr; - if (fromMouse) { - if (QGuiApplication::clipboard()->supportsSelection()) { - md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection); - } - } else { - md = QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard); + if (fromMouse) { + if (QGuiApplication::clipboard()->supportsSelection()) { + md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection); } + } else { + md = QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard); + } - if (md) - insertMimeData(md); + if (md) + insertMimeData(md); } void InputBar::insertMimeData(const QMimeData *md) { - if (!md) - return; + if (!md) + return; - nhlog::ui()->debug("Got mime formats: {}", md->formats().join(", ").toStdString()); - const auto formats = md->formats().filter("/"); - const auto image = formats.filter("image/", Qt::CaseInsensitive); - const auto audio = formats.filter("audio/", Qt::CaseInsensitive); - const auto video = formats.filter("video/", Qt::CaseInsensitive); + nhlog::ui()->debug("Got mime formats: {}", md->formats().join(", ").toStdString()); + const auto formats = md->formats().filter("/"); + const auto image = formats.filter("image/", Qt::CaseInsensitive); + const auto audio = formats.filter("audio/", Qt::CaseInsensitive); + const auto video = formats.filter("video/", Qt::CaseInsensitive); - if (!image.empty() && md->hasImage()) { - showPreview(*md, "", image); - } else if (!audio.empty()) { - showPreview(*md, "", audio); - } else if (!video.empty()) { - showPreview(*md, "", video); - } else if (md->hasUrls()) { - // Generic file path for any platform. - QString path; - for (auto &&u : md->urls()) { - if (u.isLocalFile()) { - path = u.toLocalFile(); - break; - } - } + if (!image.empty() && md->hasImage()) { + showPreview(*md, "", image); + } else if (!audio.empty()) { + showPreview(*md, "", audio); + } else if (!video.empty()) { + showPreview(*md, "", video); + } else if (md->hasUrls()) { + // Generic file path for any platform. + QString path; + for (auto &&u : md->urls()) { + if (u.isLocalFile()) { + path = u.toLocalFile(); + break; + } + } - if (!path.isEmpty() && QFileInfo{path}.exists()) { - showPreview(*md, path, formats); - } else { - nhlog::ui()->warn("Clipboard does not contain any valid file paths."); - } - } else if (md->hasFormat("x-special/gnome-copied-files")) { - // Special case for X11 users. See "Notes for X11 Users" in md. - // Source: http://doc.qt.io/qt-5/qclipboard.html + if (!path.isEmpty() && QFileInfo{path}.exists()) { + showPreview(*md, path, formats); + } else { + nhlog::ui()->warn("Clipboard does not contain any valid file paths."); + } + } else if (md->hasFormat("x-special/gnome-copied-files")) { + // Special case for X11 users. See "Notes for X11 Users" in md. + // Source: http://doc.qt.io/qt-5/qclipboard.html - // This MIME type returns a string with multiple lines separated by '\n'. The first - // line is the command to perform with the clipboard (not useful to us). The - // following lines are the file URIs. - // - // Source: the nautilus source code in file 'src/nautilus-clipboard.c' in function - // nautilus_clipboard_get_uri_list_from_selection_data() - // https://github.com/GNOME/nautilus/blob/master/src/nautilus-clipboard.c + // This MIME type returns a string with multiple lines separated by '\n'. The first + // line is the command to perform with the clipboard (not useful to us). The + // following lines are the file URIs. + // + // Source: the nautilus source code in file 'src/nautilus-clipboard.c' in function + // nautilus_clipboard_get_uri_list_from_selection_data() + // https://github.com/GNOME/nautilus/blob/master/src/nautilus-clipboard.c - auto data = md->data("x-special/gnome-copied-files").split('\n'); - if (data.size() < 2) { - nhlog::ui()->warn("MIME format is malformed, cannot perform paste."); - return; - } + auto data = md->data("x-special/gnome-copied-files").split('\n'); + if (data.size() < 2) { + nhlog::ui()->warn("MIME format is malformed, cannot perform paste."); + return; + } - QString path; - for (int i = 1; i < data.size(); ++i) { - QUrl url{data[i]}; - if (url.isLocalFile()) { - path = url.toLocalFile(); - break; - } - } + QString path; + for (int i = 1; i < data.size(); ++i) { + QUrl url{data[i]}; + if (url.isLocalFile()) { + path = url.toLocalFile(); + break; + } + } - if (!path.isEmpty()) { - showPreview(*md, path, formats); - } else { - nhlog::ui()->warn("Clipboard does not contain any valid file paths: {}", - data.join(", ").toStdString()); - } - } else if (md->hasText()) { - emit insertText(md->text()); + if (!path.isEmpty()) { + showPreview(*md, path, formats); } else { - nhlog::ui()->debug("formats: {}", md->formats().join(", ").toStdString()); + nhlog::ui()->warn("Clipboard does not contain any valid file paths: {}", + data.join(", ").toStdString()); } + } else if (md->hasText()) { + emit insertText(md->text()); + } else { + nhlog::ui()->debug("formats: {}", md->formats().join(", ").toStdString()); + } } void InputBar::updateAtRoom(const QString &t) { - bool roomMention = false; + bool roomMention = false; - if (t.size() > 4) { - QTextBoundaryFinder finder(QTextBoundaryFinder::BoundaryType::Word, t); + if (t.size() > 4) { + QTextBoundaryFinder finder(QTextBoundaryFinder::BoundaryType::Word, t); - finder.toStart(); - do { - auto start = finder.position(); - finder.toNextBoundary(); - auto end = finder.position(); - if (start > 0 && end - start >= 4 && - t.midRef(start, end - start) == "room" && - t.at(start - 1) == QChar('@')) { - roomMention = true; - break; - } - } while (finder.position() < t.size()); - } + finder.toStart(); + do { + auto start = finder.position(); + finder.toNextBoundary(); + auto end = finder.position(); + if (start > 0 && end - start >= 4 && t.midRef(start, end - start) == "room" && + t.at(start - 1) == QChar('@')) { + roomMention = true; + break; + } + } while (finder.position() < t.size()); + } - if (roomMention != this->containsAtRoom_) { - this->containsAtRoom_ = roomMention; - emit containsAtRoomChanged(); - } + if (roomMention != this->containsAtRoom_) { + this->containsAtRoom_ = roomMention; + emit containsAtRoomChanged(); + } } void InputBar::setText(QString newText) { - if (history_.empty()) - history_.push_front(newText); - else - history_.front() = newText; - history_index_ = 0; + if (history_.empty()) + history_.push_front(newText); + else + history_.front() = newText; + history_index_ = 0; - if (history_.size() == INPUT_HISTORY_SIZE) - history_.pop_back(); + if (history_.size() == INPUT_HISTORY_SIZE) + history_.pop_back(); - emit textChanged(newText); + emit textChanged(newText); } void InputBar::updateState(int selectionStart_, int selectionEnd_, int cursorPosition_, QString text_) { - if (text_.isEmpty()) - stopTyping(); - else - startTyping(); + if (text_.isEmpty()) + stopTyping(); + else + startTyping(); - if (text_ != text()) { - if (history_.empty()) - history_.push_front(text_); - else - history_.front() = text_; - history_index_ = 0; + if (text_ != text()) { + if (history_.empty()) + history_.push_front(text_); + else + history_.front() = text_; + history_index_ = 0; - updateAtRoom(text_); - } + updateAtRoom(text_); + } - selectionStart = selectionStart_; - selectionEnd = selectionEnd_; - cursorPosition = cursorPosition_; + selectionStart = selectionStart_; + selectionEnd = selectionEnd_; + cursorPosition = cursorPosition_; } QString InputBar::text() const { - if (history_index_ < history_.size()) - return history_.at(history_index_); + if (history_index_ < history_.size()) + return history_.at(history_index_); - return ""; + return ""; } QString InputBar::previousText() { - history_index_++; - if (history_index_ >= INPUT_HISTORY_SIZE) - history_index_ = INPUT_HISTORY_SIZE; - else if (text().isEmpty()) - history_index_--; + history_index_++; + if (history_index_ >= INPUT_HISTORY_SIZE) + history_index_ = INPUT_HISTORY_SIZE; + else if (text().isEmpty()) + history_index_--; - updateAtRoom(text()); - return text(); + updateAtRoom(text()); + return text(); } QString InputBar::nextText() { - history_index_--; - if (history_index_ >= INPUT_HISTORY_SIZE) - history_index_ = 0; + history_index_--; + if (history_index_ >= INPUT_HISTORY_SIZE) + history_index_ = 0; - updateAtRoom(text()); - return text(); + updateAtRoom(text()); + return text(); } void InputBar::send() { - if (text().trimmed().isEmpty()) - return; + if (text().trimmed().isEmpty()) + return; - nhlog::ui()->debug("Send: {}", text().toStdString()); + nhlog::ui()->debug("Send: {}", text().toStdString()); - auto wasEdit = !room->edit().isEmpty(); + auto wasEdit = !room->edit().isEmpty(); - if (text().startsWith('/')) { - int command_end = text().indexOf(QRegularExpression("\\s")); - if (command_end == -1) - command_end = text().size(); - auto name = text().mid(1, command_end - 1); - auto args = text().mid(command_end + 1); - if (name.isEmpty() || name == "/") { - message(args); - } else { - command(name, args); - } + if (text().startsWith('/')) { + int command_end = text().indexOf(QRegularExpression("\\s")); + if (command_end == -1) + command_end = text().size(); + auto name = text().mid(1, command_end - 1); + auto args = text().mid(command_end + 1); + if (name.isEmpty() || name == "/") { + message(args); } else { - message(text()); + command(name, args); } + } else { + message(text()); + } - if (!wasEdit) { - history_.push_front(""); - setText(""); - } + if (!wasEdit) { + history_.push_front(""); + setText(""); + } } void InputBar::openFileSelection() { - const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - const auto fileName = QFileDialog::getOpenFileName( - ChatPage::instance(), tr("Select a file"), homeFolder, tr("All Files (*)")); + const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + const auto fileName = QFileDialog::getOpenFileName( + ChatPage::instance(), tr("Select a file"), homeFolder, tr("All Files (*)")); - if (fileName.isEmpty()) - return; + if (fileName.isEmpty()) + return; - QMimeDatabase db; - QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent); + QMimeDatabase db; + QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent); - QFile file{fileName}; + QFile file{fileName}; - if (!file.open(QIODevice::ReadOnly)) { - emit ChatPage::instance()->showNotification( - QString("Error while reading media: %1").arg(file.errorString())); - return; - } + if (!file.open(QIODevice::ReadOnly)) { + emit ChatPage::instance()->showNotification( + QString("Error while reading media: %1").arg(file.errorString())); + return; + } - setUploading(true); + setUploading(true); - auto bin = file.readAll(); + auto bin = file.readAll(); - QMimeData data; - data.setData(mime.name(), bin); + QMimeData data; + data.setData(mime.name(), bin); - showPreview(data, fileName, QStringList{mime.name()}); + showPreview(data, fileName, QStringList{mime.name()}); } void InputBar::message(QString msg, MarkdownOverride useMarkdown, bool rainbowify) { - mtx::events::msg::Text text = {}; - text.body = msg.trimmed().toStdString(); + mtx::events::msg::Text text = {}; + text.body = msg.trimmed().toStdString(); - if ((ChatPage::instance()->userSettings()->markdown() && - useMarkdown == MarkdownOverride::NOT_SPECIFIED) || - useMarkdown == MarkdownOverride::ON) { - text.formatted_body = utils::markdownToHtml(msg, rainbowify).toStdString(); - // Remove markdown links by completer - text.body = - msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString(); + if ((ChatPage::instance()->userSettings()->markdown() && + useMarkdown == MarkdownOverride::NOT_SPECIFIED) || + useMarkdown == MarkdownOverride::ON) { + text.formatted_body = utils::markdownToHtml(msg, rainbowify).toStdString(); + // Remove markdown links by completer + text.body = msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString(); - // Don't send formatted_body, when we don't need to - if (text.formatted_body.find("<") == std::string::npos) - text.formatted_body = ""; - else - text.format = "org.matrix.custom.html"; - } + // Don't send formatted_body, when we don't need to + if (text.formatted_body.find("<") == std::string::npos) + text.formatted_body = ""; + else + text.format = "org.matrix.custom.html"; + } - if (!room->edit().isEmpty()) { - if (!room->reply().isEmpty()) { - text.relations.relations.push_back( - {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - } + if (!room->edit().isEmpty()) { + if (!room->reply().isEmpty()) { + text.relations.relations.push_back( + {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); + } - text.relations.relations.push_back( - {mtx::common::RelationType::Replace, room->edit().toStdString()}); + text.relations.relations.push_back( + {mtx::common::RelationType::Replace, room->edit().toStdString()}); - } else if (!room->reply().isEmpty()) { - auto related = room->relatedInfo(room->reply()); + } else if (!room->reply().isEmpty()) { + auto related = room->relatedInfo(room->reply()); - QString body; - bool firstLine = true; - for (const auto &line : related.quoted_body.split("\n")) { - if (firstLine) { - firstLine = false; - body = QString("> <%1> %2\n").arg(related.quoted_user).arg(line); - } else { - body += QString("> %1\n").arg(line); - } - } + QString body; + bool firstLine = true; + for (const auto &line : related.quoted_body.split("\n")) { + if (firstLine) { + firstLine = false; + body = QString("> <%1> %2\n").arg(related.quoted_user).arg(line); + } else { + body += QString("> %1\n").arg(line); + } + } - text.body = QString("%1\n%2").arg(body).arg(msg).toStdString(); + text.body = QString("%1\n%2").arg(body).arg(msg).toStdString(); - // NOTE(Nico): rich replies always need a formatted_body! - text.format = "org.matrix.custom.html"; - if ((ChatPage::instance()->userSettings()->markdown() && - useMarkdown == MarkdownOverride::NOT_SPECIFIED) || - useMarkdown == MarkdownOverride::ON) - text.formatted_body = utils::getFormattedQuoteBody( - related, utils::markdownToHtml(msg, rainbowify)) - .toStdString(); - else - text.formatted_body = - utils::getFormattedQuoteBody(related, msg.toHtmlEscaped()).toStdString(); + // NOTE(Nico): rich replies always need a formatted_body! + text.format = "org.matrix.custom.html"; + if ((ChatPage::instance()->userSettings()->markdown() && + useMarkdown == MarkdownOverride::NOT_SPECIFIED) || + useMarkdown == MarkdownOverride::ON) + text.formatted_body = + utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg, rainbowify)) + .toStdString(); + else + text.formatted_body = + utils::getFormattedQuoteBody(related, msg.toHtmlEscaped()).toStdString(); - text.relations.relations.push_back( - {mtx::common::RelationType::InReplyTo, related.related_event}); - } + text.relations.relations.push_back( + {mtx::common::RelationType::InReplyTo, related.related_event}); + } - room->sendMessageEvent(text, mtx::events::EventType::RoomMessage); + room->sendMessageEvent(text, mtx::events::EventType::RoomMessage); } void InputBar::emote(QString msg, bool rainbowify) { - auto html = utils::markdownToHtml(msg, rainbowify); + auto html = utils::markdownToHtml(msg, rainbowify); - mtx::events::msg::Emote emote; - emote.body = msg.trimmed().toStdString(); + mtx::events::msg::Emote emote; + emote.body = msg.trimmed().toStdString(); - if (html != msg.trimmed().toHtmlEscaped() && - ChatPage::instance()->userSettings()->markdown()) { - emote.formatted_body = html.toStdString(); - emote.format = "org.matrix.custom.html"; - // Remove markdown links by completer - emote.body = - msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString(); - } + if (html != msg.trimmed().toHtmlEscaped() && ChatPage::instance()->userSettings()->markdown()) { + emote.formatted_body = html.toStdString(); + emote.format = "org.matrix.custom.html"; + // Remove markdown links by completer + emote.body = + msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString(); + } - if (!room->reply().isEmpty()) { - emote.relations.relations.push_back( - {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - } - if (!room->edit().isEmpty()) { - emote.relations.relations.push_back( - {mtx::common::RelationType::Replace, room->edit().toStdString()}); - } + if (!room->reply().isEmpty()) { + emote.relations.relations.push_back( + {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); + } + if (!room->edit().isEmpty()) { + emote.relations.relations.push_back( + {mtx::common::RelationType::Replace, room->edit().toStdString()}); + } - room->sendMessageEvent(emote, mtx::events::EventType::RoomMessage); + room->sendMessageEvent(emote, mtx::events::EventType::RoomMessage); } void InputBar::notice(QString msg, bool rainbowify) { - auto html = utils::markdownToHtml(msg, rainbowify); + auto html = utils::markdownToHtml(msg, rainbowify); - mtx::events::msg::Notice notice; - notice.body = msg.trimmed().toStdString(); + mtx::events::msg::Notice notice; + notice.body = msg.trimmed().toStdString(); - if (html != msg.trimmed().toHtmlEscaped() && - ChatPage::instance()->userSettings()->markdown()) { - notice.formatted_body = html.toStdString(); - notice.format = "org.matrix.custom.html"; - // Remove markdown links by completer - notice.body = - msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString(); - } + if (html != msg.trimmed().toHtmlEscaped() && ChatPage::instance()->userSettings()->markdown()) { + notice.formatted_body = html.toStdString(); + notice.format = "org.matrix.custom.html"; + // Remove markdown links by completer + notice.body = + msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString(); + } - if (!room->reply().isEmpty()) { - notice.relations.relations.push_back( - {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - } - if (!room->edit().isEmpty()) { - notice.relations.relations.push_back( - {mtx::common::RelationType::Replace, room->edit().toStdString()}); - } + if (!room->reply().isEmpty()) { + notice.relations.relations.push_back( + {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); + } + if (!room->edit().isEmpty()) { + notice.relations.relations.push_back( + {mtx::common::RelationType::Replace, room->edit().toStdString()}); + } - room->sendMessageEvent(notice, mtx::events::EventType::RoomMessage); + room->sendMessageEvent(notice, mtx::events::EventType::RoomMessage); } void @@ -422,29 +418,29 @@ InputBar::image(const QString &filename, const QSize &dimensions, const QString &blurhash) { - mtx::events::msg::Image image; - image.info.mimetype = mime.toStdString(); - image.info.size = dsize; - image.info.blurhash = blurhash.toStdString(); - image.body = filename.toStdString(); - image.info.h = dimensions.height(); - image.info.w = dimensions.width(); + mtx::events::msg::Image image; + image.info.mimetype = mime.toStdString(); + image.info.size = dsize; + image.info.blurhash = blurhash.toStdString(); + image.body = filename.toStdString(); + image.info.h = dimensions.height(); + image.info.w = dimensions.width(); - if (file) - image.file = file; - else - image.url = url.toStdString(); + if (file) + image.file = file; + else + image.url = url.toStdString(); - if (!room->reply().isEmpty()) { - image.relations.relations.push_back( - {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - } - if (!room->edit().isEmpty()) { - image.relations.relations.push_back( - {mtx::common::RelationType::Replace, room->edit().toStdString()}); - } + if (!room->reply().isEmpty()) { + image.relations.relations.push_back( + {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); + } + if (!room->edit().isEmpty()) { + image.relations.relations.push_back( + {mtx::common::RelationType::Replace, room->edit().toStdString()}); + } - room->sendMessageEvent(image, mtx::events::EventType::RoomMessage); + room->sendMessageEvent(image, mtx::events::EventType::RoomMessage); } void @@ -454,26 +450,26 @@ InputBar::file(const QString &filename, const QString &mime, uint64_t dsize) { - mtx::events::msg::File file; - file.info.mimetype = mime.toStdString(); - file.info.size = dsize; - file.body = filename.toStdString(); + mtx::events::msg::File file; + file.info.mimetype = mime.toStdString(); + file.info.size = dsize; + file.body = filename.toStdString(); - if (encryptedFile) - file.file = encryptedFile; - else - file.url = url.toStdString(); + if (encryptedFile) + file.file = encryptedFile; + else + file.url = url.toStdString(); - if (!room->reply().isEmpty()) { - file.relations.relations.push_back( - {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - } - if (!room->edit().isEmpty()) { - file.relations.relations.push_back( - {mtx::common::RelationType::Replace, room->edit().toStdString()}); - } + if (!room->reply().isEmpty()) { + file.relations.relations.push_back( + {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); + } + if (!room->edit().isEmpty()) { + file.relations.relations.push_back( + {mtx::common::RelationType::Replace, room->edit().toStdString()}); + } - room->sendMessageEvent(file, mtx::events::EventType::RoomMessage); + room->sendMessageEvent(file, mtx::events::EventType::RoomMessage); } void @@ -483,27 +479,27 @@ InputBar::audio(const QString &filename, const QString &mime, uint64_t dsize) { - mtx::events::msg::Audio audio; - audio.info.mimetype = mime.toStdString(); - audio.info.size = dsize; - audio.body = filename.toStdString(); - audio.url = url.toStdString(); + mtx::events::msg::Audio audio; + audio.info.mimetype = mime.toStdString(); + audio.info.size = dsize; + audio.body = filename.toStdString(); + audio.url = url.toStdString(); - if (file) - audio.file = file; - else - audio.url = url.toStdString(); + if (file) + audio.file = file; + else + audio.url = url.toStdString(); - if (!room->reply().isEmpty()) { - audio.relations.relations.push_back( - {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - } - if (!room->edit().isEmpty()) { - audio.relations.relations.push_back( - {mtx::common::RelationType::Replace, room->edit().toStdString()}); - } + if (!room->reply().isEmpty()) { + audio.relations.relations.push_back( + {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); + } + if (!room->edit().isEmpty()) { + audio.relations.relations.push_back( + {mtx::common::RelationType::Replace, room->edit().toStdString()}); + } - room->sendMessageEvent(audio, mtx::events::EventType::RoomMessage); + room->sendMessageEvent(audio, mtx::events::EventType::RoomMessage); } void @@ -513,320 +509,310 @@ InputBar::video(const QString &filename, const QString &mime, uint64_t dsize) { - mtx::events::msg::Video video; - video.info.mimetype = mime.toStdString(); - video.info.size = dsize; - video.body = filename.toStdString(); + mtx::events::msg::Video video; + video.info.mimetype = mime.toStdString(); + video.info.size = dsize; + video.body = filename.toStdString(); - if (file) - video.file = file; - else - video.url = url.toStdString(); + if (file) + video.file = file; + else + video.url = url.toStdString(); - if (!room->reply().isEmpty()) { - video.relations.relations.push_back( - {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - } - if (!room->edit().isEmpty()) { - video.relations.relations.push_back( - {mtx::common::RelationType::Replace, room->edit().toStdString()}); - } + if (!room->reply().isEmpty()) { + video.relations.relations.push_back( + {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); + } + if (!room->edit().isEmpty()) { + video.relations.relations.push_back( + {mtx::common::RelationType::Replace, room->edit().toStdString()}); + } - room->sendMessageEvent(video, mtx::events::EventType::RoomMessage); + room->sendMessageEvent(video, mtx::events::EventType::RoomMessage); } void InputBar::sticker(CombinedImagePackModel *model, int row) { - if (!model || row < 0) - return; + if (!model || row < 0) + return; - auto img = model->imageAt(row); + auto img = model->imageAt(row); - mtx::events::msg::StickerImage sticker{}; - sticker.info = img.info.value_or(mtx::common::ImageInfo{}); - sticker.url = img.url; - sticker.body = img.body; + mtx::events::msg::StickerImage sticker{}; + sticker.info = img.info.value_or(mtx::common::ImageInfo{}); + sticker.url = img.url; + sticker.body = img.body; - // 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; + // 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; - if (!room->reply().isEmpty()) { - sticker.relations.relations.push_back( - {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - } - if (!room->edit().isEmpty()) { - sticker.relations.relations.push_back( - {mtx::common::RelationType::Replace, room->edit().toStdString()}); - } + if (!room->reply().isEmpty()) { + sticker.relations.relations.push_back( + {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); + } + if (!room->edit().isEmpty()) { + sticker.relations.relations.push_back( + {mtx::common::RelationType::Replace, room->edit().toStdString()}); + } - room->sendMessageEvent(sticker, mtx::events::EventType::Sticker); + room->sendMessageEvent(sticker, mtx::events::EventType::Sticker); } void InputBar::command(QString command, QString args) { - if (command == "me") { - emote(args, false); - } else if (command == "react") { - auto eventId = room->reply(); - if (!eventId.isEmpty()) - reaction(eventId, args.trimmed()); - } else if (command == "join") { - ChatPage::instance()->joinRoom(args); - } else if (command == "part" || command == "leave") { - MainWindow::instance()->openLeaveRoomDialog(room->roomId()); - } else if (command == "invite") { - ChatPage::instance()->inviteUser(args.section(' ', 0, 0), args.section(' ', 1, -1)); - } else if (command == "kick") { - ChatPage::instance()->kickUser(args.section(' ', 0, 0), args.section(' ', 1, -1)); - } else if (command == "ban") { - ChatPage::instance()->banUser(args.section(' ', 0, 0), args.section(' ', 1, -1)); - } else if (command == "unban") { - ChatPage::instance()->unbanUser(args.section(' ', 0, 0), args.section(' ', 1, -1)); - } else if (command == "roomnick") { - mtx::events::state::Member member; - member.display_name = args.toStdString(); - member.avatar_url = - cache::avatarUrl(room->roomId(), - QString::fromStdString(http::client()->user_id().to_string())) - .toStdString(); - member.membership = mtx::events::state::Membership::Join; + if (command == "me") { + emote(args, false); + } else if (command == "react") { + auto eventId = room->reply(); + if (!eventId.isEmpty()) + reaction(eventId, args.trimmed()); + } else if (command == "join") { + ChatPage::instance()->joinRoom(args); + } else if (command == "part" || command == "leave") { + MainWindow::instance()->openLeaveRoomDialog(room->roomId()); + } else if (command == "invite") { + ChatPage::instance()->inviteUser(args.section(' ', 0, 0), args.section(' ', 1, -1)); + } else if (command == "kick") { + ChatPage::instance()->kickUser(args.section(' ', 0, 0), args.section(' ', 1, -1)); + } else if (command == "ban") { + ChatPage::instance()->banUser(args.section(' ', 0, 0), args.section(' ', 1, -1)); + } else if (command == "unban") { + ChatPage::instance()->unbanUser(args.section(' ', 0, 0), args.section(' ', 1, -1)); + } else if (command == "roomnick") { + mtx::events::state::Member member; + member.display_name = args.toStdString(); + member.avatar_url = + cache::avatarUrl(room->roomId(), + QString::fromStdString(http::client()->user_id().to_string())) + .toStdString(); + member.membership = mtx::events::state::Membership::Join; - http::client()->send_state_event( - room->roomId().toStdString(), - http::client()->user_id().to_string(), - member, - [](mtx::responses::EventId, mtx::http::RequestErr err) { - if (err) - nhlog::net()->error("Failed to set room displayname: {}", - err->matrix_error.error); - }); - } else if (command == "shrug") { - message("¯\\_(ツ)_/¯" + (args.isEmpty() ? "" : " " + args)); - } else if (command == "fliptable") { - message("(╯°□°)╯︵ ┻━┻"); - } else if (command == "unfliptable") { - message(" ┯━┯╭( º _ º╭)"); - } else if (command == "sovietflip") { - message("ノ┬─┬ノ ︵ ( \\o°o)\\"); - } else if (command == "clear-timeline") { - room->clearTimeline(); - } else if (command == "rotate-megolm-session") { - cache::dropOutboundMegolmSession(room->roomId().toStdString()); - } else if (command == "md") { - message(args, MarkdownOverride::ON); - } else if (command == "plain") { - message(args, MarkdownOverride::OFF); - } else if (command == "rainbow") { - message(args, MarkdownOverride::ON, true); - } else if (command == "rainbowme") { - emote(args, true); - } else if (command == "notice") { - notice(args, false); - } else if (command == "rainbownotice") { - notice(args, true); - } else if (command == "goto") { - // Goto has three different modes: - // 1 - Going directly to a given event ID - if (args[0] == '$') { - room->showEvent(args); - return; - } - // 2 - Going directly to a given message index - if (args[0] >= '0' && args[0] <= '9') { - room->showEvent(args); - return; - } - // 3 - Matrix URI handler, as if you clicked the URI - if (ChatPage::instance()->handleMatrixUri(args)) { - return; - } - nhlog::net()->error("Could not resolve goto: {}", args.toStdString()); + http::client()->send_state_event(room->roomId().toStdString(), + http::client()->user_id().to_string(), + member, + [](mtx::responses::EventId, mtx::http::RequestErr err) { + if (err) + nhlog::net()->error( + "Failed to set room displayname: {}", + err->matrix_error.error); + }); + } else if (command == "shrug") { + message("¯\\_(ツ)_/¯" + (args.isEmpty() ? "" : " " + args)); + } else if (command == "fliptable") { + message("(╯°□°)╯︵ ┻━┻"); + } else if (command == "unfliptable") { + message(" ┯━┯╭( º _ º╭)"); + } else if (command == "sovietflip") { + message("ノ┬─┬ノ ︵ ( \\o°o)\\"); + } else if (command == "clear-timeline") { + room->clearTimeline(); + } else if (command == "rotate-megolm-session") { + cache::dropOutboundMegolmSession(room->roomId().toStdString()); + } else if (command == "md") { + message(args, MarkdownOverride::ON); + } else if (command == "plain") { + message(args, MarkdownOverride::OFF); + } else if (command == "rainbow") { + message(args, MarkdownOverride::ON, true); + } else if (command == "rainbowme") { + emote(args, true); + } else if (command == "notice") { + notice(args, false); + } else if (command == "rainbownotice") { + notice(args, true); + } else if (command == "goto") { + // Goto has three different modes: + // 1 - Going directly to a given event ID + if (args[0] == '$') { + room->showEvent(args); + return; + } + // 2 - Going directly to a given message index + if (args[0] >= '0' && args[0] <= '9') { + room->showEvent(args); + return; + } + // 3 - Matrix URI handler, as if you clicked the URI + if (ChatPage::instance()->handleMatrixUri(args)) { + return; } + nhlog::net()->error("Could not resolve goto: {}", args.toStdString()); + } } void InputBar::showPreview(const QMimeData &source, QString path, const QStringList &formats) { - dialogs::PreviewUploadOverlay *previewDialog_ = - new dialogs::PreviewUploadOverlay(ChatPage::instance()); - previewDialog_->setAttribute(Qt::WA_DeleteOnClose); + dialogs::PreviewUploadOverlay *previewDialog_ = + new dialogs::PreviewUploadOverlay(ChatPage::instance()); + previewDialog_->setAttribute(Qt::WA_DeleteOnClose); - if (source.hasImage()) - previewDialog_->setPreview(qvariant_cast<QImage>(source.imageData()), - formats.front()); - else if (!path.isEmpty()) - previewDialog_->setPreview(path); - else if (!formats.isEmpty()) { - auto mime = formats.first(); - previewDialog_->setPreview(source.data(mime), mime); - } else { - setUploading(false); - previewDialog_->deleteLater(); - return; - } + if (source.hasImage()) + previewDialog_->setPreview(qvariant_cast<QImage>(source.imageData()), formats.front()); + else if (!path.isEmpty()) + previewDialog_->setPreview(path); + else if (!formats.isEmpty()) { + auto mime = formats.first(); + previewDialog_->setPreview(source.data(mime), mime); + } else { + setUploading(false); + previewDialog_->deleteLater(); + return; + } - connect(previewDialog_, &dialogs::PreviewUploadOverlay::aborted, this, [this]() { - setUploading(false); - }); + connect(previewDialog_, &dialogs::PreviewUploadOverlay::aborted, this, [this]() { + setUploading(false); + }); - connect( - previewDialog_, - &dialogs::PreviewUploadOverlay::confirmUpload, - this, - [this](const QByteArray data, const QString &mime, const QString &fn) { - setUploading(true); + connect( + previewDialog_, + &dialogs::PreviewUploadOverlay::confirmUpload, + this, + [this](const QByteArray data, const QString &mime, const QString &fn) { + setUploading(true); - setText(""); + setText(""); - auto payload = std::string(data.data(), data.size()); - std::optional<mtx::crypto::EncryptedFile> encryptedFile; - if (cache::isRoomEncrypted(room->roomId().toStdString())) { - mtx::crypto::BinaryBuf buf; - std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(payload); - payload = mtx::crypto::to_string(buf); - } + auto payload = std::string(data.data(), data.size()); + std::optional<mtx::crypto::EncryptedFile> encryptedFile; + if (cache::isRoomEncrypted(room->roomId().toStdString())) { + mtx::crypto::BinaryBuf buf; + std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(payload); + payload = mtx::crypto::to_string(buf); + } - QSize dimensions; - QString blurhash; - auto mimeClass = mime.split("/")[0]; - nhlog::ui()->debug("Mime: {}", mime.toStdString()); - if (mimeClass == "image") { - QImage img = utils::readImage(data); + QSize dimensions; + QString blurhash; + auto mimeClass = mime.split("/")[0]; + nhlog::ui()->debug("Mime: {}", mime.toStdString()); + if (mimeClass == "image") { + QImage img = utils::readImage(data); - dimensions = img.size(); - if (img.height() > 200 && img.width() > 360) - img = img.scaled(360, 200, Qt::KeepAspectRatioByExpanding); - std::vector<unsigned char> data_; - for (int y = 0; y < img.height(); y++) { - for (int x = 0; x < img.width(); x++) { - auto p = img.pixel(x, y); - data_.push_back(static_cast<unsigned char>(qRed(p))); - data_.push_back(static_cast<unsigned char>(qGreen(p))); - data_.push_back(static_cast<unsigned char>(qBlue(p))); - } - } - blurhash = QString::fromStdString( - blurhash::encode(data_.data(), img.width(), img.height(), 4, 3)); + dimensions = img.size(); + if (img.height() > 200 && img.width() > 360) + img = img.scaled(360, 200, Qt::KeepAspectRatioByExpanding); + std::vector<unsigned char> data_; + for (int y = 0; y < img.height(); y++) { + for (int x = 0; x < img.width(); x++) { + auto p = img.pixel(x, y); + data_.push_back(static_cast<unsigned char>(qRed(p))); + data_.push_back(static_cast<unsigned char>(qGreen(p))); + data_.push_back(static_cast<unsigned char>(qBlue(p))); } + } + blurhash = QString::fromStdString( + blurhash::encode(data_.data(), img.width(), img.height(), 4, 3)); + } - http::client()->upload( - payload, - encryptedFile ? "application/octet-stream" : mime.toStdString(), - QFileInfo(fn).fileName().toStdString(), - [this, - filename = fn, - encryptedFile = std::move(encryptedFile), - mimeClass, - mime, - size = payload.size(), - dimensions, - blurhash](const mtx::responses::ContentURI &res, - mtx::http::RequestErr err) mutable { - if (err) { - emit ChatPage::instance()->showNotification( - tr("Failed to upload media. Please try again.")); - nhlog::net()->warn("failed to upload media: {} {} ({})", - err->matrix_error.error, - to_string(err->matrix_error.errcode), - static_cast<int>(err->status_code)); - setUploading(false); - return; - } + http::client()->upload( + payload, + encryptedFile ? "application/octet-stream" : mime.toStdString(), + QFileInfo(fn).fileName().toStdString(), + [this, + filename = fn, + encryptedFile = std::move(encryptedFile), + mimeClass, + mime, + size = payload.size(), + dimensions, + blurhash](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) mutable { + if (err) { + emit ChatPage::instance()->showNotification( + tr("Failed to upload media. Please try again.")); + nhlog::net()->warn("failed to upload media: {} {} ({})", + err->matrix_error.error, + to_string(err->matrix_error.errcode), + static_cast<int>(err->status_code)); + setUploading(false); + return; + } - auto url = QString::fromStdString(res.content_uri); - if (encryptedFile) - encryptedFile->url = res.content_uri; + auto url = QString::fromStdString(res.content_uri); + if (encryptedFile) + encryptedFile->url = res.content_uri; - if (mimeClass == "image") - image(filename, - encryptedFile, - url, - mime, - size, - dimensions, - blurhash); - else if (mimeClass == "audio") - audio(filename, encryptedFile, url, mime, size); - else if (mimeClass == "video") - video(filename, encryptedFile, url, mime, size); - else - file(filename, encryptedFile, url, mime, size); + if (mimeClass == "image") + image(filename, encryptedFile, url, mime, size, dimensions, blurhash); + else if (mimeClass == "audio") + audio(filename, encryptedFile, url, mime, size); + else if (mimeClass == "video") + video(filename, encryptedFile, url, mime, size); + else + file(filename, encryptedFile, url, mime, size); - setUploading(false); - }); - }); + setUploading(false); + }); + }); } void InputBar::startTyping() { - if (!typingRefresh_.isActive()) { - typingRefresh_.start(); + if (!typingRefresh_.isActive()) { + typingRefresh_.start(); - if (ChatPage::instance()->userSettings()->typingNotifications()) { - http::client()->start_typing( - room->roomId().toStdString(), 10'000, [](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to send typing notification: {}", - err->matrix_error.error); - } - }); - } + if (ChatPage::instance()->userSettings()->typingNotifications()) { + http::client()->start_typing( + room->roomId().toStdString(), 10'000, [](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to send typing notification: {}", + err->matrix_error.error); + } + }); } - typingTimeout_.start(); + } + typingTimeout_.start(); } void InputBar::stopTyping() { - typingRefresh_.stop(); - typingTimeout_.stop(); + typingRefresh_.stop(); + typingTimeout_.stop(); - if (!ChatPage::instance()->userSettings()->typingNotifications()) - return; + if (!ChatPage::instance()->userSettings()->typingNotifications()) + return; - http::client()->stop_typing(room->roomId().toStdString(), [](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to stop typing notifications: {}", - err->matrix_error.error); - } - }); + http::client()->stop_typing(room->roomId().toStdString(), [](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to stop typing notifications: {}", err->matrix_error.error); + } + }); } void InputBar::reaction(const QString &reactedEvent, const QString &reactionKey) { - auto reactions = room->reactions(reactedEvent.toStdString()); + auto reactions = room->reactions(reactedEvent.toStdString()); - QString selfReactedEvent; - for (const auto &reaction : reactions) { - if (reactionKey == reaction.key_) { - selfReactedEvent = reaction.selfReactedEvent_; - break; - } + QString selfReactedEvent; + for (const auto &reaction : reactions) { + if (reactionKey == reaction.key_) { + selfReactedEvent = reaction.selfReactedEvent_; + break; } + } - if (selfReactedEvent.startsWith("m")) - return; + if (selfReactedEvent.startsWith("m")) + return; - // If selfReactedEvent is empty, that means we haven't previously reacted - if (selfReactedEvent.isEmpty()) { - mtx::events::msg::Reaction reaction; - mtx::common::Relation rel; - rel.rel_type = mtx::common::RelationType::Annotation; - rel.event_id = reactedEvent.toStdString(); - rel.key = reactionKey.toStdString(); - reaction.relations.relations.push_back(rel); + // If selfReactedEvent is empty, that means we haven't previously reacted + if (selfReactedEvent.isEmpty()) { + mtx::events::msg::Reaction reaction; + mtx::common::Relation rel; + rel.rel_type = mtx::common::RelationType::Annotation; + rel.event_id = reactedEvent.toStdString(); + rel.key = reactionKey.toStdString(); + reaction.relations.relations.push_back(rel); - room->sendMessageEvent(reaction, mtx::events::EventType::Reaction); - // Otherwise, we have previously reacted and the reaction should be redacted - } else { - room->redactEvent(selfReactedEvent); - } + room->sendMessageEvent(reaction, mtx::events::EventType::Reaction); + // Otherwise, we have previously reacted and the reaction should be redacted + } else { + room->redactEvent(selfReactedEvent); + } } diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index cdc66a06..4a0f4401 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h
@@ -19,105 +19,105 @@ class QStringList; enum class MarkdownOverride { - NOT_SPECIFIED, // no override set - ON, - OFF, + NOT_SPECIFIED, // no override set + ON, + OFF, }; class InputBar : public QObject { - Q_OBJECT - Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged) - Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged) - Q_PROPERTY(QString text READ text NOTIFY textChanged) + Q_OBJECT + Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged) + Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged) + Q_PROPERTY(QString text READ text NOTIFY textChanged) public: - InputBar(TimelineModel *parent) - : QObject() - , room(parent) - { - typingRefresh_.setInterval(10'000); - typingRefresh_.setSingleShot(true); - typingTimeout_.setInterval(5'000); - typingTimeout_.setSingleShot(true); - connect(&typingRefresh_, &QTimer::timeout, this, &InputBar::startTyping); - connect(&typingTimeout_, &QTimer::timeout, this, &InputBar::stopTyping); - } + InputBar(TimelineModel *parent) + : QObject() + , room(parent) + { + typingRefresh_.setInterval(10'000); + typingRefresh_.setSingleShot(true); + typingTimeout_.setInterval(5'000); + typingTimeout_.setSingleShot(true); + connect(&typingRefresh_, &QTimer::timeout, this, &InputBar::startTyping); + connect(&typingTimeout_, &QTimer::timeout, this, &InputBar::stopTyping); + } public slots: - QString text() const; - QString previousText(); - QString nextText(); - void setText(QString newText); + QString text() const; + QString previousText(); + QString nextText(); + void setText(QString newText); - bool containsAtRoom() const { return containsAtRoom_; } + bool containsAtRoom() const { return containsAtRoom_; } - void send(); - void paste(bool fromMouse); - void insertMimeData(const QMimeData *data); - void updateState(int selectionStart, int selectionEnd, int cursorPosition, QString text); - void openFileSelection(); - bool uploading() const { return uploading_; } - void message(QString body, - MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED, - bool rainbowify = false); - void reaction(const QString &reactedEvent, const QString &reactionKey); - void sticker(CombinedImagePackModel *model, int row); + void send(); + void paste(bool fromMouse); + void insertMimeData(const QMimeData *data); + void updateState(int selectionStart, int selectionEnd, int cursorPosition, QString text); + void openFileSelection(); + bool uploading() const { return uploading_; } + void message(QString body, + MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED, + bool rainbowify = false); + void reaction(const QString &reactedEvent, const QString &reactionKey); + void sticker(CombinedImagePackModel *model, int row); private slots: - void startTyping(); - void stopTyping(); + void startTyping(); + void stopTyping(); signals: - void insertText(QString text); - void textChanged(QString newText); - void uploadingChanged(bool value); - void containsAtRoomChanged(); + void insertText(QString text); + void textChanged(QString newText); + void uploadingChanged(bool value); + void containsAtRoomChanged(); private: - void emote(QString body, bool rainbowify); - void notice(QString body, bool rainbowify); - void command(QString name, QString args); - void image(const QString &filename, - const std::optional<mtx::crypto::EncryptedFile> &file, - const QString &url, - const QString &mime, - uint64_t dsize, - const QSize &dimensions, - const QString &blurhash); - void file(const QString &filename, - const std::optional<mtx::crypto::EncryptedFile> &encryptedFile, - const QString &url, - const QString &mime, - uint64_t dsize); - void audio(const QString &filename, - const std::optional<mtx::crypto::EncryptedFile> &file, - const QString &url, - const QString &mime, - uint64_t dsize); - void video(const QString &filename, - const std::optional<mtx::crypto::EncryptedFile> &file, - const QString &url, - const QString &mime, - uint64_t dsize); + void emote(QString body, bool rainbowify); + void notice(QString body, bool rainbowify); + void command(QString name, QString args); + void image(const QString &filename, + const std::optional<mtx::crypto::EncryptedFile> &file, + const QString &url, + const QString &mime, + uint64_t dsize, + const QSize &dimensions, + const QString &blurhash); + void file(const QString &filename, + const std::optional<mtx::crypto::EncryptedFile> &encryptedFile, + const QString &url, + const QString &mime, + uint64_t dsize); + void audio(const QString &filename, + const std::optional<mtx::crypto::EncryptedFile> &file, + const QString &url, + const QString &mime, + uint64_t dsize); + void video(const QString &filename, + const std::optional<mtx::crypto::EncryptedFile> &file, + const QString &url, + const QString &mime, + uint64_t dsize); - void showPreview(const QMimeData &source, QString path, const QStringList &formats); - void setUploading(bool value) - { - if (value != uploading_) { - uploading_ = value; - emit uploadingChanged(value); - } + void showPreview(const QMimeData &source, QString path, const QStringList &formats); + void setUploading(bool value) + { + if (value != uploading_) { + uploading_ = value; + emit uploadingChanged(value); } + } - void updateAtRoom(const QString &t); + void updateAtRoom(const QString &t); - QTimer typingRefresh_; - QTimer typingTimeout_; - TimelineModel *room; - std::deque<QString> history_; - std::size_t history_index_ = 0; - int selectionStart = 0, selectionEnd = 0, cursorPosition = 0; - bool uploading_ = false; - bool containsAtRoom_ = false; + QTimer typingRefresh_; + QTimer typingTimeout_; + TimelineModel *room; + std::deque<QString> history_; + std::size_t history_index_ = 0; + int selectionStart = 0, selectionEnd = 0, cursorPosition = 0; + bool uploading_ = false; + bool containsAtRoom_ = false; }; diff --git a/src/timeline/Permissions.cpp b/src/timeline/Permissions.cpp
index 5dafc325..4e45f2e2 100644 --- a/src/timeline/Permissions.cpp +++ b/src/timeline/Permissions.cpp
@@ -12,59 +12,59 @@ Permissions::Permissions(QString roomId, QObject *parent) : QObject(parent) , roomId_(roomId) { - invalidate(); + invalidate(); } void Permissions::invalidate() { - pl = cache::client() - ->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString()) - .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{}) - .content; + pl = cache::client() + ->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString()) + .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{}) + .content; } bool Permissions::canInvite() { - return pl.user_level(http::client()->user_id().to_string()) >= pl.invite; + return pl.user_level(http::client()->user_id().to_string()) >= pl.invite; } bool Permissions::canBan() { - return pl.user_level(http::client()->user_id().to_string()) >= pl.ban; + return pl.user_level(http::client()->user_id().to_string()) >= pl.ban; } bool Permissions::canKick() { - return pl.user_level(http::client()->user_id().to_string()) >= pl.kick; + return pl.user_level(http::client()->user_id().to_string()) >= pl.kick; } bool Permissions::canRedact() { - return pl.user_level(http::client()->user_id().to_string()) >= pl.redact; + return pl.user_level(http::client()->user_id().to_string()) >= pl.redact; } bool Permissions::canChange(int eventType) { - return pl.user_level(http::client()->user_id().to_string()) >= - pl.state_level(to_string(qml_mtx_events::fromRoomEventType( - static_cast<qml_mtx_events::EventType>(eventType)))); + return pl.user_level(http::client()->user_id().to_string()) >= + pl.state_level(to_string( + qml_mtx_events::fromRoomEventType(static_cast<qml_mtx_events::EventType>(eventType)))); } bool Permissions::canSend(int eventType) { - return pl.user_level(http::client()->user_id().to_string()) >= - pl.event_level(to_string(qml_mtx_events::fromRoomEventType( - static_cast<qml_mtx_events::EventType>(eventType)))); + return pl.user_level(http::client()->user_id().to_string()) >= + pl.event_level(to_string( + qml_mtx_events::fromRoomEventType(static_cast<qml_mtx_events::EventType>(eventType)))); } bool Permissions::canPingRoom() { - return pl.user_level(http::client()->user_id().to_string()) >= - pl.notification_level(mtx::events::state::notification_keys::room); + return pl.user_level(http::client()->user_id().to_string()) >= + pl.notification_level(mtx::events::state::notification_keys::room); } diff --git a/src/timeline/Permissions.h b/src/timeline/Permissions.h
index 349520d5..b80a66aa 100644 --- a/src/timeline/Permissions.h +++ b/src/timeline/Permissions.h
@@ -12,24 +12,24 @@ class TimelineModel; class Permissions : public QObject { - Q_OBJECT + Q_OBJECT public: - Permissions(QString roomId, QObject *parent = nullptr); + Permissions(QString roomId, QObject *parent = nullptr); - Q_INVOKABLE bool canInvite(); - Q_INVOKABLE bool canBan(); - Q_INVOKABLE bool canKick(); + Q_INVOKABLE bool canInvite(); + Q_INVOKABLE bool canBan(); + Q_INVOKABLE bool canKick(); - Q_INVOKABLE bool canRedact(); - Q_INVOKABLE bool canChange(int eventType); - Q_INVOKABLE bool canSend(int eventType); + Q_INVOKABLE bool canRedact(); + Q_INVOKABLE bool canChange(int eventType); + Q_INVOKABLE bool canSend(int eventType); - Q_INVOKABLE bool canPingRoom(); + Q_INVOKABLE bool canPingRoom(); - void invalidate(); + void invalidate(); private: - QString roomId_; - mtx::events::state::PowerLevels pl; + QString roomId_; + mtx::events::state::PowerLevels pl; }; diff --git a/src/timeline/Reaction.h b/src/timeline/Reaction.h
index 788e9ced..fcdd61a4 100644 --- a/src/timeline/Reaction.h +++ b/src/timeline/Reaction.h
@@ -9,20 +9,20 @@ struct Reaction { - Q_GADGET - Q_PROPERTY(QString key READ key) - Q_PROPERTY(QString users READ users) - Q_PROPERTY(QString selfReactedEvent READ selfReactedEvent) - Q_PROPERTY(int count READ count) + Q_GADGET + Q_PROPERTY(QString key READ key) + Q_PROPERTY(QString users READ users) + Q_PROPERTY(QString selfReactedEvent READ selfReactedEvent) + Q_PROPERTY(int count READ count) public: - QString key() const { return key_.toHtmlEscaped(); } - QString users() const { return users_.toHtmlEscaped(); } - QString selfReactedEvent() const { return selfReactedEvent_; } - int count() const { return count_; } + QString key() const { return key_.toHtmlEscaped(); } + QString users() const { return users_.toHtmlEscaped(); } + QString selfReactedEvent() const { return selfReactedEvent_; } + int count() const { return count_; } - QString key_; - QString users_; - QString selfReactedEvent_; - int count_; + QString key_; + QString users_; + QString selfReactedEvent_; + int count_; }; diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 2d1dd49d..2d60dcb3 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp
@@ -17,978 +17,947 @@ RoomlistModel::RoomlistModel(TimelineViewManager *parent) : QAbstractListModel(parent) , manager(parent) { - [[maybe_unused]] static auto id = qRegisterMetaType<RoomPreview>(); + [[maybe_unused]] static auto id = qRegisterMetaType<RoomPreview>(); - connect(ChatPage::instance(), &ChatPage::decryptSidebarChanged, this, [this]() { - auto decrypt = ChatPage::instance()->userSettings()->decryptSidebar(); - QHash<QString, QSharedPointer<TimelineModel>>::iterator i; - for (i = models.begin(); i != models.end(); ++i) { - auto ptr = i.value(); + connect(ChatPage::instance(), &ChatPage::decryptSidebarChanged, this, [this]() { + auto decrypt = ChatPage::instance()->userSettings()->decryptSidebar(); + QHash<QString, QSharedPointer<TimelineModel>>::iterator i; + for (i = models.begin(); i != models.end(); ++i) { + auto ptr = i.value(); - if (!ptr.isNull()) { - ptr->setDecryptDescription(decrypt); - ptr->updateLastMessage(); - } - } - }); + if (!ptr.isNull()) { + ptr->setDecryptDescription(decrypt); + ptr->updateLastMessage(); + } + } + }); - connect(this, - &RoomlistModel::totalUnreadMessageCountUpdated, - ChatPage::instance(), - &ChatPage::unreadMessages); + connect(this, + &RoomlistModel::totalUnreadMessageCountUpdated, + ChatPage::instance(), + &ChatPage::unreadMessages); - connect( - this, - &RoomlistModel::fetchedPreview, - this, - [this](QString roomid, RoomInfo info) { - if (this->previewedRooms.contains(roomid)) { - this->previewedRooms.insert(roomid, std::move(info)); - auto idx = this->roomidToIndex(roomid); - emit dataChanged(index(idx), - index(idx), - { - Roles::RoomName, - Roles::AvatarUrl, - Roles::IsSpace, - Roles::IsPreviewFetched, - Qt::DisplayRole, - }); - } - }, - Qt::QueuedConnection); + connect( + this, + &RoomlistModel::fetchedPreview, + this, + [this](QString roomid, RoomInfo info) { + if (this->previewedRooms.contains(roomid)) { + this->previewedRooms.insert(roomid, std::move(info)); + auto idx = this->roomidToIndex(roomid); + emit dataChanged(index(idx), + index(idx), + { + Roles::RoomName, + Roles::AvatarUrl, + Roles::IsSpace, + Roles::IsPreviewFetched, + Qt::DisplayRole, + }); + } + }, + Qt::QueuedConnection); } QHash<int, QByteArray> RoomlistModel::roleNames() const { - return { - {AvatarUrl, "avatarUrl"}, - {RoomName, "roomName"}, - {RoomId, "roomId"}, - {LastMessage, "lastMessage"}, - {Time, "time"}, - {Timestamp, "timestamp"}, - {HasUnreadMessages, "hasUnreadMessages"}, - {HasLoudNotification, "hasLoudNotification"}, - {NotificationCount, "notificationCount"}, - {IsInvite, "isInvite"}, - {IsSpace, "isSpace"}, - {Tags, "tags"}, - {ParentSpaces, "parentSpaces"}, - {IsDirect, "isDirect"}, - {DirectChatOtherUserId, "directChatOtherUserId"}, - }; + return { + {AvatarUrl, "avatarUrl"}, + {RoomName, "roomName"}, + {RoomId, "roomId"}, + {LastMessage, "lastMessage"}, + {Time, "time"}, + {Timestamp, "timestamp"}, + {HasUnreadMessages, "hasUnreadMessages"}, + {HasLoudNotification, "hasLoudNotification"}, + {NotificationCount, "notificationCount"}, + {IsInvite, "isInvite"}, + {IsSpace, "isSpace"}, + {Tags, "tags"}, + {ParentSpaces, "parentSpaces"}, + {IsDirect, "isDirect"}, + {DirectChatOtherUserId, "directChatOtherUserId"}, + }; } QVariant RoomlistModel::data(const QModelIndex &index, int role) const { - if (index.row() >= 0 && static_cast<size_t>(index.row()) < roomids.size()) { - auto roomid = roomids.at(index.row()); + if (index.row() >= 0 && static_cast<size_t>(index.row()) < roomids.size()) { + auto roomid = roomids.at(index.row()); - if (role == Roles::ParentSpaces) { - auto parents = cache::client()->getParentRoomIds(roomid.toStdString()); - QStringList list; - for (const auto &t : parents) - list.push_back(QString::fromStdString(t)); - return list; - } else if (role == Roles::RoomId) { - return roomid; - } - - if (models.contains(roomid)) { - auto room = models.value(roomid); - switch (role) { - case Roles::AvatarUrl: - return room->roomAvatarUrl(); - case Roles::RoomName: - return room->plainRoomName(); - case Roles::LastMessage: - return room->lastMessage().body; - case Roles::Time: - return room->lastMessage().descriptiveTime; - case Roles::Timestamp: - return QVariant( - static_cast<quint64>(room->lastMessage().timestamp)); - case Roles::HasUnreadMessages: - return this->roomReadStatus.count(roomid) && - this->roomReadStatus.at(roomid); - case Roles::HasLoudNotification: - return room->hasMentions(); - case Roles::NotificationCount: - return room->notificationCount(); - case Roles::IsInvite: - return false; - case Roles::IsSpace: - return room->isSpace(); - case Roles::IsPreview: - return false; - case Roles::Tags: { - auto info = cache::singleRoomInfo(roomid.toStdString()); - QStringList list; - for (const auto &t : info.tags) - list.push_back(QString::fromStdString(t)); - return list; - } - case Roles::IsDirect: - return room->isDirect(); - case Roles::DirectChatOtherUserId: - return room->directChatOtherUserId(); - default: - return {}; - } - } else if (invites.contains(roomid)) { - auto room = invites.value(roomid); - switch (role) { - case Roles::AvatarUrl: - return QString::fromStdString(room.avatar_url); - case Roles::RoomName: - return QString::fromStdString(room.name); - case Roles::LastMessage: - return tr("Pending invite."); - case Roles::Time: - return QString(); - case Roles::Timestamp: - return QVariant(static_cast<quint64>(0)); - case Roles::HasUnreadMessages: - case Roles::HasLoudNotification: - return false; - case Roles::NotificationCount: - return 0; - case Roles::IsInvite: - return true; - case Roles::IsSpace: - return false; - case Roles::IsPreview: - return false; - case Roles::Tags: - return QStringList(); - case Roles::IsDirect: - // The list of users from the room doesn't contain the invited - // users, so we won't factor the invite into the count - return room.member_count == 1; - case Roles::DirectChatOtherUserId: - return cache::getMembersFromInvite(roomid.toStdString(), 0, 1) - .front() - .user_id; - default: - return {}; - } - } else if (previewedRooms.contains(roomid) && - previewedRooms.value(roomid).has_value()) { - auto room = previewedRooms.value(roomid).value(); - switch (role) { - case Roles::AvatarUrl: - return QString::fromStdString(room.avatar_url); - case Roles::RoomName: - return QString::fromStdString(room.name); - case Roles::LastMessage: - return tr("Previewing this room"); - case Roles::Time: - return QString(); - case Roles::Timestamp: - return QVariant(static_cast<quint64>(0)); - case Roles::HasUnreadMessages: - case Roles::HasLoudNotification: - return false; - case Roles::NotificationCount: - return 0; - case Roles::IsInvite: - return false; - case Roles::IsSpace: - return room.is_space; - case Roles::IsPreview: - return true; - case Roles::IsPreviewFetched: - return true; - case Roles::Tags: - return QStringList(); - case Roles::IsDirect: - return false; - case Roles::DirectChatOtherUserId: - return QString{}; // should never be reached - default: - return {}; - } - } else { - if (role == Roles::IsPreview) - return true; - else if (role == Roles::IsPreviewFetched) - return false; + if (role == Roles::ParentSpaces) { + auto parents = cache::client()->getParentRoomIds(roomid.toStdString()); + QStringList list; + for (const auto &t : parents) + list.push_back(QString::fromStdString(t)); + return list; + } else if (role == Roles::RoomId) { + return roomid; + } - fetchPreview(roomid); - switch (role) { - case Roles::AvatarUrl: - return QString(); - case Roles::RoomName: - return tr("No preview available"); - case Roles::LastMessage: - return QString(); - case Roles::Time: - return QString(); - case Roles::Timestamp: - return QVariant(static_cast<quint64>(0)); - case Roles::HasUnreadMessages: - case Roles::HasLoudNotification: - return false; - case Roles::NotificationCount: - return 0; - case Roles::IsInvite: - return false; - case Roles::IsSpace: - return false; - case Roles::Tags: - return QStringList(); - default: - return {}; - } - } + if (models.contains(roomid)) { + auto room = models.value(roomid); + switch (role) { + case Roles::AvatarUrl: + return room->roomAvatarUrl(); + case Roles::RoomName: + return room->plainRoomName(); + case Roles::LastMessage: + return room->lastMessage().body; + case Roles::Time: + return room->lastMessage().descriptiveTime; + case Roles::Timestamp: + return QVariant(static_cast<quint64>(room->lastMessage().timestamp)); + case Roles::HasUnreadMessages: + return this->roomReadStatus.count(roomid) && this->roomReadStatus.at(roomid); + case Roles::HasLoudNotification: + return room->hasMentions(); + case Roles::NotificationCount: + return room->notificationCount(); + case Roles::IsInvite: + return false; + case Roles::IsSpace: + return room->isSpace(); + case Roles::IsPreview: + return false; + case Roles::Tags: { + auto info = cache::singleRoomInfo(roomid.toStdString()); + QStringList list; + for (const auto &t : info.tags) + list.push_back(QString::fromStdString(t)); + return list; + } + case Roles::IsDirect: + return room->isDirect(); + case Roles::DirectChatOtherUserId: + return room->directChatOtherUserId(); + default: + return {}; + } + } else if (invites.contains(roomid)) { + auto room = invites.value(roomid); + switch (role) { + case Roles::AvatarUrl: + return QString::fromStdString(room.avatar_url); + case Roles::RoomName: + return QString::fromStdString(room.name); + case Roles::LastMessage: + return tr("Pending invite."); + case Roles::Time: + return QString(); + case Roles::Timestamp: + return QVariant(static_cast<quint64>(0)); + case Roles::HasUnreadMessages: + case Roles::HasLoudNotification: + return false; + case Roles::NotificationCount: + return 0; + case Roles::IsInvite: + return true; + case Roles::IsSpace: + return false; + case Roles::IsPreview: + return false; + case Roles::Tags: + return QStringList(); + case Roles::IsDirect: + // The list of users from the room doesn't contain the invited + // users, so we won't factor the invite into the count + return room.member_count == 1; + case Roles::DirectChatOtherUserId: + return cache::getMembersFromInvite(roomid.toStdString(), 0, 1).front().user_id; + default: + return {}; + } + } else if (previewedRooms.contains(roomid) && previewedRooms.value(roomid).has_value()) { + auto room = previewedRooms.value(roomid).value(); + switch (role) { + case Roles::AvatarUrl: + return QString::fromStdString(room.avatar_url); + case Roles::RoomName: + return QString::fromStdString(room.name); + case Roles::LastMessage: + return tr("Previewing this room"); + case Roles::Time: + return QString(); + case Roles::Timestamp: + return QVariant(static_cast<quint64>(0)); + case Roles::HasUnreadMessages: + case Roles::HasLoudNotification: + return false; + case Roles::NotificationCount: + return 0; + case Roles::IsInvite: + return false; + case Roles::IsSpace: + return room.is_space; + case Roles::IsPreview: + return true; + case Roles::IsPreviewFetched: + return true; + case Roles::Tags: + return QStringList(); + case Roles::IsDirect: + return false; + case Roles::DirectChatOtherUserId: + return QString{}; // should never be reached + default: + return {}; + } } else { + if (role == Roles::IsPreview) + return true; + else if (role == Roles::IsPreviewFetched) + return false; + + fetchPreview(roomid); + switch (role) { + case Roles::AvatarUrl: + return QString(); + case Roles::RoomName: + return tr("No preview available"); + case Roles::LastMessage: + return QString(); + case Roles::Time: + return QString(); + case Roles::Timestamp: + return QVariant(static_cast<quint64>(0)); + case Roles::HasUnreadMessages: + case Roles::HasLoudNotification: + return false; + case Roles::NotificationCount: + return 0; + case Roles::IsInvite: + return false; + case Roles::IsSpace: + return false; + case Roles::Tags: + return QStringList(); + default: return {}; + } } + } else { + return {}; + } } void RoomlistModel::updateReadStatus(const std::map<QString, bool> roomReadStatus_) { - std::vector<int> roomsToUpdate; - roomsToUpdate.resize(roomReadStatus_.size()); - for (const auto &[roomid, roomUnread] : roomReadStatus_) { - if (roomUnread != roomReadStatus[roomid]) { - roomsToUpdate.push_back(this->roomidToIndex(roomid)); - } - - this->roomReadStatus[roomid] = roomUnread; + std::vector<int> roomsToUpdate; + roomsToUpdate.resize(roomReadStatus_.size()); + for (const auto &[roomid, roomUnread] : roomReadStatus_) { + if (roomUnread != roomReadStatus[roomid]) { + roomsToUpdate.push_back(this->roomidToIndex(roomid)); } - for (auto idx : roomsToUpdate) { - emit dataChanged(index(idx), - index(idx), - { - Roles::HasUnreadMessages, - }); - } + this->roomReadStatus[roomid] = roomUnread; + } + + for (auto idx : roomsToUpdate) { + emit dataChanged(index(idx), + index(idx), + { + Roles::HasUnreadMessages, + }); + } } void RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification) { - if (!models.contains(room_id)) { - // ensure we get read status updates and are only connected once - connect(cache::client(), - &Cache::roomReadStatus, - this, - &RoomlistModel::updateReadStatus, - Qt::UniqueConnection); + if (!models.contains(room_id)) { + // ensure we get read status updates and are only connected once + connect(cache::client(), + &Cache::roomReadStatus, + this, + &RoomlistModel::updateReadStatus, + Qt::UniqueConnection); - QSharedPointer<TimelineModel> newRoom(new TimelineModel(manager, room_id)); - newRoom->setDecryptDescription( - ChatPage::instance()->userSettings()->decryptSidebar()); + QSharedPointer<TimelineModel> newRoom(new TimelineModel(manager, room_id)); + newRoom->setDecryptDescription(ChatPage::instance()->userSettings()->decryptSidebar()); - connect(newRoom.data(), - &TimelineModel::newEncryptedImage, - manager->imageProvider(), - &MxcImageProvider::addEncryptionInfo); - connect(newRoom.data(), - &TimelineModel::forwardToRoom, - manager, - &TimelineViewManager::forwardMessageToRoom); - connect( - newRoom.data(), &TimelineModel::lastMessageChanged, this, [room_id, this]() { - auto idx = this->roomidToIndex(room_id); - emit dataChanged(index(idx), - index(idx), - { - Roles::HasLoudNotification, - Roles::LastMessage, - Roles::Timestamp, - Roles::NotificationCount, - Qt::DisplayRole, - }); - }); - connect( - newRoom.data(), &TimelineModel::roomAvatarUrlChanged, this, [room_id, this]() { - auto idx = this->roomidToIndex(room_id); - emit dataChanged(index(idx), - index(idx), - { - Roles::AvatarUrl, - }); - }); - connect(newRoom.data(), &TimelineModel::roomNameChanged, this, [room_id, this]() { - auto idx = this->roomidToIndex(room_id); - emit dataChanged(index(idx), - index(idx), - { - Roles::RoomName, - }); - }); - connect( - newRoom.data(), &TimelineModel::notificationsChanged, this, [room_id, this]() { - auto idx = this->roomidToIndex(room_id); - emit dataChanged(index(idx), - index(idx), - { - Roles::HasLoudNotification, - Roles::NotificationCount, - Qt::DisplayRole, - }); + connect(newRoom.data(), + &TimelineModel::newEncryptedImage, + manager->imageProvider(), + &MxcImageProvider::addEncryptionInfo); + connect(newRoom.data(), + &TimelineModel::forwardToRoom, + manager, + &TimelineViewManager::forwardMessageToRoom); + connect(newRoom.data(), &TimelineModel::lastMessageChanged, this, [room_id, this]() { + auto idx = this->roomidToIndex(room_id); + emit dataChanged(index(idx), + index(idx), + { + Roles::HasLoudNotification, + Roles::LastMessage, + Roles::Timestamp, + Roles::NotificationCount, + Qt::DisplayRole, + }); + }); + connect(newRoom.data(), &TimelineModel::roomAvatarUrlChanged, this, [room_id, this]() { + auto idx = this->roomidToIndex(room_id); + emit dataChanged(index(idx), + index(idx), + { + Roles::AvatarUrl, + }); + }); + connect(newRoom.data(), &TimelineModel::roomNameChanged, this, [room_id, this]() { + auto idx = this->roomidToIndex(room_id); + emit dataChanged(index(idx), + index(idx), + { + Roles::RoomName, + }); + }); + connect(newRoom.data(), &TimelineModel::notificationsChanged, this, [room_id, this]() { + auto idx = this->roomidToIndex(room_id); + emit dataChanged(index(idx), + index(idx), + { + Roles::HasLoudNotification, + Roles::NotificationCount, + Qt::DisplayRole, + }); - int total_unread_msgs = 0; + int total_unread_msgs = 0; - for (const auto &room : models) { - if (!room.isNull()) - total_unread_msgs += room->notificationCount(); - } + for (const auto &room : models) { + if (!room.isNull()) + total_unread_msgs += room->notificationCount(); + } - emit totalUnreadMessageCountUpdated(total_unread_msgs); - }); + emit totalUnreadMessageCountUpdated(total_unread_msgs); + }); - newRoom->updateLastMessage(); + newRoom->updateLastMessage(); - std::vector<QString> previewsToAdd; - if (newRoom->isSpace()) { - auto childs = cache::client()->getChildRoomIds(room_id.toStdString()); - for (const auto &c : childs) { - auto id = QString::fromStdString(c); - if (!(models.contains(id) || invites.contains(id) || - previewedRooms.contains(id))) { - previewsToAdd.push_back(std::move(id)); - } - } + std::vector<QString> previewsToAdd; + if (newRoom->isSpace()) { + auto childs = cache::client()->getChildRoomIds(room_id.toStdString()); + for (const auto &c : childs) { + auto id = QString::fromStdString(c); + if (!(models.contains(id) || invites.contains(id) || previewedRooms.contains(id))) { + previewsToAdd.push_back(std::move(id)); } + } + } - bool wasInvite = invites.contains(room_id); - bool wasPreview = previewedRooms.contains(room_id); - if (!suppressInsertNotification && - ((!wasInvite && !wasPreview) || !previewedRooms.empty())) - // if the old room was already in the list, don't add it. Also add all - // previews at the same time. - beginInsertRows(QModelIndex(), - (int)roomids.size(), - (int)(roomids.size() + previewsToAdd.size() - - ((wasInvite || wasPreview) ? 1 : 0))); + bool wasInvite = invites.contains(room_id); + bool wasPreview = previewedRooms.contains(room_id); + if (!suppressInsertNotification && ((!wasInvite && !wasPreview) || !previewedRooms.empty())) + // if the old room was already in the list, don't add it. Also add all + // previews at the same time. + beginInsertRows( + QModelIndex(), + (int)roomids.size(), + (int)(roomids.size() + previewsToAdd.size() - ((wasInvite || wasPreview) ? 1 : 0))); - models.insert(room_id, std::move(newRoom)); - if (wasInvite) { - auto idx = roomidToIndex(room_id); - invites.remove(room_id); - emit dataChanged(index(idx), index(idx)); - } else if (wasPreview) { - auto idx = roomidToIndex(room_id); - previewedRooms.remove(room_id); - emit dataChanged(index(idx), index(idx)); - } else { - roomids.push_back(room_id); - } + models.insert(room_id, std::move(newRoom)); + if (wasInvite) { + auto idx = roomidToIndex(room_id); + invites.remove(room_id); + emit dataChanged(index(idx), index(idx)); + } else if (wasPreview) { + auto idx = roomidToIndex(room_id); + previewedRooms.remove(room_id); + emit dataChanged(index(idx), index(idx)); + } else { + roomids.push_back(room_id); + } - if ((wasInvite || wasPreview) && currentRoomPreview_ && - currentRoomPreview_->roomid() == room_id) { - currentRoom_ = models.value(room_id); - currentRoomPreview_.reset(); - emit currentRoomChanged(); - } + if ((wasInvite || wasPreview) && currentRoomPreview_ && + currentRoomPreview_->roomid() == room_id) { + currentRoom_ = models.value(room_id); + currentRoomPreview_.reset(); + emit currentRoomChanged(); + } - for (auto p : previewsToAdd) { - previewedRooms.insert(p, std::nullopt); - roomids.push_back(std::move(p)); - } + for (auto p : previewsToAdd) { + previewedRooms.insert(p, std::nullopt); + roomids.push_back(std::move(p)); + } - if (!suppressInsertNotification && - ((!wasInvite && !wasPreview) || !previewedRooms.empty())) - endInsertRows(); + if (!suppressInsertNotification && ((!wasInvite && !wasPreview) || !previewedRooms.empty())) + endInsertRows(); - emit ChatPage::instance()->newRoom(room_id); - } + emit ChatPage::instance()->newRoom(room_id); + } } void RoomlistModel::fetchPreview(QString roomid_) const { - std::string roomid = roomid_.toStdString(); - http::client()->get_state_event<mtx::events::state::Create>( - roomid, - "", - [this, roomid](const mtx::events::state::Create &c, mtx::http::RequestErr err) { - bool is_space = false; - if (!err) { - is_space = c.type == mtx::events::state::room_type::space; - } + std::string roomid = roomid_.toStdString(); + http::client()->get_state_event<mtx::events::state::Create>( + roomid, "", [this, roomid](const mtx::events::state::Create &c, mtx::http::RequestErr err) { + bool is_space = false; + if (!err) { + is_space = c.type == mtx::events::state::room_type::space; + } - http::client()->get_state_event<mtx::events::state::Avatar>( - roomid, - "", - [this, roomid, is_space](const mtx::events::state::Avatar &a, - mtx::http::RequestErr) { - auto avatar_url = a.url; + http::client()->get_state_event<mtx::events::state::Avatar>( + roomid, + "", + [this, roomid, is_space](const mtx::events::state::Avatar &a, mtx::http::RequestErr) { + auto avatar_url = a.url; - http::client()->get_state_event<mtx::events::state::Topic>( - roomid, - "", - [this, roomid, avatar_url, is_space]( - const mtx::events::state::Topic &t, mtx::http::RequestErr) { - auto topic = t.topic; - http::client()->get_state_event<mtx::events::state::Name>( - roomid, - "", - [this, roomid, topic, avatar_url, is_space]( - const mtx::events::state::Name &n, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "Failed to fetch name event to " - "create preview for {}", - roomid); - } + http::client()->get_state_event<mtx::events::state::Topic>( + roomid, + "", + [this, roomid, avatar_url, is_space](const mtx::events::state::Topic &t, + mtx::http::RequestErr) { + auto topic = t.topic; + http::client()->get_state_event<mtx::events::state::Name>( + roomid, + "", + [this, roomid, topic, avatar_url, is_space]( + const mtx::events::state::Name &n, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("Failed to fetch name event to " + "create preview for {}", + roomid); + } - // don't even add a preview, if we got not a single - // response - if (n.name.empty() && avatar_url.empty() && - topic.empty()) - return; + // don't even add a preview, if we got not a single + // response + if (n.name.empty() && avatar_url.empty() && topic.empty()) + return; - RoomInfo info{}; - info.name = n.name; - info.is_space = is_space; - info.avatar_url = avatar_url; - info.topic = topic; + RoomInfo info{}; + info.name = n.name; + info.is_space = is_space; + info.avatar_url = avatar_url; + info.topic = topic; - const_cast<RoomlistModel *>(this)->fetchedPreview( - QString::fromStdString(roomid), info); - }); - }); - }); - }); + const_cast<RoomlistModel *>(this)->fetchedPreview( + QString::fromStdString(roomid), info); + }); + }); + }); + }); } void RoomlistModel::sync(const mtx::responses::Rooms &rooms) { - for (const auto &[room_id, room] : rooms.join) { - auto qroomid = QString::fromStdString(room_id); + for (const auto &[room_id, room] : rooms.join) { + auto qroomid = QString::fromStdString(room_id); - // addRoom will only add the room, if it doesn't exist - addRoom(qroomid); - const auto &room_model = models.value(qroomid); - room_model->sync(room); - // room_model->addEvents(room.timeline); - connect(room_model.data(), - &TimelineModel::newCallEvent, - manager->callManager(), - &CallManager::syncEvent, - Qt::UniqueConnection); + // addRoom will only add the room, if it doesn't exist + addRoom(qroomid); + const auto &room_model = models.value(qroomid); + room_model->sync(room); + // room_model->addEvents(room.timeline); + connect(room_model.data(), + &TimelineModel::newCallEvent, + manager->callManager(), + &CallManager::syncEvent, + Qt::UniqueConnection); - if (ChatPage::instance()->userSettings()->typingNotifications()) { - for (const auto &ev : room.ephemeral.events) { - if (auto t = std::get_if< - mtx::events::EphemeralEvent<mtx::events::ephemeral::Typing>>( - &ev)) { - std::vector<QString> typing; - typing.reserve(t->content.user_ids.size()); - for (const auto &user : t->content.user_ids) { - if (user != http::client()->user_id().to_string()) - typing.push_back( - QString::fromStdString(user)); - } - room_model->updateTypingUsers(typing); - } - } + if (ChatPage::instance()->userSettings()->typingNotifications()) { + for (const auto &ev : room.ephemeral.events) { + if (auto t = + std::get_if<mtx::events::EphemeralEvent<mtx::events::ephemeral::Typing>>( + &ev)) { + std::vector<QString> typing; + typing.reserve(t->content.user_ids.size()); + for (const auto &user : t->content.user_ids) { + if (user != http::client()->user_id().to_string()) + typing.push_back(QString::fromStdString(user)); + } + room_model->updateTypingUsers(typing); } + } } + } - for (const auto &[room_id, room] : rooms.leave) { - (void)room; - auto qroomid = QString::fromStdString(room_id); + for (const auto &[room_id, room] : rooms.leave) { + (void)room; + auto qroomid = QString::fromStdString(room_id); - if ((currentRoom_ && currentRoom_->roomId() == qroomid) || - (currentRoomPreview_ && currentRoomPreview_->roomid() == qroomid)) - resetCurrentRoom(); + if ((currentRoom_ && currentRoom_->roomId() == qroomid) || + (currentRoomPreview_ && currentRoomPreview_->roomid() == qroomid)) + resetCurrentRoom(); - auto idx = this->roomidToIndex(qroomid); - if (idx != -1) { - beginRemoveRows(QModelIndex(), idx, idx); - roomids.erase(roomids.begin() + idx); - if (models.contains(qroomid)) - models.remove(qroomid); - else if (invites.contains(qroomid)) - invites.remove(qroomid); - endRemoveRows(); - } + auto idx = this->roomidToIndex(qroomid); + if (idx != -1) { + beginRemoveRows(QModelIndex(), idx, idx); + roomids.erase(roomids.begin() + idx); + if (models.contains(qroomid)) + models.remove(qroomid); + else if (invites.contains(qroomid)) + invites.remove(qroomid); + endRemoveRows(); } + } - for (const auto &[room_id, room] : rooms.invite) { - (void)room; - auto qroomid = QString::fromStdString(room_id); + for (const auto &[room_id, room] : rooms.invite) { + (void)room; + auto qroomid = QString::fromStdString(room_id); - auto invite = cache::client()->invite(room_id); - if (!invite) - continue; + auto invite = cache::client()->invite(room_id); + if (!invite) + continue; - if (invites.contains(qroomid)) { - invites[qroomid] = *invite; - auto idx = roomidToIndex(qroomid); - emit dataChanged(index(idx), index(idx)); - } else { - beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size()); - invites.insert(qroomid, *invite); - roomids.push_back(std::move(qroomid)); - endInsertRows(); - } + if (invites.contains(qroomid)) { + invites[qroomid] = *invite; + auto idx = roomidToIndex(qroomid); + emit dataChanged(index(idx), index(idx)); + } else { + beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size()); + invites.insert(qroomid, *invite); + roomids.push_back(std::move(qroomid)); + endInsertRows(); } + } } void RoomlistModel::initializeRooms() { - beginResetModel(); - models.clear(); - roomids.clear(); - invites.clear(); - currentRoom_ = nullptr; + beginResetModel(); + models.clear(); + roomids.clear(); + invites.clear(); + currentRoom_ = nullptr; - invites = cache::client()->invites(); - for (const auto &id : invites.keys()) - roomids.push_back(id); + invites = cache::client()->invites(); + for (const auto &id : invites.keys()) + roomids.push_back(id); - for (const auto &id : cache::client()->roomIds()) - addRoom(id, true); + for (const auto &id : cache::client()->roomIds()) + addRoom(id, true); - nhlog::db()->info("Restored {} rooms from cache", rowCount()); + nhlog::db()->info("Restored {} rooms from cache", rowCount()); - endResetModel(); + endResetModel(); } void RoomlistModel::clear() { - beginResetModel(); - models.clear(); - invites.clear(); - roomids.clear(); - currentRoom_ = nullptr; - emit currentRoomChanged(); - endResetModel(); + beginResetModel(); + models.clear(); + invites.clear(); + roomids.clear(); + currentRoom_ = nullptr; + emit currentRoomChanged(); + endResetModel(); } void RoomlistModel::joinPreview(QString roomid, QString parentSpace) { - if (previewedRooms.contains(roomid)) { - auto child = cache::client()->getStateEvent<mtx::events::state::space::Child>( - parentSpace.toStdString(), roomid.toStdString()); - ChatPage::instance()->joinRoomVia(roomid.toStdString(), - (child && child->content.via) - ? child->content.via.value() - : std::vector<std::string>{}, - false); - } + if (previewedRooms.contains(roomid)) { + auto child = cache::client()->getStateEvent<mtx::events::state::space::Child>( + parentSpace.toStdString(), roomid.toStdString()); + ChatPage::instance()->joinRoomVia( + roomid.toStdString(), + (child && child->content.via) ? child->content.via.value() : std::vector<std::string>{}, + false); + } } void RoomlistModel::acceptInvite(QString roomid) { - if (invites.contains(roomid)) { - // Don't remove invite yet, so that we can switch to it - ChatPage::instance()->joinRoom(roomid); - } + if (invites.contains(roomid)) { + // Don't remove invite yet, so that we can switch to it + ChatPage::instance()->joinRoom(roomid); + } } void RoomlistModel::declineInvite(QString roomid) { - if (invites.contains(roomid)) { - auto idx = roomidToIndex(roomid); + if (invites.contains(roomid)) { + auto idx = roomidToIndex(roomid); - if (idx != -1) { - beginRemoveRows(QModelIndex(), idx, idx); - roomids.erase(roomids.begin() + idx); - invites.remove(roomid); - endRemoveRows(); - ChatPage::instance()->leaveRoom(roomid); - } + if (idx != -1) { + beginRemoveRows(QModelIndex(), idx, idx); + roomids.erase(roomids.begin() + idx); + invites.remove(roomid); + endRemoveRows(); + ChatPage::instance()->leaveRoom(roomid); } + } } void RoomlistModel::leave(QString roomid) { - if (models.contains(roomid)) { - auto idx = roomidToIndex(roomid); + if (models.contains(roomid)) { + auto idx = roomidToIndex(roomid); - if (idx != -1) { - beginRemoveRows(QModelIndex(), idx, idx); - roomids.erase(roomids.begin() + idx); - models.remove(roomid); - endRemoveRows(); - ChatPage::instance()->leaveRoom(roomid); - } + if (idx != -1) { + beginRemoveRows(QModelIndex(), idx, idx); + roomids.erase(roomids.begin() + idx); + models.remove(roomid); + endRemoveRows(); + ChatPage::instance()->leaveRoom(roomid); } + } } void RoomlistModel::setCurrentRoom(QString roomid) { - if ((currentRoom_ && currentRoom_->roomId() == roomid) || - (currentRoomPreview_ && currentRoomPreview_->roomid() == roomid)) - return; + if ((currentRoom_ && currentRoom_->roomId() == roomid) || + (currentRoomPreview_ && currentRoomPreview_->roomid() == roomid)) + return; - nhlog::ui()->debug("Trying to switch to: {}", roomid.toStdString()); - if (models.contains(roomid)) { - currentRoom_ = models.value(roomid); - currentRoomPreview_.reset(); - emit currentRoomChanged(); - nhlog::ui()->debug("Switched to: {}", roomid.toStdString()); - } else if (invites.contains(roomid) || previewedRooms.contains(roomid)) { - currentRoom_ = nullptr; - std::optional<RoomInfo> i; - - RoomPreview p; + nhlog::ui()->debug("Trying to switch to: {}", roomid.toStdString()); + if (models.contains(roomid)) { + currentRoom_ = models.value(roomid); + currentRoomPreview_.reset(); + emit currentRoomChanged(); + nhlog::ui()->debug("Switched to: {}", roomid.toStdString()); + } else if (invites.contains(roomid) || previewedRooms.contains(roomid)) { + currentRoom_ = nullptr; + std::optional<RoomInfo> i; - if (invites.contains(roomid)) { - i = invites.value(roomid); - p.isInvite_ = true; - } else { - i = previewedRooms.value(roomid); - p.isInvite_ = false; - } + RoomPreview p; - if (i) { - p.roomid_ = roomid; - p.roomName_ = QString::fromStdString(i->name); - p.roomTopic_ = QString::fromStdString(i->topic); - p.roomAvatarUrl_ = QString::fromStdString(i->avatar_url); - currentRoomPreview_ = std::move(p); - } + if (invites.contains(roomid)) { + i = invites.value(roomid); + p.isInvite_ = true; + } else { + i = previewedRooms.value(roomid); + p.isInvite_ = false; + } - emit currentRoomChanged(); - nhlog::ui()->debug("Switched to: {}", roomid.toStdString()); + if (i) { + p.roomid_ = roomid; + p.roomName_ = QString::fromStdString(i->name); + p.roomTopic_ = QString::fromStdString(i->topic); + p.roomAvatarUrl_ = QString::fromStdString(i->avatar_url); + currentRoomPreview_ = std::move(p); } + + emit currentRoomChanged(); + nhlog::ui()->debug("Switched to: {}", roomid.toStdString()); + } } namespace { enum NotificationImportance : short { - ImportanceDisabled = -3, - NoPreview = -2, - Preview = -1, - AllEventsRead = 0, - NewMessage = 1, - NewMentions = 2, - Invite = 3, - SubSpace = 4, - CurrentSpace = 5, + ImportanceDisabled = -3, + NoPreview = -2, + Preview = -1, + AllEventsRead = 0, + NewMessage = 1, + NewMentions = 2, + Invite = 3, + SubSpace = 4, + CurrentSpace = 5, }; } short int FilteredRoomlistModel::calculateImportance(const QModelIndex &idx) const { - // Returns the degree of importance of the unread messages in the room. - // If sorting by importance is disabled in settings, this only ever - // returns ImportanceDisabled or Invite - if (sourceModel()->data(idx, RoomlistModel::IsSpace).toBool()) { - if (filterType == FilterBy::Space && - filterStr == sourceModel()->data(idx, RoomlistModel::RoomId).toString()) - return CurrentSpace; - else - return SubSpace; - } else if (sourceModel()->data(idx, RoomlistModel::IsPreview).toBool()) { - if (sourceModel()->data(idx, RoomlistModel::IsPreviewFetched).toBool()) - return Preview; - else - return NoPreview; - } else if (sourceModel()->data(idx, RoomlistModel::IsInvite).toBool()) { - return Invite; - } else if (!this->sortByImportance) { - return ImportanceDisabled; - } else if (sourceModel()->data(idx, RoomlistModel::HasLoudNotification).toBool()) { - return NewMentions; - } else if (sourceModel()->data(idx, RoomlistModel::NotificationCount).toInt() > 0) { - return NewMessage; - } else { - return AllEventsRead; - } + // Returns the degree of importance of the unread messages in the room. + // If sorting by importance is disabled in settings, this only ever + // returns ImportanceDisabled or Invite + if (sourceModel()->data(idx, RoomlistModel::IsSpace).toBool()) { + if (filterType == FilterBy::Space && + filterStr == sourceModel()->data(idx, RoomlistModel::RoomId).toString()) + return CurrentSpace; + else + return SubSpace; + } else if (sourceModel()->data(idx, RoomlistModel::IsPreview).toBool()) { + if (sourceModel()->data(idx, RoomlistModel::IsPreviewFetched).toBool()) + return Preview; + else + return NoPreview; + } else if (sourceModel()->data(idx, RoomlistModel::IsInvite).toBool()) { + return Invite; + } else if (!this->sortByImportance) { + return ImportanceDisabled; + } else if (sourceModel()->data(idx, RoomlistModel::HasLoudNotification).toBool()) { + return NewMentions; + } else if (sourceModel()->data(idx, RoomlistModel::NotificationCount).toInt() > 0) { + return NewMessage; + } else { + return AllEventsRead; + } } bool FilteredRoomlistModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - QModelIndex const left_idx = sourceModel()->index(left.row(), 0, QModelIndex()); - QModelIndex const right_idx = sourceModel()->index(right.row(), 0, QModelIndex()); + QModelIndex const left_idx = sourceModel()->index(left.row(), 0, QModelIndex()); + QModelIndex const right_idx = sourceModel()->index(right.row(), 0, QModelIndex()); - // Sort by "importance" (i.e. invites before mentions before - // notifs before new events before old events), then secondly - // by recency. + // Sort by "importance" (i.e. invites before mentions before + // notifs before new events before old events), then secondly + // by recency. - // Checking importance first - const auto a_importance = calculateImportance(left_idx); - const auto b_importance = calculateImportance(right_idx); - if (a_importance != b_importance) { - return a_importance > b_importance; - } + // Checking importance first + const auto a_importance = calculateImportance(left_idx); + const auto b_importance = calculateImportance(right_idx); + if (a_importance != b_importance) { + return a_importance > b_importance; + } - // Now sort by recency - // Zero if empty, otherwise the time that the event occured - uint64_t a_recency = sourceModel()->data(left_idx, RoomlistModel::Timestamp).toULongLong(); - uint64_t b_recency = sourceModel()->data(right_idx, RoomlistModel::Timestamp).toULongLong(); + // Now sort by recency + // Zero if empty, otherwise the time that the event occured + uint64_t a_recency = sourceModel()->data(left_idx, RoomlistModel::Timestamp).toULongLong(); + uint64_t b_recency = sourceModel()->data(right_idx, RoomlistModel::Timestamp).toULongLong(); - if (a_recency != b_recency) - return a_recency > b_recency; - else - return left.row() < right.row(); + if (a_recency != b_recency) + return a_recency > b_recency; + else + return left.row() < right.row(); } FilteredRoomlistModel::FilteredRoomlistModel(RoomlistModel *model, QObject *parent) : QSortFilterProxyModel(parent) , roomlistmodel(model) { - this->sortByImportance = UserSettings::instance()->sortByImportance(); - setSourceModel(model); - setDynamicSortFilter(true); + this->sortByImportance = UserSettings::instance()->sortByImportance(); + setSourceModel(model); + setDynamicSortFilter(true); - QObject::connect(UserSettings::instance().get(), - &UserSettings::roomSortingChanged, - this, - [this](bool sortByImportance_) { - this->sortByImportance = sortByImportance_; - invalidate(); - }); + QObject::connect(UserSettings::instance().get(), + &UserSettings::roomSortingChanged, + this, + [this](bool sortByImportance_) { + this->sortByImportance = sortByImportance_; + invalidate(); + }); - connect(roomlistmodel, - &RoomlistModel::currentRoomChanged, - this, - &FilteredRoomlistModel::currentRoomChanged); + connect(roomlistmodel, + &RoomlistModel::currentRoomChanged, + this, + &FilteredRoomlistModel::currentRoomChanged); - sort(0); + sort(0); } void FilteredRoomlistModel::updateHiddenTagsAndSpaces() { - hiddenTags.clear(); - hiddenSpaces.clear(); - for (const auto &t : UserSettings::instance()->hiddenTags()) { - if (t.startsWith("tag:")) - hiddenTags.push_back(t.mid(4)); - else if (t.startsWith("space:")) - hiddenSpaces.push_back(t.mid(6)); - } + hiddenTags.clear(); + hiddenSpaces.clear(); + for (const auto &t : UserSettings::instance()->hiddenTags()) { + if (t.startsWith("tag:")) + hiddenTags.push_back(t.mid(4)); + else if (t.startsWith("space:")) + hiddenSpaces.push_back(t.mid(6)); + } - invalidateFilter(); + invalidateFilter(); } bool FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) const { - if (filterType == FilterBy::Nothing) { - if (sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview) - .toBool()) { - return false; - } - - if (sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace) - .toBool()) { - return false; - } + if (filterType == FilterBy::Nothing) { + if (sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview) + .toBool()) { + return false; + } - if (!hiddenTags.empty()) { - auto tags = - sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags) - .toStringList(); + if (sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace) + .toBool()) { + return false; + } - for (const auto &t : tags) - if (hiddenTags.contains(t)) - return false; - } + if (!hiddenTags.empty()) { + auto tags = sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags) + .toStringList(); - if (!hiddenSpaces.empty()) { - auto parents = - sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces) - .toStringList(); - for (const auto &t : parents) - if (hiddenSpaces.contains(t)) - return false; - } + for (const auto &t : tags) + if (hiddenTags.contains(t)) + return false; + } - return true; - } else if (filterType == FilterBy::Tag) { - if (sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview) - .toBool()) { - return false; - } + if (!hiddenSpaces.empty()) { + auto parents = sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces) + .toStringList(); + for (const auto &t : parents) + if (hiddenSpaces.contains(t)) + return false; + } - if (sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace) - .toBool()) { - return false; - } + return true; + } else if (filterType == FilterBy::Tag) { + if (sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview) + .toBool()) { + return false; + } - auto tags = sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags) - .toStringList(); + if (sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace) + .toBool()) { + return false; + } - if (!tags.contains(filterStr)) - return false; + auto tags = sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags) + .toStringList(); - if (!hiddenTags.empty()) { - for (const auto &t : tags) - if (t != filterStr && hiddenTags.contains(t)) - return false; - } + if (!tags.contains(filterStr)) + return false; - if (!hiddenSpaces.empty()) { - auto parents = - sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces) - .toStringList(); - for (const auto &t : parents) - if (hiddenSpaces.contains(t)) - return false; - } + if (!hiddenTags.empty()) { + for (const auto &t : tags) + if (t != filterStr && hiddenTags.contains(t)) + return false; + } - return true; - } else if (filterType == FilterBy::Space) { - if (filterStr == sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::RoomId) - .toString()) - return true; + if (!hiddenSpaces.empty()) { + auto parents = sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces) + .toStringList(); + for (const auto &t : parents) + if (hiddenSpaces.contains(t)) + return false; + } - auto parents = - sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces) - .toStringList(); + return true; + } else if (filterType == FilterBy::Space) { + if (filterStr == sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::RoomId) + .toString()) + return true; - if (!parents.contains(filterStr)) - return false; + auto parents = sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces) + .toStringList(); - if (!hiddenTags.empty()) { - auto tags = - sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags) - .toStringList(); + if (!parents.contains(filterStr)) + return false; - for (const auto &t : tags) - if (hiddenTags.contains(t)) - return false; - } + if (!hiddenTags.empty()) { + auto tags = sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags) + .toStringList(); - if (!hiddenSpaces.empty()) { - for (const auto &t : parents) - if (hiddenSpaces.contains(t)) - return false; - } + for (const auto &t : tags) + if (hiddenTags.contains(t)) + return false; + } - if (sourceModel() - ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace) - .toBool() && - !parents.contains(filterStr)) { - return false; - } + if (!hiddenSpaces.empty()) { + for (const auto &t : parents) + if (hiddenSpaces.contains(t)) + return false; + } - return true; - } else { - return true; + if (sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace) + .toBool() && + !parents.contains(filterStr)) { + return false; } + + return true; + } else { + return true; + } } void FilteredRoomlistModel::toggleTag(QString roomid, QString tag, bool on) { - if (on) { - http::client()->put_tag( - roomid.toStdString(), tag.toStdString(), {}, [tag](mtx::http::RequestErr err) { - if (err) { - nhlog::ui()->error("Failed to add tag: {}, {}", - tag.toStdString(), - err->matrix_error.error); - } - }); - } else { - http::client()->delete_tag( - roomid.toStdString(), tag.toStdString(), [tag](mtx::http::RequestErr err) { - if (err) { - nhlog::ui()->error("Failed to delete tag: {}, {}", - tag.toStdString(), - err->matrix_error.error); - } - }); - } + if (on) { + http::client()->put_tag( + roomid.toStdString(), tag.toStdString(), {}, [tag](mtx::http::RequestErr err) { + if (err) { + nhlog::ui()->error( + "Failed to add tag: {}, {}", tag.toStdString(), err->matrix_error.error); + } + }); + } else { + http::client()->delete_tag( + roomid.toStdString(), tag.toStdString(), [tag](mtx::http::RequestErr err) { + if (err) { + nhlog::ui()->error( + "Failed to delete tag: {}, {}", tag.toStdString(), err->matrix_error.error); + } + }); + } } void FilteredRoomlistModel::nextRoomWithActivity() { - int roomWithMention = -1; - int roomWithNotification = -1; - int roomWithUnreadMessage = -1; - auto r = currentRoom(); - int currentRoomIdx = r ? roomidToIndex(r->roomId()) : -1; - // first look for mentions - for (int i = 0; i < (int)roomlistmodel->roomids.size(); i++) { - if (i == currentRoomIdx) - continue; - if (this->data(index(i, 0), RoomlistModel::HasLoudNotification).toBool()) { - roomWithMention = i; - break; - } - if (roomWithNotification == -1 && - this->data(index(i, 0), RoomlistModel::NotificationCount).toInt() > 0) { - roomWithNotification = i; - // don't break, we must continue looking for rooms with mentions - } - if (roomWithNotification == -1 && roomWithUnreadMessage == -1 && - this->data(index(i, 0), RoomlistModel::HasUnreadMessages).toBool()) { - roomWithUnreadMessage = i; - // don't break, we must continue looking for rooms with mentions - } + int roomWithMention = -1; + int roomWithNotification = -1; + int roomWithUnreadMessage = -1; + auto r = currentRoom(); + int currentRoomIdx = r ? roomidToIndex(r->roomId()) : -1; + // first look for mentions + for (int i = 0; i < (int)roomlistmodel->roomids.size(); i++) { + if (i == currentRoomIdx) + continue; + if (this->data(index(i, 0), RoomlistModel::HasLoudNotification).toBool()) { + roomWithMention = i; + break; } - QString targetRoomId = nullptr; - if (roomWithMention != -1) { - targetRoomId = - this->data(index(roomWithMention, 0), RoomlistModel::RoomId).toString(); - nhlog::ui()->debug("choosing {} for mentions", targetRoomId.toStdString()); - } else if (roomWithNotification != -1) { - targetRoomId = - this->data(index(roomWithNotification, 0), RoomlistModel::RoomId).toString(); - nhlog::ui()->debug("choosing {} for notifications", targetRoomId.toStdString()); - } else if (roomWithUnreadMessage != -1) { - targetRoomId = - this->data(index(roomWithUnreadMessage, 0), RoomlistModel::RoomId).toString(); - nhlog::ui()->debug("choosing {} for unread messages", targetRoomId.toStdString()); + if (roomWithNotification == -1 && + this->data(index(i, 0), RoomlistModel::NotificationCount).toInt() > 0) { + roomWithNotification = i; + // don't break, we must continue looking for rooms with mentions } - if (targetRoomId != nullptr) { - setCurrentRoom(targetRoomId); + if (roomWithNotification == -1 && roomWithUnreadMessage == -1 && + this->data(index(i, 0), RoomlistModel::HasUnreadMessages).toBool()) { + roomWithUnreadMessage = i; + // don't break, we must continue looking for rooms with mentions } + } + QString targetRoomId = nullptr; + if (roomWithMention != -1) { + targetRoomId = this->data(index(roomWithMention, 0), RoomlistModel::RoomId).toString(); + nhlog::ui()->debug("choosing {} for mentions", targetRoomId.toStdString()); + } else if (roomWithNotification != -1) { + targetRoomId = this->data(index(roomWithNotification, 0), RoomlistModel::RoomId).toString(); + nhlog::ui()->debug("choosing {} for notifications", targetRoomId.toStdString()); + } else if (roomWithUnreadMessage != -1) { + targetRoomId = + this->data(index(roomWithUnreadMessage, 0), RoomlistModel::RoomId).toString(); + nhlog::ui()->debug("choosing {} for unread messages", targetRoomId.toStdString()); + } + if (targetRoomId != nullptr) { + setCurrentRoom(targetRoomId); + } } void FilteredRoomlistModel::nextRoom() { - auto r = currentRoom(); + auto r = currentRoom(); - if (r) { - int idx = roomidToIndex(r->roomId()); - idx++; - if (idx < rowCount()) { - setCurrentRoom( - data(index(idx, 0), RoomlistModel::Roles::RoomId).toString()); - } + if (r) { + int idx = roomidToIndex(r->roomId()); + idx++; + if (idx < rowCount()) { + setCurrentRoom(data(index(idx, 0), RoomlistModel::Roles::RoomId).toString()); } + } } void FilteredRoomlistModel::previousRoom() { - auto r = currentRoom(); + auto r = currentRoom(); - if (r) { - int idx = roomidToIndex(r->roomId()); - idx--; - if (idx >= 0) { - setCurrentRoom( - data(index(idx, 0), RoomlistModel::Roles::RoomId).toString()); - } + if (r) { + int idx = roomidToIndex(r->roomId()); + idx--; + if (idx >= 0) { + setCurrentRoom(data(index(idx, 0), RoomlistModel::Roles::RoomId).toString()); } + } } diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index 27c14bec..458e0fe7 100644 --- a/src/timeline/RoomlistModel.h +++ b/src/timeline/RoomlistModel.h
@@ -20,195 +20,191 @@ class TimelineViewManager; class RoomPreview { - Q_GADGET - Q_PROPERTY(QString roomid READ roomid CONSTANT) - Q_PROPERTY(QString roomName READ roomName CONSTANT) - Q_PROPERTY(QString roomTopic READ roomTopic CONSTANT) - Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl CONSTANT) - Q_PROPERTY(bool isInvite READ isInvite CONSTANT) + Q_GADGET + Q_PROPERTY(QString roomid READ roomid CONSTANT) + Q_PROPERTY(QString roomName READ roomName CONSTANT) + Q_PROPERTY(QString roomTopic READ roomTopic CONSTANT) + Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl CONSTANT) + Q_PROPERTY(bool isInvite READ isInvite CONSTANT) public: - RoomPreview() {} + RoomPreview() {} - QString roomid() const { return roomid_; } - QString roomName() const { return roomName_; } - QString roomTopic() const { return roomTopic_; } - QString roomAvatarUrl() const { return roomAvatarUrl_; } - bool isInvite() const { return isInvite_; } + QString roomid() const { return roomid_; } + QString roomName() const { return roomName_; } + QString roomTopic() const { return roomTopic_; } + QString roomAvatarUrl() const { return roomAvatarUrl_; } + bool isInvite() const { return isInvite_; } - QString roomid_, roomName_, roomAvatarUrl_, roomTopic_; - bool isInvite_ = false; + QString roomid_, roomName_, roomAvatarUrl_, roomTopic_; + bool isInvite_ = false; }; class RoomlistModel : public QAbstractListModel { - Q_OBJECT - Q_PROPERTY(TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET - resetCurrentRoom) - Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged - RESET resetCurrentRoom) + Q_OBJECT + Q_PROPERTY( + TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET resetCurrentRoom) + Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged + RESET resetCurrentRoom) public: - enum Roles - { - AvatarUrl = Qt::UserRole, - RoomName, - RoomId, - LastMessage, - Time, - Timestamp, - HasUnreadMessages, - HasLoudNotification, - NotificationCount, - IsInvite, - IsSpace, - IsPreview, - IsPreviewFetched, - Tags, - ParentSpaces, - IsDirect, - DirectChatOtherUserId, - }; + enum Roles + { + AvatarUrl = Qt::UserRole, + RoomName, + RoomId, + LastMessage, + Time, + Timestamp, + HasUnreadMessages, + HasLoudNotification, + NotificationCount, + IsInvite, + IsSpace, + IsPreview, + IsPreviewFetched, + Tags, + ParentSpaces, + IsDirect, + DirectChatOtherUserId, + }; - RoomlistModel(TimelineViewManager *parent = nullptr); - QHash<int, QByteArray> roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - (void)parent; - return (int)roomids.size(); - } - QVariant data(const QModelIndex &index, int role) const override; - QSharedPointer<TimelineModel> getRoomById(QString id) const - { - if (models.contains(id)) - return models.value(id); - else - return {}; - } + RoomlistModel(TimelineViewManager *parent = nullptr); + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return (int)roomids.size(); + } + QVariant data(const QModelIndex &index, int role) const override; + QSharedPointer<TimelineModel> getRoomById(QString id) const + { + if (models.contains(id)) + return models.value(id); + else + return {}; + } public slots: - void initializeRooms(); - void sync(const mtx::responses::Rooms &rooms); - void clear(); - int roomidToIndex(QString roomid) - { - for (int i = 0; i < (int)roomids.size(); i++) { - if (roomids[i] == roomid) - return i; - } - - return -1; - } - void joinPreview(QString roomid, QString parentSpace); - void acceptInvite(QString roomid); - void declineInvite(QString roomid); - void leave(QString roomid); - TimelineModel *currentRoom() const { return currentRoom_.get(); } - RoomPreview currentRoomPreview() const - { - return currentRoomPreview_.value_or(RoomPreview{}); - } - void setCurrentRoom(QString roomid); - void resetCurrentRoom() - { - currentRoom_ = nullptr; - currentRoomPreview_.reset(); - emit currentRoomChanged(); + void initializeRooms(); + void sync(const mtx::responses::Rooms &rooms); + void clear(); + int roomidToIndex(QString roomid) + { + for (int i = 0; i < (int)roomids.size(); i++) { + if (roomids[i] == roomid) + return i; } + return -1; + } + void joinPreview(QString roomid, QString parentSpace); + void acceptInvite(QString roomid); + void declineInvite(QString roomid); + void leave(QString roomid); + TimelineModel *currentRoom() const { return currentRoom_.get(); } + RoomPreview currentRoomPreview() const { return currentRoomPreview_.value_or(RoomPreview{}); } + void setCurrentRoom(QString roomid); + void resetCurrentRoom() + { + currentRoom_ = nullptr; + currentRoomPreview_.reset(); + emit currentRoomChanged(); + } + private slots: - void updateReadStatus(const std::map<QString, bool> roomReadStatus_); + void updateReadStatus(const std::map<QString, bool> roomReadStatus_); signals: - void totalUnreadMessageCountUpdated(int unreadMessages); - void currentRoomChanged(); - void fetchedPreview(QString roomid, RoomInfo info); + void totalUnreadMessageCountUpdated(int unreadMessages); + void currentRoomChanged(); + void fetchedPreview(QString roomid, RoomInfo info); private: - void addRoom(const QString &room_id, bool suppressInsertNotification = false); - void fetchPreview(QString roomid) const; + void addRoom(const QString &room_id, bool suppressInsertNotification = false); + void fetchPreview(QString roomid) const; - TimelineViewManager *manager = nullptr; - std::vector<QString> roomids; - QHash<QString, RoomInfo> invites; - QHash<QString, QSharedPointer<TimelineModel>> models; - std::map<QString, bool> roomReadStatus; - QHash<QString, std::optional<RoomInfo>> previewedRooms; + TimelineViewManager *manager = nullptr; + std::vector<QString> roomids; + QHash<QString, RoomInfo> invites; + QHash<QString, QSharedPointer<TimelineModel>> models; + std::map<QString, bool> roomReadStatus; + QHash<QString, std::optional<RoomInfo>> previewedRooms; - QSharedPointer<TimelineModel> currentRoom_; - std::optional<RoomPreview> currentRoomPreview_; + QSharedPointer<TimelineModel> currentRoom_; + std::optional<RoomPreview> currentRoomPreview_; - friend class FilteredRoomlistModel; + friend class FilteredRoomlistModel; }; class FilteredRoomlistModel : public QSortFilterProxyModel { - Q_OBJECT - Q_PROPERTY(TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET - resetCurrentRoom) - Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged - RESET resetCurrentRoom) + Q_OBJECT + Q_PROPERTY( + TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET resetCurrentRoom) + Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged + RESET resetCurrentRoom) public: - FilteredRoomlistModel(RoomlistModel *model, QObject *parent = nullptr); - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; - bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override; + FilteredRoomlistModel(RoomlistModel *model, QObject *parent = nullptr); + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override; public slots: - int roomidToIndex(QString roomid) - { - return mapFromSource(roomlistmodel->index(roomlistmodel->roomidToIndex(roomid))) - .row(); - } - void joinPreview(QString roomid) - { - roomlistmodel->joinPreview(roomid, filterType == FilterBy::Space ? filterStr : ""); - } - void acceptInvite(QString roomid) { roomlistmodel->acceptInvite(roomid); } - void declineInvite(QString roomid) { roomlistmodel->declineInvite(roomid); } - void leave(QString roomid) { roomlistmodel->leave(roomid); } - void toggleTag(QString roomid, QString tag, bool on); + int roomidToIndex(QString roomid) + { + return mapFromSource(roomlistmodel->index(roomlistmodel->roomidToIndex(roomid))).row(); + } + void joinPreview(QString roomid) + { + roomlistmodel->joinPreview(roomid, filterType == FilterBy::Space ? filterStr : ""); + } + void acceptInvite(QString roomid) { roomlistmodel->acceptInvite(roomid); } + void declineInvite(QString roomid) { roomlistmodel->declineInvite(roomid); } + void leave(QString roomid) { roomlistmodel->leave(roomid); } + void toggleTag(QString roomid, QString tag, bool on); - TimelineModel *currentRoom() const { return roomlistmodel->currentRoom(); } - RoomPreview currentRoomPreview() const { return roomlistmodel->currentRoomPreview(); } - void setCurrentRoom(QString roomid) { roomlistmodel->setCurrentRoom(std::move(roomid)); } - void resetCurrentRoom() { roomlistmodel->resetCurrentRoom(); } + TimelineModel *currentRoom() const { return roomlistmodel->currentRoom(); } + RoomPreview currentRoomPreview() const { return roomlistmodel->currentRoomPreview(); } + void setCurrentRoom(QString roomid) { roomlistmodel->setCurrentRoom(std::move(roomid)); } + void resetCurrentRoom() { roomlistmodel->resetCurrentRoom(); } - void nextRoomWithActivity(); - void nextRoom(); - void previousRoom(); + void nextRoomWithActivity(); + void nextRoom(); + void previousRoom(); - void updateFilterTag(QString tagId) - { - if (tagId.startsWith("tag:")) { - filterType = FilterBy::Tag; - filterStr = tagId.mid(4); - } else if (tagId.startsWith("space:")) { - filterType = FilterBy::Space; - filterStr = tagId.mid(6); - } else { - filterType = FilterBy::Nothing; - filterStr.clear(); - } - - invalidateFilter(); + void updateFilterTag(QString tagId) + { + if (tagId.startsWith("tag:")) { + filterType = FilterBy::Tag; + filterStr = tagId.mid(4); + } else if (tagId.startsWith("space:")) { + filterType = FilterBy::Space; + filterStr = tagId.mid(6); + } else { + filterType = FilterBy::Nothing; + filterStr.clear(); } - void updateHiddenTagsAndSpaces(); + invalidateFilter(); + } + + void updateHiddenTagsAndSpaces(); signals: - void currentRoomChanged(); + void currentRoomChanged(); private: - short int calculateImportance(const QModelIndex &idx) const; - RoomlistModel *roomlistmodel; - bool sortByImportance = true; + short int calculateImportance(const QModelIndex &idx) const; + RoomlistModel *roomlistmodel; + bool sortByImportance = true; - enum class FilterBy - { - Tag, - Space, - Nothing, - }; - QString filterStr = ""; - FilterBy filterType = FilterBy::Nothing; - QStringList hiddenTags, hiddenSpaces; + enum class FilterBy + { + Tag, + Space, + Nothing, + }; + QString filterStr = ""; + FilterBy filterType = FilterBy::Nothing; + QStringList hiddenTags, hiddenSpaces; }; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 00f6d9df..720a78fe 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp
@@ -38,288 +38,285 @@ namespace std { inline uint qHash(const std::string &key, uint seed = 0) { - return qHash(QByteArray::fromRawData(key.data(), (int)key.length()), seed); + return qHash(QByteArray::fromRawData(key.data(), (int)key.length()), seed); } } namespace { struct RoomEventType { - template<class T> - qml_mtx_events::EventType operator()(const mtx::events::Event<T> &e) - { - using mtx::events::EventType; - switch (e.type) { - case EventType::RoomKeyRequest: - return qml_mtx_events::EventType::KeyRequest; - case EventType::Reaction: - return qml_mtx_events::EventType::Reaction; - case EventType::RoomAliases: - return qml_mtx_events::EventType::Aliases; - case EventType::RoomAvatar: - return qml_mtx_events::EventType::Avatar; - case EventType::RoomCanonicalAlias: - return qml_mtx_events::EventType::CanonicalAlias; - case EventType::RoomCreate: - return qml_mtx_events::EventType::RoomCreate; - case EventType::RoomEncrypted: - return qml_mtx_events::EventType::Encrypted; - case EventType::RoomEncryption: - return qml_mtx_events::EventType::Encryption; - case EventType::RoomGuestAccess: - return qml_mtx_events::EventType::RoomGuestAccess; - case EventType::RoomHistoryVisibility: - return qml_mtx_events::EventType::RoomHistoryVisibility; - case EventType::RoomJoinRules: - return qml_mtx_events::EventType::RoomJoinRules; - case EventType::RoomMember: - return qml_mtx_events::EventType::Member; - case EventType::RoomMessage: - return qml_mtx_events::EventType::UnknownMessage; - case EventType::RoomName: - return qml_mtx_events::EventType::Name; - case EventType::RoomPowerLevels: - return qml_mtx_events::EventType::PowerLevels; - case EventType::RoomTopic: - return qml_mtx_events::EventType::Topic; - case EventType::RoomTombstone: - return qml_mtx_events::EventType::Tombstone; - case EventType::RoomRedaction: - return qml_mtx_events::EventType::Redaction; - case EventType::RoomPinnedEvents: - return qml_mtx_events::EventType::PinnedEvents; - case EventType::Sticker: - return qml_mtx_events::EventType::Sticker; - case EventType::Tag: - return qml_mtx_events::EventType::Tag; - case EventType::Unsupported: - return qml_mtx_events::EventType::Unsupported; - default: - return qml_mtx_events::EventType::UnknownMessage; - } - } - qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Audio> &) - { - return qml_mtx_events::EventType::AudioMessage; - } - qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Emote> &) - { - return qml_mtx_events::EventType::EmoteMessage; - } - qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::File> &) - { - return qml_mtx_events::EventType::FileMessage; - } - qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Image> &) - { - return qml_mtx_events::EventType::ImageMessage; - } - qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Notice> &) - { - return qml_mtx_events::EventType::NoticeMessage; - } - qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Text> &) - { - return qml_mtx_events::EventType::TextMessage; - } - qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Video> &) - { - return qml_mtx_events::EventType::VideoMessage; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::KeyVerificationRequest> &) - { - return qml_mtx_events::EventType::KeyVerificationRequest; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::KeyVerificationStart> &) - { - return qml_mtx_events::EventType::KeyVerificationStart; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::KeyVerificationMac> &) - { - return qml_mtx_events::EventType::KeyVerificationMac; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::KeyVerificationAccept> &) - { - return qml_mtx_events::EventType::KeyVerificationAccept; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::KeyVerificationReady> &) - { - return qml_mtx_events::EventType::KeyVerificationReady; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::KeyVerificationCancel> &) - { - return qml_mtx_events::EventType::KeyVerificationCancel; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::KeyVerificationKey> &) - { - return qml_mtx_events::EventType::KeyVerificationKey; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::KeyVerificationDone> &) - { - return qml_mtx_events::EventType::KeyVerificationDone; - } - qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Redacted> &) - { - return qml_mtx_events::EventType::Redacted; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::CallInvite> &) - { - return qml_mtx_events::EventType::CallInvite; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::CallAnswer> &) - { - return qml_mtx_events::EventType::CallAnswer; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::CallHangUp> &) - { - return qml_mtx_events::EventType::CallHangUp; - } - qml_mtx_events::EventType operator()( - const mtx::events::Event<mtx::events::msg::CallCandidates> &) - { - return qml_mtx_events::EventType::CallCandidates; + template<class T> + qml_mtx_events::EventType operator()(const mtx::events::Event<T> &e) + { + using mtx::events::EventType; + switch (e.type) { + case EventType::RoomKeyRequest: + return qml_mtx_events::EventType::KeyRequest; + case EventType::Reaction: + return qml_mtx_events::EventType::Reaction; + case EventType::RoomAliases: + return qml_mtx_events::EventType::Aliases; + case EventType::RoomAvatar: + return qml_mtx_events::EventType::Avatar; + case EventType::RoomCanonicalAlias: + return qml_mtx_events::EventType::CanonicalAlias; + case EventType::RoomCreate: + return qml_mtx_events::EventType::RoomCreate; + case EventType::RoomEncrypted: + return qml_mtx_events::EventType::Encrypted; + case EventType::RoomEncryption: + return qml_mtx_events::EventType::Encryption; + case EventType::RoomGuestAccess: + return qml_mtx_events::EventType::RoomGuestAccess; + case EventType::RoomHistoryVisibility: + return qml_mtx_events::EventType::RoomHistoryVisibility; + case EventType::RoomJoinRules: + return qml_mtx_events::EventType::RoomJoinRules; + case EventType::RoomMember: + return qml_mtx_events::EventType::Member; + case EventType::RoomMessage: + return qml_mtx_events::EventType::UnknownMessage; + case EventType::RoomName: + return qml_mtx_events::EventType::Name; + case EventType::RoomPowerLevels: + return qml_mtx_events::EventType::PowerLevels; + case EventType::RoomTopic: + return qml_mtx_events::EventType::Topic; + case EventType::RoomTombstone: + return qml_mtx_events::EventType::Tombstone; + case EventType::RoomRedaction: + return qml_mtx_events::EventType::Redaction; + case EventType::RoomPinnedEvents: + return qml_mtx_events::EventType::PinnedEvents; + case EventType::Sticker: + return qml_mtx_events::EventType::Sticker; + case EventType::Tag: + return qml_mtx_events::EventType::Tag; + case EventType::Unsupported: + return qml_mtx_events::EventType::Unsupported; + default: + return qml_mtx_events::EventType::UnknownMessage; } - // ::EventType::Type operator()(const Event<mtx::events::msg::Location> &e) { return - // ::EventType::LocationMessage; } + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Audio> &) + { + return qml_mtx_events::EventType::AudioMessage; + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Emote> &) + { + return qml_mtx_events::EventType::EmoteMessage; + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::File> &) + { + return qml_mtx_events::EventType::FileMessage; + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Image> &) + { + return qml_mtx_events::EventType::ImageMessage; + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Notice> &) + { + return qml_mtx_events::EventType::NoticeMessage; + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Text> &) + { + return qml_mtx_events::EventType::TextMessage; + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Video> &) + { + return qml_mtx_events::EventType::VideoMessage; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event<mtx::events::msg::KeyVerificationRequest> &) + { + return qml_mtx_events::EventType::KeyVerificationRequest; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event<mtx::events::msg::KeyVerificationStart> &) + { + return qml_mtx_events::EventType::KeyVerificationStart; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event<mtx::events::msg::KeyVerificationMac> &) + { + return qml_mtx_events::EventType::KeyVerificationMac; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event<mtx::events::msg::KeyVerificationAccept> &) + { + return qml_mtx_events::EventType::KeyVerificationAccept; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event<mtx::events::msg::KeyVerificationReady> &) + { + return qml_mtx_events::EventType::KeyVerificationReady; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event<mtx::events::msg::KeyVerificationCancel> &) + { + return qml_mtx_events::EventType::KeyVerificationCancel; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event<mtx::events::msg::KeyVerificationKey> &) + { + return qml_mtx_events::EventType::KeyVerificationKey; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event<mtx::events::msg::KeyVerificationDone> &) + { + return qml_mtx_events::EventType::KeyVerificationDone; + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::Redacted> &) + { + return qml_mtx_events::EventType::Redacted; + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::CallInvite> &) + { + return qml_mtx_events::EventType::CallInvite; + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::CallAnswer> &) + { + return qml_mtx_events::EventType::CallAnswer; + } + qml_mtx_events::EventType operator()(const mtx::events::Event<mtx::events::msg::CallHangUp> &) + { + return qml_mtx_events::EventType::CallHangUp; + } + qml_mtx_events::EventType operator()( + const mtx::events::Event<mtx::events::msg::CallCandidates> &) + { + return qml_mtx_events::EventType::CallCandidates; + } + // ::EventType::Type operator()(const Event<mtx::events::msg::Location> &e) { return + // ::EventType::LocationMessage; } }; } qml_mtx_events::EventType toRoomEventType(const mtx::events::collections::TimelineEvents &event) { - return std::visit(RoomEventType{}, event); + return std::visit(RoomEventType{}, event); } QString toRoomEventTypeString(const mtx::events::collections::TimelineEvents &event) { - return std::visit([](const auto &e) { return QString::fromStdString(to_string(e.type)); }, - event); + return std::visit([](const auto &e) { return QString::fromStdString(to_string(e.type)); }, + event); } mtx::events::EventType qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t) { - switch (t) { - // Unsupported event - case qml_mtx_events::Unsupported: - return mtx::events::EventType::Unsupported; + switch (t) { + // Unsupported event + case qml_mtx_events::Unsupported: + return mtx::events::EventType::Unsupported; - /// m.room_key_request - case qml_mtx_events::KeyRequest: - return mtx::events::EventType::RoomKeyRequest; - /// m.reaction: - case qml_mtx_events::Reaction: - return mtx::events::EventType::Reaction; - /// m.room.aliases - case qml_mtx_events::Aliases: - return mtx::events::EventType::RoomAliases; - /// m.room.avatar - case qml_mtx_events::Avatar: - return mtx::events::EventType::RoomAvatar; - /// m.call.invite - case qml_mtx_events::CallInvite: - return mtx::events::EventType::CallInvite; - /// m.call.answer - case qml_mtx_events::CallAnswer: - return mtx::events::EventType::CallAnswer; - /// m.call.hangup - case qml_mtx_events::CallHangUp: - return mtx::events::EventType::CallHangUp; - /// m.call.candidates - case qml_mtx_events::CallCandidates: - return mtx::events::EventType::CallCandidates; - /// m.room.canonical_alias - case qml_mtx_events::CanonicalAlias: - return mtx::events::EventType::RoomCanonicalAlias; - /// m.room.create - case qml_mtx_events::RoomCreate: - return mtx::events::EventType::RoomCreate; - /// m.room.encrypted. - case qml_mtx_events::Encrypted: - return mtx::events::EventType::RoomEncrypted; - /// m.room.encryption. - case qml_mtx_events::Encryption: - return mtx::events::EventType::RoomEncryption; - /// m.room.guest_access - case qml_mtx_events::RoomGuestAccess: - return mtx::events::EventType::RoomGuestAccess; - /// m.room.history_visibility - case qml_mtx_events::RoomHistoryVisibility: - return mtx::events::EventType::RoomHistoryVisibility; - /// m.room.join_rules - case qml_mtx_events::RoomJoinRules: - return mtx::events::EventType::RoomJoinRules; - /// m.room.member - case qml_mtx_events::Member: - return mtx::events::EventType::RoomMember; - /// m.room.name - case qml_mtx_events::Name: - return mtx::events::EventType::RoomName; - /// m.room.power_levels - case qml_mtx_events::PowerLevels: - return mtx::events::EventType::RoomPowerLevels; - /// m.room.tombstone - case qml_mtx_events::Tombstone: - return mtx::events::EventType::RoomTombstone; - /// m.room.topic - case qml_mtx_events::Topic: - return mtx::events::EventType::RoomTopic; - /// m.room.redaction - case qml_mtx_events::Redaction: - return mtx::events::EventType::RoomRedaction; - /// m.room.pinned_events - case qml_mtx_events::PinnedEvents: - return mtx::events::EventType::RoomPinnedEvents; - // m.sticker - case qml_mtx_events::Sticker: - return mtx::events::EventType::Sticker; - // m.tag - case qml_mtx_events::Tag: - return mtx::events::EventType::Tag; - /// m.room.message - case qml_mtx_events::AudioMessage: - case qml_mtx_events::EmoteMessage: - case qml_mtx_events::FileMessage: - case qml_mtx_events::ImageMessage: - case qml_mtx_events::LocationMessage: - case qml_mtx_events::NoticeMessage: - case qml_mtx_events::TextMessage: - case qml_mtx_events::VideoMessage: - case qml_mtx_events::Redacted: - case qml_mtx_events::UnknownMessage: - case qml_mtx_events::KeyVerificationRequest: - case qml_mtx_events::KeyVerificationStart: - case qml_mtx_events::KeyVerificationMac: - case qml_mtx_events::KeyVerificationAccept: - case qml_mtx_events::KeyVerificationCancel: - case qml_mtx_events::KeyVerificationKey: - 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::ImagePackInRoom; - //! 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; - }; + /// m.room_key_request + case qml_mtx_events::KeyRequest: + return mtx::events::EventType::RoomKeyRequest; + /// m.reaction: + case qml_mtx_events::Reaction: + return mtx::events::EventType::Reaction; + /// m.room.aliases + case qml_mtx_events::Aliases: + return mtx::events::EventType::RoomAliases; + /// m.room.avatar + case qml_mtx_events::Avatar: + return mtx::events::EventType::RoomAvatar; + /// m.call.invite + case qml_mtx_events::CallInvite: + return mtx::events::EventType::CallInvite; + /// m.call.answer + case qml_mtx_events::CallAnswer: + return mtx::events::EventType::CallAnswer; + /// m.call.hangup + case qml_mtx_events::CallHangUp: + return mtx::events::EventType::CallHangUp; + /// m.call.candidates + case qml_mtx_events::CallCandidates: + return mtx::events::EventType::CallCandidates; + /// m.room.canonical_alias + case qml_mtx_events::CanonicalAlias: + return mtx::events::EventType::RoomCanonicalAlias; + /// m.room.create + case qml_mtx_events::RoomCreate: + return mtx::events::EventType::RoomCreate; + /// m.room.encrypted. + case qml_mtx_events::Encrypted: + return mtx::events::EventType::RoomEncrypted; + /// m.room.encryption. + case qml_mtx_events::Encryption: + return mtx::events::EventType::RoomEncryption; + /// m.room.guest_access + case qml_mtx_events::RoomGuestAccess: + return mtx::events::EventType::RoomGuestAccess; + /// m.room.history_visibility + case qml_mtx_events::RoomHistoryVisibility: + return mtx::events::EventType::RoomHistoryVisibility; + /// m.room.join_rules + case qml_mtx_events::RoomJoinRules: + return mtx::events::EventType::RoomJoinRules; + /// m.room.member + case qml_mtx_events::Member: + return mtx::events::EventType::RoomMember; + /// m.room.name + case qml_mtx_events::Name: + return mtx::events::EventType::RoomName; + /// m.room.power_levels + case qml_mtx_events::PowerLevels: + return mtx::events::EventType::RoomPowerLevels; + /// m.room.tombstone + case qml_mtx_events::Tombstone: + return mtx::events::EventType::RoomTombstone; + /// m.room.topic + case qml_mtx_events::Topic: + return mtx::events::EventType::RoomTopic; + /// m.room.redaction + case qml_mtx_events::Redaction: + return mtx::events::EventType::RoomRedaction; + /// m.room.pinned_events + case qml_mtx_events::PinnedEvents: + return mtx::events::EventType::RoomPinnedEvents; + // m.sticker + case qml_mtx_events::Sticker: + return mtx::events::EventType::Sticker; + // m.tag + case qml_mtx_events::Tag: + return mtx::events::EventType::Tag; + /// m.room.message + case qml_mtx_events::AudioMessage: + case qml_mtx_events::EmoteMessage: + case qml_mtx_events::FileMessage: + case qml_mtx_events::ImageMessage: + case qml_mtx_events::LocationMessage: + case qml_mtx_events::NoticeMessage: + case qml_mtx_events::TextMessage: + case qml_mtx_events::VideoMessage: + case qml_mtx_events::Redacted: + case qml_mtx_events::UnknownMessage: + case qml_mtx_events::KeyVerificationRequest: + case qml_mtx_events::KeyVerificationStart: + case qml_mtx_events::KeyVerificationMac: + case qml_mtx_events::KeyVerificationAccept: + case qml_mtx_events::KeyVerificationCancel: + case qml_mtx_events::KeyVerificationKey: + 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::ImagePackInRoom; + //! 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; + }; } TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObject *parent) @@ -329,566 +326,549 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , manager_(manager) , permissions_{room_id} { - lastMessage_.timestamp = 0; + lastMessage_.timestamp = 0; - if (auto create = - cache::client()->getStateEvent<mtx::events::state::Create>(room_id.toStdString())) - this->isSpace_ = create->content.type == mtx::events::state::room_type::space; - this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); + if (auto create = + cache::client()->getStateEvent<mtx::events::state::Create>(room_id.toStdString())) + this->isSpace_ = create->content.type == mtx::events::state::room_type::space; + this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); - // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it - // needs to be - connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged); + // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it + // needs to be + connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged); - connect( - this, - &TimelineModel::redactionFailed, - this, - [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, - Qt::QueuedConnection); + connect( + this, + &TimelineModel::redactionFailed, + this, + [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, + Qt::QueuedConnection); - connect(this, - &TimelineModel::newMessageToSend, - this, - &TimelineModel::addPendingMessage, - Qt::QueuedConnection); - connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending); + connect(this, + &TimelineModel::newMessageToSend, + this, + &TimelineModel::addPendingMessage, + Qt::QueuedConnection); + connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending); - connect(&events, &EventStore::dataChanged, this, [this](int from, int to) { - relatedEventCacheBuster++; - nhlog::ui()->debug( - "data changed {} to {}", events.size() - to - 1, events.size() - from - 1); - emit dataChanged(index(events.size() - to - 1, 0), - index(events.size() - from - 1, 0)); - }); + connect(&events, &EventStore::dataChanged, this, [this](int from, int to) { + relatedEventCacheBuster++; + nhlog::ui()->debug( + "data changed {} to {}", events.size() - to - 1, events.size() - from - 1); + emit dataChanged(index(events.size() - to - 1, 0), index(events.size() - from - 1, 0)); + }); - connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) { - int first = events.size() - to; - int last = events.size() - from; - if (from >= events.size()) { - int batch_size = to - from; - first += batch_size; - last += batch_size; - } else { - first -= 1; - last -= 1; - } - nhlog::ui()->debug("begin insert from {} to {}", first, last); - beginInsertRows(QModelIndex(), first, last); - }); - connect(&events, &EventStore::endInsertRows, this, [this]() { endInsertRows(); }); - connect(&events, &EventStore::beginResetModel, this, [this]() { beginResetModel(); }); - connect(&events, &EventStore::endResetModel, this, [this]() { endResetModel(); }); - connect(&events, &EventStore::newEncryptedImage, this, &TimelineModel::newEncryptedImage); - connect( - &events, &EventStore::fetchedMore, this, [this]() { setPaginationInProgress(false); }); - connect(&events, - &EventStore::startDMVerification, - this, - [this](mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> msg) { - ChatPage::instance()->receivedRoomDeviceVerificationRequest(msg, this); - }); - connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) { - this->updateFlowEventId(event_id); - }); + connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) { + int first = events.size() - to; + int last = events.size() - from; + if (from >= events.size()) { + int batch_size = to - from; + first += batch_size; + last += batch_size; + } else { + first -= 1; + last -= 1; + } + nhlog::ui()->debug("begin insert from {} to {}", first, last); + beginInsertRows(QModelIndex(), first, last); + }); + connect(&events, &EventStore::endInsertRows, this, [this]() { endInsertRows(); }); + connect(&events, &EventStore::beginResetModel, this, [this]() { beginResetModel(); }); + connect(&events, &EventStore::endResetModel, this, [this]() { endResetModel(); }); + connect(&events, &EventStore::newEncryptedImage, this, &TimelineModel::newEncryptedImage); + connect(&events, &EventStore::fetchedMore, this, [this]() { setPaginationInProgress(false); }); + connect(&events, + &EventStore::startDMVerification, + this, + [this](mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> msg) { + ChatPage::instance()->receivedRoomDeviceVerificationRequest(msg, this); + }); + connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) { + this->updateFlowEventId(event_id); + }); - // When a message is sent, check if the current edit/reply relates to that message, - // and update the event_id so that it points to the sent message and not the pending one. - connect(&events, - &EventStore::messageSent, - this, - [this](std::string txn_id, std::string event_id) { - if (edit_.toStdString() == txn_id) { - edit_ = QString::fromStdString(event_id); - emit editChanged(edit_); - } - if (reply_.toStdString() == txn_id) { - reply_ = QString::fromStdString(event_id); - emit replyChanged(reply_); - } - }); + // When a message is sent, check if the current edit/reply relates to that message, + // and update the event_id so that it points to the sent message and not the pending one. + connect( + &events, &EventStore::messageSent, this, [this](std::string txn_id, std::string event_id) { + if (edit_.toStdString() == txn_id) { + edit_ = QString::fromStdString(event_id); + emit editChanged(edit_); + } + if (reply_.toStdString() == txn_id) { + reply_ = QString::fromStdString(event_id); + emit replyChanged(reply_); + } + }); - connect(manager_, - &TimelineViewManager::initialSyncChanged, - &events, - &EventStore::enableKeyRequests); + connect( + manager_, &TimelineViewManager::initialSyncChanged, &events, &EventStore::enableKeyRequests); - connect(this, &TimelineModel::encryptionChanged, this, &TimelineModel::trustlevelChanged); - connect( - this, &TimelineModel::roomMemberCountChanged, this, &TimelineModel::trustlevelChanged); - connect(cache::client(), - &Cache::verificationStatusChanged, - this, - &TimelineModel::trustlevelChanged); + connect(this, &TimelineModel::encryptionChanged, this, &TimelineModel::trustlevelChanged); + connect(this, &TimelineModel::roomMemberCountChanged, this, &TimelineModel::trustlevelChanged); + connect( + cache::client(), &Cache::verificationStatusChanged, this, &TimelineModel::trustlevelChanged); - showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent); + showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent); } QHash<int, QByteArray> TimelineModel::roleNames() const { - return { - {Type, "type"}, - {TypeString, "typeString"}, - {IsOnlyEmoji, "isOnlyEmoji"}, - {Body, "body"}, - {FormattedBody, "formattedBody"}, - {PreviousMessageUserId, "previousMessageUserId"}, - {IsSender, "isSender"}, - {UserId, "userId"}, - {UserName, "userName"}, - {PreviousMessageDay, "previousMessageDay"}, - {Day, "day"}, - {Timestamp, "timestamp"}, - {Url, "url"}, - {ThumbnailUrl, "thumbnailUrl"}, - {Blurhash, "blurhash"}, - {Filename, "filename"}, - {Filesize, "filesize"}, - {MimeType, "mimetype"}, - {OriginalHeight, "originalHeight"}, - {OriginalWidth, "originalWidth"}, - {ProportionalHeight, "proportionalHeight"}, - {EventId, "eventId"}, - {State, "status"}, - {IsEdited, "isEdited"}, - {IsEditable, "isEditable"}, - {IsEncrypted, "isEncrypted"}, - {Trustlevel, "trustlevel"}, - {EncryptionError, "encryptionError"}, - {ReplyTo, "replyTo"}, - {Reactions, "reactions"}, - {RoomId, "roomId"}, - {RoomName, "roomName"}, - {RoomTopic, "roomTopic"}, - {CallType, "callType"}, - {Dump, "dump"}, - {RelatedEventCacheBuster, "relatedEventCacheBuster"}, - }; + return { + {Type, "type"}, + {TypeString, "typeString"}, + {IsOnlyEmoji, "isOnlyEmoji"}, + {Body, "body"}, + {FormattedBody, "formattedBody"}, + {PreviousMessageUserId, "previousMessageUserId"}, + {IsSender, "isSender"}, + {UserId, "userId"}, + {UserName, "userName"}, + {PreviousMessageDay, "previousMessageDay"}, + {Day, "day"}, + {Timestamp, "timestamp"}, + {Url, "url"}, + {ThumbnailUrl, "thumbnailUrl"}, + {Blurhash, "blurhash"}, + {Filename, "filename"}, + {Filesize, "filesize"}, + {MimeType, "mimetype"}, + {OriginalHeight, "originalHeight"}, + {OriginalWidth, "originalWidth"}, + {ProportionalHeight, "proportionalHeight"}, + {EventId, "eventId"}, + {State, "status"}, + {IsEdited, "isEdited"}, + {IsEditable, "isEditable"}, + {IsEncrypted, "isEncrypted"}, + {Trustlevel, "trustlevel"}, + {EncryptionError, "encryptionError"}, + {ReplyTo, "replyTo"}, + {Reactions, "reactions"}, + {RoomId, "roomId"}, + {RoomName, "roomName"}, + {RoomTopic, "roomTopic"}, + {CallType, "callType"}, + {Dump, "dump"}, + {RelatedEventCacheBuster, "relatedEventCacheBuster"}, + }; } int TimelineModel::rowCount(const QModelIndex &parent) const { - Q_UNUSED(parent); - return this->events.size(); + Q_UNUSED(parent); + return this->events.size(); } QVariantMap TimelineModel::getDump(QString eventId, QString relatedTo) const { - if (auto event = events.get(eventId.toStdString(), relatedTo.toStdString())) - return data(*event, Dump).toMap(); - return {}; + if (auto event = events.get(eventId.toStdString(), relatedTo.toStdString())) + return data(*event, Dump).toMap(); + return {}; } QVariant TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int role) const { - using namespace mtx::accessors; - namespace acc = mtx::accessors; + using namespace mtx::accessors; + namespace acc = mtx::accessors; - switch (role) { - case IsSender: - return QVariant(acc::sender(event) == http::client()->user_id().to_string()); - case UserId: - return QVariant(QString::fromStdString(acc::sender(event))); - case UserName: - return QVariant(displayName(QString::fromStdString(acc::sender(event)))); + switch (role) { + case IsSender: + return QVariant(acc::sender(event) == http::client()->user_id().to_string()); + case UserId: + return QVariant(QString::fromStdString(acc::sender(event))); + case UserName: + return QVariant(displayName(QString::fromStdString(acc::sender(event)))); - case Day: { - QDateTime prevDate = origin_server_ts(event); - prevDate.setTime(QTime()); - return QVariant(prevDate.toMSecsSinceEpoch()); - } - case Timestamp: - return QVariant(origin_server_ts(event)); - case Type: - return QVariant(toRoomEventType(event)); - case TypeString: - return QVariant(toRoomEventTypeString(event)); - case IsOnlyEmoji: { - QString qBody = QString::fromStdString(body(event)); - - QVector<uint> utf32_string = qBody.toUcs4(); - int emojiCount = 0; + case Day: { + QDateTime prevDate = origin_server_ts(event); + prevDate.setTime(QTime()); + return QVariant(prevDate.toMSecsSinceEpoch()); + } + case Timestamp: + return QVariant(origin_server_ts(event)); + case Type: + return QVariant(toRoomEventType(event)); + case TypeString: + return QVariant(toRoomEventTypeString(event)); + case IsOnlyEmoji: { + QString qBody = QString::fromStdString(body(event)); - for (auto &code : utf32_string) { - if (utils::codepointIsEmoji(code)) { - emojiCount++; - } else { - return QVariant(0); - } - } + QVector<uint> utf32_string = qBody.toUcs4(); + int emojiCount = 0; - return QVariant(emojiCount); + for (auto &code : utf32_string) { + if (utils::codepointIsEmoji(code)) { + emojiCount++; + } else { + return QVariant(0); + } } - case Body: - return QVariant( - utils::replaceEmoji(QString::fromStdString(body(event)).toHtmlEscaped())); - case FormattedBody: { - const static QRegularExpression replyFallback( - "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption); - auto ascent = QFontMetrics(UserSettings::instance()->font()).ascent(); + return QVariant(emojiCount); + } + case Body: + return QVariant(utils::replaceEmoji(QString::fromStdString(body(event)).toHtmlEscaped())); + case FormattedBody: { + const static QRegularExpression replyFallback( + "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption); - bool isReply = utils::isReply(event); + auto ascent = QFontMetrics(UserSettings::instance()->font()).ascent(); - auto formattedBody_ = QString::fromStdString(formatted_body(event)); - if (formattedBody_.isEmpty()) { - auto body_ = QString::fromStdString(body(event)); + bool isReply = utils::isReply(event); - if (isReply) { - while (body_.startsWith("> ")) - body_ = body_.right(body_.size() - body_.indexOf('\n') - 1); - if (body_.startsWith('\n')) - body_ = body_.right(body_.size() - 1); - } - formattedBody_ = body_.toHtmlEscaped().replace('\n', "<br>"); - } else { - if (isReply) - formattedBody_ = formattedBody_.remove(replyFallback); - } - - // TODO(Nico): Don't parse html with a regex - const static QRegularExpression matchImgUri( - "(<img [^>]*)src=\"mxc://([^\"]*)\"([^>]*>)"); - formattedBody_.replace(matchImgUri, "\\1 src=\"image://mxcImage/\\2\"\\3"); - // Same regex but for single quotes around the src - const static QRegularExpression matchImgUri2( - "(<img [^>]*)src=\'mxc://([^\']*)\'([^>]*>)"); - formattedBody_.replace(matchImgUri2, "\\1 src=\"image://mxcImage/\\2\"\\3"); - const static QRegularExpression matchEmoticonHeight( - "(<img data-mx-emoticon [^>]*)height=\"([^\"]*)\"([^>]*>)"); - formattedBody_.replace(matchEmoticonHeight, - QString("\\1 height=\"%1\"\\3").arg(ascent)); + auto formattedBody_ = QString::fromStdString(formatted_body(event)); + if (formattedBody_.isEmpty()) { + auto body_ = QString::fromStdString(body(event)); - return QVariant(utils::replaceEmoji( - utils::linkifyMessage(utils::escapeBlacklistedHtml(formattedBody_)))); + if (isReply) { + while (body_.startsWith("> ")) + body_ = body_.right(body_.size() - body_.indexOf('\n') - 1); + if (body_.startsWith('\n')) + body_ = body_.right(body_.size() - 1); + } + formattedBody_ = body_.toHtmlEscaped().replace('\n', "<br>"); + } else { + if (isReply) + formattedBody_ = formattedBody_.remove(replyFallback); } - case Url: - return QVariant(QString::fromStdString(url(event))); - case ThumbnailUrl: - return QVariant(QString::fromStdString(thumbnail_url(event))); - case Blurhash: - return QVariant(QString::fromStdString(blurhash(event))); - case Filename: - return QVariant(QString::fromStdString(filename(event))); - case Filesize: - return QVariant(utils::humanReadableFileSize(filesize(event))); - case MimeType: - return QVariant(QString::fromStdString(mimetype(event))); - case OriginalHeight: - return QVariant(qulonglong{media_height(event)}); - case OriginalWidth: - return QVariant(qulonglong{media_width(event)}); - case ProportionalHeight: { - auto w = media_width(event); - if (w == 0) - w = 1; - double prop = media_height(event) / (double)w; + // TODO(Nico): Don't parse html with a regex + const static QRegularExpression matchImgUri("(<img [^>]*)src=\"mxc://([^\"]*)\"([^>]*>)"); + formattedBody_.replace(matchImgUri, "\\1 src=\"image://mxcImage/\\2\"\\3"); + // Same regex but for single quotes around the src + const static QRegularExpression matchImgUri2("(<img [^>]*)src=\'mxc://([^\']*)\'([^>]*>)"); + formattedBody_.replace(matchImgUri2, "\\1 src=\"image://mxcImage/\\2\"\\3"); + const static QRegularExpression matchEmoticonHeight( + "(<img data-mx-emoticon [^>]*)height=\"([^\"]*)\"([^>]*>)"); + formattedBody_.replace(matchEmoticonHeight, QString("\\1 height=\"%1\"\\3").arg(ascent)); - return QVariant(prop > 0 ? prop : 1.); - } - case EventId: { - if (auto replaces = relations(event).replaces()) - return QVariant(QString::fromStdString(replaces.value())); - else - return QVariant(QString::fromStdString(event_id(event))); - } - case State: { - auto id = QString::fromStdString(event_id(event)); - auto containsOthers = [](const auto &vec) { - for (const auto &e : vec) - if (e.second != http::client()->user_id().to_string()) - return true; - return false; - }; + return QVariant( + utils::replaceEmoji(utils::linkifyMessage(utils::escapeBlacklistedHtml(formattedBody_)))); + } + case Url: + return QVariant(QString::fromStdString(url(event))); + case ThumbnailUrl: + return QVariant(QString::fromStdString(thumbnail_url(event))); + case Blurhash: + return QVariant(QString::fromStdString(blurhash(event))); + case Filename: + return QVariant(QString::fromStdString(filename(event))); + case Filesize: + return QVariant(utils::humanReadableFileSize(filesize(event))); + case MimeType: + return QVariant(QString::fromStdString(mimetype(event))); + case OriginalHeight: + return QVariant(qulonglong{media_height(event)}); + case OriginalWidth: + return QVariant(qulonglong{media_width(event)}); + case ProportionalHeight: { + auto w = media_width(event); + if (w == 0) + w = 1; - // only show read receipts for messages not from us - if (acc::sender(event) != http::client()->user_id().to_string()) - return qml_mtx_events::Empty; - else if (!id.isEmpty() && id[0] == "m") - return qml_mtx_events::Sent; - else if (read.contains(id) || containsOthers(cache::readReceipts(id, room_id_))) - return qml_mtx_events::Read; - else - return qml_mtx_events::Received; - } - case IsEdited: - return QVariant(relations(event).replaces().has_value()); - case IsEditable: - return QVariant(!is_state_event(event) && mtx::accessors::sender(event) == - http::client()->user_id().to_string()); - case IsEncrypted: { - auto id = event_id(event); - auto encrypted_event = events.get(id, "", false); - return encrypted_event && - std::holds_alternative< - mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( - *encrypted_event); - } + double prop = media_height(event) / (double)w; - case Trustlevel: { - auto id = event_id(event); - auto encrypted_event = events.get(id, "", false); - if (encrypted_event) { - if (auto encrypted = - std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( - &*encrypted_event)) { - return olm::calculate_trust( - encrypted->sender, - MegolmSessionIndex(room_id_.toStdString(), encrypted->content)); - } - } - return crypto::Trust::Unverified; - } + return QVariant(prop > 0 ? prop : 1.); + } + case EventId: { + if (auto replaces = relations(event).replaces()) + return QVariant(QString::fromStdString(replaces.value())); + else + return QVariant(QString::fromStdString(event_id(event))); + } + case State: { + auto id = QString::fromStdString(event_id(event)); + auto containsOthers = [](const auto &vec) { + for (const auto &e : vec) + if (e.second != http::client()->user_id().to_string()) + return true; + return false; + }; - case EncryptionError: - return events.decryptionError(event_id(event)); + // only show read receipts for messages not from us + if (acc::sender(event) != http::client()->user_id().to_string()) + return qml_mtx_events::Empty; + else if (!id.isEmpty() && id[0] == "m") + return qml_mtx_events::Sent; + else if (read.contains(id) || containsOthers(cache::readReceipts(id, room_id_))) + return qml_mtx_events::Read; + else + return qml_mtx_events::Received; + } + case IsEdited: + return QVariant(relations(event).replaces().has_value()); + case IsEditable: + return QVariant(!is_state_event(event) && + mtx::accessors::sender(event) == http::client()->user_id().to_string()); + case IsEncrypted: { + auto id = event_id(event); + auto encrypted_event = events.get(id, "", false); + return encrypted_event && + std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( + *encrypted_event); + } - case ReplyTo: - return QVariant(QString::fromStdString(relations(event).reply_to().value_or(""))); - case Reactions: { - auto id = relations(event).replaces().value_or(event_id(event)); - return QVariant::fromValue(events.reactions(id)); + case Trustlevel: { + auto id = event_id(event); + auto encrypted_event = events.get(id, "", false); + if (encrypted_event) { + if (auto encrypted = + std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( + &*encrypted_event)) { + return olm::calculate_trust( + encrypted->sender, + MegolmSessionIndex(room_id_.toStdString(), encrypted->content)); + } } - case RoomId: - return QVariant(room_id_); - case RoomName: - return QVariant( - utils::replaceEmoji(QString::fromStdString(room_name(event)).toHtmlEscaped())); - case RoomTopic: - return QVariant(utils::replaceEmoji( - utils::linkifyMessage(QString::fromStdString(room_topic(event)) - .toHtmlEscaped() - .replace("\n", "<br>")))); - case CallType: - return QVariant(QString::fromStdString(call_type(event))); - case Dump: { - QVariantMap m; - auto names = roleNames(); + return crypto::Trust::Unverified; + } - m.insert(names[Type], data(event, static_cast<int>(Type))); - m.insert(names[TypeString], data(event, static_cast<int>(TypeString))); - m.insert(names[IsOnlyEmoji], data(event, static_cast<int>(IsOnlyEmoji))); - m.insert(names[Body], data(event, static_cast<int>(Body))); - m.insert(names[FormattedBody], data(event, static_cast<int>(FormattedBody))); - m.insert(names[IsSender], data(event, static_cast<int>(IsSender))); - m.insert(names[UserId], data(event, static_cast<int>(UserId))); - m.insert(names[UserName], data(event, static_cast<int>(UserName))); - m.insert(names[Day], data(event, static_cast<int>(Day))); - m.insert(names[Timestamp], data(event, static_cast<int>(Timestamp))); - m.insert(names[Url], data(event, static_cast<int>(Url))); - m.insert(names[ThumbnailUrl], data(event, static_cast<int>(ThumbnailUrl))); - m.insert(names[Blurhash], data(event, static_cast<int>(Blurhash))); - m.insert(names[Filename], data(event, static_cast<int>(Filename))); - m.insert(names[Filesize], data(event, static_cast<int>(Filesize))); - m.insert(names[MimeType], data(event, static_cast<int>(MimeType))); - m.insert(names[OriginalHeight], data(event, static_cast<int>(OriginalHeight))); - m.insert(names[OriginalWidth], data(event, static_cast<int>(OriginalWidth))); - m.insert(names[ProportionalHeight], - data(event, static_cast<int>(ProportionalHeight))); - m.insert(names[EventId], data(event, static_cast<int>(EventId))); - m.insert(names[State], data(event, static_cast<int>(State))); - m.insert(names[IsEdited], data(event, static_cast<int>(IsEdited))); - m.insert(names[IsEditable], data(event, static_cast<int>(IsEditable))); - m.insert(names[IsEncrypted], data(event, static_cast<int>(IsEncrypted))); - m.insert(names[ReplyTo], data(event, static_cast<int>(ReplyTo))); - m.insert(names[RoomName], data(event, static_cast<int>(RoomName))); - m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic))); - m.insert(names[CallType], data(event, static_cast<int>(CallType))); - m.insert(names[EncryptionError], data(event, static_cast<int>(EncryptionError))); + case EncryptionError: + return events.decryptionError(event_id(event)); - return QVariant(m); - } - case RelatedEventCacheBuster: - return relatedEventCacheBuster; - default: - return QVariant(); - } + case ReplyTo: + return QVariant(QString::fromStdString(relations(event).reply_to().value_or(""))); + case Reactions: { + auto id = relations(event).replaces().value_or(event_id(event)); + return QVariant::fromValue(events.reactions(id)); + } + case RoomId: + return QVariant(room_id_); + case RoomName: + return QVariant( + utils::replaceEmoji(QString::fromStdString(room_name(event)).toHtmlEscaped())); + case RoomTopic: + return QVariant(utils::replaceEmoji(utils::linkifyMessage( + QString::fromStdString(room_topic(event)).toHtmlEscaped().replace("\n", "<br>")))); + case CallType: + return QVariant(QString::fromStdString(call_type(event))); + case Dump: { + QVariantMap m; + auto names = roleNames(); + + m.insert(names[Type], data(event, static_cast<int>(Type))); + m.insert(names[TypeString], data(event, static_cast<int>(TypeString))); + m.insert(names[IsOnlyEmoji], data(event, static_cast<int>(IsOnlyEmoji))); + m.insert(names[Body], data(event, static_cast<int>(Body))); + m.insert(names[FormattedBody], data(event, static_cast<int>(FormattedBody))); + m.insert(names[IsSender], data(event, static_cast<int>(IsSender))); + m.insert(names[UserId], data(event, static_cast<int>(UserId))); + m.insert(names[UserName], data(event, static_cast<int>(UserName))); + m.insert(names[Day], data(event, static_cast<int>(Day))); + m.insert(names[Timestamp], data(event, static_cast<int>(Timestamp))); + m.insert(names[Url], data(event, static_cast<int>(Url))); + m.insert(names[ThumbnailUrl], data(event, static_cast<int>(ThumbnailUrl))); + m.insert(names[Blurhash], data(event, static_cast<int>(Blurhash))); + m.insert(names[Filename], data(event, static_cast<int>(Filename))); + m.insert(names[Filesize], data(event, static_cast<int>(Filesize))); + m.insert(names[MimeType], data(event, static_cast<int>(MimeType))); + m.insert(names[OriginalHeight], data(event, static_cast<int>(OriginalHeight))); + m.insert(names[OriginalWidth], data(event, static_cast<int>(OriginalWidth))); + m.insert(names[ProportionalHeight], data(event, static_cast<int>(ProportionalHeight))); + m.insert(names[EventId], data(event, static_cast<int>(EventId))); + m.insert(names[State], data(event, static_cast<int>(State))); + m.insert(names[IsEdited], data(event, static_cast<int>(IsEdited))); + m.insert(names[IsEditable], data(event, static_cast<int>(IsEditable))); + m.insert(names[IsEncrypted], data(event, static_cast<int>(IsEncrypted))); + m.insert(names[ReplyTo], data(event, static_cast<int>(ReplyTo))); + m.insert(names[RoomName], data(event, static_cast<int>(RoomName))); + m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic))); + m.insert(names[CallType], data(event, static_cast<int>(CallType))); + m.insert(names[EncryptionError], data(event, static_cast<int>(EncryptionError))); + + return QVariant(m); + } + case RelatedEventCacheBuster: + return relatedEventCacheBuster; + default: + return QVariant(); + } } QVariant TimelineModel::data(const QModelIndex &index, int role) const { - using namespace mtx::accessors; - namespace acc = mtx::accessors; - if (index.row() < 0 && index.row() >= rowCount()) - return QVariant(); + using namespace mtx::accessors; + namespace acc = mtx::accessors; + if (index.row() < 0 && index.row() >= rowCount()) + return QVariant(); - // HACK(Nico): fetchMore likes to break with dynamically sized delegates and reuseItems - if (index.row() + 1 == rowCount() && !m_paginationInProgress) - const_cast<TimelineModel *>(this)->fetchMore(index); + // HACK(Nico): fetchMore likes to break with dynamically sized delegates and reuseItems + if (index.row() + 1 == rowCount() && !m_paginationInProgress) + const_cast<TimelineModel *>(this)->fetchMore(index); - auto event = events.get(rowCount() - index.row() - 1); + auto event = events.get(rowCount() - index.row() - 1); - if (!event) - return ""; + if (!event) + return ""; - if (role == PreviousMessageDay || role == PreviousMessageUserId) { - int prevIdx = rowCount() - index.row() - 2; - if (prevIdx < 0) - return QVariant(); - auto tempEv = events.get(prevIdx); - if (!tempEv) - return QVariant(); - if (role == PreviousMessageUserId) - return data(*tempEv, UserId); - else - return data(*tempEv, Day); - } + if (role == PreviousMessageDay || role == PreviousMessageUserId) { + int prevIdx = rowCount() - index.row() - 2; + if (prevIdx < 0) + return QVariant(); + auto tempEv = events.get(prevIdx); + if (!tempEv) + return QVariant(); + if (role == PreviousMessageUserId) + return data(*tempEv, UserId); + else + return data(*tempEv, Day); + } - return data(*event, role); + return data(*event, role); } QVariant TimelineModel::dataById(QString id, int role, QString relatedTo) { - if (auto event = events.get(id.toStdString(), relatedTo.toStdString())) - return data(*event, role); - return QVariant(); + if (auto event = events.get(id.toStdString(), relatedTo.toStdString())) + return data(*event, role); + return QVariant(); } bool TimelineModel::canFetchMore(const QModelIndex &) const { - if (!events.size()) - return true; - if (auto first = events.get(0); - first && - !std::holds_alternative<mtx::events::StateEvent<mtx::events::state::Create>>(*first)) - return true; - else + if (!events.size()) + return true; + if (auto first = events.get(0); + first && + !std::holds_alternative<mtx::events::StateEvent<mtx::events::state::Create>>(*first)) + return true; + else - return false; + return false; } void TimelineModel::setPaginationInProgress(const bool paginationInProgress) { - if (m_paginationInProgress == paginationInProgress) { - return; - } + if (m_paginationInProgress == paginationInProgress) { + return; + } - m_paginationInProgress = paginationInProgress; - emit paginationInProgressChanged(m_paginationInProgress); + m_paginationInProgress = paginationInProgress; + emit paginationInProgressChanged(m_paginationInProgress); } void TimelineModel::fetchMore(const QModelIndex &) { - if (m_paginationInProgress) { - nhlog::ui()->warn("Already loading older messages"); - return; - } + if (m_paginationInProgress) { + nhlog::ui()->warn("Already loading older messages"); + return; + } - setPaginationInProgress(true); + setPaginationInProgress(true); - events.fetchMore(); + events.fetchMore(); } void TimelineModel::sync(const mtx::responses::JoinedRoom &room) { - this->syncState(room.state); - this->addEvents(room.timeline); + this->syncState(room.state); + this->addEvents(room.timeline); - if (room.unread_notifications.highlight_count != highlight_count || - room.unread_notifications.notification_count != notification_count) { - notification_count = room.unread_notifications.notification_count; - highlight_count = room.unread_notifications.highlight_count; - emit notificationsChanged(); - } + if (room.unread_notifications.highlight_count != highlight_count || + room.unread_notifications.notification_count != notification_count) { + notification_count = room.unread_notifications.notification_count; + highlight_count = room.unread_notifications.highlight_count; + emit notificationsChanged(); + } } void TimelineModel::syncState(const mtx::responses::State &s) { - using namespace mtx::events; + using namespace mtx::events; - for (const auto &e : s.events) { - if (std::holds_alternative<StateEvent<state::Avatar>>(e)) - emit roomAvatarUrlChanged(); - else if (std::holds_alternative<StateEvent<state::Name>>(e)) - emit roomNameChanged(); - else if (std::holds_alternative<StateEvent<state::Topic>>(e)) - emit roomTopicChanged(); - else if (std::holds_alternative<StateEvent<state::Topic>>(e)) { - permissions_.invalidate(); - emit permissionsChanged(); - } else if (std::holds_alternative<StateEvent<state::Member>>(e)) { - emit roomAvatarUrlChanged(); - emit roomNameChanged(); - emit roomMemberCountChanged(); + for (const auto &e : s.events) { + if (std::holds_alternative<StateEvent<state::Avatar>>(e)) + emit roomAvatarUrlChanged(); + else if (std::holds_alternative<StateEvent<state::Name>>(e)) + emit roomNameChanged(); + else if (std::holds_alternative<StateEvent<state::Topic>>(e)) + emit roomTopicChanged(); + else if (std::holds_alternative<StateEvent<state::Topic>>(e)) { + permissions_.invalidate(); + emit permissionsChanged(); + } else if (std::holds_alternative<StateEvent<state::Member>>(e)) { + emit roomAvatarUrlChanged(); + emit roomNameChanged(); + emit roomMemberCountChanged(); - if (roomMemberCount() <= 2) { - emit isDirectChanged(); - emit directChatOtherUserIdChanged(); - } - } else if (std::holds_alternative<StateEvent<state::Encryption>>(e)) { - this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); - emit encryptionChanged(); - } + if (roomMemberCount() <= 2) { + emit isDirectChanged(); + emit directChatOtherUserIdChanged(); + } + } else if (std::holds_alternative<StateEvent<state::Encryption>>(e)) { + this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); + emit encryptionChanged(); } + } } void TimelineModel::addEvents(const mtx::responses::Timeline &timeline) { - if (timeline.events.empty()) - return; + if (timeline.events.empty()) + return; - events.handleSync(timeline); + events.handleSync(timeline); - using namespace mtx::events; + using namespace mtx::events; - for (auto e : timeline.events) { - if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) { - MegolmSessionIndex index(room_id_.toStdString(), encryptedEvent->content); + for (auto e : timeline.events) { + if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) { + MegolmSessionIndex index(room_id_.toStdString(), encryptedEvent->content); - auto result = olm::decryptEvent(index, *encryptedEvent); - if (result.event) - e = result.event.value(); - } + auto result = olm::decryptEvent(index, *encryptedEvent); + if (result.event) + e = result.event.value(); + } - if (std::holds_alternative<RoomEvent<msg::CallCandidates>>(e) || - std::holds_alternative<RoomEvent<msg::CallInvite>>(e) || - std::holds_alternative<RoomEvent<msg::CallAnswer>>(e) || - std::holds_alternative<RoomEvent<msg::CallHangUp>>(e)) - std::visit( - [this](auto &event) { - event.room_id = room_id_.toStdString(); - if constexpr (std::is_same_v<std::decay_t<decltype(event)>, - RoomEvent<msg::CallAnswer>> || - std::is_same_v<std::decay_t<decltype(event)>, - RoomEvent<msg::CallHangUp>>) - emit newCallEvent(event); - else { - if (event.sender != http::client()->user_id().to_string()) - emit newCallEvent(event); - } - }, - e); - else if (std::holds_alternative<StateEvent<state::Avatar>>(e)) - emit roomAvatarUrlChanged(); - else if (std::holds_alternative<StateEvent<state::Name>>(e)) - emit roomNameChanged(); - else if (std::holds_alternative<StateEvent<state::Topic>>(e)) - emit roomTopicChanged(); - else if (std::holds_alternative<StateEvent<state::PowerLevels>>(e)) { - permissions_.invalidate(); - emit permissionsChanged(); - } else if (std::holds_alternative<StateEvent<state::Member>>(e)) { - emit roomAvatarUrlChanged(); - emit roomNameChanged(); - emit roomMemberCountChanged(); - } else if (std::holds_alternative<StateEvent<state::Encryption>>(e)) { - this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); - emit encryptionChanged(); - } + if (std::holds_alternative<RoomEvent<msg::CallCandidates>>(e) || + std::holds_alternative<RoomEvent<msg::CallInvite>>(e) || + std::holds_alternative<RoomEvent<msg::CallAnswer>>(e) || + std::holds_alternative<RoomEvent<msg::CallHangUp>>(e)) + std::visit( + [this](auto &event) { + event.room_id = room_id_.toStdString(); + if constexpr (std::is_same_v<std::decay_t<decltype(event)>, + RoomEvent<msg::CallAnswer>> || + std::is_same_v<std::decay_t<decltype(event)>, + RoomEvent<msg::CallHangUp>>) + emit newCallEvent(event); + else { + if (event.sender != http::client()->user_id().to_string()) + emit newCallEvent(event); + } + }, + e); + else if (std::holds_alternative<StateEvent<state::Avatar>>(e)) + emit roomAvatarUrlChanged(); + else if (std::holds_alternative<StateEvent<state::Name>>(e)) + emit roomNameChanged(); + else if (std::holds_alternative<StateEvent<state::Topic>>(e)) + emit roomTopicChanged(); + else if (std::holds_alternative<StateEvent<state::PowerLevels>>(e)) { + permissions_.invalidate(); + emit permissionsChanged(); + } else if (std::holds_alternative<StateEvent<state::Member>>(e)) { + emit roomAvatarUrlChanged(); + emit roomNameChanged(); + emit roomMemberCountChanged(); + } else if (std::holds_alternative<StateEvent<state::Encryption>>(e)) { + this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); + emit encryptionChanged(); } - updateLastMessage(); + } + updateLastMessage(); } template<typename T> @@ -896,1216 +876,1191 @@ auto isMessage(const mtx::events::RoomEvent<T> &e) -> std::enable_if_t<std::is_same<decltype(e.content.msgtype), std::string>::value, bool> { - return true; + return true; } template<typename T> auto isMessage(const mtx::events::Event<T> &) { - return false; + return false; } template<typename T> auto isMessage(const mtx::events::EncryptedEvent<T> &) { - return true; + return true; } auto isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &) { - return true; + return true; } auto isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &) { - return true; + return true; } auto isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &) { - return true; + return true; } // Workaround. We also want to see a room at the top, if we just joined it auto isYourJoin(const mtx::events::StateEvent<mtx::events::state::Member> &e) { - return e.content.membership == mtx::events::state::Membership::Join && - e.state_key == http::client()->user_id().to_string(); + return e.content.membership == mtx::events::state::Membership::Join && + e.state_key == http::client()->user_id().to_string(); } template<typename T> auto isYourJoin(const mtx::events::Event<T> &) { - return false; + return false; } void TimelineModel::updateLastMessage() { - for (auto it = events.size() - 1; it >= 0; --it) { - auto event = events.get(it, decryptDescription); - if (!event) - continue; + for (auto it = events.size() - 1; it >= 0; --it) { + auto event = events.get(it, decryptDescription); + if (!event) + continue; - if (std::visit([](const auto &e) -> bool { return isYourJoin(e); }, *event)) { - auto time = mtx::accessors::origin_server_ts(*event); - uint64_t ts = time.toMSecsSinceEpoch(); - auto description = - DescInfo{QString::fromStdString(mtx::accessors::event_id(*event)), - QString::fromStdString(http::client()->user_id().to_string()), - tr("You joined this room."), - utils::descriptiveTime(time), - ts, - time}; - if (description != lastMessage_) { - lastMessage_ = description; - emit lastMessageChanged(); - } - return; - } - if (!std::visit([](const auto &e) -> bool { return isMessage(e); }, *event)) - continue; + if (std::visit([](const auto &e) -> bool { return isYourJoin(e); }, *event)) { + auto time = mtx::accessors::origin_server_ts(*event); + uint64_t ts = time.toMSecsSinceEpoch(); + auto description = + DescInfo{QString::fromStdString(mtx::accessors::event_id(*event)), + QString::fromStdString(http::client()->user_id().to_string()), + tr("You joined this room."), + utils::descriptiveTime(time), + ts, + time}; + if (description != lastMessage_) { + lastMessage_ = description; + emit lastMessageChanged(); + } + return; + } + if (!std::visit([](const auto &e) -> bool { return isMessage(e); }, *event)) + continue; - auto description = utils::getMessageDescription( - *event, - QString::fromStdString(http::client()->user_id().to_string()), - cache::displayName(room_id_, - QString::fromStdString(mtx::accessors::sender(*event)))); - if (description != lastMessage_) { - lastMessage_ = description; - emit lastMessageChanged(); - } - return; + auto description = utils::getMessageDescription( + *event, + QString::fromStdString(http::client()->user_id().to_string()), + cache::displayName(room_id_, QString::fromStdString(mtx::accessors::sender(*event)))); + if (description != lastMessage_) { + lastMessage_ = description; + emit lastMessageChanged(); } + return; + } } void TimelineModel::setCurrentIndex(int index) { - auto oldIndex = idToIndex(currentId); - currentId = indexToId(index); - if (index != oldIndex) - emit currentIndexChanged(index); + auto oldIndex = idToIndex(currentId); + currentId = indexToId(index); + if (index != oldIndex) + emit currentIndexChanged(index); - if (!ChatPage::instance()->isActiveWindow()) - return; + if (!ChatPage::instance()->isActiveWindow()) + return; - if (!currentId.startsWith("m")) { - auto oldReadIndex = - cache::getEventIndex(roomId().toStdString(), currentReadId.toStdString()); - auto nextEventIndexAndId = - cache::lastInvisibleEventAfter(roomId().toStdString(), currentId.toStdString()); + if (!currentId.startsWith("m")) { + auto oldReadIndex = + cache::getEventIndex(roomId().toStdString(), currentReadId.toStdString()); + auto nextEventIndexAndId = + cache::lastInvisibleEventAfter(roomId().toStdString(), currentId.toStdString()); - if (nextEventIndexAndId && - (!oldReadIndex || *oldReadIndex < nextEventIndexAndId->first)) { - readEvent(nextEventIndexAndId->second); - currentReadId = QString::fromStdString(nextEventIndexAndId->second); - } + if (nextEventIndexAndId && (!oldReadIndex || *oldReadIndex < nextEventIndexAndId->first)) { + readEvent(nextEventIndexAndId->second); + currentReadId = QString::fromStdString(nextEventIndexAndId->second); } + } } void TimelineModel::readEvent(const std::string &id) { - http::client()->read_event(room_id_.toStdString(), id, [this](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to read_event ({}, {})", - room_id_.toStdString(), - currentId.toStdString()); - } - }); + http::client()->read_event(room_id_.toStdString(), id, [this](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to read_event ({}, {})", room_id_.toStdString(), currentId.toStdString()); + } + }); } QString TimelineModel::displayName(QString id) const { - return cache::displayName(room_id_, id).toHtmlEscaped(); + return cache::displayName(room_id_, id).toHtmlEscaped(); } QString TimelineModel::avatarUrl(QString id) const { - return cache::avatarUrl(room_id_, id); + return cache::avatarUrl(room_id_, id); } QString TimelineModel::formatDateSeparator(QDate date) const { - auto now = QDateTime::currentDateTime(); + auto now = QDateTime::currentDateTime(); - QString fmt = QLocale::system().dateFormat(QLocale::LongFormat); + QString fmt = QLocale::system().dateFormat(QLocale::LongFormat); - if (now.date().year() == date.year()) { - QRegularExpression rx("[^a-zA-Z]*y+[^a-zA-Z]*"); - fmt = fmt.remove(rx); - } + if (now.date().year() == date.year()) { + QRegularExpression rx("[^a-zA-Z]*y+[^a-zA-Z]*"); + fmt = fmt.remove(rx); + } - return date.toString(fmt); + return date.toString(fmt); } void TimelineModel::viewRawMessage(QString id) { - auto e = events.get(id.toStdString(), "", false); - if (!e) - return; - std::string ev = mtx::accessors::serialize_event(*e).dump(4); - emit showRawMessageDialog(QString::fromStdString(ev)); + auto e = events.get(id.toStdString(), "", false); + if (!e) + return; + std::string ev = mtx::accessors::serialize_event(*e).dump(4); + emit showRawMessageDialog(QString::fromStdString(ev)); } void TimelineModel::forwardMessage(QString eventId, QString roomId) { - auto e = events.get(eventId.toStdString(), ""); - if (!e) - return; + auto e = events.get(eventId.toStdString(), ""); + if (!e) + return; - emit forwardToRoom(e, roomId); + emit forwardToRoom(e, roomId); } void TimelineModel::viewDecryptedRawMessage(QString id) { - auto e = events.get(id.toStdString(), ""); - if (!e) - return; + auto e = events.get(id.toStdString(), ""); + if (!e) + return; - std::string ev = mtx::accessors::serialize_event(*e).dump(4); - emit showRawMessageDialog(QString::fromStdString(ev)); + std::string ev = mtx::accessors::serialize_event(*e).dump(4); + emit showRawMessageDialog(QString::fromStdString(ev)); } void TimelineModel::openUserProfile(QString userid) { - UserProfile *userProfile = new UserProfile(room_id_, userid, manager_, this); - connect( - this, &TimelineModel::roomAvatarUrlChanged, userProfile, &UserProfile::updateAvatarUrl); - emit manager_->openProfile(userProfile); + UserProfile *userProfile = new UserProfile(room_id_, userid, manager_, this); + connect(this, &TimelineModel::roomAvatarUrlChanged, userProfile, &UserProfile::updateAvatarUrl); + emit manager_->openProfile(userProfile); } void TimelineModel::replyAction(QString id) { - setReply(id); + setReply(id); } void TimelineModel::editAction(QString id) { - setEdit(id); + setEdit(id); } RelatedInfo TimelineModel::relatedInfo(QString id) { - auto event = events.get(id.toStdString(), ""); - if (!event) - return {}; + auto event = events.get(id.toStdString(), ""); + if (!event) + return {}; - return utils::stripReplyFallbacks(*event, id.toStdString(), room_id_); + return utils::stripReplyFallbacks(*event, id.toStdString(), room_id_); } void TimelineModel::showReadReceipts(QString id) { - emit openReadReceiptsDialog(new ReadReceiptsProxy{id, roomId(), this}); + emit openReadReceiptsDialog(new ReadReceiptsProxy{id, roomId(), this}); } void TimelineModel::redactEvent(QString id) { - if (!id.isEmpty()) - http::client()->redact_event( - room_id_.toStdString(), - id.toStdString(), - [this, id](const mtx::responses::EventId &, mtx::http::RequestErr err) { - if (err) { - emit redactionFailed( - tr("Message redaction failed: %1") - .arg(QString::fromStdString(err->matrix_error.error))); - return; - } + if (!id.isEmpty()) + http::client()->redact_event( + room_id_.toStdString(), + id.toStdString(), + [this, id](const mtx::responses::EventId &, mtx::http::RequestErr err) { + if (err) { + emit redactionFailed(tr("Message redaction failed: %1") + .arg(QString::fromStdString(err->matrix_error.error))); + return; + } - emit eventRedacted(id); - }); + emit eventRedacted(id); + }); } int TimelineModel::idToIndex(QString id) const { - if (id.isEmpty()) - return -1; + if (id.isEmpty()) + return -1; - auto idx = events.idToIndex(id.toStdString()); - if (idx) - return events.size() - *idx - 1; - else - return -1; + auto idx = events.idToIndex(id.toStdString()); + if (idx) + return events.size() - *idx - 1; + else + return -1; } QString TimelineModel::indexToId(int index) const { - auto id = events.indexToId(events.size() - index - 1); - return id ? QString::fromStdString(*id) : ""; + auto id = events.indexToId(events.size() - index - 1); + return id ? QString::fromStdString(*id) : ""; } // Note: this will only be called for our messages void TimelineModel::markEventsAsRead(const std::vector<QString> &event_ids) { - for (const auto &id : event_ids) { - read.insert(id); - int idx = idToIndex(id); - if (idx < 0) { - return; - } - emit dataChanged(index(idx, 0), index(idx, 0)); + for (const auto &id : event_ids) { + read.insert(id); + int idx = idToIndex(id); + if (idx < 0) { + return; } + emit dataChanged(index(idx, 0), index(idx, 0)); + } } template<typename T> void TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType) { - const auto room_id = room_id_.toStdString(); + const auto room_id = room_id_.toStdString(); - using namespace mtx::events; - using namespace mtx::identifiers; + using namespace mtx::events; + using namespace mtx::identifiers; - json doc = {{"type", mtx::events::to_string(eventType)}, - {"content", json(msg.content)}, - {"room_id", room_id}}; + json doc = {{"type", mtx::events::to_string(eventType)}, + {"content", json(msg.content)}, + {"room_id", room_id}}; - try { - mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> event; - event.content = - olm::encrypt_group_message(room_id, http::client()->device_id(), doc); - event.event_id = msg.event_id; - event.room_id = room_id; - event.sender = http::client()->user_id().to_string(); - event.type = mtx::events::EventType::RoomEncrypted; - event.origin_server_ts = QDateTime::currentMSecsSinceEpoch(); + try { + mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> event; + event.content = olm::encrypt_group_message(room_id, http::client()->device_id(), doc); + event.event_id = msg.event_id; + event.room_id = room_id; + event.sender = http::client()->user_id().to_string(); + event.type = mtx::events::EventType::RoomEncrypted; + event.origin_server_ts = QDateTime::currentMSecsSinceEpoch(); - emit this->addPendingMessageToStore(event); + emit this->addPendingMessageToStore(event); - // TODO: Let the user know about the errors. - } catch (const lmdb::error &e) { - nhlog::db()->critical( - "failed to open outbound megolm session ({}): {}", room_id, e.what()); - emit ChatPage::instance()->showNotification( - tr("Failed to encrypt event, sending aborted!")); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical( - "failed to open outbound megolm session ({}): {}", room_id, e.what()); - emit ChatPage::instance()->showNotification( - tr("Failed to encrypt event, sending aborted!")); - } + // TODO: Let the user know about the errors. + } catch (const lmdb::error &e) { + nhlog::db()->critical("failed to open outbound megolm session ({}): {}", room_id, e.what()); + emit ChatPage::instance()->showNotification( + tr("Failed to encrypt event, sending aborted!")); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical( + "failed to open outbound megolm session ({}): {}", room_id, e.what()); + emit ChatPage::instance()->showNotification( + tr("Failed to encrypt event, sending aborted!")); + } } struct SendMessageVisitor { - explicit SendMessageVisitor(TimelineModel *model) - : model_(model) - {} + explicit SendMessageVisitor(TimelineModel *model) + : model_(model) + {} - template<typename T, mtx::events::EventType Event> - void sendRoomEvent(mtx::events::RoomEvent<T> msg) - { - if (cache::isRoomEncrypted(model_->room_id_.toStdString())) { - auto encInfo = mtx::accessors::file(msg); - if (encInfo) - emit model_->newEncryptedImage(encInfo.value()); + template<typename T, mtx::events::EventType Event> + void sendRoomEvent(mtx::events::RoomEvent<T> msg) + { + if (cache::isRoomEncrypted(model_->room_id_.toStdString())) { + auto encInfo = mtx::accessors::file(msg); + if (encInfo) + emit model_->newEncryptedImage(encInfo.value()); - model_->sendEncryptedMessage(msg, Event); - } else { - msg.type = Event; - emit model_->addPendingMessageToStore(msg); - } + model_->sendEncryptedMessage(msg, Event); + } else { + msg.type = Event; + emit model_->addPendingMessageToStore(msg); } + } - // Do-nothing operator for all unhandled events - template<typename T> - void operator()(const mtx::events::Event<T> &) - {} + // Do-nothing operator for all unhandled events + template<typename T> + void operator()(const mtx::events::Event<T> &) + {} - // Operator for m.room.message events that contain a msgtype in their content - template<typename T, - std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0> - void operator()(mtx::events::RoomEvent<T> msg) - { - sendRoomEvent<T, mtx::events::EventType::RoomMessage>(msg); - } + // Operator for m.room.message events that contain a msgtype in their content + template<typename T, + std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0> + void operator()(mtx::events::RoomEvent<T> msg) + { + sendRoomEvent<T, mtx::events::EventType::RoomMessage>(msg); + } - // Special operator for reactions, which are a type of m.room.message, but need to be - // handled distinctly for their differences from normal room messages. Specifically, - // reactions need to have the relation outside of ciphertext, or synapse / the homeserver - // cannot handle it correctly. See the MSC for more details: - // https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption - void operator()(mtx::events::RoomEvent<mtx::events::msg::Reaction> msg) - { - msg.type = mtx::events::EventType::Reaction; - emit model_->addPendingMessageToStore(msg); - } + // Special operator for reactions, which are a type of m.room.message, but need to be + // handled distinctly for their differences from normal room messages. Specifically, + // reactions need to have the relation outside of ciphertext, or synapse / the homeserver + // cannot handle it correctly. See the MSC for more details: + // https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption + void operator()(mtx::events::RoomEvent<mtx::events::msg::Reaction> msg) + { + msg.type = mtx::events::EventType::Reaction; + emit model_->addPendingMessageToStore(msg); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &event) - { - sendRoomEvent<mtx::events::msg::CallInvite, mtx::events::EventType::CallInvite>( - event); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &event) + { + sendRoomEvent<mtx::events::msg::CallInvite, mtx::events::EventType::CallInvite>(event); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &event) - { - sendRoomEvent<mtx::events::msg::CallCandidates, - mtx::events::EventType::CallCandidates>(event); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &event) + { + sendRoomEvent<mtx::events::msg::CallCandidates, mtx::events::EventType::CallCandidates>( + event); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &event) - { - sendRoomEvent<mtx::events::msg::CallAnswer, mtx::events::EventType::CallAnswer>( - event); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &event) + { + sendRoomEvent<mtx::events::msg::CallAnswer, mtx::events::EventType::CallAnswer>(event); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &event) - { - sendRoomEvent<mtx::events::msg::CallHangUp, mtx::events::EventType::CallHangUp>( - event); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &event) + { + sendRoomEvent<mtx::events::msg::CallHangUp, mtx::events::EventType::CallHangUp>(event); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg) - { - sendRoomEvent<mtx::events::msg::KeyVerificationRequest, - mtx::events::EventType::RoomMessage>(msg); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg) + { + sendRoomEvent<mtx::events::msg::KeyVerificationRequest, + mtx::events::EventType::RoomMessage>(msg); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady> &msg) - { - sendRoomEvent<mtx::events::msg::KeyVerificationReady, - mtx::events::EventType::KeyVerificationReady>(msg); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady> &msg) + { + sendRoomEvent<mtx::events::msg::KeyVerificationReady, + mtx::events::EventType::KeyVerificationReady>(msg); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart> &msg) - { - sendRoomEvent<mtx::events::msg::KeyVerificationStart, - mtx::events::EventType::KeyVerificationStart>(msg); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart> &msg) + { + sendRoomEvent<mtx::events::msg::KeyVerificationStart, + mtx::events::EventType::KeyVerificationStart>(msg); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept> &msg) - { - sendRoomEvent<mtx::events::msg::KeyVerificationAccept, - mtx::events::EventType::KeyVerificationAccept>(msg); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept> &msg) + { + sendRoomEvent<mtx::events::msg::KeyVerificationAccept, + mtx::events::EventType::KeyVerificationAccept>(msg); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac> &msg) - { - sendRoomEvent<mtx::events::msg::KeyVerificationMac, - mtx::events::EventType::KeyVerificationMac>(msg); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac> &msg) + { + sendRoomEvent<mtx::events::msg::KeyVerificationMac, + mtx::events::EventType::KeyVerificationMac>(msg); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey> &msg) - { - sendRoomEvent<mtx::events::msg::KeyVerificationKey, - mtx::events::EventType::KeyVerificationKey>(msg); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey> &msg) + { + sendRoomEvent<mtx::events::msg::KeyVerificationKey, + mtx::events::EventType::KeyVerificationKey>(msg); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone> &msg) - { - sendRoomEvent<mtx::events::msg::KeyVerificationDone, - mtx::events::EventType::KeyVerificationDone>(msg); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone> &msg) + { + sendRoomEvent<mtx::events::msg::KeyVerificationDone, + mtx::events::EventType::KeyVerificationDone>(msg); + } - void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel> &msg) - { - sendRoomEvent<mtx::events::msg::KeyVerificationCancel, - mtx::events::EventType::KeyVerificationCancel>(msg); - } - void operator()(mtx::events::Sticker msg) - { - msg.type = mtx::events::EventType::Sticker; - if (cache::isRoomEncrypted(model_->room_id_.toStdString())) { - model_->sendEncryptedMessage(msg, mtx::events::EventType::Sticker); - } else - emit model_->addPendingMessageToStore(msg); - } + void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel> &msg) + { + sendRoomEvent<mtx::events::msg::KeyVerificationCancel, + mtx::events::EventType::KeyVerificationCancel>(msg); + } + void operator()(mtx::events::Sticker msg) + { + msg.type = mtx::events::EventType::Sticker; + if (cache::isRoomEncrypted(model_->room_id_.toStdString())) { + model_->sendEncryptedMessage(msg, mtx::events::EventType::Sticker); + } else + emit model_->addPendingMessageToStore(msg); + } - TimelineModel *model_; + TimelineModel *model_; }; void TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event) { - std::visit( - [](auto &msg) { - // gets overwritten for reactions and stickers in SendMessageVisitor - msg.type = mtx::events::EventType::RoomMessage; - msg.event_id = "m" + http::client()->generate_txn_id(); - msg.sender = http::client()->user_id().to_string(); - msg.origin_server_ts = QDateTime::currentMSecsSinceEpoch(); - }, - event); + std::visit( + [](auto &msg) { + // gets overwritten for reactions and stickers in SendMessageVisitor + msg.type = mtx::events::EventType::RoomMessage; + msg.event_id = "m" + http::client()->generate_txn_id(); + msg.sender = http::client()->user_id().to_string(); + msg.origin_server_ts = QDateTime::currentMSecsSinceEpoch(); + }, + event); - std::visit(SendMessageVisitor{this}, event); + std::visit(SendMessageVisitor{this}, event); } void TimelineModel::openMedia(QString eventId) { - cacheMedia(eventId, [](QString filename) { - QDesktopServices::openUrl(QUrl::fromLocalFile(filename)); - }); + cacheMedia(eventId, + [](QString filename) { QDesktopServices::openUrl(QUrl::fromLocalFile(filename)); }); } bool TimelineModel::saveMedia(QString eventId) const { - mtx::events::collections::TimelineEvents *event = events.get(eventId.toStdString(), ""); - if (!event) - return false; + mtx::events::collections::TimelineEvents *event = events.get(eventId.toStdString(), ""); + if (!event) + return false; - QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event)); - QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event)); - QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)); + QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event)); + QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event)); + QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)); - auto encryptionInfo = mtx::accessors::file(*event); + auto encryptionInfo = mtx::accessors::file(*event); - qml_mtx_events::EventType eventType = toRoomEventType(*event); + qml_mtx_events::EventType eventType = toRoomEventType(*event); - QString dialogTitle; - if (eventType == qml_mtx_events::EventType::ImageMessage) { - dialogTitle = tr("Save image"); - } else if (eventType == qml_mtx_events::EventType::VideoMessage) { - dialogTitle = tr("Save video"); - } else if (eventType == qml_mtx_events::EventType::AudioMessage) { - dialogTitle = tr("Save audio"); - } else { - dialogTitle = tr("Save file"); - } + QString dialogTitle; + if (eventType == qml_mtx_events::EventType::ImageMessage) { + dialogTitle = tr("Save image"); + } else if (eventType == qml_mtx_events::EventType::VideoMessage) { + dialogTitle = tr("Save video"); + } else if (eventType == qml_mtx_events::EventType::AudioMessage) { + dialogTitle = tr("Save audio"); + } else { + dialogTitle = tr("Save file"); + } - const QString filterString = QMimeDatabase().mimeTypeForName(mimeType).filterString(); - const QString downloadsFolder = - QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); - const QString openLocation = downloadsFolder + "/" + originalFilename; + const QString filterString = QMimeDatabase().mimeTypeForName(mimeType).filterString(); + const QString downloadsFolder = + QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + const QString openLocation = downloadsFolder + "/" + originalFilename; - const QString filename = QFileDialog::getSaveFileName( - manager_->getWidget(), dialogTitle, openLocation, filterString); + const QString filename = + QFileDialog::getSaveFileName(manager_->getWidget(), dialogTitle, openLocation, filterString); - if (filename.isEmpty()) - return false; + if (filename.isEmpty()) + return false; - const auto url = mxcUrl.toStdString(); + const auto url = mxcUrl.toStdString(); - http::client()->download( - url, - [filename, url, encryptionInfo](const std::string &data, - const std::string &, - const std::string &, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to retrieve image {}: {} {}", - url, - err->matrix_error.error, - static_cast<int>(err->status_code)); - return; - } + http::client()->download(url, + [filename, url, encryptionInfo](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve image {}: {} {}", + url, + err->matrix_error.error, + static_cast<int>(err->status_code)); + return; + } - try { - auto temp = data; - if (encryptionInfo) - temp = mtx::crypto::to_string( - mtx::crypto::decrypt_file(temp, encryptionInfo.value())); + try { + auto temp = data; + if (encryptionInfo) + temp = mtx::crypto::to_string( + mtx::crypto::decrypt_file(temp, encryptionInfo.value())); - QFile file(filename); + QFile file(filename); - if (!file.open(QIODevice::WriteOnly)) - return; + if (!file.open(QIODevice::WriteOnly)) + return; - file.write(QByteArray(temp.data(), (int)temp.size())); - file.close(); + file.write(QByteArray(temp.data(), (int)temp.size())); + file.close(); - return; - } catch (const std::exception &e) { - nhlog::ui()->warn("Error while saving file to: {}", e.what()); - } - }); - return true; + return; + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while saving file to: {}", e.what()); + } + }); + return true; } void TimelineModel::cacheMedia(QString eventId, std::function<void(const QString)> callback) { - mtx::events::collections::TimelineEvents *event = events.get(eventId.toStdString(), ""); - if (!event) - return; + mtx::events::collections::TimelineEvents *event = events.get(eventId.toStdString(), ""); + if (!event) + return; - QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event)); - QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event)); - QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)); + QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event)); + QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event)); + QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)); - auto encryptionInfo = mtx::accessors::file(*event); + auto encryptionInfo = mtx::accessors::file(*event); - // If the message is a link to a non mxcUrl, don't download it - if (!mxcUrl.startsWith("mxc://")) { - emit mediaCached(mxcUrl, mxcUrl); - return; - } + // If the message is a link to a non mxcUrl, don't download it + if (!mxcUrl.startsWith("mxc://")) { + emit mediaCached(mxcUrl, mxcUrl); + return; + } - QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix(); + QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix(); - const auto url = mxcUrl.toStdString(); - const auto name = QString(mxcUrl).remove("mxc://"); - QFileInfo filename(QString("%1/media_cache/%2.%3") - .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) - .arg(name) - .arg(suffix)); - if (QDir::cleanPath(name) != name) { - nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url); - return; - } + const auto url = mxcUrl.toStdString(); + const auto name = QString(mxcUrl).remove("mxc://"); + QFileInfo filename(QString("%1/media_cache/%2.%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + .arg(name) + .arg(suffix)); + if (QDir::cleanPath(name) != name) { + nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url); + return; + } - QDir().mkpath(filename.path()); + QDir().mkpath(filename.path()); - if (filename.isReadable()) { + if (filename.isReadable()) { #if defined(Q_OS_WIN) - emit mediaCached(mxcUrl, filename.filePath()); + emit mediaCached(mxcUrl, filename.filePath()); #else - emit mediaCached(mxcUrl, "file://" + filename.filePath()); + emit mediaCached(mxcUrl, "file://" + filename.filePath()); #endif - if (callback) { - callback(filename.filePath()); - } - return; + if (callback) { + callback(filename.filePath()); } + return; + } - http::client()->download( - url, - [this, callback, mxcUrl, filename, url, encryptionInfo](const std::string &data, - const std::string &, - const std::string &, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to retrieve image {}: {} {}", - url, - err->matrix_error.error, - static_cast<int>(err->status_code)); - return; - } + http::client()->download( + url, + [this, callback, mxcUrl, filename, url, encryptionInfo](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve image {}: {} {}", + url, + err->matrix_error.error, + static_cast<int>(err->status_code)); + return; + } - try { - auto temp = data; - if (encryptionInfo) - temp = mtx::crypto::to_string( - mtx::crypto::decrypt_file(temp, encryptionInfo.value())); + try { + auto temp = data; + if (encryptionInfo) + temp = + mtx::crypto::to_string(mtx::crypto::decrypt_file(temp, encryptionInfo.value())); - QFile file(filename.filePath()); + QFile file(filename.filePath()); - if (!file.open(QIODevice::WriteOnly)) - return; + if (!file.open(QIODevice::WriteOnly)) + return; - file.write(QByteArray(temp.data(), (int)temp.size())); - file.close(); + file.write(QByteArray(temp.data(), (int)temp.size())); + file.close(); - if (callback) { - callback(filename.filePath()); - } - } catch (const std::exception &e) { - nhlog::ui()->warn("Error while saving file to: {}", e.what()); - } + if (callback) { + callback(filename.filePath()); + } + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while saving file to: {}", e.what()); + } #if defined(Q_OS_WIN) - emit mediaCached(mxcUrl, filename.filePath()); + emit mediaCached(mxcUrl, filename.filePath()); #else - emit mediaCached(mxcUrl, "file://" + filename.filePath()); + emit mediaCached(mxcUrl, "file://" + filename.filePath()); #endif - }); + }); } void TimelineModel::cacheMedia(QString eventId) { - cacheMedia(eventId, NULL); + cacheMedia(eventId, NULL); } void TimelineModel::showEvent(QString eventId) { - using namespace std::chrono_literals; - // Direct to eventId - if (eventId[0] == '$') { - int idx = idToIndex(eventId); - if (idx == -1) { - nhlog::ui()->warn("Scrolling to event id {}, failed - no known index", - eventId.toStdString()); - return; - } - eventIdToShow = eventId; - emit scrollTargetChanged(); - showEventTimer.start(50ms); - return; + using namespace std::chrono_literals; + // Direct to eventId + if (eventId[0] == '$') { + int idx = idToIndex(eventId); + if (idx == -1) { + nhlog::ui()->warn("Scrolling to event id {}, failed - no known index", + eventId.toStdString()); + return; } - // to message index - eventId = indexToId(eventId.toInt()); eventIdToShow = eventId; emit scrollTargetChanged(); showEventTimer.start(50ms); return; + } + // to message index + eventId = indexToId(eventId.toInt()); + eventIdToShow = eventId; + emit scrollTargetChanged(); + showEventTimer.start(50ms); + return; } void TimelineModel::eventShown() { - eventIdToShow.clear(); - emit scrollTargetChanged(); + eventIdToShow.clear(); + emit scrollTargetChanged(); } QString TimelineModel::scrollTarget() const { - return eventIdToShow; + return eventIdToShow; } void TimelineModel::scrollTimerEvent() { - if (eventIdToShow.isEmpty() || showEventTimerCounter > 3) { - showEventTimer.stop(); - showEventTimerCounter = 0; - } else { - emit scrollToIndex(idToIndex(eventIdToShow)); - showEventTimerCounter++; - } + if (eventIdToShow.isEmpty() || showEventTimerCounter > 3) { + showEventTimer.stop(); + showEventTimerCounter = 0; + } else { + emit scrollToIndex(idToIndex(eventIdToShow)); + showEventTimerCounter++; + } } void TimelineModel::requestKeyForEvent(QString id) { - auto encrypted_event = events.get(id.toStdString(), "", false); - if (encrypted_event) { - if (auto ev = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( - encrypted_event)) - events.requestSession(*ev, true); - } + auto encrypted_event = events.get(id.toStdString(), "", false); + if (encrypted_event) { + if (auto ev = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>( + encrypted_event)) + events.requestSession(*ev, true); + } } void TimelineModel::copyLinkToEvent(QString eventId) const { - QStringList vias; + QStringList vias; - auto alias = cache::client()->getRoomAliases(room_id_.toStdString()); - QString room; - if (alias) { - room = QString::fromStdString(alias->alias); - if (room.isEmpty() && !alias->alt_aliases.empty()) { - room = QString::fromStdString(alias->alt_aliases.front()); - } + auto alias = cache::client()->getRoomAliases(room_id_.toStdString()); + QString room; + if (alias) { + room = QString::fromStdString(alias->alias); + if (room.isEmpty() && !alias->alt_aliases.empty()) { + room = QString::fromStdString(alias->alt_aliases.front()); } + } - if (room.isEmpty()) - room = room_id_; + if (room.isEmpty()) + room = room_id_; - vias.push_back(QString("via=%1").arg(QString( - QUrl::toPercentEncoding(QString::fromStdString(http::client()->user_id().hostname()))))); - auto members = cache::getMembers(room_id_.toStdString(), 0, 100); - for (const auto &m : members) { - if (vias.size() >= 4) - break; + vias.push_back(QString("via=%1").arg(QString( + QUrl::toPercentEncoding(QString::fromStdString(http::client()->user_id().hostname()))))); + auto members = cache::getMembers(room_id_.toStdString(), 0, 100); + for (const auto &m : members) { + if (vias.size() >= 4) + break; - auto user_id = - mtx::identifiers::parse<mtx::identifiers::User>(m.user_id.toStdString()); - QString server = QString("via=%1").arg( - QString(QUrl::toPercentEncoding(QString::fromStdString(user_id.hostname())))); + auto user_id = mtx::identifiers::parse<mtx::identifiers::User>(m.user_id.toStdString()); + QString server = QString("via=%1").arg( + QString(QUrl::toPercentEncoding(QString::fromStdString(user_id.hostname())))); - if (!vias.contains(server)) - vias.push_back(server); - } + if (!vias.contains(server)) + vias.push_back(server); + } - auto link = QString("https://matrix.to/#/%1/%2?%3") - .arg(QString(QUrl::toPercentEncoding(room)), - QString(QUrl::toPercentEncoding(eventId)), - vias.join('&')); + auto link = QString("https://matrix.to/#/%1/%2?%3") + .arg(QString(QUrl::toPercentEncoding(room)), + QString(QUrl::toPercentEncoding(eventId)), + vias.join('&')); - QGuiApplication::clipboard()->setText(link); + QGuiApplication::clipboard()->setText(link); } QString TimelineModel::formatTypingUsers(const std::vector<QString> &users, QColor bg) { - QString temp = - tr("%1 and %2 are typing.", - "Multiple users are typing. First argument is a comma separated list of potentially " - "multiple users. Second argument is the last user of that list. (If only one user is " - "typing, %1 is empty. You should still use it in your string though to silence Qt " - "warnings.)", - (int)users.size()); + QString temp = + tr("%1 and %2 are typing.", + "Multiple users are typing. First argument is a comma separated list of potentially " + "multiple users. Second argument is the last user of that list. (If only one user is " + "typing, %1 is empty. You should still use it in your string though to silence Qt " + "warnings.)", + (int)users.size()); - if (users.empty()) { - return ""; - } + if (users.empty()) { + return ""; + } - QStringList uidWithoutLast; + QStringList uidWithoutLast; - auto formatUser = [this, bg](const QString &user_id) -> QString { - auto uncoloredUsername = utils::replaceEmoji(displayName(user_id)); - QString prefix = - QString("<font color=\"%1\">").arg(manager_->userColor(user_id, bg).name()); + auto formatUser = [this, bg](const QString &user_id) -> QString { + auto uncoloredUsername = utils::replaceEmoji(displayName(user_id)); + QString prefix = + QString("<font color=\"%1\">").arg(manager_->userColor(user_id, bg).name()); - // color only parts that don't have a font already specified - QString coloredUsername; - int index = 0; - do { - auto startIndex = uncoloredUsername.indexOf("<font", index); + // color only parts that don't have a font already specified + QString coloredUsername; + int index = 0; + do { + auto startIndex = uncoloredUsername.indexOf("<font", index); - if (startIndex - index != 0) - coloredUsername += - prefix + - uncoloredUsername.midRef( - index, startIndex > 0 ? startIndex - index : -1) + - "</font>"; + if (startIndex - index != 0) + coloredUsername += + prefix + + uncoloredUsername.midRef(index, startIndex > 0 ? startIndex - index : -1) + + "</font>"; - auto endIndex = uncoloredUsername.indexOf("</font>", startIndex); - if (endIndex > 0) - endIndex += sizeof("</font>") - 1; + auto endIndex = uncoloredUsername.indexOf("</font>", startIndex); + if (endIndex > 0) + endIndex += sizeof("</font>") - 1; - if (endIndex - startIndex != 0) - coloredUsername += - uncoloredUsername.midRef(startIndex, endIndex - startIndex); + if (endIndex - startIndex != 0) + coloredUsername += uncoloredUsername.midRef(startIndex, endIndex - startIndex); - index = endIndex; - } while (index > 0 && index < uncoloredUsername.size()); + index = endIndex; + } while (index > 0 && index < uncoloredUsername.size()); - return coloredUsername; - }; + return coloredUsername; + }; - for (size_t i = 0; i + 1 < users.size(); i++) { - uidWithoutLast.append(formatUser(users[i])); - } + for (size_t i = 0; i + 1 < users.size(); i++) { + uidWithoutLast.append(formatUser(users[i])); + } - return temp.arg(uidWithoutLast.join(", ")).arg(formatUser(users.back())); + return temp.arg(uidWithoutLast.join(", ")).arg(formatUser(users.back())); } QString TimelineModel::formatJoinRuleEvent(QString id) { - mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); - if (!e) - return ""; + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return ""; - auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::JoinRules>>(e); - if (!event) - return ""; + auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::JoinRules>>(e); + if (!event) + return ""; - QString user = QString::fromStdString(event->sender); - QString name = utils::replaceEmoji(displayName(user)); + QString user = QString::fromStdString(event->sender); + QString name = utils::replaceEmoji(displayName(user)); - switch (event->content.join_rule) { - case mtx::events::state::JoinRule::Public: - return tr("%1 opened the room to the public.").arg(name); - case mtx::events::state::JoinRule::Invite: - return tr("%1 made this room require and invitation to join.").arg(name); - case mtx::events::state::JoinRule::Knock: - return tr("%1 allowed to join this room by knocking.").arg(name); - case mtx::events::state::JoinRule::Restricted: { - QStringList rooms; - for (const auto &r : event->content.allow) { - if (r.type == mtx::events::state::JoinAllowanceType::RoomMembership) - rooms.push_back(QString::fromStdString(r.room_id)); - } - return tr("%1 allowed members of the following rooms to automatically join this " - "room: %2") - .arg(name) - .arg(rooms.join(", ")); - } - default: - // Currently, knock and private are reserved keywords and not implemented in Matrix. - return ""; + switch (event->content.join_rule) { + case mtx::events::state::JoinRule::Public: + return tr("%1 opened the room to the public.").arg(name); + case mtx::events::state::JoinRule::Invite: + return tr("%1 made this room require and invitation to join.").arg(name); + case mtx::events::state::JoinRule::Knock: + return tr("%1 allowed to join this room by knocking.").arg(name); + case mtx::events::state::JoinRule::Restricted: { + QStringList rooms; + for (const auto &r : event->content.allow) { + if (r.type == mtx::events::state::JoinAllowanceType::RoomMembership) + rooms.push_back(QString::fromStdString(r.room_id)); } + return tr("%1 allowed members of the following rooms to automatically join this " + "room: %2") + .arg(name) + .arg(rooms.join(", ")); + } + default: + // Currently, knock and private are reserved keywords and not implemented in Matrix. + return ""; + } } QString TimelineModel::formatGuestAccessEvent(QString id) { - mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); - if (!e) - return ""; + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return ""; - auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::GuestAccess>>(e); - if (!event) - return ""; + auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::GuestAccess>>(e); + if (!event) + return ""; - QString user = QString::fromStdString(event->sender); - QString name = utils::replaceEmoji(displayName(user)); + QString user = QString::fromStdString(event->sender); + QString name = utils::replaceEmoji(displayName(user)); - switch (event->content.guest_access) { - case mtx::events::state::AccessState::CanJoin: - return tr("%1 made the room open to guests.").arg(name); - case mtx::events::state::AccessState::Forbidden: - return tr("%1 has closed the room to guest access.").arg(name); - default: - return ""; - } + switch (event->content.guest_access) { + case mtx::events::state::AccessState::CanJoin: + return tr("%1 made the room open to guests.").arg(name); + case mtx::events::state::AccessState::Forbidden: + return tr("%1 has closed the room to guest access.").arg(name); + default: + return ""; + } } QString TimelineModel::formatHistoryVisibilityEvent(QString id) { - mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); - if (!e) - return ""; + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return ""; - auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::HistoryVisibility>>(e); + auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::HistoryVisibility>>(e); - if (!event) - return ""; + if (!event) + return ""; - QString user = QString::fromStdString(event->sender); - QString name = utils::replaceEmoji(displayName(user)); + QString user = QString::fromStdString(event->sender); + QString name = utils::replaceEmoji(displayName(user)); - switch (event->content.history_visibility) { - case mtx::events::state::Visibility::WorldReadable: - return tr("%1 made the room history world readable. Events may be now read by " - "non-joined people.") - .arg(name); - case mtx::events::state::Visibility::Shared: - return tr("%1 set the room history visible to members from this point on.") - .arg(name); - case mtx::events::state::Visibility::Invited: - return tr("%1 set the room history visible to members since they were invited.") - .arg(name); - case mtx::events::state::Visibility::Joined: - return tr("%1 set the room history visible to members since they joined the room.") - .arg(name); - default: - return ""; - } + switch (event->content.history_visibility) { + case mtx::events::state::Visibility::WorldReadable: + return tr("%1 made the room history world readable. Events may be now read by " + "non-joined people.") + .arg(name); + case mtx::events::state::Visibility::Shared: + return tr("%1 set the room history visible to members from this point on.").arg(name); + case mtx::events::state::Visibility::Invited: + return tr("%1 set the room history visible to members since they were invited.").arg(name); + case mtx::events::state::Visibility::Joined: + return tr("%1 set the room history visible to members since they joined the room.") + .arg(name); + default: + return ""; + } } QString TimelineModel::formatPowerLevelEvent(QString id) { - mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); - if (!e) - return ""; + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return ""; - auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::PowerLevels>>(e); - if (!event) - return ""; + auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::PowerLevels>>(e); + if (!event) + return ""; - QString user = QString::fromStdString(event->sender); - QString name = utils::replaceEmoji(displayName(user)); + QString user = QString::fromStdString(event->sender); + QString name = utils::replaceEmoji(displayName(user)); - // TODO: power levels rendering is actually a bit complex. work on this later. - return tr("%1 has changed the room's permissions.").arg(name); + // TODO: power levels rendering is actually a bit complex. work on this later. + return tr("%1 has changed the room's permissions.").arg(name); } void TimelineModel::acceptKnock(QString id) { - mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); - if (!e) - return; + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return; - auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e); - if (!event) - return; + auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e); + if (!event) + return; - if (!permissions_.canInvite()) - return; + if (!permissions_.canInvite()) + return; - if (cache::isRoomMember(event->state_key, room_id_.toStdString())) - return; + if (cache::isRoomMember(event->state_key, room_id_.toStdString())) + return; - using namespace mtx::events::state; - if (event->content.membership != Membership::Knock) - return; + using namespace mtx::events::state; + if (event->content.membership != Membership::Knock) + return; - ChatPage::instance()->inviteUser(QString::fromStdString(event->state_key), ""); + ChatPage::instance()->inviteUser(QString::fromStdString(event->state_key), ""); } bool TimelineModel::showAcceptKnockButton(QString id) { - mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); - if (!e) - return false; + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return false; - auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e); - if (!event) - return false; + auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e); + if (!event) + return false; - if (!permissions_.canInvite()) - return false; + if (!permissions_.canInvite()) + return false; - if (cache::isRoomMember(event->state_key, room_id_.toStdString())) - return false; + if (cache::isRoomMember(event->state_key, room_id_.toStdString())) + return false; - using namespace mtx::events::state; - return event->content.membership == Membership::Knock; + using namespace mtx::events::state; + return event->content.membership == Membership::Knock; } QString TimelineModel::formatMemberEvent(QString id) { - mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); - if (!e) - return ""; + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return ""; - auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e); - if (!event) - return ""; + auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e); + if (!event) + return ""; - mtx::events::StateEvent<mtx::events::state::Member> *prevEvent = nullptr; - if (!event->unsigned_data.replaces_state.empty()) { - auto tempPrevEvent = - events.get(event->unsigned_data.replaces_state, event->event_id); - if (tempPrevEvent) { - prevEvent = - std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>( - tempPrevEvent); - } + mtx::events::StateEvent<mtx::events::state::Member> *prevEvent = nullptr; + if (!event->unsigned_data.replaces_state.empty()) { + auto tempPrevEvent = events.get(event->unsigned_data.replaces_state, event->event_id); + if (tempPrevEvent) { + prevEvent = + std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(tempPrevEvent); } + } - QString user = QString::fromStdString(event->state_key); - QString name = utils::replaceEmoji(displayName(user)); - QString rendered; + QString user = QString::fromStdString(event->state_key); + QString name = utils::replaceEmoji(displayName(user)); + QString rendered; - // see table https://matrix.org/docs/spec/client_server/latest#m-room-member - using namespace mtx::events::state; - switch (event->content.membership) { - case Membership::Invite: - rendered = tr("%1 was invited.").arg(name); - break; - case Membership::Join: - if (prevEvent && prevEvent->content.membership == Membership::Join) { - QString oldName = utils::replaceEmoji( - QString::fromStdString(prevEvent->content.display_name).toHtmlEscaped()); + // see table https://matrix.org/docs/spec/client_server/latest#m-room-member + using namespace mtx::events::state; + switch (event->content.membership) { + case Membership::Invite: + rendered = tr("%1 was invited.").arg(name); + break; + case Membership::Join: + if (prevEvent && prevEvent->content.membership == Membership::Join) { + QString oldName = utils::replaceEmoji( + QString::fromStdString(prevEvent->content.display_name).toHtmlEscaped()); - bool displayNameChanged = - prevEvent->content.display_name != event->content.display_name; - bool avatarChanged = - prevEvent->content.avatar_url != event->content.avatar_url; + bool displayNameChanged = + prevEvent->content.display_name != event->content.display_name; + bool avatarChanged = prevEvent->content.avatar_url != event->content.avatar_url; - if (displayNameChanged && avatarChanged) - rendered = tr("%1 has changed their avatar and changed their " - "display name to %2.") - .arg(oldName, name); - else if (displayNameChanged) - rendered = - tr("%1 has changed their display name to %2.").arg(oldName, name); - else if (avatarChanged) - rendered = tr("%1 changed their avatar.").arg(name); - else - rendered = tr("%1 changed some profile info.").arg(name); - // the case of nothing changed but join follows join shouldn't happen, so - // just show it as join - } else { - if (event->content.join_authorised_via_users_server.empty()) - rendered = tr("%1 joined.").arg(name); - else - rendered = tr("%1 joined via authorisation from %2's server.") - .arg(name) - .arg(QString::fromStdString( - event->content.join_authorised_via_users_server)); - } - break; - case Membership::Leave: - if (!prevEvent) // Should only ever happen temporarily - return ""; + if (displayNameChanged && avatarChanged) + rendered = tr("%1 has changed their avatar and changed their " + "display name to %2.") + .arg(oldName, name); + else if (displayNameChanged) + rendered = tr("%1 has changed their display name to %2.").arg(oldName, name); + else if (avatarChanged) + rendered = tr("%1 changed their avatar.").arg(name); + else + rendered = tr("%1 changed some profile info.").arg(name); + // the case of nothing changed but join follows join shouldn't happen, so + // just show it as join + } else { + if (event->content.join_authorised_via_users_server.empty()) + rendered = tr("%1 joined.").arg(name); + else + rendered = + tr("%1 joined via authorisation from %2's server.") + .arg(name) + .arg(QString::fromStdString(event->content.join_authorised_via_users_server)); + } + break; + case Membership::Leave: + if (!prevEvent) // Should only ever happen temporarily + return ""; - if (prevEvent->content.membership == Membership::Invite) { - if (event->state_key == event->sender) - rendered = tr("%1 rejected their invite.").arg(name); - else - rendered = tr("Revoked the invite to %1.").arg(name); - } else if (prevEvent->content.membership == Membership::Join) { - if (event->state_key == event->sender) - rendered = tr("%1 left the room.").arg(name); - else - rendered = tr("Kicked %1.").arg(name); - } else if (prevEvent->content.membership == Membership::Ban) { - rendered = tr("Unbanned %1.").arg(name); - } else if (prevEvent->content.membership == Membership::Knock) { - if (event->state_key == event->sender) - rendered = tr("%1 redacted their knock.").arg(name); - else - rendered = tr("Rejected the knock from %1.").arg(name); - } else - return tr("%1 left after having already left!", - "This is a leave event after the user already left and shouldn't " - "happen apart from state resets") - .arg(name); - break; + if (prevEvent->content.membership == Membership::Invite) { + if (event->state_key == event->sender) + rendered = tr("%1 rejected their invite.").arg(name); + else + rendered = tr("Revoked the invite to %1.").arg(name); + } else if (prevEvent->content.membership == Membership::Join) { + if (event->state_key == event->sender) + rendered = tr("%1 left the room.").arg(name); + else + rendered = tr("Kicked %1.").arg(name); + } else if (prevEvent->content.membership == Membership::Ban) { + rendered = tr("Unbanned %1.").arg(name); + } else if (prevEvent->content.membership == Membership::Knock) { + if (event->state_key == event->sender) + rendered = tr("%1 redacted their knock.").arg(name); + else + rendered = tr("Rejected the knock from %1.").arg(name); + } else + return tr("%1 left after having already left!", + "This is a leave event after the user already left and shouldn't " + "happen apart from state resets") + .arg(name); + break; - case Membership::Ban: - rendered = tr("%1 was banned.").arg(name); - break; - case Membership::Knock: - rendered = tr("%1 knocked.").arg(name); - break; - } + case Membership::Ban: + rendered = tr("%1 was banned.").arg(name); + break; + case Membership::Knock: + rendered = tr("%1 knocked.").arg(name); + break; + } - if (event->content.reason != "") { - rendered += - " " + tr("Reason: %1").arg(QString::fromStdString(event->content.reason)); - } + if (event->content.reason != "") { + rendered += " " + tr("Reason: %1").arg(QString::fromStdString(event->content.reason)); + } - return rendered; + return rendered; } void TimelineModel::setEdit(QString newEdit) { - if (newEdit.isEmpty()) { - resetEdit(); - return; - } + if (newEdit.isEmpty()) { + resetEdit(); + return; + } - if (edit_.isEmpty()) { - this->textBeforeEdit = input()->text(); - this->replyBeforeEdit = reply_; - nhlog::ui()->debug("Stored: {}", textBeforeEdit.toStdString()); - } + if (edit_.isEmpty()) { + this->textBeforeEdit = input()->text(); + this->replyBeforeEdit = reply_; + nhlog::ui()->debug("Stored: {}", textBeforeEdit.toStdString()); + } - if (edit_ != newEdit) { - auto ev = events.get(newEdit.toStdString(), ""); - if (ev && mtx::accessors::sender(*ev) == http::client()->user_id().to_string()) { - auto e = *ev; - setReply(QString::fromStdString( - mtx::accessors::relations(e).reply_to().value_or(""))); + if (edit_ != newEdit) { + auto ev = events.get(newEdit.toStdString(), ""); + if (ev && mtx::accessors::sender(*ev) == http::client()->user_id().to_string()) { + auto e = *ev; + setReply(QString::fromStdString(mtx::accessors::relations(e).reply_to().value_or(""))); - auto msgType = mtx::accessors::msg_type(e); - if (msgType == mtx::events::MessageType::Text || - msgType == mtx::events::MessageType::Notice || - msgType == mtx::events::MessageType::Emote) { - auto relInfo = relatedInfo(newEdit); - auto editText = relInfo.quoted_body; + auto msgType = mtx::accessors::msg_type(e); + if (msgType == mtx::events::MessageType::Text || + msgType == mtx::events::MessageType::Notice || + msgType == mtx::events::MessageType::Emote) { + auto relInfo = relatedInfo(newEdit); + auto editText = relInfo.quoted_body; - if (!relInfo.quoted_formatted_body.isEmpty()) { - auto matches = conf::strings::matrixToLink.globalMatch( - relInfo.quoted_formatted_body); - std::map<QString, QString> reverseNameMapping; - while (matches.hasNext()) { - auto m = matches.next(); - reverseNameMapping[m.captured(2)] = m.captured(1); - } + if (!relInfo.quoted_formatted_body.isEmpty()) { + auto matches = + conf::strings::matrixToLink.globalMatch(relInfo.quoted_formatted_body); + std::map<QString, QString> reverseNameMapping; + while (matches.hasNext()) { + auto m = matches.next(); + reverseNameMapping[m.captured(2)] = m.captured(1); + } - for (const auto &[user, link] : reverseNameMapping) { - // TODO(Nico): html unescape the user name - editText.replace( - user, QStringLiteral("[%1](%2)").arg(user, link)); - } - } + for (const auto &[user, link] : reverseNameMapping) { + // TODO(Nico): html unescape the user name + editText.replace(user, QStringLiteral("[%1](%2)").arg(user, link)); + } + } - if (msgType == mtx::events::MessageType::Emote) - input()->setText("/me " + editText); - else - input()->setText(editText); - } else { - input()->setText(""); - } + if (msgType == mtx::events::MessageType::Emote) + input()->setText("/me " + editText); + else + input()->setText(editText); + } else { + input()->setText(""); + } - edit_ = newEdit; - } else { - resetReply(); + edit_ = newEdit; + } else { + resetReply(); - input()->setText(""); - edit_ = ""; - } - emit editChanged(edit_); + input()->setText(""); + edit_ = ""; } + emit editChanged(edit_); + } } void TimelineModel::resetEdit() { - if (!edit_.isEmpty()) { - edit_ = ""; - emit editChanged(edit_); - nhlog::ui()->debug("Restoring: {}", textBeforeEdit.toStdString()); - input()->setText(textBeforeEdit); - textBeforeEdit.clear(); - if (replyBeforeEdit.isEmpty()) - resetReply(); - else - setReply(replyBeforeEdit); - replyBeforeEdit.clear(); - } + if (!edit_.isEmpty()) { + edit_ = ""; + emit editChanged(edit_); + nhlog::ui()->debug("Restoring: {}", textBeforeEdit.toStdString()); + input()->setText(textBeforeEdit); + textBeforeEdit.clear(); + if (replyBeforeEdit.isEmpty()) + resetReply(); + else + setReply(replyBeforeEdit); + replyBeforeEdit.clear(); + } } QString TimelineModel::roomName() const { - auto info = cache::getRoomInfo({room_id_.toStdString()}); + auto info = cache::getRoomInfo({room_id_.toStdString()}); - if (!info.count(room_id_)) - return ""; - else - return utils::replaceEmoji( - QString::fromStdString(info[room_id_].name).toHtmlEscaped()); + if (!info.count(room_id_)) + return ""; + else + return utils::replaceEmoji(QString::fromStdString(info[room_id_].name).toHtmlEscaped()); } QString TimelineModel::plainRoomName() const { - auto info = cache::getRoomInfo({room_id_.toStdString()}); + auto info = cache::getRoomInfo({room_id_.toStdString()}); - if (!info.count(room_id_)) - return ""; - else - return QString::fromStdString(info[room_id_].name); + if (!info.count(room_id_)) + return ""; + else + return QString::fromStdString(info[room_id_].name); } QString TimelineModel::roomAvatarUrl() const { - auto info = cache::getRoomInfo({room_id_.toStdString()}); + auto info = cache::getRoomInfo({room_id_.toStdString()}); - if (!info.count(room_id_)) - return ""; - else - return QString::fromStdString(info[room_id_].avatar_url); + if (!info.count(room_id_)) + return ""; + else + return QString::fromStdString(info[room_id_].avatar_url); } QString TimelineModel::roomTopic() const { - auto info = cache::getRoomInfo({room_id_.toStdString()}); + auto info = cache::getRoomInfo({room_id_.toStdString()}); - if (!info.count(room_id_)) - return ""; - else - return utils::replaceEmoji(utils::linkifyMessage( - QString::fromStdString(info[room_id_].topic).toHtmlEscaped())); + if (!info.count(room_id_)) + return ""; + else + return utils::replaceEmoji( + utils::linkifyMessage(QString::fromStdString(info[room_id_].topic).toHtmlEscaped())); } crypto::Trust TimelineModel::trustlevel() const { - if (!isEncrypted_) - return crypto::Trust::Unverified; + if (!isEncrypted_) + return crypto::Trust::Unverified; - return cache::client()->roomVerificationStatus(room_id_.toStdString()); + return cache::client()->roomVerificationStatus(room_id_.toStdString()); } int TimelineModel::roomMemberCount() const { - return (int)cache::client()->memberCount(room_id_.toStdString()); + return (int)cache::client()->memberCount(room_id_.toStdString()); } QString TimelineModel::directChatOtherUserId() const { - if (roomMemberCount() < 3) { - QString id; - for (auto member : cache::getMembers(room_id_.toStdString())) - if (member.user_id != UserSettings::instance()->userId()) - id = member.user_id; - return id; - } else - return ""; + if (roomMemberCount() < 3) { + QString id; + for (auto member : cache::getMembers(room_id_.toStdString())) + if (member.user_id != UserSettings::instance()->userId()) + id = member.user_id; + return id; + } else + return ""; } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 66e0622e..f16529e2 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h
@@ -39,95 +39,95 @@ Q_NAMESPACE enum EventType { - // Unsupported event - Unsupported, - /// m.room_key_request - KeyRequest, - /// m.reaction, - Reaction, - /// m.room.aliases - Aliases, - /// m.room.avatar - Avatar, - /// m.call.invite - CallInvite, - /// m.call.answer - CallAnswer, - /// m.call.hangup - CallHangUp, - /// m.call.candidates - CallCandidates, - /// m.room.canonical_alias - CanonicalAlias, - /// m.room.create - RoomCreate, - /// m.room.encrypted. - Encrypted, - /// m.room.encryption. - Encryption, - /// m.room.guest_access - RoomGuestAccess, - /// m.room.history_visibility - RoomHistoryVisibility, - /// m.room.join_rules - RoomJoinRules, - /// m.room.member - Member, - /// m.room.name - Name, - /// m.room.power_levels - PowerLevels, - /// m.room.tombstone - Tombstone, - /// m.room.topic - Topic, - /// m.room.redaction - Redaction, - /// m.room.pinned_events - PinnedEvents, - // m.sticker - Sticker, - // m.tag - Tag, - /// m.room.message - AudioMessage, - EmoteMessage, - FileMessage, - ImageMessage, - LocationMessage, - NoticeMessage, - TextMessage, - VideoMessage, - Redacted, - UnknownMessage, - KeyVerificationRequest, - KeyVerificationStart, - KeyVerificationMac, - KeyVerificationAccept, - KeyVerificationCancel, - KeyVerificationKey, - KeyVerificationDone, - 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, + // Unsupported event + Unsupported, + /// m.room_key_request + KeyRequest, + /// m.reaction, + Reaction, + /// m.room.aliases + Aliases, + /// m.room.avatar + Avatar, + /// m.call.invite + CallInvite, + /// m.call.answer + CallAnswer, + /// m.call.hangup + CallHangUp, + /// m.call.candidates + CallCandidates, + /// m.room.canonical_alias + CanonicalAlias, + /// m.room.create + RoomCreate, + /// m.room.encrypted. + Encrypted, + /// m.room.encryption. + Encryption, + /// m.room.guest_access + RoomGuestAccess, + /// m.room.history_visibility + RoomHistoryVisibility, + /// m.room.join_rules + RoomJoinRules, + /// m.room.member + Member, + /// m.room.name + Name, + /// m.room.power_levels + PowerLevels, + /// m.room.tombstone + Tombstone, + /// m.room.topic + Topic, + /// m.room.redaction + Redaction, + /// m.room.pinned_events + PinnedEvents, + // m.sticker + Sticker, + // m.tag + Tag, + /// m.room.message + AudioMessage, + EmoteMessage, + FileMessage, + ImageMessage, + LocationMessage, + NoticeMessage, + TextMessage, + VideoMessage, + Redacted, + UnknownMessage, + KeyVerificationRequest, + KeyVerificationStart, + KeyVerificationMac, + KeyVerificationAccept, + KeyVerificationCancel, + KeyVerificationKey, + KeyVerificationDone, + 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); enum EventState { - //! The plaintext message was received by the server. - Received, - //! At least one of the participants has read the message. - Read, - //! The client sent the message. Not yet received. - Sent, - //! When the message is loaded from cache or backfill. - Empty, + //! The plaintext message was received by the server. + Received, + //! At least one of the participants has read the message. + Read, + //! The client sent the message. Not yet received. + Sent, + //! When the message is loaded from cache or backfill. + Empty, }; Q_ENUM_NS(EventState) } @@ -135,330 +135,329 @@ Q_ENUM_NS(EventState) class StateKeeper { public: - StateKeeper(std::function<void()> &&fn) - : fn_(std::move(fn)) - {} + StateKeeper(std::function<void()> &&fn) + : fn_(std::move(fn)) + {} - ~StateKeeper() { fn_(); } + ~StateKeeper() { fn_(); } private: - std::function<void()> fn_; + std::function<void()> fn_; }; struct DecryptionResult { - //! The decrypted content as a normal plaintext event. - mtx::events::collections::TimelineEvents event; - //! Whether or not the decryption was successful. - bool isDecrypted = false; + //! The decrypted content as a normal plaintext event. + mtx::events::collections::TimelineEvents event; + //! Whether or not the decryption was successful. + bool isDecrypted = false; }; class TimelineViewManager; class TimelineModel : public QAbstractListModel { - Q_OBJECT - Q_PROPERTY( - int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) - Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY - typingUsersChanged) - Q_PROPERTY(QString scrollTarget READ scrollTarget NOTIFY scrollTargetChanged) - Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply) - Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit) - Q_PROPERTY( - bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged) - Q_PROPERTY(QString roomId READ roomId CONSTANT) - Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) - Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY plainRoomNameChanged) - Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged) - Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged) - Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged) - Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged) - Q_PROPERTY(bool isSpace READ isSpace CONSTANT) - Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged) - Q_PROPERTY(bool isDirect READ isDirect NOTIFY isDirectChanged) - Q_PROPERTY(QString directChatOtherUserId READ directChatOtherUserId NOTIFY - directChatOtherUserIdChanged) - Q_PROPERTY(InputBar *input READ input CONSTANT) - Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged) + Q_OBJECT + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY + typingUsersChanged) + Q_PROPERTY(QString scrollTarget READ scrollTarget NOTIFY scrollTargetChanged) + Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply) + Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit) + Q_PROPERTY( + bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged) + Q_PROPERTY(QString roomId READ roomId CONSTANT) + Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) + Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY plainRoomNameChanged) + Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged) + Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged) + Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged) + Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged) + Q_PROPERTY(bool isSpace READ isSpace CONSTANT) + Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged) + Q_PROPERTY(bool isDirect READ isDirect NOTIFY isDirectChanged) + Q_PROPERTY( + QString directChatOtherUserId READ directChatOtherUserId NOTIFY directChatOtherUserIdChanged) + Q_PROPERTY(InputBar *input READ input CONSTANT) + Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged) public: - explicit TimelineModel(TimelineViewManager *manager, - QString room_id, - QObject *parent = nullptr); + explicit TimelineModel(TimelineViewManager *manager, + QString room_id, + QObject *parent = nullptr); - enum Roles - { - Type, - TypeString, - IsOnlyEmoji, - Body, - FormattedBody, - PreviousMessageUserId, - IsSender, - UserId, - UserName, - PreviousMessageDay, - Day, - Timestamp, - Url, - ThumbnailUrl, - Blurhash, - Filename, - Filesize, - MimeType, - OriginalHeight, - OriginalWidth, - ProportionalHeight, - EventId, - State, - IsEdited, - IsEditable, - IsEncrypted, - Trustlevel, - EncryptionError, - ReplyTo, - Reactions, - RoomId, - RoomName, - RoomTopic, - CallType, - Dump, - RelatedEventCacheBuster, - }; - Q_ENUM(Roles); + enum Roles + { + Type, + TypeString, + IsOnlyEmoji, + Body, + FormattedBody, + PreviousMessageUserId, + IsSender, + UserId, + UserName, + PreviousMessageDay, + Day, + Timestamp, + Url, + ThumbnailUrl, + Blurhash, + Filename, + Filesize, + MimeType, + OriginalHeight, + OriginalWidth, + ProportionalHeight, + EventId, + State, + IsEdited, + IsEditable, + IsEncrypted, + Trustlevel, + EncryptionError, + ReplyTo, + Reactions, + RoomId, + RoomName, + RoomTopic, + CallType, + Dump, + RelatedEventCacheBuster, + }; + Q_ENUM(Roles); - QHash<int, QByteArray> roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const; - Q_INVOKABLE QVariant dataById(QString id, int role, QString relatedTo); + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const; + Q_INVOKABLE QVariant dataById(QString id, int role, QString relatedTo); - bool canFetchMore(const QModelIndex &) const override; - void fetchMore(const QModelIndex &) override; + bool canFetchMore(const QModelIndex &) const override; + void fetchMore(const QModelIndex &) override; - Q_INVOKABLE QString displayName(QString id) const; - Q_INVOKABLE QString avatarUrl(QString id) const; - Q_INVOKABLE QString formatDateSeparator(QDate date) const; - Q_INVOKABLE QString formatTypingUsers(const std::vector<QString> &users, QColor bg); - Q_INVOKABLE bool showAcceptKnockButton(QString id); - Q_INVOKABLE void acceptKnock(QString id); - Q_INVOKABLE QString formatMemberEvent(QString id); - Q_INVOKABLE QString formatJoinRuleEvent(QString id); - Q_INVOKABLE QString formatHistoryVisibilityEvent(QString id); - Q_INVOKABLE QString formatGuestAccessEvent(QString id); - Q_INVOKABLE QString formatPowerLevelEvent(QString id); + Q_INVOKABLE QString displayName(QString id) const; + Q_INVOKABLE QString avatarUrl(QString id) const; + Q_INVOKABLE QString formatDateSeparator(QDate date) const; + Q_INVOKABLE QString formatTypingUsers(const std::vector<QString> &users, QColor bg); + Q_INVOKABLE bool showAcceptKnockButton(QString id); + Q_INVOKABLE void acceptKnock(QString id); + Q_INVOKABLE QString formatMemberEvent(QString id); + Q_INVOKABLE QString formatJoinRuleEvent(QString id); + Q_INVOKABLE QString formatHistoryVisibilityEvent(QString id); + Q_INVOKABLE QString formatGuestAccessEvent(QString id); + Q_INVOKABLE QString formatPowerLevelEvent(QString id); - Q_INVOKABLE void viewRawMessage(QString id); - Q_INVOKABLE void forwardMessage(QString eventId, QString roomId); - Q_INVOKABLE void viewDecryptedRawMessage(QString id); - Q_INVOKABLE void openUserProfile(QString userid); - Q_INVOKABLE void editAction(QString id); - Q_INVOKABLE void replyAction(QString id); - Q_INVOKABLE void showReadReceipts(QString id); - Q_INVOKABLE void redactEvent(QString id); - Q_INVOKABLE int idToIndex(QString id) const; - Q_INVOKABLE QString indexToId(int index) const; - Q_INVOKABLE void openMedia(QString eventId); - Q_INVOKABLE void cacheMedia(QString eventId); - Q_INVOKABLE bool saveMedia(QString eventId) const; - Q_INVOKABLE void showEvent(QString eventId); - Q_INVOKABLE void copyLinkToEvent(QString eventId) const; - void cacheMedia(QString eventId, std::function<void(const QString filename)> callback); - Q_INVOKABLE void sendReset() - { - beginResetModel(); - endResetModel(); - } + Q_INVOKABLE void viewRawMessage(QString id); + Q_INVOKABLE void forwardMessage(QString eventId, QString roomId); + Q_INVOKABLE void viewDecryptedRawMessage(QString id); + Q_INVOKABLE void openUserProfile(QString userid); + Q_INVOKABLE void editAction(QString id); + Q_INVOKABLE void replyAction(QString id); + Q_INVOKABLE void showReadReceipts(QString id); + Q_INVOKABLE void redactEvent(QString id); + Q_INVOKABLE int idToIndex(QString id) const; + Q_INVOKABLE QString indexToId(int index) const; + Q_INVOKABLE void openMedia(QString eventId); + Q_INVOKABLE void cacheMedia(QString eventId); + Q_INVOKABLE bool saveMedia(QString eventId) const; + Q_INVOKABLE void showEvent(QString eventId); + Q_INVOKABLE void copyLinkToEvent(QString eventId) const; + void cacheMedia(QString eventId, std::function<void(const QString filename)> callback); + Q_INVOKABLE void sendReset() + { + beginResetModel(); + endResetModel(); + } - Q_INVOKABLE void requestKeyForEvent(QString id); + Q_INVOKABLE void requestKeyForEvent(QString id); - std::vector<::Reaction> reactions(const std::string &event_id) - { - auto list = events.reactions(event_id); - std::vector<::Reaction> vec; - for (const auto &r : list) - vec.push_back(r.value<Reaction>()); - return vec; - } + std::vector<::Reaction> reactions(const std::string &event_id) + { + auto list = events.reactions(event_id); + std::vector<::Reaction> vec; + for (const auto &r : list) + vec.push_back(r.value<Reaction>()); + return vec; + } - void updateLastMessage(); - void sync(const mtx::responses::JoinedRoom &room); - void addEvents(const mtx::responses::Timeline &events); - void syncState(const mtx::responses::State &state); - template<class T> - void sendMessageEvent(const T &content, mtx::events::EventType eventType); - RelatedInfo relatedInfo(QString id); + void updateLastMessage(); + void sync(const mtx::responses::JoinedRoom &room); + void addEvents(const mtx::responses::Timeline &events); + void syncState(const mtx::responses::State &state); + template<class T> + void sendMessageEvent(const T &content, mtx::events::EventType eventType); + RelatedInfo relatedInfo(QString id); - DescInfo lastMessage() const { return lastMessage_; } - bool isSpace() const { return isSpace_; } - bool isEncrypted() const { return isEncrypted_; } - crypto::Trust trustlevel() const; - int roomMemberCount() const; - bool isDirect() const { return roomMemberCount() <= 2; } - QString directChatOtherUserId() const; + DescInfo lastMessage() const { return lastMessage_; } + bool isSpace() const { return isSpace_; } + bool isEncrypted() const { return isEncrypted_; } + crypto::Trust trustlevel() const; + int roomMemberCount() const; + bool isDirect() const { return roomMemberCount() <= 2; } + QString directChatOtherUserId() const; - std::optional<mtx::events::collections::TimelineEvents> eventById(const QString &id) - { - auto e = events.get(id.toStdString(), ""); - if (e) - return *e; - else - return std::nullopt; - } + std::optional<mtx::events::collections::TimelineEvents> eventById(const QString &id) + { + auto e = events.get(id.toStdString(), ""); + if (e) + return *e; + else + return std::nullopt; + } public slots: - void setCurrentIndex(int index); - int currentIndex() const { return idToIndex(currentId); } - void eventShown(); - void markEventsAsRead(const std::vector<QString> &event_ids); - QVariantMap getDump(QString eventId, QString relatedTo) const; - void updateTypingUsers(const std::vector<QString> &users) - { - if (this->typingUsers_ != users) { - this->typingUsers_ = users; - emit typingUsersChanged(typingUsers_); - } + void setCurrentIndex(int index); + int currentIndex() const { return idToIndex(currentId); } + void eventShown(); + void markEventsAsRead(const std::vector<QString> &event_ids); + QVariantMap getDump(QString eventId, QString relatedTo) const; + void updateTypingUsers(const std::vector<QString> &users) + { + if (this->typingUsers_ != users) { + this->typingUsers_ = users; + emit typingUsersChanged(typingUsers_); } - std::vector<QString> typingUsers() const { return typingUsers_; } - bool paginationInProgress() const { return m_paginationInProgress; } - QString reply() const { return reply_; } - void setReply(QString newReply) - { - if (edit_.startsWith('m')) - return; + } + std::vector<QString> typingUsers() const { return typingUsers_; } + bool paginationInProgress() const { return m_paginationInProgress; } + QString reply() const { return reply_; } + void setReply(QString newReply) + { + if (edit_.startsWith('m')) + return; - if (reply_ != newReply) { - reply_ = newReply; - emit replyChanged(reply_); - } + if (reply_ != newReply) { + reply_ = newReply; + emit replyChanged(reply_); } - void resetReply() - { - if (!reply_.isEmpty()) { - reply_ = ""; - emit replyChanged(reply_); - } - } - QString edit() const { return edit_; } - void setEdit(QString newEdit); - void resetEdit(); - void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; } - void clearTimeline() { events.clearTimeline(); } - void receivedSessionKey(const std::string &session_key) - { - events.receivedSessionKey(session_key); + } + void resetReply() + { + if (!reply_.isEmpty()) { + reply_ = ""; + emit replyChanged(reply_); } + } + QString edit() const { return edit_; } + void setEdit(QString newEdit); + void resetEdit(); + void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; } + void clearTimeline() { events.clearTimeline(); } + void receivedSessionKey(const std::string &session_key) + { + events.receivedSessionKey(session_key); + } - QString roomName() const; - QString plainRoomName() const; - QString roomTopic() const; - InputBar *input() { return &input_; } - Permissions *permissions() { return &permissions_; } - QString roomAvatarUrl() const; - QString roomId() const { return room_id_; } + QString roomName() const; + QString plainRoomName() const; + QString roomTopic() const; + InputBar *input() { return &input_; } + Permissions *permissions() { return &permissions_; } + QString roomAvatarUrl() const; + QString roomId() const { return room_id_; } - bool hasMentions() { return highlight_count > 0; } - int notificationCount() { return notification_count; } + bool hasMentions() { return highlight_count > 0; } + int notificationCount() { return notification_count; } - QString scrollTarget() const; + QString scrollTarget() const; private slots: - void addPendingMessage(mtx::events::collections::TimelineEvents event); - void scrollTimerEvent(); + void addPendingMessage(mtx::events::collections::TimelineEvents event); + void scrollTimerEvent(); signals: - void currentIndexChanged(int index); - void redactionFailed(QString id); - void eventRedacted(QString id); - void mediaCached(QString mxcUrl, QString cacheUrl); - void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo); - void typingUsersChanged(std::vector<QString> users); - void replyChanged(QString reply); - void editChanged(QString reply); - void openReadReceiptsDialog(ReadReceiptsProxy *rr); - void showRawMessageDialog(QString rawMessage); - void paginationInProgressChanged(const bool); - void newCallEvent(const mtx::events::collections::TimelineEvents &event); - void scrollToIndex(int index); + void currentIndexChanged(int index); + void redactionFailed(QString id); + void eventRedacted(QString id); + void mediaCached(QString mxcUrl, QString cacheUrl); + void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo); + void typingUsersChanged(std::vector<QString> users); + void replyChanged(QString reply); + void editChanged(QString reply); + void openReadReceiptsDialog(ReadReceiptsProxy *rr); + void showRawMessageDialog(QString rawMessage); + void paginationInProgressChanged(const bool); + void newCallEvent(const mtx::events::collections::TimelineEvents &event); + void scrollToIndex(int index); - void lastMessageChanged(); - void notificationsChanged(); + void lastMessageChanged(); + void notificationsChanged(); - void newMessageToSend(mtx::events::collections::TimelineEvents event); - void addPendingMessageToStore(mtx::events::collections::TimelineEvents event); - void updateFlowEventId(std::string event_id); + void newMessageToSend(mtx::events::collections::TimelineEvents event); + void addPendingMessageToStore(mtx::events::collections::TimelineEvents event); + void updateFlowEventId(std::string event_id); - void encryptionChanged(); - void trustlevelChanged(); - void roomNameChanged(); - void plainRoomNameChanged(); - void roomTopicChanged(); - void roomAvatarUrlChanged(); - void roomMemberCountChanged(); - void isDirectChanged(); - void directChatOtherUserIdChanged(); - void permissionsChanged(); - void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId); + void encryptionChanged(); + void trustlevelChanged(); + void roomNameChanged(); + void plainRoomNameChanged(); + void roomTopicChanged(); + void roomAvatarUrlChanged(); + void roomMemberCountChanged(); + void isDirectChanged(); + void directChatOtherUserIdChanged(); + void permissionsChanged(); + void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId); - void scrollTargetChanged(); + void scrollTargetChanged(); private: - template<typename T> - void sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType); - void readEvent(const std::string &id); + template<typename T> + void sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType); + void readEvent(const std::string &id); - void setPaginationInProgress(const bool paginationInProgress); + void setPaginationInProgress(const bool paginationInProgress); - QSet<QString> read; + QSet<QString> read; - mutable EventStore events; + mutable EventStore events; - QString room_id_; + QString room_id_; - QString currentId, currentReadId; - QString reply_, edit_; - QString textBeforeEdit, replyBeforeEdit; - std::vector<QString> typingUsers_; + QString currentId, currentReadId; + QString reply_, edit_; + QString textBeforeEdit, replyBeforeEdit; + std::vector<QString> typingUsers_; - TimelineViewManager *manager_; + TimelineViewManager *manager_; - InputBar input_{this}; - Permissions permissions_; + InputBar input_{this}; + Permissions permissions_; - QTimer showEventTimer{this}; - QString eventIdToShow; - int showEventTimerCounter = 0; + QTimer showEventTimer{this}; + QString eventIdToShow; + int showEventTimerCounter = 0; - DescInfo lastMessage_{}; + DescInfo lastMessage_{}; - friend struct SendMessageVisitor; + friend struct SendMessageVisitor; - int notification_count = 0, highlight_count = 0; + int notification_count = 0, highlight_count = 0; - unsigned int relatedEventCacheBuster = 0; + unsigned int relatedEventCacheBuster = 0; - bool decryptDescription = true; - bool m_paginationInProgress = false; - bool isSpace_ = false; - bool isEncrypted_ = false; + bool decryptDescription = true; + bool m_paginationInProgress = false; + bool isSpace_ = false; + bool isEncrypted_ = false; }; template<class T> void TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType) { - if constexpr (std::is_same_v<T, mtx::events::msg::StickerImage>) { - mtx::events::Sticker msgCopy = {}; - msgCopy.content = content; - msgCopy.type = eventType; - emit newMessageToSend(msgCopy); - } else { - mtx::events::RoomEvent<T> msgCopy = {}; - msgCopy.content = content; - msgCopy.type = eventType; - emit newMessageToSend(msgCopy); - } - resetReply(); - resetEdit(); + if constexpr (std::is_same_v<T, mtx::events::msg::StickerImage>) { + mtx::events::Sticker msgCopy = {}; + msgCopy.content = content; + msgCopy.type = eventType; + emit newMessageToSend(msgCopy); + } else { + mtx::events::RoomEvent<T> msgCopy = {}; + msgCopy.content = content; + msgCopy.type = eventType; + emit newMessageToSend(msgCopy); + } + resetReply(); + resetEdit(); } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index ea231b03..8a33dc2b 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp
@@ -67,73 +67,72 @@ template<typename T> static constexpr bool messageWithFileAndUrl(const mtx::events::Event<T> &) { - return is_detected<file_t, T>::value && is_detected<url_t, T>::value; + return is_detected<file_t, T>::value && is_detected<url_t, T>::value; } template<typename T> static constexpr void removeReplyFallback(mtx::events::Event<T> &e) { - if constexpr (is_detected<body_t, T>::value) { - if constexpr (std::is_same_v<std::optional<std::string>, - std::remove_cv_t<decltype(e.content.body)>>) { - if (e.content.body) { - e.content.body = utils::stripReplyFromBody(e.content.body); - } - } else if constexpr (std::is_same_v<std::string, - std::remove_cv_t<decltype(e.content.body)>>) { - e.content.body = utils::stripReplyFromBody(e.content.body); - } + if constexpr (is_detected<body_t, T>::value) { + if constexpr (std::is_same_v<std::optional<std::string>, + std::remove_cv_t<decltype(e.content.body)>>) { + if (e.content.body) { + e.content.body = utils::stripReplyFromBody(e.content.body); + } + } else if constexpr (std::is_same_v<std::string, + std::remove_cv_t<decltype(e.content.body)>>) { + e.content.body = utils::stripReplyFromBody(e.content.body); } + } - if constexpr (is_detected<formatted_body_t, T>::value) { - if (e.content.format == "org.matrix.custom.html") { - e.content.formatted_body = - utils::stripReplyFromFormattedBody(e.content.formatted_body); - } + if constexpr (is_detected<formatted_body_t, T>::value) { + if (e.content.format == "org.matrix.custom.html") { + e.content.formatted_body = utils::stripReplyFromFormattedBody(e.content.formatted_body); } + } } } void TimelineViewManager::updateColorPalette() { - userColors.clear(); + userColors.clear(); - if (ChatPage::instance()->userSettings()->theme() == "light") { - view->rootContext()->setContextProperty("currentActivePalette", QPalette()); - view->rootContext()->setContextProperty("currentInactivePalette", QPalette()); - } else if (ChatPage::instance()->userSettings()->theme() == "dark") { - view->rootContext()->setContextProperty("currentActivePalette", QPalette()); - view->rootContext()->setContextProperty("currentInactivePalette", QPalette()); - } else { - view->rootContext()->setContextProperty("currentActivePalette", QPalette()); - view->rootContext()->setContextProperty("currentInactivePalette", nullptr); - } + if (ChatPage::instance()->userSettings()->theme() == "light") { + view->rootContext()->setContextProperty("currentActivePalette", QPalette()); + view->rootContext()->setContextProperty("currentInactivePalette", QPalette()); + } else if (ChatPage::instance()->userSettings()->theme() == "dark") { + view->rootContext()->setContextProperty("currentActivePalette", QPalette()); + view->rootContext()->setContextProperty("currentInactivePalette", QPalette()); + } else { + view->rootContext()->setContextProperty("currentActivePalette", QPalette()); + view->rootContext()->setContextProperty("currentInactivePalette", nullptr); + } } QColor TimelineViewManager::userColor(QString id, QColor background) { - if (!userColors.contains(id)) - userColors.insert(id, QColor(utils::generateContrastingHexColor(id, background))); - return userColors.value(id); + if (!userColors.contains(id)) + userColors.insert(id, QColor(utils::generateContrastingHexColor(id, background))); + return userColors.value(id); } QString TimelineViewManager::userPresence(QString id) const { - if (id.isEmpty()) - return ""; - else - return QString::fromStdString( - mtx::presence::to_string(cache::presenceState(id.toStdString()))); + if (id.isEmpty()) + return ""; + else + return QString::fromStdString( + mtx::presence::to_string(cache::presenceState(id.toStdString()))); } QString TimelineViewManager::userStatus(QString id) const { - return QString::fromStdString(cache::statusMessage(id.toStdString())); + return QString::fromStdString(cache::statusMessage(id.toStdString())); } TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent) @@ -146,453 +145,432 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par , rooms_(new RoomlistModel(this)) , communities_(new CommunitiesModel(this)) { - qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationDone>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationKey>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationMac>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationReady>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>(); - qRegisterMetaType<mtx::events::msg::KeyVerificationStart>(); - qRegisterMetaType<CombinedImagePackModel *>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationDone>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationKey>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationMac>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationReady>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>(); + qRegisterMetaType<mtx::events::msg::KeyVerificationStart>(); + qRegisterMetaType<CombinedImagePackModel *>(); - qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>(); + qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>(); - qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, - "im.nheko", - 1, - 0, - "MtxEvent", - "Can't instantiate enum!"); - qmlRegisterUncreatableMetaObject( - olm::staticMetaObject, "im.nheko", 1, 0, "Olm", "Can't instantiate enum!"); - qmlRegisterUncreatableMetaObject( - crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!"); - qmlRegisterUncreatableMetaObject(verification::staticMetaObject, - "im.nheko", - 1, - 0, - "VerificationStatus", - "Can't instantiate enum!"); + qmlRegisterUncreatableMetaObject( + qml_mtx_events::staticMetaObject, "im.nheko", 1, 0, "MtxEvent", "Can't instantiate enum!"); + qmlRegisterUncreatableMetaObject( + olm::staticMetaObject, "im.nheko", 1, 0, "Olm", "Can't instantiate enum!"); + qmlRegisterUncreatableMetaObject( + crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!"); + qmlRegisterUncreatableMetaObject(verification::staticMetaObject, + "im.nheko", + 1, + 0, + "VerificationStatus", + "Can't instantiate enum!"); - qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice"); - qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser"); - qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea"); - qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape"); - qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage"); - qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia"); - qmlRegisterUncreatableType<DeviceVerificationFlow>( - "im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!"); - qmlRegisterUncreatableType<UserProfile>( - "im.nheko", - 1, - 0, - "UserProfileModel", - "UserProfile needs to be instantiated on the C++ side"); - qmlRegisterUncreatableType<MemberList>( - "im.nheko", 1, 0, "MemberList", "MemberList needs to be instantiated on the C++ side"); - qmlRegisterUncreatableType<RoomSettings>( - "im.nheko", - 1, - 0, - "RoomSettingsModel", - "Room Settings needs to be instantiated on the C++ side"); - qmlRegisterUncreatableType<TimelineModel>( - "im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side"); - qmlRegisterUncreatableType<ImagePackListModel>( - "im.nheko", - 1, - 0, - "ImagePackListModel", - "ImagePackListModel needs to be instantiated on the C++ side"); - qmlRegisterUncreatableType<SingleImagePackModel>( - "im.nheko", - 1, - 0, - "SingleImagePackModel", - "SingleImagePackModel needs to be instantiated on the C++ side"); - qmlRegisterUncreatableType<InviteesModel>( - "im.nheko", - 1, - 0, - "InviteesModel", - "InviteesModel needs to be instantiated on the C++ side"); - qmlRegisterUncreatableType<ReadReceiptsProxy>( - "im.nheko", - 1, - 0, - "ReadReceiptsProxy", - "ReadReceiptsProxy needs to be instantiated on the C++ side"); + qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice"); + qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser"); + qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea"); + qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape"); + qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage"); + qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia"); + qmlRegisterUncreatableType<DeviceVerificationFlow>( + "im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!"); + qmlRegisterUncreatableType<UserProfile>( + "im.nheko", 1, 0, "UserProfileModel", "UserProfile needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType<MemberList>( + "im.nheko", 1, 0, "MemberList", "MemberList needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType<RoomSettings>( + "im.nheko", + 1, + 0, + "RoomSettingsModel", + "Room Settings needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType<TimelineModel>( + "im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType<ImagePackListModel>( + "im.nheko", + 1, + 0, + "ImagePackListModel", + "ImagePackListModel needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType<SingleImagePackModel>( + "im.nheko", + 1, + 0, + "SingleImagePackModel", + "SingleImagePackModel needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType<InviteesModel>( + "im.nheko", 1, 0, "InviteesModel", "InviteesModel needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType<ReadReceiptsProxy>( + "im.nheko", + 1, + 0, + "ReadReceiptsProxy", + "ReadReceiptsProxy needs to be instantiated on the C++ side"); - static auto self = this; - qmlRegisterSingletonType<MainWindow>( - "im.nheko", 1, 0, "MainWindow", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = MainWindow::instance(); - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType<TimelineViewManager>( - "im.nheko", 1, 0, "TimelineManager", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = self; - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType<RoomlistModel>( - "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = new FilteredRoomlistModel(self->rooms_); + static auto self = this; + qmlRegisterSingletonType<MainWindow>( + "im.nheko", 1, 0, "MainWindow", [](QQmlEngine *, QJSEngine *) -> QObject * { + auto ptr = MainWindow::instance(); + QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); + return ptr; + }); + qmlRegisterSingletonType<TimelineViewManager>( + "im.nheko", 1, 0, "TimelineManager", [](QQmlEngine *, QJSEngine *) -> QObject * { + auto ptr = self; + QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); + return ptr; + }); + qmlRegisterSingletonType<RoomlistModel>( + "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * { + auto ptr = new FilteredRoomlistModel(self->rooms_); - connect(self->communities_, - &CommunitiesModel::currentTagIdChanged, - ptr, - &FilteredRoomlistModel::updateFilterTag); - connect(self->communities_, - &CommunitiesModel::hiddenTagsChanged, - ptr, - &FilteredRoomlistModel::updateHiddenTagsAndSpaces); - return ptr; - }); - qmlRegisterSingletonType<RoomlistModel>( - "im.nheko", 1, 0, "Communities", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = self->communities_; - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType<UserSettings>( - "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = ChatPage::instance()->userSettings().data(); - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType<CallManager>( - "im.nheko", 1, 0, "CallManager", [](QQmlEngine *, QJSEngine *) -> QObject * { - auto ptr = ChatPage::instance()->callManager(); - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - }); - qmlRegisterSingletonType<Clipboard>( - "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new Clipboard(); - }); - qmlRegisterSingletonType<Nheko>( - "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * { - return new Nheko(); - }); + connect(self->communities_, + &CommunitiesModel::currentTagIdChanged, + ptr, + &FilteredRoomlistModel::updateFilterTag); + connect(self->communities_, + &CommunitiesModel::hiddenTagsChanged, + ptr, + &FilteredRoomlistModel::updateHiddenTagsAndSpaces); + return ptr; + }); + qmlRegisterSingletonType<RoomlistModel>( + "im.nheko", 1, 0, "Communities", [](QQmlEngine *, QJSEngine *) -> QObject * { + auto ptr = self->communities_; + QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); + return ptr; + }); + qmlRegisterSingletonType<UserSettings>( + "im.nheko", 1, 0, "Settings", [](QQmlEngine *, QJSEngine *) -> QObject * { + auto ptr = ChatPage::instance()->userSettings().data(); + QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); + return ptr; + }); + qmlRegisterSingletonType<CallManager>( + "im.nheko", 1, 0, "CallManager", [](QQmlEngine *, QJSEngine *) -> QObject * { + auto ptr = ChatPage::instance()->callManager(); + QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); + return ptr; + }); + qmlRegisterSingletonType<Clipboard>( + "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * { + return new Clipboard(); + }); + qmlRegisterSingletonType<Nheko>( + "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * { + return new Nheko(); + }); - qRegisterMetaType<mtx::events::collections::TimelineEvents>(); - qRegisterMetaType<std::vector<DeviceInfo>>(); + qRegisterMetaType<mtx::events::collections::TimelineEvents>(); + qRegisterMetaType<std::vector<DeviceInfo>>(); - qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel"); - qmlRegisterUncreatableType<emoji::Emoji>( - "im.nheko.EmojiModel", 1, 0, "Emoji", "Used by emoji models"); - qmlRegisterUncreatableMetaObject(emoji::staticMetaObject, - "im.nheko.EmojiModel", - 1, - 0, - "EmojiCategory", - "Error: Only enums"); + qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel"); + qmlRegisterUncreatableType<emoji::Emoji>( + "im.nheko.EmojiModel", 1, 0, "Emoji", "Used by emoji models"); + qmlRegisterUncreatableMetaObject( + emoji::staticMetaObject, "im.nheko.EmojiModel", 1, 0, "EmojiCategory", "Error: Only enums"); - qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel"); + qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel"); #ifdef USE_QUICK_VIEW - view = new QQuickView(parent); - container = QWidget::createWindowContainer(view, parent); + view = new QQuickView(parent); + container = QWidget::createWindowContainer(view, parent); #else - view = new QQuickWidget(parent); - container = view; - view->setResizeMode(QQuickWidget::SizeRootObjectToView); - container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + view = new QQuickWidget(parent); + container = view; + view->setResizeMode(QQuickWidget::SizeRootObjectToView); + container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) { - nhlog::ui()->debug("Status changed to {}", status); - }); + connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) { + nhlog::ui()->debug("Status changed to {}", status); + }); #endif - container->setMinimumSize(200, 200); - updateColorPalette(); - view->engine()->addImageProvider("MxcImage", imgProvider); - view->engine()->addImageProvider("colorimage", colorImgProvider); - view->engine()->addImageProvider("blurhash", blurhashProvider); - if (JdenticonProvider::isAvailable()) - view->engine()->addImageProvider("jdenticon", jdenticonProvider); - view->setSource(QUrl("qrc:///qml/Root.qml")); + container->setMinimumSize(200, 200); + updateColorPalette(); + view->engine()->addImageProvider("MxcImage", imgProvider); + view->engine()->addImageProvider("colorimage", colorImgProvider); + view->engine()->addImageProvider("blurhash", blurhashProvider); + if (JdenticonProvider::isAvailable()) + view->engine()->addImageProvider("jdenticon", jdenticonProvider); + view->setSource(QUrl("qrc:///qml/Root.qml")); - connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette); - connect( - dynamic_cast<ChatPage *>(parent), - &ChatPage::receivedRoomDeviceVerificationRequest, - this, - [this](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message, - TimelineModel *model) { - if (this->isInitialSync_) - return; + connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette); + connect(dynamic_cast<ChatPage *>(parent), + &ChatPage::receivedRoomDeviceVerificationRequest, + this, + [this](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message, + TimelineModel *model) { + if (this->isInitialSync_) + return; - auto event_id = QString::fromStdString(message.event_id); - if (!this->dvList.contains(event_id)) { - if (auto flow = DeviceVerificationFlow::NewInRoomVerification( - this, - model, - message.content, - QString::fromStdString(message.sender), - event_id)) { - dvList[event_id] = flow; - emit newDeviceVerificationRequest(flow.data()); - } - } - }); - connect(dynamic_cast<ChatPage *>(parent), - &ChatPage::receivedDeviceVerificationRequest, - this, - [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) { - if (this->isInitialSync_) - return; + auto event_id = QString::fromStdString(message.event_id); + if (!this->dvList.contains(event_id)) { + if (auto flow = DeviceVerificationFlow::NewInRoomVerification( + this, + model, + message.content, + QString::fromStdString(message.sender), + event_id)) { + dvList[event_id] = flow; + emit newDeviceVerificationRequest(flow.data()); + } + } + }); + connect(dynamic_cast<ChatPage *>(parent), + &ChatPage::receivedDeviceVerificationRequest, + this, + [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) { + if (this->isInitialSync_) + return; - if (!msg.transaction_id) - return; + if (!msg.transaction_id) + return; - auto txnid = QString::fromStdString(msg.transaction_id.value()); - if (!this->dvList.contains(txnid)) { - if (auto flow = DeviceVerificationFlow::NewToDeviceVerification( - this, msg, QString::fromStdString(sender), txnid)) { - dvList[txnid] = flow; - emit newDeviceVerificationRequest(flow.data()); - } - } - }); - connect(dynamic_cast<ChatPage *>(parent), - &ChatPage::receivedDeviceVerificationStart, - this, - [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) { - if (this->isInitialSync_) - return; + auto txnid = QString::fromStdString(msg.transaction_id.value()); + if (!this->dvList.contains(txnid)) { + if (auto flow = DeviceVerificationFlow::NewToDeviceVerification( + this, msg, QString::fromStdString(sender), txnid)) { + dvList[txnid] = flow; + emit newDeviceVerificationRequest(flow.data()); + } + } + }); + connect(dynamic_cast<ChatPage *>(parent), + &ChatPage::receivedDeviceVerificationStart, + this, + [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) { + if (this->isInitialSync_) + return; - if (!msg.transaction_id) - return; + if (!msg.transaction_id) + return; - auto txnid = QString::fromStdString(msg.transaction_id.value()); - if (!this->dvList.contains(txnid)) { - if (auto flow = DeviceVerificationFlow::NewToDeviceVerification( - this, msg, QString::fromStdString(sender), txnid)) { - dvList[txnid] = flow; - emit newDeviceVerificationRequest(flow.data()); - } - } - }); - connect(parent, &ChatPage::loggedOut, this, [this]() { - isInitialSync_ = true; - emit initialSyncChanged(true); - }); + auto txnid = QString::fromStdString(msg.transaction_id.value()); + if (!this->dvList.contains(txnid)) { + if (auto flow = DeviceVerificationFlow::NewToDeviceVerification( + this, msg, QString::fromStdString(sender), txnid)) { + dvList[txnid] = flow; + emit newDeviceVerificationRequest(flow.data()); + } + } + }); + connect(parent, &ChatPage::loggedOut, this, [this]() { + isInitialSync_ = true; + emit initialSyncChanged(true); + }); - connect(this, - &TimelineViewManager::openImageOverlayInternalCb, - this, - &TimelineViewManager::openImageOverlayInternal); + connect(this, + &TimelineViewManager::openImageOverlayInternalCb, + this, + &TimelineViewManager::openImageOverlayInternal); } void TimelineViewManager::openRoomMembers(TimelineModel *room) { - if (!room) - return; - MemberList *memberList = new MemberList(room->roomId(), this); - emit openRoomMembersDialog(memberList, room); + if (!room) + return; + MemberList *memberList = new MemberList(room->roomId(), this); + emit openRoomMembersDialog(memberList, room); } void TimelineViewManager::openRoomSettings(QString room_id) { - RoomSettings *settings = new RoomSettings(room_id, this); - connect(rooms_->getRoomById(room_id).data(), - &TimelineModel::roomAvatarUrlChanged, - settings, - &RoomSettings::avatarChanged); - emit openRoomSettingsDialog(settings); + RoomSettings *settings = new RoomSettings(room_id, this); + connect(rooms_->getRoomById(room_id).data(), + &TimelineModel::roomAvatarUrlChanged, + settings, + &RoomSettings::avatarChanged); + emit openRoomSettingsDialog(settings); } void TimelineViewManager::openInviteUsers(QString roomId) { - InviteesModel *model = new InviteesModel{this}; - connect(model, &InviteesModel::accept, this, [this, model, roomId]() { - emit inviteUsers(roomId, model->mxids()); - }); - emit openInviteUsersDialog(model); + InviteesModel *model = new InviteesModel{this}; + connect(model, &InviteesModel::accept, this, [this, model, roomId]() { + emit inviteUsers(roomId, model->mxids()); + }); + emit openInviteUsersDialog(model); } void TimelineViewManager::openGlobalUserProfile(QString userId) { - UserProfile *profile = new UserProfile{QString{}, userId, this}; - emit openProfile(profile); + UserProfile *profile = new UserProfile{QString{}, userId, this}; + emit openProfile(profile); } void TimelineViewManager::setVideoCallItem() { - WebRTCSession::instance().setVideoItem( - view->rootObject()->findChild<QQuickItem *>("videoCallItem")); + WebRTCSession::instance().setVideoItem( + view->rootObject()->findChild<QQuickItem *>("videoCallItem")); } void TimelineViewManager::sync(const mtx::responses::Rooms &rooms_res) { - this->rooms_->sync(rooms_res); - this->communities_->sync(rooms_res); + this->rooms_->sync(rooms_res); + this->communities_->sync(rooms_res); - if (isInitialSync_) { - this->isInitialSync_ = false; - emit initialSyncChanged(false); - } + if (isInitialSync_) { + this->isInitialSync_ = false; + emit initialSyncChanged(false); + } } void TimelineViewManager::showEvent(const QString &room_id, const QString &event_id) { - if (auto room = rooms_->getRoomById(room_id)) { - if (rooms_->currentRoom() != room) { - rooms_->setCurrentRoom(room_id); - container->setFocus(); - nhlog::ui()->info("Activated room {}", room_id.toStdString()); - } - - room->showEvent(event_id); + if (auto room = rooms_->getRoomById(room_id)) { + if (rooms_->currentRoom() != room) { + rooms_->setCurrentRoom(room_id); + container->setFocus(); + nhlog::ui()->info("Activated room {}", room_id.toStdString()); } + + room->showEvent(event_id); + } } QString TimelineViewManager::escapeEmoji(QString str) const { - return utils::replaceEmoji(str); + return utils::replaceEmoji(str); } void TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) { - if (mxcUrl.isEmpty()) { - return; - } + if (mxcUrl.isEmpty()) { + return; + } - MxcImageProvider::download( - mxcUrl.remove("mxc://"), QSize(), [this, eventId](QString, QSize, QImage img, QString) { - if (img.isNull()) { - nhlog::ui()->error("Error when retrieving image for overlay."); - return; - } + MxcImageProvider::download( + mxcUrl.remove("mxc://"), QSize(), [this, eventId](QString, QSize, QImage img, QString) { + if (img.isNull()) { + nhlog::ui()->error("Error when retrieving image for overlay."); + return; + } - emit openImageOverlayInternalCb(eventId, std::move(img)); - }); + emit openImageOverlayInternalCb(eventId, std::move(img)); + }); } void TimelineViewManager::openImagePackSettings(QString roomid) { - emit showImagePackSettings(new ImagePackListModel(roomid.toStdString(), this)); + emit showImagePackSettings(new ImagePackListModel(roomid.toStdString(), this)); } void TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) { - auto pixmap = QPixmap::fromImage(img); + auto pixmap = QPixmap::fromImage(img); - auto imgDialog = new dialogs::ImageOverlay(pixmap); - imgDialog->showFullScreen(); + auto imgDialog = new dialogs::ImageOverlay(pixmap); + imgDialog->showFullScreen(); - auto room = rooms_->currentRoom(); - connect(imgDialog, &dialogs::ImageOverlay::saving, room, [eventId, imgDialog, room]() { - // hide the overlay while presenting the save dialog for better - // cross platform support. - imgDialog->hide(); + auto room = rooms_->currentRoom(); + connect(imgDialog, &dialogs::ImageOverlay::saving, room, [eventId, imgDialog, room]() { + // hide the overlay while presenting the save dialog for better + // cross platform support. + imgDialog->hide(); - if (!room->saveMedia(eventId)) { - imgDialog->show(); - } else { - imgDialog->close(); - } - }); + if (!room->saveMedia(eventId)) { + imgDialog->show(); + } else { + imgDialog->close(); + } + }); } void TimelineViewManager::openLeaveRoomDialog(QString roomid) const { - MainWindow::instance()->openLeaveRoomDialog(roomid); + MainWindow::instance()->openLeaveRoomDialog(roomid); } void TimelineViewManager::verifyUser(QString userid) { - auto joined_rooms = cache::joinedRooms(); - auto room_infos = cache::getRoomInfo(joined_rooms); + auto joined_rooms = cache::joinedRooms(); + auto room_infos = cache::getRoomInfo(joined_rooms); - for (std::string room_id : joined_rooms) { - if ((room_infos[QString::fromStdString(room_id)].member_count == 2) && - cache::isRoomEncrypted(room_id)) { - auto room_members = cache::roomMembers(room_id); - if (std::find(room_members.begin(), - room_members.end(), - (userid).toStdString()) != room_members.end()) { - if (auto model = - rooms_->getRoomById(QString::fromStdString(room_id))) { - auto flow = - DeviceVerificationFlow::InitiateUserVerification( - this, model.data(), userid); - connect(model.data(), - &TimelineModel::updateFlowEventId, - this, - [this, flow](std::string eventId) { - dvList[QString::fromStdString(eventId)] = - flow; - }); - emit newDeviceVerificationRequest(flow.data()); - return; - } - } + for (std::string room_id : joined_rooms) { + if ((room_infos[QString::fromStdString(room_id)].member_count == 2) && + cache::isRoomEncrypted(room_id)) { + auto room_members = cache::roomMembers(room_id); + if (std::find(room_members.begin(), room_members.end(), (userid).toStdString()) != + room_members.end()) { + if (auto model = rooms_->getRoomById(QString::fromStdString(room_id))) { + auto flow = + DeviceVerificationFlow::InitiateUserVerification(this, model.data(), userid); + connect(model.data(), + &TimelineModel::updateFlowEventId, + this, + [this, flow](std::string eventId) { + dvList[QString::fromStdString(eventId)] = flow; + }); + emit newDeviceVerificationRequest(flow.data()); + return; } + } } + } - emit ChatPage::instance()->showNotification( - tr("No encrypted private chat found with this user. Create an " - "encrypted private chat with this user and try again.")); + emit ChatPage::instance()->showNotification( + tr("No encrypted private chat found with this user. Create an " + "encrypted private chat with this user and try again.")); } void TimelineViewManager::removeVerificationFlow(DeviceVerificationFlow *flow) { - for (auto it = dvList.keyValueBegin(); it != dvList.keyValueEnd(); ++it) { - if ((*it).second == flow) { - dvList.remove((*it).first); - return; - } + for (auto it = dvList.keyValueBegin(); it != dvList.keyValueEnd(); ++it) { + if ((*it).second == flow) { + dvList.remove((*it).first); + return; } + } } void TimelineViewManager::verifyDevice(QString userid, QString deviceid) { - auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid); - this->dvList[flow->transactionId()] = flow; - emit newDeviceVerificationRequest(flow.data()); + auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid); + this->dvList[flow->transactionId()] = flow; + emit newDeviceVerificationRequest(flow.data()); } void TimelineViewManager::updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids) { - if (auto room = rooms_->getRoomById(room_id)) { - room->markEventsAsRead(event_ids); - } + if (auto room = rooms_->getRoomById(room_id)) { + room->markEventsAsRead(event_ids); + } } void TimelineViewManager::receivedSessionKey(const std::string &room_id, const std::string &session_id) { - if (auto room = rooms_->getRoomById(QString::fromStdString(room_id))) { - room->receivedSessionKey(session_id); - } + if (auto room = rooms_->getRoomById(QString::fromStdString(room_id))) { + room->receivedSessionKey(session_id); + } } void TimelineViewManager::initializeRoomlist() { - rooms_->initializeRooms(); - communities_->initializeSidebar(); + rooms_->initializeRooms(); + communities_->initializeSidebar(); } void @@ -600,178 +578,175 @@ TimelineViewManager::queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody) { - if (auto room = rooms_->getRoomById(roomid)) { - room->setReply(repliedToEvent); - room->input()->message(replyBody); - } + if (auto room = rooms_->getRoomById(roomid)) { + room->setReply(repliedToEvent); + room->input()->message(replyBody); + } } void TimelineViewManager::queueCallMessage(const QString &roomid, const mtx::events::msg::CallInvite &callInvite) { - if (auto room = rooms_->getRoomById(roomid)) - room->sendMessageEvent(callInvite, mtx::events::EventType::CallInvite); + if (auto room = rooms_->getRoomById(roomid)) + room->sendMessageEvent(callInvite, mtx::events::EventType::CallInvite); } void TimelineViewManager::queueCallMessage(const QString &roomid, const mtx::events::msg::CallCandidates &callCandidates) { - if (auto room = rooms_->getRoomById(roomid)) - room->sendMessageEvent(callCandidates, mtx::events::EventType::CallCandidates); + if (auto room = rooms_->getRoomById(roomid)) + room->sendMessageEvent(callCandidates, mtx::events::EventType::CallCandidates); } void TimelineViewManager::queueCallMessage(const QString &roomid, const mtx::events::msg::CallAnswer &callAnswer) { - if (auto room = rooms_->getRoomById(roomid)) - room->sendMessageEvent(callAnswer, mtx::events::EventType::CallAnswer); + if (auto room = rooms_->getRoomById(roomid)) + room->sendMessageEvent(callAnswer, mtx::events::EventType::CallAnswer); } void TimelineViewManager::queueCallMessage(const QString &roomid, const mtx::events::msg::CallHangUp &callHangUp) { - if (auto room = rooms_->getRoomById(roomid)) - room->sendMessageEvent(callHangUp, mtx::events::EventType::CallHangUp); + if (auto room = rooms_->getRoomById(roomid)) + room->sendMessageEvent(callHangUp, mtx::events::EventType::CallHangUp); } void TimelineViewManager::focusMessageInput() { - emit focusInput(); + emit focusInput(); } QObject * TimelineViewManager::completerFor(QString completerName, QString roomId) { - if (completerName == "user") { - auto userModel = new UsersModel(roomId.toStdString()); - auto proxy = new CompletionProxyModel(userModel); - userModel->setParent(proxy); - return proxy; - } else if (completerName == "emoji") { - auto emojiModel = new emoji::EmojiModel(); - auto proxy = new CompletionProxyModel(emojiModel); - emojiModel->setParent(proxy); - return proxy; - } else if (completerName == "allemoji") { - auto emojiModel = new emoji::EmojiModel(); - auto proxy = new CompletionProxyModel(emojiModel, 1, static_cast<size_t>(-1) / 4); - emojiModel->setParent(proxy); - return proxy; - } else if (completerName == "room") { - auto roomModel = new RoomsModel(false); - auto proxy = new CompletionProxyModel(roomModel, 4); - roomModel->setParent(proxy); - return proxy; - } else if (completerName == "roomAliases") { - auto roomModel = new RoomsModel(true); - auto proxy = new CompletionProxyModel(roomModel); - roomModel->setParent(proxy); - return proxy; - } else if (completerName == "stickers") { - auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), true); - auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4); - stickerModel->setParent(proxy); - return proxy; - } - return nullptr; + if (completerName == "user") { + auto userModel = new UsersModel(roomId.toStdString()); + auto proxy = new CompletionProxyModel(userModel); + userModel->setParent(proxy); + return proxy; + } else if (completerName == "emoji") { + auto emojiModel = new emoji::EmojiModel(); + auto proxy = new CompletionProxyModel(emojiModel); + emojiModel->setParent(proxy); + return proxy; + } else if (completerName == "allemoji") { + auto emojiModel = new emoji::EmojiModel(); + auto proxy = new CompletionProxyModel(emojiModel, 1, static_cast<size_t>(-1) / 4); + emojiModel->setParent(proxy); + return proxy; + } else if (completerName == "room") { + auto roomModel = new RoomsModel(false); + auto proxy = new CompletionProxyModel(roomModel, 4); + roomModel->setParent(proxy); + return proxy; + } else if (completerName == "roomAliases") { + auto roomModel = new RoomsModel(true); + auto proxy = new CompletionProxyModel(roomModel); + roomModel->setParent(proxy); + return proxy; + } else if (completerName == "stickers") { + auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), true); + auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4); + stickerModel->setParent(proxy); + return proxy; + } + return nullptr; } void TimelineViewManager::focusTimeline() { - getWidget()->setFocus(); + getWidget()->setFocus(); } void TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e, QString roomId) { - auto room = rooms_->getRoomById(roomId); - auto content = mtx::accessors::url(*e); - std::optional<mtx::crypto::EncryptedFile> encryptionInfo = mtx::accessors::file(*e); + auto room = rooms_->getRoomById(roomId); + auto content = mtx::accessors::url(*e); + std::optional<mtx::crypto::EncryptedFile> encryptionInfo = mtx::accessors::file(*e); - if (encryptionInfo) { - http::client()->download( - content, - [this, roomId, e, encryptionInfo](const std::string &res, - const std::string &content_type, - const std::string &originalFilename, - mtx::http::RequestErr err) { - if (err) - return; + if (encryptionInfo) { + http::client()->download( + content, + [this, roomId, e, encryptionInfo](const std::string &res, + const std::string &content_type, + const std::string &originalFilename, + mtx::http::RequestErr err) { + if (err) + return; - auto data = mtx::crypto::to_string( - mtx::crypto::decrypt_file(res, encryptionInfo.value())); + auto data = + mtx::crypto::to_string(mtx::crypto::decrypt_file(res, encryptionInfo.value())); - http::client()->upload( - data, - content_type, - originalFilename, - [this, roomId, e](const mtx::responses::ContentURI &res, - mtx::http::RequestErr err) mutable { - if (err) { - nhlog::net()->warn("failed to upload media: {} {} ({})", - err->matrix_error.error, - to_string(err->matrix_error.errcode), - static_cast<int>(err->status_code)); - return; - } + http::client()->upload( + data, + content_type, + originalFilename, + [this, roomId, e](const mtx::responses::ContentURI &res, + mtx::http::RequestErr err) mutable { + if (err) { + nhlog::net()->warn("failed to upload media: {} {} ({})", + err->matrix_error.error, + to_string(err->matrix_error.errcode), + static_cast<int>(err->status_code)); + return; + } - std::visit( - [this, roomId, url = res.content_uri](auto ev) { - if constexpr (mtx::events::message_content_to_type< - decltype(ev.content)> == - mtx::events::EventType::RoomMessage) { - if constexpr (messageWithFileAndUrl(ev)) { - ev.content.relations.relations - .clear(); - ev.content.file.reset(); - ev.content.url = url; - } + std::visit( + [this, roomId, url = res.content_uri](auto ev) { + using namespace mtx::events; + if constexpr (EventType::RoomMessage == + message_content_to_type<decltype(ev.content)> || + EventType::Sticker == + message_content_to_type<decltype(ev.content)>) { + if constexpr (messageWithFileAndUrl(ev)) { + ev.content.relations.relations.clear(); + ev.content.file.reset(); + ev.content.url = url; + } - if (auto room = rooms_->getRoomById(roomId)) { - removeReplyFallback(ev); - ev.content.relations.relations - .clear(); - room->sendMessageEvent( - ev.content, - mtx::events::EventType:: - RoomMessage); - } - } - }, - *e); - }); + if (auto room = rooms_->getRoomById(roomId)) { + removeReplyFallback(ev); + ev.content.relations.relations.clear(); + room->sendMessageEvent(ev.content, + mtx::events::EventType::RoomMessage); + } + } + }, + *e); + }); - return; - }); + return; + }); - return; - } + return; + } - std::visit( - [room](auto e) { - if constexpr (mtx::events::message_content_to_type<decltype(e.content)> == - mtx::events::EventType::RoomMessage) { - e.content.relations.relations.clear(); - removeReplyFallback(e); - room->sendMessageEvent(e.content, mtx::events::EventType::RoomMessage); - } - }, - *e); + std::visit( + [room](auto e) { + if constexpr (mtx::events::message_content_to_type<decltype(e.content)> == + mtx::events::EventType::RoomMessage) { + e.content.relations.relations.clear(); + removeReplyFallback(e); + room->sendMessageEvent(e.content, mtx::events::EventType::RoomMessage); + } + }, + *e); } //! WORKAROUND(Nico): for https://bugreports.qt.io/browse/QTBUG-93281 void TimelineViewManager::fixImageRendering(QQuickTextDocument *t, QQuickItem *i) { - if (t) { - QObject::connect( - t->textDocument(), SIGNAL(imagesLoaded()), i, SLOT(updateWholeDocument())); - } + if (t) { + QObject::connect(t->textDocument(), SIGNAL(imagesLoaded()), i, SLOT(updateWholeDocument())); + } } diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 8991de55..f7b01315 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h
@@ -38,123 +38,121 @@ class ImagePackListModel; class TimelineViewManager : public QObject { - Q_OBJECT + Q_OBJECT - Q_PROPERTY( - bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged) - Q_PROPERTY( - bool isWindowFocused MEMBER isWindowFocused_ READ isWindowFocused NOTIFY focusChanged) + Q_PROPERTY( + bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged) + Q_PROPERTY( + bool isWindowFocused MEMBER isWindowFocused_ READ isWindowFocused NOTIFY focusChanged) public: - TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr); - QWidget *getWidget() const { return container; } + TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr); + QWidget *getWidget() const { return container; } - void sync(const mtx::responses::Rooms &rooms); + void sync(const mtx::responses::Rooms &rooms); - MxcImageProvider *imageProvider() { return imgProvider; } - CallManager *callManager() { return callManager_; } + MxcImageProvider *imageProvider() { return imgProvider; } + CallManager *callManager() { return callManager_; } - void clearAll() { rooms_->clear(); } + void clearAll() { rooms_->clear(); } - Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } - bool isWindowFocused() const { return isWindowFocused_; } - Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId); - Q_INVOKABLE void openImagePackSettings(QString roomid); - Q_INVOKABLE QColor userColor(QString id, QColor background); - Q_INVOKABLE QString escapeEmoji(QString str) const; - Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); } + Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } + bool isWindowFocused() const { return isWindowFocused_; } + Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId); + Q_INVOKABLE void openImagePackSettings(QString roomid); + Q_INVOKABLE QColor userColor(QString id, QColor background); + Q_INVOKABLE QString escapeEmoji(QString str) const; + Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); } - Q_INVOKABLE QString userPresence(QString id) const; - Q_INVOKABLE QString userStatus(QString id) const; + Q_INVOKABLE QString userPresence(QString id) const; + Q_INVOKABLE QString userStatus(QString id) const; - Q_INVOKABLE void openRoomMembers(TimelineModel *room); - Q_INVOKABLE void openRoomSettings(QString room_id); - Q_INVOKABLE void openInviteUsers(QString roomId); - Q_INVOKABLE void openGlobalUserProfile(QString userId); + Q_INVOKABLE void openRoomMembers(TimelineModel *room); + Q_INVOKABLE void openRoomSettings(QString room_id); + Q_INVOKABLE void openInviteUsers(QString roomId); + Q_INVOKABLE void openGlobalUserProfile(QString userId); - Q_INVOKABLE void focusMessageInput(); - Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const; - Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); + Q_INVOKABLE void focusMessageInput(); + Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const; + Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); - Q_INVOKABLE void fixImageRendering(QQuickTextDocument *t, QQuickItem *i); + Q_INVOKABLE void fixImageRendering(QQuickTextDocument *t, QQuickItem *i); - void verifyUser(QString userid); - void verifyDevice(QString userid, QString deviceid); + void verifyUser(QString userid); + void verifyDevice(QString userid, QString deviceid); signals: - void activeTimelineChanged(TimelineModel *timeline); - void initialSyncChanged(bool isInitialSync); - void replyingEventChanged(QString replyingEvent); - void replyClosed(); - void newDeviceVerificationRequest(DeviceVerificationFlow *flow); - void inviteUsers(QString roomId, QStringList users); - void showRoomList(); - void narrowViewChanged(); - void focusChanged(); - void focusInput(); - void openImageOverlayInternalCb(QString eventId, QImage img); - void openRoomMembersDialog(MemberList *members, TimelineModel *room); - void openRoomSettingsDialog(RoomSettings *settings); - void openInviteUsersDialog(InviteesModel *invitees); - void openProfile(UserProfile *profile); - void showImagePackSettings(ImagePackListModel *packlist); + void activeTimelineChanged(TimelineModel *timeline); + void initialSyncChanged(bool isInitialSync); + void replyingEventChanged(QString replyingEvent); + void replyClosed(); + void newDeviceVerificationRequest(DeviceVerificationFlow *flow); + void inviteUsers(QString roomId, QStringList users); + void showRoomList(); + void narrowViewChanged(); + void focusChanged(); + void focusInput(); + void openImageOverlayInternalCb(QString eventId, QImage img); + void openRoomMembersDialog(MemberList *members, TimelineModel *room); + void openRoomSettingsDialog(RoomSettings *settings); + void openInviteUsersDialog(InviteesModel *invitees); + void openProfile(UserProfile *profile); + void showImagePackSettings(ImagePackListModel *packlist); public slots: - void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids); - void receivedSessionKey(const std::string &room_id, const std::string &session_id); - void initializeRoomlist(); - void chatFocusChanged(bool focused) - { - isWindowFocused_ = focused; - emit focusChanged(); - } + void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids); + void receivedSessionKey(const std::string &room_id, const std::string &session_id); + void initializeRoomlist(); + void chatFocusChanged(bool focused) + { + isWindowFocused_ = focused; + emit focusChanged(); + } - void showEvent(const QString &room_id, const QString &event_id); - void focusTimeline(); + void showEvent(const QString &room_id, const QString &event_id); + void focusTimeline(); - void updateColorPalette(); - void queueReply(const QString &roomid, - const QString &repliedToEvent, - const QString &replyBody); - void queueCallMessage(const QString &roomid, const mtx::events::msg::CallInvite &); - void queueCallMessage(const QString &roomid, const mtx::events::msg::CallCandidates &); - void queueCallMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); - void queueCallMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); + void updateColorPalette(); + void queueReply(const QString &roomid, const QString &repliedToEvent, const QString &replyBody); + void queueCallMessage(const QString &roomid, const mtx::events::msg::CallInvite &); + void queueCallMessage(const QString &roomid, const mtx::events::msg::CallCandidates &); + void queueCallMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); + void queueCallMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); - void setVideoCallItem(); + void setVideoCallItem(); - QObject *completerFor(QString completerName, QString roomId = ""); - void forwardMessageToRoom(mtx::events::collections::TimelineEvents *e, QString roomId); + QObject *completerFor(QString completerName, QString roomId = ""); + void forwardMessageToRoom(mtx::events::collections::TimelineEvents *e, QString roomId); - RoomlistModel *rooms() { return rooms_; } + RoomlistModel *rooms() { return rooms_; } private slots: - void openImageOverlayInternal(QString eventId, QImage img); + void openImageOverlayInternal(QString eventId, QImage img); private: #ifdef USE_QUICK_VIEW - QQuickView *view; + QQuickView *view; #else - QQuickWidget *view; + QQuickWidget *view; #endif - QWidget *container; + QWidget *container; - MxcImageProvider *imgProvider; - ColorImageProvider *colorImgProvider; - BlurhashProvider *blurhashProvider; - JdenticonProvider *jdenticonProvider; + MxcImageProvider *imgProvider; + ColorImageProvider *colorImgProvider; + BlurhashProvider *blurhashProvider; + JdenticonProvider *jdenticonProvider; - CallManager *callManager_ = nullptr; + CallManager *callManager_ = nullptr; - bool isInitialSync_ = true; - bool isWindowFocused_ = false; + bool isInitialSync_ = true; + bool isWindowFocused_ = false; - RoomlistModel *rooms_ = nullptr; - CommunitiesModel *communities_ = nullptr; + RoomlistModel *rooms_ = nullptr; + CommunitiesModel *communities_ = nullptr; - QHash<QString, QColor> userColors; + QHash<QString, QColor> userColors; - QHash<QString, QSharedPointer<DeviceVerificationFlow>> dvList; + QHash<QString, QSharedPointer<DeviceVerificationFlow>> dvList; }; Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationAccept) Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationCancel)