Code reformatting && tooltip.

This commit is contained in:
Black Hat 2018-07-09 10:45:26 +08:00
parent d6b5cba61f
commit 6bd059ce63
22 changed files with 826 additions and 854 deletions

View File

@ -1,50 +1,50 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QNetworkProxy> #include <QNetworkProxy>
#include <QQmlApplicationEngine>
#include <QQmlContext> #include <QQmlContext>
#include "room.h"
#include "matrix/controller.h" #include "matrix/controller.h"
#include "matrix/roomlistmodel.h"
#include "matrix/imageprovider.h" #include "matrix/imageprovider.h"
#include "matrix/messageeventmodel.h" #include "matrix/messageeventmodel.h"
#include "matrix/roomlistmodel.h"
#include "room.h"
using namespace QMatrixClient; using namespace QMatrixClient;
int main(int argc, char *argv[]) int main(int argc, char *argv[]) {
{
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif #endif
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
// Enable this if you need proxy. // Enable this if you need proxy.
// QNetworkProxy proxy; // QNetworkProxy proxy;
// proxy.setType(QNetworkProxy::HttpProxy); // proxy.setType(QNetworkProxy::HttpProxy);
// proxy.setHostName("localhost"); // proxy.setHostName("localhost");
// proxy.setPort(1082); // proxy.setPort(1082);
// QNetworkProxy::setApplicationProxy(proxy); // QNetworkProxy::setApplicationProxy(proxy);
qmlRegisterType<Room>(); qRegisterMetaType<Room*> ("Room*"); qmlRegisterType<Room>();
qRegisterMetaType<Room *>("Room*");
qmlRegisterType<Controller>("Matrique", 0, 1, "Controller"); qmlRegisterType<Controller>("Matrique", 0, 1, "Controller");
qmlRegisterType<RoomListModel>("Matrique", 0, 1, "RoomListModel"); qmlRegisterType<RoomListModel>("Matrique", 0, 1, "RoomListModel");
qmlRegisterType<MessageEventModel>("Matrique", 0, 1, "MessageEventModel"); qmlRegisterType<MessageEventModel>("Matrique", 0, 1, "MessageEventModel");
qRegisterMetaType<User*>("User*"); qRegisterMetaType<User *>("User*");
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
ImageProvider* m_provider = new ImageProvider(); ImageProvider *m_provider = new ImageProvider();
engine.rootContext()->setContextProperty("imageProvider", m_provider->getConnection()); engine.rootContext()->setContextProperty("imageProvider",
m_provider->getConnection());
engine.addImageProvider(QLatin1String("mxc"), m_provider); engine.addImageProvider(QLatin1String("mxc"), m_provider);
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
if (engine.rootObjects().isEmpty()) if (engine.rootObjects().isEmpty()) return -1;
return -1;
return app.exec(); return app.exec();
} }

View File

@ -3,67 +3,71 @@
#include "connection.h" #include "connection.h"
Controller::Controller(QObject *parent) : QObject(parent) { Controller::Controller(QObject *parent) : QObject(parent) {
connect(m_connection, &QMatrixClient::Connection::connected, this, &Controller::connected); connect(m_connection, &QMatrixClient::Connection::connected, this,
connect(m_connection, &QMatrixClient::Connection::resolveError, this, &Controller::reconnect); &Controller::connected);
connect(m_connection, &QMatrixClient::Connection::syncError, this, &Controller::reconnect); connect(m_connection, &QMatrixClient::Connection::resolveError, this,
connect(m_connection, &QMatrixClient::Connection::syncDone, this, &Controller::resync); &Controller::reconnect);
connect(m_connection, &QMatrixClient::Connection::connected, this, &Controller::connectionChanged); 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);
} }
Controller::~Controller() { Controller::~Controller() { m_connection->stopSync(); }
m_connection->stopSync();
}
void Controller::login() { void Controller::login() {
if (!isLogin) { if (!isLogin) {
qDebug() << "UserID:" << userID; qDebug() << "UserID:" << userID;
qDebug() << "Token:" << token; qDebug() << "Token:" << token;
m_connection->setHomeserver(QUrl(homeserver)); m_connection->setHomeserver(QUrl(homeserver));
m_connection->connectWithToken(userID, token, ""); m_connection->connectWithToken(userID, token, "");
} }
} }
void Controller::loginWithCredentials(QString serverAddr, QString user, QString pass) { void Controller::loginWithCredentials(QString serverAddr, QString user,
if(!isLogin) { QString pass) {
qDebug() << "Server:" << serverAddr; if (!isLogin) {
qDebug() << "User:" << user; qDebug() << "Server:" << serverAddr;
qDebug() << "Pass:" << pass; qDebug() << "User:" << user;
qDebug() << "Pass:" << pass;
if(!user.isEmpty() && !pass.isEmpty()) { if (!user.isEmpty() && !pass.isEmpty()) {
qDebug() << "Using given credential."; qDebug() << "Using given credential.";
m_connection->setHomeserver(QUrl(serverAddr)); m_connection->setHomeserver(QUrl(serverAddr));
m_connection->connectToServer(user, pass, ""); m_connection->connectToServer(user, pass, "");
}
} else {
qDebug() << "You are already logged in.";
} }
} else {
qDebug() << "You are already logged in.";
}
} }
void Controller::logout() { void Controller::logout() {
qDebug() << "Logging out."; qDebug() << "Logging out.";
setUserID(""); setUserID("");
setToken(""); setToken("");
setIsLogin(false); setIsLogin(false);
} }
void Controller::connected() { void Controller::connected() {
qDebug() << "Logged in."; qDebug() << "Logged in.";
setHomeserver(m_connection->homeserver().toString()); setHomeserver(m_connection->homeserver().toString());
setUserID(m_connection->userId()); setUserID(m_connection->userId());
setToken(m_connection->accessToken()); setToken(m_connection->accessToken());
m_connection->loadState(); m_connection->loadState();
resync(); resync();
setIsLogin(true); setIsLogin(true);
} }
void Controller::resync() { void Controller::resync() {
qDebug() << "Syncing Matrix."; qDebug() << "Syncing Matrix.";
m_connection->sync(30000); m_connection->sync(30000);
m_connection->saveState(); m_connection->saveState();
} }
void Controller::reconnect() { void Controller::reconnect() {
qDebug() << "Connection lost. Reconnecting..."; qDebug() << "Connection lost. Reconnecting...";
m_connection->connectWithToken(userID, token, ""); m_connection->connectWithToken(userID, token, "");
} }

