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