Alter code structure && change room list filtering mechanics && add
sorting && init categoriy for rooms.
This commit is contained in:
82
src/controller.cpp
Normal file
82
src/controller.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "controller.h"
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
Controller::Controller(QObject *parent) : QObject(parent) {
|
||||
connect(m_connection, &QMatrixClient::Connection::connected, this,
|
||||
&Controller::connected);
|
||||
connect(m_connection, &QMatrixClient::Connection::resolveError, this,
|
||||
&Controller::reconnect);
|
||||
connect(m_connection, &QMatrixClient::Connection::syncError, this,
|
||||
&Controller::reconnect);
|
||||
connect(m_connection, &QMatrixClient::Connection::syncDone, this,
|
||||
&Controller::resync);
|
||||
connect(m_connection, &QMatrixClient::Connection::connected, this,
|
||||
&Controller::connectionChanged);
|
||||
|
||||
connect(m_connection, &QMatrixClient::Connection::connected,
|
||||
[=] { setBusy(true); });
|
||||
connect(m_connection, &QMatrixClient::Connection::syncDone,
|
||||
[=] { setBusy(false); });
|
||||
}
|
||||
|
||||
Controller::~Controller() { m_connection->stopSync(); }
|
||||
|
||||
void Controller::login() {
|
||||
if (!isLogin) {
|
||||
qDebug() << "UserID:" << userID;
|
||||
qDebug() << "Token:" << token;
|
||||
|
||||
m_connection->setHomeserver(QUrl(homeserver));
|
||||
m_connection->connectWithToken(userID, token, "");
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::loginWithCredentials(QString serverAddr, QString user,
|
||||
QString pass) {
|
||||
if (!isLogin) {
|
||||
qDebug() << "Server:" << serverAddr;
|
||||
qDebug() << "User:" << user;
|
||||
qDebug() << "Pass:" << pass;
|
||||
|
||||
if (!user.isEmpty() && !pass.isEmpty()) {
|
||||
qDebug() << "Using given credential.";
|
||||
m_connection->setHomeserver(QUrl(serverAddr));
|
||||
m_connection->connectToServer(user, pass, "");
|
||||
}
|
||||
} else {
|
||||
qDebug() << "You are already logged in.";
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::logout() {
|
||||
qDebug() << "Logging out.";
|
||||
setUserID("");
|
||||
setToken("");
|
||||
setIsLogin(false);
|
||||
}
|
||||
|
||||
void Controller::uploadFile(QString filename) {
|
||||
m_connection->uploadFile(filename);
|
||||
}
|
||||
|
||||
void Controller::connected() {
|
||||
qDebug() << "Logged in.";
|
||||
setHomeserver(m_connection->homeserver().toString());
|
||||
setUserID(m_connection->userId());
|
||||
setToken(m_connection->accessToken());
|
||||
m_connection->loadState();
|
||||
resync();
|
||||
setIsLogin(true);
|
||||
}
|
||||
|
||||
void Controller::resync() {
|
||||
qDebug() << "Syncing Matrix.";
|
||||
m_connection->sync(30000);
|
||||
m_connection->saveState();
|
||||
}
|
||||
|
||||
void Controller::reconnect() {
|
||||
qDebug() << "Connection lost. Reconnecting...";
|
||||
m_connection->connectWithToken(userID, token, "");
|
||||
}
|
||||
105
src/controller.h
Normal file
105
src/controller.h
Normal file
@@ -0,0 +1,105 @@
|
||||
#ifndef CONTROLLER_H
|
||||
#define CONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include "connection.h"
|
||||
#include "roomlistmodel.h"
|
||||
#include "user.h"
|
||||
|
||||
namespace QMatrixClient {
|
||||
class Connection;
|
||||
}
|
||||
|
||||
class Controller : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QMatrixClient::Connection* connection READ getConnection NOTIFY
|
||||
connectionChanged)
|
||||
Q_PROPERTY(
|
||||
bool isLogin READ getIsLogin WRITE setIsLogin NOTIFY isLoginChanged)
|
||||
Q_PROPERTY(QString homeserver READ getHomeserver WRITE setHomeserver NOTIFY
|
||||
homeserverChanged)
|
||||
Q_PROPERTY(QString userID READ getUserID WRITE setUserID NOTIFY userIDChanged)
|
||||
Q_PROPERTY(QByteArray token READ getToken WRITE setToken NOTIFY tokenChanged)
|
||||
Q_PROPERTY(bool busy READ getBusy WRITE setBusy NOTIFY busyChanged)
|
||||
|
||||
public:
|
||||
explicit Controller(QObject* parent = nullptr);
|
||||
~Controller();
|
||||
|
||||
// All the Q_INVOKABLEs.
|
||||
Q_INVOKABLE void login();
|
||||
Q_INVOKABLE void loginWithCredentials(QString, QString, QString);
|
||||
Q_INVOKABLE void logout();
|
||||
|
||||
Q_INVOKABLE void uploadFile(QString);
|
||||
|
||||
// All the non-Q_INVOKABLE functions.
|
||||
|
||||
// All the Q_PROPERTYs.
|
||||
QMatrixClient::Connection* m_connection = new QMatrixClient::Connection();
|
||||
QMatrixClient::Connection* getConnection() { return m_connection; }
|
||||
|
||||
bool isLogin = false;
|
||||
bool getIsLogin() { return isLogin; }
|
||||
void setIsLogin(bool n) {
|
||||
if (n != isLogin) {
|
||||
isLogin = n;
|
||||
emit isLoginChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString userID;
|
||||
QString getUserID() { return userID; }
|
||||
void setUserID(QString n) {
|
||||
if (n != userID) {
|
||||
userID = n;
|
||||
emit userIDChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray token;
|
||||
QByteArray getToken() { return token; }
|
||||
void setToken(QByteArray n) {
|
||||
if (n != token) {
|
||||
token = n;
|
||||
emit tokenChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString homeserver;
|
||||
QString getHomeserver() { return homeserver; }
|
||||
void setHomeserver(QString n) {
|
||||
if (n != homeserver) {
|
||||
homeserver = n;
|
||||
emit homeserverChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool busy = false;
|
||||
bool getBusy() { return busy; }
|
||||
void setBusy(bool b) {
|
||||
if (b != busy) {
|
||||
busy = b;
|
||||
emit busyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void connected();
|
||||
void resync();
|
||||
void reconnect();
|
||||
|
||||
signals:
|
||||
void connectionChanged();
|
||||
void isLoginChanged();
|
||||
void userIDChanged();
|
||||
void tokenChanged();
|
||||
void homeserverChanged();
|
||||
void busyChanged();
|
||||
void errorOccured();
|
||||
|
||||
public slots:
|
||||
};
|
||||
|
||||
#endif // CONTROLLER_H
|
||||
67
src/imageprovider.cpp
Normal file
67
src/imageprovider.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "imageprovider.h"
|
||||
|
||||
#include <QMetaObject>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QWaitCondition>
|
||||
|
||||
#include "jobs/mediathumbnailjob.h"
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
using QMatrixClient::MediaThumbnailJob;
|
||||
|
||||
ImageProvider::ImageProvider(QObject* parent)
|
||||
: QQuickImageProvider(
|
||||
QQmlImageProviderBase::Image,
|
||||
QQmlImageProviderBase::ForceAsynchronousImageLoading) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
|
||||
qRegisterMetaType<MediaThumbnailJob*>();
|
||||
#endif
|
||||
m_connection = new ImageProviderConnection();
|
||||
}
|
||||
|
||||
QImage ImageProvider::requestImage(const QString& id, QSize* pSize,
|
||||
const QSize& requestedSize) {
|
||||
if (!id.startsWith("mxc://")) {
|
||||
qWarning() << "ImageProvider: won't fetch an invalid id:" << id
|
||||
<< "doesn't follow server/mediaId pattern";
|
||||
return {};
|
||||
}
|
||||
|
||||
QUrl mxcUri{id};
|
||||
|
||||
MediaThumbnailJob* job = nullptr;
|
||||
QReadLocker locker(&m_lock);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
QMetaObject::invokeMethod(
|
||||
m_connection,
|
||||
[=] {
|
||||
return m_connection->getConnection()->getThumbnail(mxcUri,
|
||||
requestedSize);
|
||||
},
|
||||
Qt::BlockingQueuedConnection, &job);
|
||||
#else
|
||||
QMetaObject::invokeMethod(m_connection, "getThumbnail",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(MediaThumbnailJob*, job),
|
||||
Q_ARG(QUrl, mxcUri), Q_ARG(QSize, requestedSize));
|
||||
#endif
|
||||
if (!job) {
|
||||
qDebug() << "ImageProvider: failed to send a request";
|
||||
return {};
|
||||
}
|
||||
QImage result;
|
||||
{
|
||||
QWaitCondition condition; // The most compact way to block on a signal
|
||||
job->connect(job, &MediaThumbnailJob::finished, job, [&] {
|
||||
result = job->thumbnail();
|
||||
condition.wakeAll();
|
||||
});
|
||||
condition.wait(&m_lock);
|
||||
}
|
||||
|
||||
if (pSize != nullptr) *pSize = result.size();
|
||||
|
||||
return result;
|
||||
}
|
||||
27
src/imageprovider.h
Normal file
27
src/imageprovider.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef IMAGEPROVIDER_H
|
||||
#define IMAGEPROVIDER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QtCore/QReadWriteLock>
|
||||
#include <QtQuick/QQuickImageProvider>
|
||||
|
||||
#include "connection.h"
|
||||
#include "imageproviderconnection.h"
|
||||
|
||||
class ImageProvider : public QQuickImageProvider {
|
||||
public:
|
||||
explicit ImageProvider(QObject* parent = nullptr);
|
||||
|
||||
QImage requestImage(const QString& id, QSize* pSize,
|
||||
const QSize& requestedSize) override;
|
||||
|
||||
void initializeEngine(QQmlEngine* engine, const char* uri);
|
||||
|
||||
ImageProviderConnection* getConnection() { return m_connection; }
|
||||
|
||||
private:
|
||||
QReadWriteLock m_lock;
|
||||
ImageProviderConnection* m_connection;
|
||||
};
|
||||
|
||||
#endif // IMAGEPROVIDER_H
|
||||
6
src/imageproviderconnection.cpp
Normal file
6
src/imageproviderconnection.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "imageproviderconnection.h"
|
||||
|
||||
ImageProviderConnection::ImageProviderConnection(QObject* parent)
|
||||
: QObject(parent) {}
|
||||
|
||||
ImageProviderConnection::~ImageProviderConnection() {}
|
||||
30
src/imageproviderconnection.h
Normal file
30
src/imageproviderconnection.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef IMAGEPROVIDERCONNECTION_H
|
||||
#define IMAGEPROVIDERCONNECTION_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
class ImageProviderConnection : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QMatrixClient::Connection* connection READ getConnection WRITE
|
||||
setConnection NOTIFY connectionChanged)
|
||||
|
||||
public:
|
||||
explicit ImageProviderConnection(QObject* parent = nullptr);
|
||||
~ImageProviderConnection();
|
||||
|
||||
QMatrixClient::Connection* getConnection() { return m_connection; }
|
||||
void setConnection(QMatrixClient::Connection* connection) {
|
||||
emit connectionChanged();
|
||||
m_connection = connection;
|
||||
}
|
||||
|
||||
private:
|
||||
QMatrixClient::Connection* m_connection;
|
||||
signals:
|
||||
void connectionChanged();
|
||||
};
|
||||
|
||||
#endif // IMAGEPROVIDERCONNECTION_H
|
||||
49
src/main.cpp
Normal file
49
src/main.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QNetworkProxy>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
|
||||
#include "controller.h"
|
||||
#include "imageprovider.h"
|
||||
#include "messageeventmodel.h"
|
||||
#include "roomlistmodel.h"
|
||||
#include "room.h"
|
||||
|
||||
using namespace QMatrixClient;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
#if defined(Q_OS_WIN)
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
#endif
|
||||
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
// Enable this if you need proxy.
|
||||
// QNetworkProxy proxy;
|
||||
// proxy.setType(QNetworkProxy::HttpProxy);
|
||||
// proxy.setHostName("localhost");
|
||||
// proxy.setPort(1082);
|
||||
// QNetworkProxy::setApplicationProxy(proxy);
|
||||
|
||||
qRegisterMetaType<Room *>("Room*");
|
||||
|
||||
qmlRegisterType<Controller>("Matrique", 0, 1, "Controller");
|
||||
qmlRegisterType<RoomListModel>("Matrique", 0, 1, "RoomListModel");
|
||||
qmlRegisterType<MessageEventModel>("Matrique", 0, 1, "MessageEventModel");
|
||||
qRegisterMetaType<User *>("User*");
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
ImageProvider *m_provider = new ImageProvider();
|
||||
|
||||
engine.rootContext()->setContextProperty("imageProvider",
|
||||
m_provider->getConnection());
|
||||
|
||||
engine.addImageProvider(QLatin1String("mxc"), m_provider);
|
||||
|
||||
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
|
||||
|
||||
if (engine.rootObjects().isEmpty()) return -1;
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
379
src/messageeventmodel.cpp
Normal file
379
src/messageeventmodel.cpp
Normal file
@@ -0,0 +1,379 @@
|
||||
#include "messageeventmodel.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QSettings>
|
||||
#include <QtQml> // for qmlRegisterType()
|
||||
|
||||
#include "events/redactionevent.h"
|
||||
#include "events/roomavatarevent.h"
|
||||
#include "events/roommemberevent.h"
|
||||
#include "events/simplestateevents.h"
|
||||
|
||||
#include "connection.h"
|
||||
#include "settings.h"
|
||||
#include "user.h"
|
||||
|
||||
MessageEventModel::MessageEventModel(QObject* parent)
|
||||
: QAbstractListModel(parent) {
|
||||
qmlRegisterType<QMatrixClient::FileTransferInfo>();
|
||||
qRegisterMetaType<QMatrixClient::FileTransferInfo>();
|
||||
}
|
||||
|
||||
MessageEventModel::~MessageEventModel() {}
|
||||
|
||||
void MessageEventModel::setRoom(QMatrixClient::Room* room) {
|
||||
if (room == m_currentRoom) return;
|
||||
|
||||
beginResetModel();
|
||||
if (m_currentRoom) {
|
||||
m_currentRoom->disconnect(this);
|
||||
qDebug() << "Disconnected from" << m_currentRoom->id();
|
||||
}
|
||||
|
||||
m_currentRoom = room;
|
||||
if (room) {
|
||||
lastReadEventId = room->readMarkerEventId();
|
||||
using namespace QMatrixClient;
|
||||
connect(m_currentRoom, &Room::aboutToAddNewMessages, this,
|
||||
[=](RoomEventsRange events) {
|
||||
beginInsertRows(QModelIndex(), 0, int(events.size()) - 1);
|
||||
});
|
||||
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this,
|
||||
[=](RoomEventsRange events) {
|
||||
if (rowCount() > 0) nextNewerRow = rowCount() - 1;
|
||||
beginInsertRows(QModelIndex(), rowCount(),
|
||||
rowCount() + int(events.size()) - 1);
|
||||
});
|
||||
connect(m_currentRoom, &Room::addedMessages, this, [=] {
|
||||
if (nextNewerRow > -1) {
|
||||
const auto idx = index(nextNewerRow);
|
||||
emit dataChanged(idx, idx);
|
||||
nextNewerRow = -1;
|
||||
}
|
||||
endInsertRows();
|
||||
});
|
||||
connect(m_currentRoom, &Room::readMarkerMoved, this, [this] {
|
||||
refreshEventRoles(
|
||||
std::exchange(lastReadEventId, m_currentRoom->readMarkerEventId()),
|
||||
{ReadMarkerRole});
|
||||
refreshEventRoles(lastReadEventId, {ReadMarkerRole});
|
||||
});
|
||||
connect(
|
||||
m_currentRoom, &Room::replacedEvent, this,
|
||||
[this](const RoomEvent* newEvent) { refreshEvent(newEvent->id()); });
|
||||
connect(m_currentRoom, &Room::fileTransferProgress, this,
|
||||
&MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferCompleted, this,
|
||||
&MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferFailed, this,
|
||||
&MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferCancelled, this,
|
||||
&MessageEventModel::refreshEvent);
|
||||
qDebug() << "Connected to room" << room->id() << "as"
|
||||
<< room->localUser()->id();
|
||||
} else
|
||||
lastReadEventId.clear();
|
||||
endResetModel();
|
||||
emit roomChanged();
|
||||
}
|
||||
|
||||
void MessageEventModel::refreshEvent(const QString& eventId) {
|
||||
refreshEventRoles(eventId, {});
|
||||
}
|
||||
|
||||
void MessageEventModel::refreshEventRoles(const QString& eventId,
|
||||
const QVector<int> roles) {
|
||||
const auto it = m_currentRoom->findInTimeline(eventId);
|
||||
if (it != m_currentRoom->timelineEdge()) {
|
||||
const auto row = it - m_currentRoom->messageEvents().rbegin();
|
||||
emit dataChanged(index(row), index(row), roles);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) {
|
||||
return ti->timestamp().isValid();
|
||||
}
|
||||
|
||||
QDateTime MessageEventModel::makeMessageTimestamp(
|
||||
QMatrixClient::Room::rev_iter_t baseIt) const {
|
||||
const auto& timeline = m_currentRoom->messageEvents();
|
||||
auto ts = baseIt->event()->timestamp();
|
||||
if (ts.isValid()) return ts;
|
||||
|
||||
// The event is most likely redacted or just invalid.
|
||||
// Look for the nearest date around and slap zero time to it.
|
||||
using QMatrixClient::TimelineItem;
|
||||
auto rit = std::find_if(baseIt, timeline.rend(), hasValidTimestamp);
|
||||
if (rit != timeline.rend())
|
||||
return {rit->event()->timestamp().date(), {0, 0}, Qt::LocalTime};
|
||||
auto it = std::find_if(baseIt.base(), timeline.end(), hasValidTimestamp);
|
||||
if (it != timeline.end())
|
||||
return {it->event()->timestamp().date(), {0, 0}, Qt::LocalTime};
|
||||
|
||||
// What kind of room is that?..
|
||||
qCritical() << "No valid timestamps in the room timeline!";
|
||||
return {};
|
||||
}
|
||||
|
||||
QString MessageEventModel::makeDateString(
|
||||
QMatrixClient::Room::rev_iter_t baseIt) const {
|
||||
auto date = makeMessageTimestamp(baseIt).toLocalTime().date();
|
||||
if (QMatrixClient::SettingsGroup("UI")
|
||||
.value("banner_human_friendly_date", true)
|
||||
.toBool()) {
|
||||
if (date == QDate::currentDate()) return tr("Today");
|
||||
if (date == QDate::currentDate().addDays(-1)) return tr("Yesterday");
|
||||
if (date == QDate::currentDate().addDays(-2))
|
||||
return tr("The day before yesterday");
|
||||
if (date > QDate::currentDate().addDays(-7)) return date.toString("dddd");
|
||||
}
|
||||
return date.toString(Qt::DefaultLocaleShortDate);
|
||||
}
|
||||
|
||||
int MessageEventModel::rowCount(const QModelIndex& parent) const {
|
||||
if (!m_currentRoom || parent.isValid()) return 0;
|
||||
return m_currentRoom->timelineSize();
|
||||
}
|
||||
|
||||
QVariant MessageEventModel::data(const QModelIndex& index, int role) const {
|
||||
if (!m_currentRoom || index.row() < 0 ||
|
||||
index.row() >= m_currentRoom->timelineSize())
|
||||
return QVariant();
|
||||
|
||||
const auto timelineIt = m_currentRoom->messageEvents().rbegin() + index.row();
|
||||
const auto& ti = *timelineIt;
|
||||
|
||||
using namespace QMatrixClient;
|
||||
if (role == Qt::DisplayRole) {
|
||||
if (ti->isRedacted()) {
|
||||
auto reason = ti->redactedBecause()->reason();
|
||||
if (reason.isEmpty())
|
||||
return tr("Redacted");
|
||||
else
|
||||
return tr("Redacted: %1").arg(ti->redactedBecause()->reason());
|
||||
}
|
||||
|
||||
if (ti->type() == EventType::RoomMessage) {
|
||||
using namespace MessageEventContent;
|
||||
|
||||
auto* e = ti.viewAs<RoomMessageEvent>();
|
||||
if (e->hasTextContent() && e->mimeType().name() != "text/plain")
|
||||
return static_cast<const TextContent*>(e->content())->body;
|
||||
if (e->hasFileContent()) {
|
||||
auto fileCaption = e->content()->fileInfo()->originalName;
|
||||
if (fileCaption.isEmpty())
|
||||
fileCaption = m_currentRoom->prettyPrint(e->plainBody());
|
||||
if (fileCaption.isEmpty()) return tr("a file");
|
||||
}
|
||||
return m_currentRoom->prettyPrint(e->plainBody());
|
||||
}
|
||||
if (ti->type() == EventType::RoomMember) {
|
||||
auto* e = ti.viewAs<RoomMemberEvent>();
|
||||
// FIXME: Rewind to the name that was at the time of this event
|
||||
QString subjectName = m_currentRoom->roomMembername(e->userId());
|
||||
// The below code assumes senderName output in AuthorRole
|
||||
switch (e->membership()) {
|
||||
case MembershipType::Invite:
|
||||
if (e->repeatsState())
|
||||
return tr("reinvited %1 to the room").arg(subjectName);
|
||||
// [[fallthrough]]
|
||||
case MembershipType::Join: {
|
||||
if (e->repeatsState()) return tr("joined the room (repeated)");
|
||||
if (!e->prev_content() ||
|
||||
e->membership() != e->prev_content()->membership) {
|
||||
return e->membership() == MembershipType::Invite
|
||||
? tr("invited %1 to the room").arg(subjectName)
|
||||
: tr("joined the room");
|
||||
}
|
||||
QString text{};
|
||||
if (e->displayName() != e->prev_content()->displayName) {
|
||||
if (e->displayName().isEmpty())
|
||||
text = tr("cleared the display name");
|
||||
else
|
||||
text = tr("changed the display name to %1").arg(e->displayName());
|
||||
}
|
||||
if (e->avatarUrl() != e->prev_content()->avatarUrl) {
|
||||
if (!text.isEmpty()) text += " and ";
|
||||
if (e->avatarUrl().isEmpty())
|
||||
text += tr("cleared the avatar");
|
||||
else
|
||||
text += tr("updated the avatar");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
case MembershipType::Leave:
|
||||
if (e->prev_content() &&
|
||||
e->prev_content()->membership == MembershipType::Ban) {
|
||||
if (e->senderId() != e->userId())
|
||||
return tr("unbanned %1").arg(subjectName);
|
||||
else
|
||||
return tr("self-unbanned");
|
||||
}
|
||||
if (e->senderId() != e->userId())
|
||||
return tr("has put %1 out of the room").arg(subjectName);
|
||||
else
|
||||
return tr("left the room");
|
||||
case MembershipType::Ban:
|
||||
if (e->senderId() != e->userId())
|
||||
return tr("banned %1 from the room").arg(subjectName);
|
||||
else
|
||||
return tr("self-banned from the room");
|
||||
case MembershipType::Knock:
|
||||
return tr("knocked");
|
||||
case MembershipType::Undefined:
|
||||
return tr("made something unknown");
|
||||
}
|
||||
}
|
||||
if (ti->type() == EventType::RoomAliases) {
|
||||
auto* e = ti.viewAs<RoomAliasesEvent>();
|
||||
return tr("set aliases to: %1").arg(e->aliases().join(", "));
|
||||
}
|
||||
if (ti->type() == EventType::RoomCanonicalAlias) {
|
||||
auto* e = ti.viewAs<RoomCanonicalAliasEvent>();
|
||||
if (e->alias().isEmpty())
|
||||
return tr("cleared the room main alias");
|
||||
else
|
||||
return tr("set the room main alias to: %1").arg(e->alias());
|
||||
}
|
||||
if (ti->type() == EventType::RoomName) {
|
||||
auto* e = ti.viewAs<RoomNameEvent>();
|
||||
if (e->name().isEmpty())
|
||||
return tr("cleared the room name");
|
||||
else
|
||||
return tr("set the room name to: %1").arg(e->name());
|
||||
}
|
||||
if (ti->type() == EventType::RoomTopic) {
|
||||
auto* e = ti.viewAs<RoomTopicEvent>();
|
||||
if (e->topic().isEmpty())
|
||||
return tr("cleared the topic");
|
||||
else
|
||||
return tr("set the topic to: %1").arg(e->topic());
|
||||
}
|
||||
if (ti->type() == EventType::RoomAvatar) {
|
||||
return tr("changed the room avatar");
|
||||
}
|
||||
if (ti->type() == EventType::RoomEncryption) {
|
||||
return tr("activated End-to-End Encryption");
|
||||
}
|
||||
return tr("Unknown Event");
|
||||
}
|
||||
|
||||
if (role == Qt::ToolTipRole) {
|
||||
return ti->originalJson();
|
||||
}
|
||||
|
||||
if (role == EventTypeRole) {
|
||||
if (ti->isStateEvent()) return "state";
|
||||
|
||||
if (ti->type() == EventType::RoomMessage) {
|
||||
switch (ti.viewAs<RoomMessageEvent>()->msgtype()) {
|
||||
case MessageEventType::Emote:
|
||||
return "emote";
|
||||
case MessageEventType::Notice:
|
||||
return "notice";
|
||||
case MessageEventType::Image:
|
||||
return "image";
|
||||
case MessageEventType::File:
|
||||
case MessageEventType::Audio:
|
||||
case MessageEventType::Video:
|
||||
return "file";
|
||||
default:
|
||||
return "message";
|
||||
}
|
||||
}
|
||||
|
||||
return "other";
|
||||
}
|
||||
|
||||
if (role == TimeRole) return makeMessageTimestamp(timelineIt);
|
||||
|
||||
if (role == SectionRole)
|
||||
return makeDateString(timelineIt); // FIXME: move date rendering to QML
|
||||
|
||||
if (role == AuthorRole) {
|
||||
auto userId = ti->senderId();
|
||||
// FIXME: It shouldn't be User, it should be its state "as of event"
|
||||
return QVariant::fromValue(m_currentRoom->user(userId));
|
||||
}
|
||||
|
||||
if (role == ContentTypeRole) {
|
||||
if (ti->type() == EventType::RoomMessage) {
|
||||
const auto& contentType =
|
||||
ti.viewAs<RoomMessageEvent>()->mimeType().name();
|
||||
return contentType == "text/plain" ? "text/html" : contentType;
|
||||
}
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
if (role == ContentRole) {
|
||||
if (ti->isRedacted()) {
|
||||
auto reason = ti->redactedBecause()->reason();
|
||||
if (reason.isEmpty())
|
||||
return tr("Redacted");
|
||||
else
|
||||
return tr("Redacted: %1").arg(ti->redactedBecause()->reason());
|
||||
}
|
||||
|
||||
if (ti->type() == EventType::RoomMessage) {
|
||||
using namespace MessageEventContent;
|
||||
|
||||
auto* e = ti.viewAs<RoomMessageEvent>();
|
||||
switch (e->msgtype()) {
|
||||
case MessageEventType::Image:
|
||||
case MessageEventType::File:
|
||||
case MessageEventType::Audio:
|
||||
case MessageEventType::Video:
|
||||
return QVariant::fromValue(e->content()->originalJson);
|
||||
default:;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (role == ReadMarkerRole) return ti->id() == lastReadEventId;
|
||||
|
||||
if (role == SpecialMarksRole) {
|
||||
if (ti->isStateEvent() && ti.viewAs<StateEventBase>()->repeatsState())
|
||||
return "hidden";
|
||||
return ti->isRedacted() ? "redacted" : "";
|
||||
}
|
||||
|
||||
if (role == EventIdRole) return ti->id();
|
||||
|
||||
if (role == LongOperationRole) {
|
||||
if (ti->type() == EventType::RoomMessage &&
|
||||
ti.viewAs<RoomMessageEvent>()->hasFileContent()) {
|
||||
auto info = m_currentRoom->fileTransferInfo(ti->id());
|
||||
return QVariant::fromValue(info);
|
||||
}
|
||||
}
|
||||
|
||||
auto aboveEventIt = timelineIt + 1; // FIXME: shouldn't be here, because #312
|
||||
if (aboveEventIt != m_currentRoom->timelineEdge()) {
|
||||
if (role == AboveSectionRole) return makeDateString(aboveEventIt);
|
||||
|
||||
if (role == AboveAuthorRole)
|
||||
return QVariant::fromValue(
|
||||
m_currentRoom->user((*aboveEventIt)->senderId()));
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> MessageEventModel::roleNames() const {
|
||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||
roles[EventTypeRole] = "eventType";
|
||||
roles[EventIdRole] = "eventId";
|
||||
roles[TimeRole] = "time";
|
||||
roles[SectionRole] = "section";
|
||||
roles[AboveSectionRole] = "aboveSection";
|
||||
roles[AuthorRole] = "author";
|
||||
roles[AboveAuthorRole] = "aboveAuthor";
|
||||
roles[ContentRole] = "content";
|
||||
roles[ContentTypeRole] = "contentType";
|
||||
roles[HighlightRole] = "highlight";
|
||||
roles[ReadMarkerRole] = "readMarker";
|
||||
roles[SpecialMarksRole] = "marks";
|
||||
roles[LongOperationRole] = "progressInfo";
|
||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||
return roles;
|
||||
}
|
||||
58
src/messageeventmodel.h
Normal file
58
src/messageeventmodel.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef MESSAGEEVENTMODEL_H
|
||||
#define MESSAGEEVENTMODEL_H
|
||||
|
||||
#include <QtCore/QAbstractListModel>
|
||||
#include "room.h"
|
||||
|
||||
class MessageEventModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(
|
||||
QMatrixClient::Room* room READ getRoom WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
enum EventRoles {
|
||||
EventTypeRole = Qt::UserRole + 1,
|
||||
EventIdRole,
|
||||
TimeRole,
|
||||
SectionRole,
|
||||
AboveSectionRole,
|
||||
AuthorRole,
|
||||
AboveAuthorRole,
|
||||
ContentRole,
|
||||
ContentTypeRole,
|
||||
HighlightRole,
|
||||
ReadMarkerRole,
|
||||
SpecialMarksRole,
|
||||
LongOperationRole,
|
||||
// For debugging
|
||||
EventResolvedTypeRole,
|
||||
};
|
||||
|
||||
explicit MessageEventModel(QObject* parent = nullptr);
|
||||
~MessageEventModel();
|
||||
|
||||
QMatrixClient::Room* getRoom() { return m_currentRoom; }
|
||||
void setRoom(QMatrixClient::Room* room);
|
||||
|
||||
Q_INVOKABLE int rowCount(
|
||||
const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
QHash<int, QByteArray> roleNames() const;
|
||||
|
||||
private slots:
|
||||
void refreshEvent(const QString& eventId);
|
||||
|
||||
private:
|
||||
QMatrixClient::Room* m_currentRoom = nullptr;
|
||||
QString lastReadEventId;
|
||||
int nextNewerRow = -1;
|
||||
|
||||
QDateTime makeMessageTimestamp(QMatrixClient::Room::rev_iter_t baseIt) const;
|
||||
QString makeDateString(QMatrixClient::Room::rev_iter_t baseIt) const;
|
||||
void refreshEventRoles(const QString& eventId, const QVector<int> roles);
|
||||
|
||||
signals:
|
||||
void roomChanged();
|
||||
};
|
||||
|
||||
#endif // MESSAGEEVENTMODEL_H
|
||||
107
src/roomlistmodel.cpp
Normal file
107
src/roomlistmodel.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "roomlistmodel.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtGui/QBrush>
|
||||
#include <QtGui/QColor>
|
||||
|
||||
RoomListModel::RoomListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||
|
||||
RoomListModel::~RoomListModel() {}
|
||||
|
||||
void RoomListModel::setConnection(QMatrixClient::Connection* connection) {
|
||||
Q_ASSERT(connection);
|
||||
|
||||
using QMatrixClient::Connection;
|
||||
using QMatrixClient::Room;
|
||||
beginResetModel();
|
||||
m_connection = connection;
|
||||
connect(connection, &Connection::loggedOut, this,
|
||||
[=] { setConnection(connection); });
|
||||
// connect(connection, &Connection::invitedRoom, this,
|
||||
// &RoomListModel::updateRoom);
|
||||
// connect(connection, &Connection::joinedRoom, this,
|
||||
// &RoomListModel::updateRoom);
|
||||
// connect(connection, &Connection::leftRoom, this,
|
||||
// &RoomListModel::updateRoom);
|
||||
connect(connection, &Connection::aboutToDeleteRoom, this,
|
||||
&RoomListModel::deleteRoom);
|
||||
|
||||
for (auto r : connection->roomMap()) addRoom(r);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QMatrixClient::Room* RoomListModel::roomAt(int row) { return m_rooms.at(row); }
|
||||
|
||||
void RoomListModel::addRoom(QMatrixClient::Room* room) {
|
||||
beginInsertRows(QModelIndex(), m_rooms.count(), m_rooms.count());
|
||||
connect(room, &QMatrixClient::Room::namesChanged, this,
|
||||
&RoomListModel::namesChanged);
|
||||
m_rooms.append(room);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void RoomListModel::deleteRoom(QMatrixClient::Room* room) {
|
||||
const auto it = std::find(m_rooms.begin(), m_rooms.end(), room);
|
||||
if (it == m_rooms.end()) return; // Already deleted, nothing to do
|
||||
const int row = it - m_rooms.begin();
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_rooms.erase(it);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
int RoomListModel::rowCount(const QModelIndex& parent) const {
|
||||
if (parent.isValid()) return 0;
|
||||
return m_rooms.count();
|
||||
}
|
||||
|
||||
QVariant RoomListModel::data(const QModelIndex& index, int role) const {
|
||||
if (!index.isValid()) return QVariant();
|
||||
|
||||
if (index.row() >= m_rooms.count()) {
|
||||
qDebug() << "UserListModel: something wrong here...";
|
||||
return QVariant();
|
||||
}
|
||||
QMatrixClient::Room* room = m_rooms.at(index.row());
|
||||
if (role == NameRole) {
|
||||
return room->displayName();
|
||||
}
|
||||
if (role == AvatarRole) {
|
||||
if (room->avatarUrl().toString() != "") {
|
||||
return room->avatarUrl();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
if (role == TopicRole) {
|
||||
return room->topic();
|
||||
}
|
||||
if (role == CategoryRole) {
|
||||
if (room->isFavourite()) return "Favorites";
|
||||
if (room->isLowPriority()) return "Low Priorities";
|
||||
return "Rooms";
|
||||
}
|
||||
if (role == HighlightRole) {
|
||||
if (room->highlightCount() > 0) return QBrush(QColor("orange"));
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void RoomListModel::namesChanged(QMatrixClient::Room* room) {
|
||||
int row = m_rooms.indexOf(room);
|
||||
emit dataChanged(index(row), index(row));
|
||||
}
|
||||
|
||||
void RoomListModel::unreadMessagesChanged(QMatrixClient::Room* room) {
|
||||
int row = m_rooms.indexOf(room);
|
||||
emit dataChanged(index(row), index(row));
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> RoomListModel::roleNames() const {
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[NameRole] = "name";
|
||||
roles[AvatarRole] = "avatar";
|
||||
roles[TopicRole] = "topic";
|
||||
roles[CategoryRole] = "category";
|
||||
roles[HighlightRole] = "highlight";
|
||||
return roles;
|
||||
}
|
||||
51
src/roomlistmodel.h
Normal file
51
src/roomlistmodel.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef ROOMLISTMODEL_H
|
||||
#define ROOMLISTMODEL_H
|
||||
|
||||
#include <QtCore/QAbstractListModel>
|
||||
#include "connection.h"
|
||||
#include "room.h"
|
||||
|
||||
class RoomListModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QMatrixClient::Connection* connection READ getConnection WRITE
|
||||
setConnection)
|
||||
|
||||
public:
|
||||
enum EventRoles {
|
||||
NameRole = Qt::UserRole + 1,
|
||||
AvatarRole,
|
||||
TopicRole,
|
||||
CategoryRole,
|
||||
HighlightRole,
|
||||
};
|
||||
|
||||
RoomListModel(QObject* parent = 0);
|
||||
virtual ~RoomListModel();
|
||||
|
||||
QMatrixClient::Connection* getConnection() { return m_connection; }
|
||||
void setConnection(QMatrixClient::Connection* connection);
|
||||
|
||||
Q_INVOKABLE QMatrixClient::Room* roomAt(int row);
|
||||
|
||||
QVariant data(const QModelIndex& index,
|
||||
int role = Qt::DisplayRole) const override;
|
||||
Q_INVOKABLE int rowCount(
|
||||
const QModelIndex& parent = QModelIndex()) const override;
|
||||
|
||||
QHash<int, QByteArray> roleNames() const;
|
||||
|
||||
private slots:
|
||||
void namesChanged(QMatrixClient::Room* room);
|
||||
void unreadMessagesChanged(QMatrixClient::Room* room);
|
||||
void addRoom(QMatrixClient::Room* room);
|
||||
void deleteRoom(QMatrixClient::Room* room);
|
||||
|
||||
private:
|
||||
QMatrixClient::Connection* m_connection = nullptr;
|
||||
QList<QMatrixClient::Room*> m_rooms;
|
||||
|
||||
signals:
|
||||
void connectionChanged();
|
||||
};
|
||||
|
||||
#endif // ROOMLISTMODEL_H
|
||||
Reference in New Issue
Block a user