View File

@ -3,99 +3,101 @@
#include <QObject> #include <QObject>
#include "connection.h" #include "connection.h"
#include "user.h"
#include "roomlistmodel.h" #include "roomlistmodel.h"
#include "user.h"
namespace QMatrixClient { namespace QMatrixClient {
class Connection; class Connection;
} }
class Controller : public QObject class Controller : public QObject {
{ Q_OBJECT
Q_OBJECT
Q_PROPERTY(QMatrixClient::Connection* connection READ getConnection NOTIFY connectionChanged) Q_PROPERTY(QMatrixClient::Connection* connection READ getConnection NOTIFY
Q_PROPERTY(bool isLogin READ getIsLogin WRITE setIsLogin NOTIFY isLoginChanged) connectionChanged)
Q_PROPERTY(QString homeserver READ getHomeserver WRITE setHomeserver NOTIFY homeserverChanged) Q_PROPERTY(
Q_PROPERTY(QString userID READ getUserID WRITE setUserID NOTIFY userIDChanged) bool isLogin READ getIsLogin WRITE setIsLogin NOTIFY isLoginChanged)
Q_PROPERTY(QByteArray token READ getToken WRITE setToken NOTIFY tokenChanged) Q_PROPERTY(QString homeserver READ getHomeserver WRITE setHomeserver NOTIFY
Q_PROPERTY(bool busy READ getBusy WRITE setBusy NOTIFY busyChanged) 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: public:
explicit Controller(QObject *parent = nullptr); explicit Controller(QObject* parent = nullptr);
~Controller(); ~Controller();
// All the Q_INVOKABLEs. // All the Q_INVOKABLEs.
Q_INVOKABLE void login(); Q_INVOKABLE void login();
Q_INVOKABLE void loginWithCredentials(QString, QString, QString); Q_INVOKABLE void loginWithCredentials(QString, QString, QString);
Q_INVOKABLE void logout(); Q_INVOKABLE void logout();
// All the non-Q_INVOKABLE functions. // All the non-Q_INVOKABLE functions.
// All the Q_PROPERTYs. // All the Q_PROPERTYs.
QMatrixClient::Connection* m_connection = new QMatrixClient::Connection(); QMatrixClient::Connection* m_connection = new QMatrixClient::Connection();
QMatrixClient::Connection* getConnection() { return m_connection; } QMatrixClient::Connection* getConnection() { return m_connection; }
bool isLogin = false; bool isLogin = false;
bool getIsLogin() { return isLogin; } bool getIsLogin() { return isLogin; }
void setIsLogin(bool n) { void setIsLogin(bool n) {
if(n != isLogin) { if (n != isLogin) {
isLogin = n; isLogin = n;
emit isLoginChanged(); emit isLoginChanged();
} }
} }
QString userID; QString userID;
QString getUserID() { return userID; } QString getUserID() { return userID; }
void setUserID(QString n) { void setUserID(QString n) {
if(n != userID) { if (n != userID) {
userID = n; userID = n;
emit userIDChanged(); emit userIDChanged();
} }
} }
QByteArray token; QByteArray token;
QByteArray getToken() { return token; } QByteArray getToken() { return token; }
void setToken(QByteArray n) { void setToken(QByteArray n) {
if(n != token) { if (n != token) {
token = n; token = n;
emit tokenChanged(); emit tokenChanged();
} }
} }
QString homeserver; QString homeserver;
QString getHomeserver() { return homeserver; } QString getHomeserver() { return homeserver; }
void setHomeserver(QString n) { void setHomeserver(QString n) {
if (n != homeserver) { if (n != homeserver) {
homeserver = n; homeserver = n;
emit homeserverChanged(); emit homeserverChanged();
} }
} }
bool busy = false; bool busy = false;
bool getBusy() { return busy; } bool getBusy() { return busy; }
void setBusy(bool b) { void setBusy(bool b) {
if (b != busy) { if (b != busy) {
busy = b; busy = b;
emit busyChanged(); emit busyChanged();
} }
} }
private: private:
void connected(); void connected();
void resync(); void resync();
void reconnect(); void reconnect();
signals: signals:
void connectionChanged(); void connectionChanged();
void isLoginChanged(); void isLoginChanged();
void userIDChanged(); void userIDChanged();
void tokenChanged(); void tokenChanged();
void homeserverChanged(); void homeserverChanged();
void busyChanged(); void busyChanged();
void errorOccured(); void errorOccured();
public slots: public slots:
}; };
#endif // CONTROLLER_H #endif // CONTROLLER_H

View File

@ -1,8 +1,8 @@
#include "imageprovider.h" #include "imageprovider.h"
#include <QtCore/QWaitCondition>
#include <QtCore/QDebug>
#include <QMetaObject> #include <QMetaObject>
#include <QtCore/QDebug>
#include <QtCore/QWaitCondition>
#include "jobs/mediathumbnailjob.h" #include "jobs/mediathumbnailjob.h"
@ -11,58 +11,57 @@
using QMatrixClient::MediaThumbnailJob; using QMatrixClient::MediaThumbnailJob;
ImageProvider::ImageProvider(QObject* parent) ImageProvider::ImageProvider(QObject* parent)
: QQuickImageProvider(QQmlImageProviderBase::Image, : QQuickImageProvider(
QQmlImageProviderBase::ForceAsynchronousImageLoading) QQmlImageProviderBase::Image,
{ QQmlImageProviderBase::ForceAsynchronousImageLoading) {
#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
qRegisterMetaType<MediaThumbnailJob*>(); qRegisterMetaType<MediaThumbnailJob*>();
#endif #endif
m_connection = new ImageProviderConnection(); m_connection = new ImageProviderConnection();
} }
QImage ImageProvider::requestImage(const QString& id, QImage ImageProvider::requestImage(const QString& id, QSize* pSize,
QSize* pSize, const QSize& requestedSize) const QSize& requestedSize) {
{ if (!id.startsWith("mxc://")) {
if (!id.startsWith("mxc://")) qWarning() << "ImageProvider: won't fetch an invalid id:" << id
{ << "doesn't follow server/mediaId pattern";
qWarning() << "ImageProvider: won't fetch an invalid id:" << id return {};
<< "doesn't follow server/mediaId pattern"; }
return {};
}
QUrl mxcUri { id }; QUrl mxcUri{id};
qDebug() << "ImageProvider::requestImage:" << mxcUri.toString();
MediaThumbnailJob* job = nullptr; MediaThumbnailJob* job = nullptr;
QReadLocker locker(&m_lock); QReadLocker locker(&m_lock);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(m_connection, QMetaObject::invokeMethod(
[=] { return m_connection->getConnection()->getThumbnail(mxcUri, requestedSize); }, m_connection,
Qt::BlockingQueuedConnection, &job); [=] {
return m_connection->getConnection()->getThumbnail(mxcUri,
requestedSize);
},
Qt::BlockingQueuedConnection, &job);
#else #else
QMetaObject::invokeMethod(m_connection, "getThumbnail", QMetaObject::invokeMethod(m_connection, "getThumbnail",
Qt::BlockingQueuedConnection, Q_RETURN_ARG(MediaThumbnailJob*, job), Qt::BlockingQueuedConnection,
Q_ARG(QUrl, mxcUri), Q_ARG(QSize, requestedSize)); Q_RETURN_ARG(MediaThumbnailJob*, job),
Q_ARG(QUrl, mxcUri), Q_ARG(QSize, requestedSize));
#endif #endif
if (!job) if (!job) {
{ qDebug() << "ImageProvider: failed to send a request";
qDebug() << "ImageProvider: failed to send a request"; return {};
return {}; }
} QImage result;
QImage result; {
{ QWaitCondition condition; // The most compact way to block on a signal
QWaitCondition condition; // The most compact way to block on a signal job->connect(job, &MediaThumbnailJob::finished, job, [&] {
job->connect(job, &MediaThumbnailJob::finished, job, [&] { result = job->thumbnail();
result = job->thumbnail(); condition.wakeAll();
condition.wakeAll(); });
}); condition.wait(&m_lock);
condition.wait(&m_lock); }
}
if(pSize != nullptr) if (pSize != nullptr) *pSize = result.size();
*pSize = result.size();
return result; return result;
} }

View File

@ -1,28 +1,27 @@
#ifndef IMAGEPROVIDER_H #ifndef IMAGEPROVIDER_H
#define IMAGEPROVIDER_H #define IMAGEPROVIDER_H
#include <QtQuick/QQuickImageProvider>
#include <QtCore/QReadWriteLock>
#include <QObject> #include <QObject>
#include <QtCore/QReadWriteLock>
#include <QtQuick/QQuickImageProvider>
#include "connection.h" #include "connection.h"
#include "imageproviderconnection.h" #include "imageproviderconnection.h"
class ImageProvider: public QQuickImageProvider class ImageProvider : public QQuickImageProvider {
{ public:
public: explicit ImageProvider(QObject* parent = nullptr);
explicit ImageProvider(QObject* parent = nullptr);
QImage requestImage(const QString& id, QSize* pSize, QImage requestImage(const QString& id, QSize* pSize,
const QSize& requestedSize) override; const QSize& requestedSize) override;
void initializeEngine(QQmlEngine *engine, const char *uri); void initializeEngine(QQmlEngine* engine, const char* uri);
ImageProviderConnection* getConnection() { return m_connection; } ImageProviderConnection* getConnection() { return m_connection; }
private: private:
QReadWriteLock m_lock; QReadWriteLock m_lock;
ImageProviderConnection* m_connection; ImageProviderConnection* m_connection;
}; };
#endif // IMAGEPROVIDER_H #endif // IMAGEPROVIDER_H

View File

@ -1,9 +1,6 @@
#include "imageproviderconnection.h" #include "imageproviderconnection.h"
ImageProviderConnection::ImageProviderConnection(QObject* parent) : QObject(parent) { ImageProviderConnection::ImageProviderConnection(QObject* parent)
: QObject(parent) {}
} ImageProviderConnection::~ImageProviderConnection() {}
ImageProviderConnection::~ImageProviderConnection() {
}

View File

@ -5,25 +5,26 @@
#include "connection.h" #include "connection.h"
class ImageProviderConnection : public QObject class ImageProviderConnection : public QObject {
{ Q_OBJECT
Q_OBJECT
Q_PROPERTY(QMatrixClient::Connection* connection READ getConnection WRITE setConnection NOTIFY connectionChanged) Q_PROPERTY(QMatrixClient::Connection* connection READ getConnection WRITE
setConnection NOTIFY connectionChanged)
public: public:
explicit ImageProviderConnection(QObject* parent = nullptr); explicit ImageProviderConnection(QObject* parent = nullptr);
~ImageProviderConnection(); ~ImageProviderConnection();
QMatrixClient::Connection* getConnection() { return m_connection; } QMatrixClient::Connection* getConnection() { return m_connection; }
void setConnection(QMatrixClient::Connection* connection) { void setConnection(QMatrixClient::Connection* connection) {
emit connectionChanged(); emit connectionChanged();
m_connection = connection; m_connection = connection;
} }
private:
QMatrixClient::Connection* m_connection; private:
signals: QMatrixClient::Connection* m_connection;
void connectionChanged(); signals:
void connectionChanged();
}; };
#endif // IMAGEPROVIDERCONNECTION_H #endif // IMAGEPROVIDERCONNECTION_H

View File

@ -1,452 +1,379 @@
#include "messageeventmodel.h" #include "messageeventmodel.h"
#include <QtCore/QSettings>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtQml> // for qmlRegisterType() #include <QtCore/QSettings>
#include <QtQml> // for qmlRegisterType()
#include "events/roommemberevent.h"
#include "events/simplestateevents.h"
#include "events/redactionevent.h" #include "events/redactionevent.h"
#include "events/roomavatarevent.h" #include "events/roomavatarevent.h"
#include "events/roommemberevent.h"
#include "events/simplestateevents.h"
#include "connection.h" #include "connection.h"
#include "user.h"
#include "settings.h" #include "settings.h"
#include "user.h"
MessageEventModel::MessageEventModel(QObject* parent) MessageEventModel::MessageEventModel(QObject* parent)
: QAbstractListModel(parent) : QAbstractListModel(parent) {
{ qmlRegisterType<QMatrixClient::FileTransferInfo>();
qmlRegisterType<QMatrixClient::FileTransferInfo>(); qRegisterMetaType<QMatrixClient::FileTransferInfo>();
qRegisterMetaType<QMatrixClient::FileTransferInfo>();
} }
MessageEventModel::~MessageEventModel() 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::setRoom(QMatrixClient::Room* room) void MessageEventModel::refreshEvent(const QString& eventId) {
{ refreshEventRoles(eventId, {});
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, void MessageEventModel::refreshEventRoles(const QString& eventId,
const QVector<int> roles) const QVector<int> roles) {
{ const auto it = m_currentRoom->findInTimeline(eventId);
const auto it = m_currentRoom->findInTimeline(eventId); if (it != m_currentRoom->timelineEdge()) {
if (it != m_currentRoom->timelineEdge()) const auto row = it - m_currentRoom->messageEvents().rbegin();
{ emit dataChanged(index(row), index(row), roles);
const auto row = it - m_currentRoom->messageEvents().rbegin(); }
emit dataChanged(index(row), index(row), roles);
}
} }
inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) {
{ return ti->timestamp().isValid();
return ti->timestamp().isValid();
} }
QDateTime MessageEventModel::makeMessageTimestamp(QMatrixClient::Room::rev_iter_t baseIt) const QDateTime MessageEventModel::makeMessageTimestamp(
{ QMatrixClient::Room::rev_iter_t baseIt) const {
const auto& timeline = m_currentRoom->messageEvents(); const auto& timeline = m_currentRoom->messageEvents();
auto ts = baseIt->event()->timestamp(); auto ts = baseIt->event()->timestamp();
if (ts.isValid()) if (ts.isValid()) return ts;
return ts;
// The event is most likely redacted or just invalid. // The event is most likely redacted or just invalid.
// Look for the nearest date around and slap zero time to it. // Look for the nearest date around and slap zero time to it.
using QMatrixClient::TimelineItem; using QMatrixClient::TimelineItem;
auto rit = std::find_if(baseIt, timeline.rend(), auto rit = std::find_if(baseIt, timeline.rend(), hasValidTimestamp);
hasValidTimestamp); if (rit != timeline.rend())
if (rit != timeline.rend()) return {rit->event()->timestamp().date(), {0, 0}, Qt::LocalTime};
return { rit->event()->timestamp().date(), {0,0}, Qt::LocalTime }; auto it = std::find_if(baseIt.base(), timeline.end(), hasValidTimestamp);
auto it = std::find_if(baseIt.base(), timeline.end(), hasValidTimestamp); if (it != timeline.end())
if (it != timeline.end()) return {it->event()->timestamp().date(), {0, 0}, Qt::LocalTime};
return { it->event()->timestamp().date(), {0,0}, Qt::LocalTime };
// What kind of room is that?.. // What kind of room is that?..
qCritical() << "No valid timestamps in the room timeline!"; qCritical() << "No valid timestamps in the room timeline!";
return {}; return {};
} }
QString MessageEventModel::makeDateString(QMatrixClient::Room::rev_iter_t baseIt) const QString MessageEventModel::makeDateString(
{ QMatrixClient::Room::rev_iter_t baseIt) const {
auto date = makeMessageTimestamp(baseIt).toLocalTime().date(); auto date = makeMessageTimestamp(baseIt).toLocalTime().date();
if (QMatrixClient::SettingsGroup("UI") if (QMatrixClient::SettingsGroup("UI")
.value("banner_human_friendly_date", true).toBool()) .value("banner_human_friendly_date", true)
{ .toBool()) {
if (date == QDate::currentDate()) if (date == QDate::currentDate()) return tr("Today");
return tr("Today"); if (date == QDate::currentDate().addDays(-1)) return tr("Yesterday");
if (date == QDate::currentDate().addDays(-1)) if (date == QDate::currentDate().addDays(-2))
return tr("Yesterday"); return tr("The day before yesterday");
if (date == QDate::currentDate().addDays(-2)) if (date > QDate::currentDate().addDays(-7)) return date.toString("dddd");
return tr("The day before yesterday"); }
if (date > QDate::currentDate().addDays(-7)) return date.toString(Qt::DefaultLocaleShortDate);
return date.toString("dddd");
}
return date.toString(Qt::DefaultLocaleShortDate);
} }
int MessageEventModel::rowCount(const QModelIndex& parent) const int MessageEventModel::rowCount(const QModelIndex& parent) const {
{ if (!m_currentRoom || parent.isValid()) return 0;
if(!m_currentRoom || parent.isValid()) return m_currentRoom->timelineSize();
return 0;
return m_currentRoom->timelineSize();
} }
QVariant MessageEventModel::data(const QModelIndex& index, int role) const QVariant MessageEventModel::data(const QModelIndex& index, int role) const {
{ if (!m_currentRoom || index.row() < 0 ||
if( !m_currentRoom || index.row() >= m_currentRoom->timelineSize())
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(); 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> MessageEventModel::roleNames() const QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
{ roles[EventTypeRole] = "eventType";
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); roles[EventIdRole] = "eventId";
roles[EventTypeRole] = "eventType"; roles[TimeRole] = "time";
roles[EventIdRole] = "eventId"; roles[SectionRole] = "section";
roles[TimeRole] = "time"; roles[AboveSectionRole] = "aboveSection";
roles[SectionRole] = "section"; roles[AuthorRole] = "author";
roles[AboveSectionRole] = "aboveSection"; roles[AboveAuthorRole] = "aboveAuthor";
roles[AuthorRole] = "author"; roles[ContentRole] = "content";
roles[AboveAuthorRole] = "aboveAuthor"; roles[ContentTypeRole] = "contentType";
roles[ContentRole] = "content"; roles[HighlightRole] = "highlight";
roles[ContentTypeRole] = "contentType"; roles[ReadMarkerRole] = "readMarker";
roles[HighlightRole] = "highlight"; roles[SpecialMarksRole] = "marks";
roles[ReadMarkerRole] = "readMarker"; roles[LongOperationRole] = "progressInfo";
roles[SpecialMarksRole] = "marks"; roles[EventResolvedTypeRole] = "eventResolvedType";
roles[LongOperationRole] = "progressInfo"; return roles;
roles[EventResolvedTypeRole] = "eventResolvedType";
return roles;
} }

View File

@ -4,54 +4,55 @@
#include <QtCore/QAbstractListModel> #include <QtCore/QAbstractListModel>
#include "room.h" #include "room.h"
class MessageEventModel: public QAbstractListModel class MessageEventModel : public QAbstractListModel {
{ Q_OBJECT
Q_OBJECT Q_PROPERTY(
Q_PROPERTY(QMatrixClient::Room* room READ getRoom WRITE setRoom NOTIFY roomChanged) QMatrixClient::Room* room READ getRoom WRITE setRoom NOTIFY roomChanged)
public: public:
enum EventRoles { enum EventRoles {
EventTypeRole = Qt::UserRole + 1, EventTypeRole = Qt::UserRole + 1,
EventIdRole, EventIdRole,
TimeRole, TimeRole,
SectionRole, SectionRole,
AboveSectionRole, AboveSectionRole,
AuthorRole, AuthorRole,
AboveAuthorRole, AboveAuthorRole,
ContentRole, ContentRole,
ContentTypeRole, ContentTypeRole,
HighlightRole, HighlightRole,
ReadMarkerRole, ReadMarkerRole,
SpecialMarksRole, SpecialMarksRole,
LongOperationRole, LongOperationRole,
// For debugging // For debugging
EventResolvedTypeRole, EventResolvedTypeRole,
}; };
explicit MessageEventModel(QObject* parent = nullptr); explicit MessageEventModel(QObject* parent = nullptr);
~MessageEventModel(); ~MessageEventModel();
QMatrixClient::Room* getRoom() { return m_currentRoom; } QMatrixClient::Room* getRoom() { return m_currentRoom; }
void setRoom(QMatrixClient::Room* room); void setRoom(QMatrixClient::Room* room);
Q_INVOKABLE int rowCount(const QModelIndex& parent = QModelIndex()) const override; Q_INVOKABLE int rowCount(
QVariant data(const QModelIndex& index, int role) const override; const QModelIndex& parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const; QVariant data(const QModelIndex& index, int role) const override;
QHash<int, QByteArray> roleNames() const;
private slots: private slots:
void refreshEvent(const QString& eventId); void refreshEvent(const QString& eventId);
private: private:
QMatrixClient::Room* m_currentRoom = nullptr; QMatrixClient::Room* m_currentRoom = nullptr;
QString lastReadEventId; QString lastReadEventId;
int nextNewerRow = -1; int nextNewerRow = -1;
QDateTime makeMessageTimestamp(QMatrixClient::Room::rev_iter_t baseIt) const; QDateTime makeMessageTimestamp(QMatrixClient::Room::rev_iter_t baseIt) const;
QString makeDateString(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); void refreshEventRoles(const QString& eventId, const QVector<int> roles);
signals: signals:
void roomChanged(); void roomChanged();
}; };
#endif // MESSAGEEVENTMODEL_H #endif // MESSAGEEVENTMODEL_H

View File

@ -1,103 +1,83 @@
#include "roomlistmodel.h" #include "roomlistmodel.h"
#include <QtCore/QDebug>
#include <QtGui/QBrush> #include <QtGui/QBrush>
#include <QtGui/QColor> #include <QtGui/QColor>
#include <QtCore/QDebug>
RoomListModel::RoomListModel(QObject* parent) RoomListModel::RoomListModel(QObject* parent) : QAbstractListModel(parent) {}
: QAbstractListModel(parent)
{
} RoomListModel::~RoomListModel() {}
RoomListModel::~RoomListModel() void RoomListModel::setConnection(QMatrixClient::Connection* connection) {
{ beginResetModel();
} m_connection = connection;
m_rooms.clear();
void RoomListModel::setConnection(QMatrixClient::Connection* connection) connect(connection, &QMatrixClient::Connection::newRoom, this,
{ &RoomListModel::addRoom);
beginResetModel(); for (QMatrixClient::Room* room : connection->roomMap().values()) {
m_connection = connection; connect(room, &QMatrixClient::Room::namesChanged, this,
m_rooms.clear(); &RoomListModel::namesChanged);
connect( connection, &QMatrixClient::Connection::newRoom, this, &RoomListModel::addRoom );
for( QMatrixClient::Room* room: connection->roomMap().values() ) {
connect( room, &QMatrixClient::Room::namesChanged, this, &RoomListModel::namesChanged );
m_rooms.append(room);
}
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); m_rooms.append(room);
endInsertRows(); }
endResetModel();
} }
int RoomListModel::rowCount(const QModelIndex& parent) const QMatrixClient::Room* RoomListModel::roomAt(int row) { return m_rooms.at(row); }
{
if( parent.isValid() ) void RoomListModel::addRoom(QMatrixClient::Room* room) {
return 0; beginInsertRows(QModelIndex(), m_rooms.count(), m_rooms.count());
return m_rooms.count(); connect(room, &QMatrixClient::Room::namesChanged, this,
&RoomListModel::namesChanged);
m_rooms.append(room);
endInsertRows();
} }
QVariant RoomListModel::data(const QModelIndex& index, int role) const int RoomListModel::rowCount(const QModelIndex& parent) const {
{ if (parent.isValid()) return 0;
if( !index.isValid() ) return m_rooms.count();
return QVariant(); }
if( index.row() >= m_rooms.count() ) QVariant RoomListModel::data(const QModelIndex& index, int role) const {
{ if (!index.isValid()) return QVariant();
qDebug() << "UserListModel: something wrong here...";
return QVariant(); if (index.row() >= m_rooms.count()) {
} qDebug() << "UserListModel: something wrong here...";
QMatrixClient::Room* room = m_rooms.at(index.row()); return QVariant();
if( role == Qt::DisplayRole ) }
{ QMatrixClient::Room* room = m_rooms.at(index.row());
return room->displayName(); if (role == Qt::DisplayRole) {
} return room->displayName();
if( role == Qt::ForegroundRole ) }
{ if (role == Qt::ForegroundRole) {
if( room->highlightCount() > 0 ) if (room->highlightCount() > 0) return QBrush(QColor("orange"));
return QBrush(QColor("orange")); return QVariant();
return QVariant(); }
} if (role == Qt::DecorationRole) {
if( role == Qt::DecorationRole ) if (room->avatarUrl().toString() != "") {
{ return room->avatarUrl();
if ( room->avatarUrl().toString() != "" ) {
return room->avatarUrl();
}
return QVariant();
}
if ( role == Qt::StatusTipRole )
{
return room->topic();
} }
return QVariant(); return QVariant();
}
if (role == Qt::StatusTipRole) {
return room->topic();
}
return QVariant();
} }
void RoomListModel::namesChanged(QMatrixClient::Room* room) void RoomListModel::namesChanged(QMatrixClient::Room* room) {
{ int row = m_rooms.indexOf(room);
int row = m_rooms.indexOf(room); emit dataChanged(index(row), index(row));
emit dataChanged(index(row), index(row));
} }
void RoomListModel::unreadMessagesChanged(QMatrixClient::Room* room) void RoomListModel::unreadMessagesChanged(QMatrixClient::Room* room) {
{ int row = m_rooms.indexOf(room);
int row = m_rooms.indexOf(room); emit dataChanged(index(row), index(row));
emit dataChanged(index(row), index(row));
} }
QHash<int, QByteArray> RoomListModel::roleNames() const { QHash<int, QByteArray> RoomListModel::roleNames() const {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[Qt::DisplayRole] = "name"; roles[Qt::DisplayRole] = "name";
roles[Qt::DecorationRole] = "avatar"; roles[Qt::DecorationRole] = "avatar";
roles[Qt::StatusTipRole] = "topic"; roles[Qt::StatusTipRole] = "topic";
return roles; return roles;
} }

View File

@ -2,39 +2,41 @@
#define ROOMLISTMODEL_H #define ROOMLISTMODEL_H
#include <QtCore/QAbstractListModel> #include <QtCore/QAbstractListModel>
#include "room.h"
#include "connection.h" #include "connection.h"
#include "room.h"
class RoomListModel: public QAbstractListModel class RoomListModel : public QAbstractListModel {
{ Q_OBJECT
Q_OBJECT Q_PROPERTY(QMatrixClient::Connection* connection READ getConnection WRITE
Q_PROPERTY(QMatrixClient::Connection *connection READ getConnection WRITE setConnection) setConnection)
public: public:
RoomListModel(QObject* parent=0); RoomListModel(QObject* parent = 0);
virtual ~RoomListModel(); virtual ~RoomListModel();
QMatrixClient::Connection* getConnection() {return m_connection;} QMatrixClient::Connection* getConnection() { return m_connection; }
void setConnection(QMatrixClient::Connection* connection); void setConnection(QMatrixClient::Connection* connection);
Q_INVOKABLE QMatrixClient::Room* roomAt(int row); Q_INVOKABLE QMatrixClient::Room* roomAt(int row);
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex& index,
Q_INVOKABLE int rowCount(const QModelIndex& parent=QModelIndex()) const override; int role = Qt::DisplayRole) const override;
Q_INVOKABLE int rowCount(
const QModelIndex& parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const; QHash<int, QByteArray> roleNames() const;
private slots: private slots:
void namesChanged(QMatrixClient::Room* room); void namesChanged(QMatrixClient::Room* room);
void unreadMessagesChanged(QMatrixClient::Room* room); void unreadMessagesChanged(QMatrixClient::Room* room);
void addRoom(QMatrixClient::Room* room); void addRoom(QMatrixClient::Room* room);
private: private:
QMatrixClient::Connection* m_connection = nullptr; QMatrixClient::Connection* m_connection = nullptr;
QList<QMatrixClient::Room*> m_rooms; QList<QMatrixClient::Room*> m_rooms;
signals: signals:
void connectionChanged(); void connectionChanged();
}; };
#endif // ROOMLISTMODEL_H #endif // ROOMLISTMODEL_H

View File

@ -1,8 +1,8 @@
import QtQuick 2.10 import QtQuick 2.11
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.11
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick.Controls 2.3 import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.3 import QtQuick.Controls.Material 2.4
import Qt.labs.settings 1.0 import Qt.labs.settings 1.0
import "qrc:/qml/component" import "qrc:/qml/component"
@ -60,13 +60,13 @@ Page {
id: mainCol id: mainCol
width: parent.width width: parent.width
ImageStatus { // ImageStatus {
Layout.preferredWidth: 96 // Layout.preferredWidth: 96
Layout.preferredHeight: 96 // Layout.preferredHeight: 96
Layout.alignment: Qt.AlignHCenter // Layout.alignment: Qt.AlignHCenter
source: "qrc:/asset/img/avatar.png" // source: "qrc:/asset/img/avatar.png"
} // }
TextField { TextField {
id: serverField id: serverField

View File

@ -1,6 +1,6 @@
import QtQuick 2.11 import QtQuick 2.11
import QtQuick.Controls 2.3 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.11
import Matrique 0.1 import Matrique 0.1
import "qrc:/qml/form" import "qrc:/qml/form"

View File

@ -1,5 +1,5 @@
import QtQuick 2.3 import QtQuick 2.11
import QtQuick.Controls 2.3 import QtQuick.Controls 2.4
ItemDelegate { ItemDelegate {
id: itemDelegate id: itemDelegate

View File

@ -1,17 +1,22 @@
import QtQuick 2.11 import QtQuick 2.11
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick.Controls.Material 2.4
Item { Item {
property bool opaqueBackground: false property bool opaqueBackground: false
property alias source: avatar.source property bool round: true
property string source: ""
property string displayText: ""
readonly property bool showImage: source
readonly property bool showInitial: !showImage && displayText
id: item id: item
Rectangle { Rectangle {
width: item.width width: item.width
height: item.width height: item.width
radius: item.width / 2 radius: round ? item.width / 2 : 0
color: "white" color: "white"
visible: opaqueBackground visible: opaqueBackground
} }
@ -20,6 +25,8 @@ Item {
id: avatar id: avatar
width: item.width width: item.width
height: item.width height: item.width
visible: showImage
source: item.source
mipmap: true mipmap: true
layer.enabled: true layer.enabled: true
@ -35,9 +42,42 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
width: avatar.width width: avatar.width
height: avatar.width height: avatar.width
radius: avatar.width / 2 radius: round? avatar.width / 2 : 0
} }
} }
} }
} }
Label {
anchors.fill: parent
color: "white"
visible: showInitial
text: showInitial ? getInitials(displayText)[0] : ""
font.pixelSize: 22
font.bold: true
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle {
anchors.fill: parent
radius: round? width / 2 : 0
color: showInitial ? stringToColor(displayText) : Material.accent
}
}
function getInitials(text) {
return text.toUpperCase().replace(/[^a-zA-Z- ]/g, "").match(/\b\w/g);
}
function stringToColor(str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
var colour = '#';
for (var i = 0; i < 3; i++) {
var value = (hash >> (i * 8)) & 0xFF;
colour += ('00' + value.toString(16)).substr(-2);
}
return colour;
}
} }

View File

@ -1,7 +1,7 @@
import QtQuick 2.10 import QtQuick 2.11
import QtQuick.Controls 2.3 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.11
import QtQuick.Controls.Material 2.3 import QtQuick.Controls.Material 2.4
Item { Item {
property alias icon: iconText.text property alias icon: iconText.text
@ -17,5 +17,5 @@ Item {
color: item.color color: item.color
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
} }

View File

@ -1,7 +1,7 @@
import QtQuick 2.10 import QtQuick 2.11
import QtQuick.Controls 2.3 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.11
import QtQuick.Controls.Material 2.3 import QtQuick.Controls.Material 2.4
Item { Item {
Rectangle { Rectangle {

View File

@ -1,7 +1,7 @@
import QtQuick 2.10 import QtQuick 2.11
import QtQuick.Controls 2.3 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.11
import QtQuick.Controls.Material 2.3 import QtQuick.Controls.Material 2.4
Item { Item {
property var page property var page
@ -29,7 +29,7 @@ Item {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
if(page != null && stackView.currentItem !== page) { if(!page && stackView.currentItem !== page) {
if(stackView.depth === 1) { if(stackView.depth === 1) {
stackView.replace(page) stackView.replace(page)
} else { } else {

View File

@ -1,7 +1,7 @@
import QtQuick 2.10 import QtQuick 2.11
import QtQuick.Controls 2.3 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.4
import QtQuick.Controls.Material 2.3 import QtQuick.Controls.Material 2.4
import "qrc:/qml/component" import "qrc:/qml/component"
Item { Item {

View File

@ -2,8 +2,8 @@ import QtQuick 2.11
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11 import QtQuick.Layouts 1.11
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick.Controls.Material 2.3 import QtQuick.Controls.Material 2.4
import QtQml.Models 2.3 import QtQml.Models 2.4
import Matrique 0.1 import Matrique 0.1
import "qrc:/qml/component" import "qrc:/qml/component"
@ -27,6 +27,10 @@ Item {
height: 80 height: 80
onClicked: listView.currentIndex = index onClicked: listView.currentIndex = index
ToolTip.visible: pressed
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: name
contentItem: RowLayout { contentItem: RowLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: 16 anchors.margins: 16
@ -36,7 +40,8 @@ Item {
Layout.preferredWidth: height Layout.preferredWidth: height
Layout.fillHeight: true Layout.fillHeight: true
source: avatar == null || avatar == "" ? "qrc:/asset/img/avatar.png" : "image://mxc/" + avatar source: avatar ? "image://mxc/" + avatar : ""
displayText: name
opaqueBackground: true opaqueBackground: true
} }
@ -52,10 +57,10 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
text: { text: {
if (name != "") { if (name) {
return name; return name;
} }
if (alias != "") { if (alias) {
return alias; return alias;
} }
return id return id
@ -69,7 +74,7 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
text: topic === "" ? "No topic yet." : topic text: topic ? topic : "No topic yet."
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
@ -133,7 +138,7 @@ Item {
} }
Rectangle { Rectangle {
width: searchField.activeFocus || searchField.text != "" ? parent.width : 0 width: searchField.activeFocus || searchField.text ? parent.width : 0
height: parent.height height: parent.height
color: "white" color: "white"

View File

@ -1,7 +1,7 @@
import QtQuick 2.10 import QtQuick 2.11
import QtQuick.Controls 2.3 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.11
import QtQuick.Controls.Material 2.3 import QtQuick.Controls.Material 2.4
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import Matrique 0.1 import Matrique 0.1
import "qrc:/qml/component" import "qrc:/qml/component"
@ -16,7 +16,7 @@ Item {
background: Item { background: Item {
anchors.fill: parent anchors.fill: parent
visible: currentRoom == null visible: !currentRoom
Pane { Pane {
anchors.fill: parent anchors.fill: parent
} }
@ -32,7 +32,7 @@ Item {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
visible: currentRoom != null visible: currentRoom
Pane { Pane {
z: 10 z: 10
@ -52,7 +52,8 @@ Item {
ImageStatus { ImageStatus {
Layout.preferredWidth: parent.height Layout.preferredWidth: parent.height
Layout.fillHeight: true Layout.fillHeight: true
source: currentRoom != null && currentRoom.avatarUrl != "" ? "image://mxc/" + currentRoom.avatarUrl : "qrc:/asset/img/avatar.png" source: currentRoom && currentRoom.avatarUrl != "" ? "image://mxc/" + currentRoom.avatarUrl : null
displayText: currentRoom ? currentRoom.displayName : ""
} }
ColumnLayout { ColumnLayout {
@ -61,7 +62,7 @@ Item {
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: currentRoom != null ? currentRoom.displayName : "" text: currentRoom ? currentRoom.displayName : ""
font.pointSize: 16 font.pointSize: 16
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
@ -69,7 +70,7 @@ Item {
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: currentRoom != null ? currentRoom.topic : "" text: currentRoom ? currentRoom.topic : ""
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
@ -88,12 +89,14 @@ Item {
displayMarginEnd: 40 displayMarginEnd: 40
verticalLayoutDirection: ListView.BottomToTop verticalLayoutDirection: ListView.BottomToTop
spacing: 12 spacing: 12
model: MessageEventModel{ model: MessageEventModel{
id: messageEventModel id: messageEventModel
room: currentRoom room: currentRoom
onModelReset: currentRoom.getPreviousContent(50) onModelReset: currentRoom.getPreviousContent(50)
} }
delegate: Row { delegate: Row {
readonly property bool sentByMe: author === currentRoom.localUser readonly property bool sentByMe: author === currentRoom.localUser
@ -102,18 +105,28 @@ Item {
anchors.right: sentByMe ? parent.right : undefined anchors.right: sentByMe ? parent.right : undefined
spacing: 6 spacing: 6
Image { ImageStatus {
id: avatar id: avatar
width: height width: height
height: 40 height: 40
mipmap: true round: false
visible: !sentByMe visible: !sentByMe
source: author.avatarUrl != "" ? "image://mxc/" + author.avatarUrl : "qrc:/asset/img/avatar.png" source: author.avatarUrl != "" ? "image://mxc/" + author.avatarUrl : null
displayText: author.displayName
MouseArea {
id: mouseArea
anchors.fill: parent
ToolTip.visible: pressed
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: author.displayName
}
} }
Rectangle { Rectangle {
width: Math.min(messageText.implicitWidth + 24, width: Math.min(messageText.implicitWidth + 24,
messageListView.width - (!sentByMe ? avatar.width + messageRow.spacing : 0)) messageListView.width - (!sentByMe ? avatar.width + messageRow.spacing : 0))
height: messageText.implicitHeight + 24 height: messageText.implicitHeight + 24
color: sentByMe ? "lightgrey" : Material.accent color: sentByMe ? "lightgrey" : Material.accent
@ -121,6 +134,7 @@ Item {
id: messageText id: messageText
text: display text: display
color: sentByMe ? "black" : "white" color: sentByMe ? "black" : "white"
linkColor: sentByMe ? Material.accent : "white"
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.margins: 12
wrapMode: Label.Wrap wrapMode: Label.Wrap

View File

@ -1,6 +1,6 @@
import QtQuick 2.11 import QtQuick 2.11
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.4 import QtQuick.Layouts 1.11
import QtQuick.Controls.Material 2.4 import QtQuick.Controls.Material 2.4
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import Qt.labs.settings 1.0 import Qt.labs.settings 1.0
@ -28,16 +28,16 @@ ApplicationWindow {
property alias token: matriqueController.token property alias token: matriqueController.token
} }
// Platform.SystemTrayIcon { // Platform.SystemTrayIcon {
// visible: true // visible: true
// iconSource: "qrc:/asset/img/icon.png" // iconSource: "qrc:/asset/img/icon.png"
// onActivated: { // onActivated: {
// window.show() // window.show()
// window.raise() // window.raise()
// window.requestActivate() // window.requestActivate()
// } // }
// } // }
Controller { Controller {
id: matriqueController id: matriqueController
@ -118,7 +118,8 @@ ApplicationWindow {
anchors.fill: parent anchors.fill: parent
anchors.margins: 15 anchors.margins: 15
source: matriqueController.connection.localUser != null ? "image://mxc/" + matriqueController.connection.localUser.avatarUrl : "qrc:/asset/img/avatar.png" source: matriqueController.connection.localUser && matriqueController.connection.localUser.avatarUrl ? "image://mxc/" + matriqueController.connection.localUser.avatarUrl : ""
displayText: matriqueController.connection.localUser && matriqueController.connection.localUser.displayText ? matriqueController.connection.localUser.displayText : "N"
opaqueBackground: false opaqueBackground: false
} }
@ -158,7 +159,7 @@ ApplicationWindow {
imageProvider.connection = matriqueController.connection imageProvider.connection = matriqueController.connection
console.log(matriqueController.homeserver, matriqueController.userID, matriqueController.token) console.log(matriqueController.homeserver, matriqueController.userID, matriqueController.token)
if (matriqueController.userID != "" && matriqueController.token != "") { if (matriqueController.userID && matriqueController.token) {
console.log("Perform auto-login."); console.log("Perform auto-login.");
matriqueController.login(); matriqueController.login();
} else { } else {