Code reformatting && tooltip.

square-messages
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 <QQmlApplicationEngine>
#include <QNetworkProxy>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "room.h"
#include "matrix/controller.h"
#include "matrix/roomlistmodel.h"
#include "matrix/imageprovider.h"
#include "matrix/messageeventmodel.h"
#include "matrix/roomlistmodel.h"
#include "room.h"
using namespace QMatrixClient;
int main(int argc, char *argv[])
{
int main(int argc, char *argv[]) {
#if defined(Q_OS_WIN)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
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);
// Enable this if you need proxy.
// QNetworkProxy proxy;
// proxy.setType(QNetworkProxy::HttpProxy);
// proxy.setHostName("localhost");
// proxy.setPort(1082);
// QNetworkProxy::setApplicationProxy(proxy);
qmlRegisterType<Room>(); qRegisterMetaType<Room*> ("Room*");
qmlRegisterType<Room>();
qRegisterMetaType<Room *>("Room*");
qmlRegisterType<Controller>("Matrique", 0, 1, "Controller");
qmlRegisterType<RoomListModel>("Matrique", 0, 1, "RoomListModel");
qmlRegisterType<MessageEventModel>("Matrique", 0, 1, "MessageEventModel");
qRegisterMetaType<User*>("User*");
qmlRegisterType<Controller>("Matrique", 0, 1, "Controller");
qmlRegisterType<RoomListModel>("Matrique", 0, 1, "RoomListModel");
qmlRegisterType<MessageEventModel>("Matrique", 0, 1, "MessageEventModel");
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())
return -1;
if (engine.rootObjects().isEmpty()) return -1;
return app.exec();
return app.exec();
}

View File

@ -3,67 +3,71 @@
#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, 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);
}
Controller::~Controller() {
m_connection->stopSync();
}
Controller::~Controller() { m_connection->stopSync(); }
void Controller::login() {
if (!isLogin) {
qDebug() << "UserID:" << userID;
qDebug() << "Token:" << token;
if (!isLogin) {
qDebug() << "UserID:" << userID;
qDebug() << "Token:" << token;
m_connection->setHomeserver(QUrl(homeserver));
m_connection->connectWithToken(userID, 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;
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.";
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);
qDebug() << "Logging out.";
setUserID("");
setToken("");
setIsLogin(false);
}
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);
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();
qDebug() << "Syncing Matrix.";
m_connection->sync(30000);
m_connection->saveState();
}
void Controller::reconnect() {
qDebug() << "Connection lost. Reconnecting...";
m_connection->connectWithToken(userID, token, "");
qDebug() << "Connection lost. Reconnecting...";
m_connection->connectWithToken(userID, token, "");
}

View File

@ -3,99 +3,101 @@
#include <QObject>
#include "connection.h"
#include "user.h"
#include "roomlistmodel.h"
#include "user.h"
namespace QMatrixClient {
class Connection;
class Connection;
}
class Controller : public QObject
{
Q_OBJECT
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)
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();
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();
// All the Q_INVOKABLEs.
Q_INVOKABLE void login();
Q_INVOKABLE void loginWithCredentials(QString, QString, QString);
Q_INVOKABLE void logout();
// All the non-Q_INVOKABLE functions.
// All the non-Q_INVOKABLE functions.
// All the Q_PROPERTYs.
QMatrixClient::Connection* m_connection = new QMatrixClient::Connection();
QMatrixClient::Connection* getConnection() { return m_connection; }
// 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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
private:
void connected();
void resync();
void reconnect();
signals:
void connectionChanged();
void isLoginChanged();
void userIDChanged();
void tokenChanged();
void homeserverChanged();
void busyChanged();
void errorOccured();
signals:
void connectionChanged();
void isLoginChanged();
void userIDChanged();
void tokenChanged();
void homeserverChanged();
void busyChanged();
void errorOccured();
public slots:
public slots:
};
#endif // CONTROLLER_H
#endif // CONTROLLER_H

View File

@ -1,8 +1,8 @@
#include "imageprovider.h"
#include <QtCore/QWaitCondition>
#include <QtCore/QDebug>
#include <QMetaObject>
#include <QtCore/QDebug>
#include <QtCore/QWaitCondition>
#include "jobs/mediathumbnailjob.h"
@ -11,58 +11,57 @@
using QMatrixClient::MediaThumbnailJob;
ImageProvider::ImageProvider(QObject* parent)
: QQuickImageProvider(QQmlImageProviderBase::Image,
QQmlImageProviderBase::ForceAsynchronousImageLoading)
{
: QQuickImageProvider(
QQmlImageProviderBase::Image,
QQmlImageProviderBase::ForceAsynchronousImageLoading) {
#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
qRegisterMetaType<MediaThumbnailJob*>();
qRegisterMetaType<MediaThumbnailJob*>();
#endif
m_connection = new ImageProviderConnection();
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 {};
}
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 };
qDebug() << "ImageProvider::requestImage:" << mxcUri.toString();
QUrl mxcUri{id};
MediaThumbnailJob* job = nullptr;
QReadLocker locker(&m_lock);
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);
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));
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 (!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();
if (pSize != nullptr) *pSize = result.size();
return result;
return result;
}

View File

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

View File

@ -1,9 +1,6 @@
#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"
class ImageProviderConnection : public QObject
{
Q_OBJECT
class ImageProviderConnection : public QObject {
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:
explicit ImageProviderConnection(QObject* parent = nullptr);
~ImageProviderConnection();
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();
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
#endif // IMAGEPROVIDERCONNECTION_H

View File

@ -1,452 +1,379 @@
#include "messageeventmodel.h"
#include <QtCore/QSettings>
#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/roomavatarevent.h"
#include "events/roommemberevent.h"
#include "events/simplestateevents.h"
#include "connection.h"
#include "user.h"
#include "settings.h"
#include "user.h"
MessageEventModel::MessageEventModel(QObject* parent)
: QAbstractListModel(parent)
{
qmlRegisterType<QMatrixClient::FileTransferInfo>();
qRegisterMetaType<QMatrixClient::FileTransferInfo>();
: QAbstractListModel(parent) {
qmlRegisterType<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)
{
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::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);
}
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();
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;
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 };
// 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 {};
// 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);
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();
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()));
}
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;
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;
}

View File

@ -4,54 +4,55 @@
#include <QtCore/QAbstractListModel>
#include "room.h"
class MessageEventModel: public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QMatrixClient::Room* room READ getRoom WRITE setRoom NOTIFY roomChanged)
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,
};
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();
explicit MessageEventModel(QObject* parent = nullptr);
~MessageEventModel();
QMatrixClient::Room* getRoom() { return m_currentRoom; }
void setRoom(QMatrixClient::Room* room);
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;
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 slots:
void refreshEvent(const QString& eventId);
private:
QMatrixClient::Room* m_currentRoom = nullptr;
QString lastReadEventId;
int nextNewerRow = -1;
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);
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();
signals:
void roomChanged();
};
#endif // MESSAGEEVENTMODEL_H
#endif // MESSAGEEVENTMODEL_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,22 @@
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtGraphicalEffects 1.0
import QtQuick.Controls.Material 2.4
Item {
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
Rectangle {
width: item.width
height: item.width
radius: item.width / 2
radius: round ? item.width / 2 : 0
color: "white"
visible: opaqueBackground
}
@ -20,6 +25,8 @@ Item {
id: avatar
width: item.width
height: item.width
visible: showImage
source: item.source
mipmap: true
layer.enabled: true
@ -35,9 +42,42 @@ Item {
anchors.centerIn: parent
width: 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.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.3
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import QtQuick.Controls.Material 2.4
Item {
property alias icon: iconText.text
@ -17,5 +17,5 @@ Item {
color: item.color
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.3
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import QtQuick.Controls.Material 2.4
import QtGraphicalEffects 1.0
import Matrique 0.1
import "qrc:/qml/component"
@ -16,7 +16,7 @@ Item {
background: Item {
anchors.fill: parent
visible: currentRoom == null
visible: !currentRoom
Pane {
anchors.fill: parent
}
@ -32,7 +32,7 @@ Item {
anchors.fill: parent
spacing: 0
visible: currentRoom != null
visible: currentRoom
Pane {
z: 10
@ -52,7 +52,8 @@ Item {
ImageStatus {
Layout.preferredWidth: parent.height
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 {
@ -61,7 +62,7 @@ Item {
Label {
Layout.fillWidth: true
text: currentRoom != null ? currentRoom.displayName : ""
text: currentRoom ? currentRoom.displayName : ""
font.pointSize: 16
elide: Text.ElideRight
wrapMode: Text.NoWrap
@ -69,7 +70,7 @@ Item {
Label {
Layout.fillWidth: true
text: currentRoom != null ? currentRoom.topic : ""
text: currentRoom ? currentRoom.topic : ""
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
@ -88,12 +89,14 @@ Item {
displayMarginEnd: 40
verticalLayoutDirection: ListView.BottomToTop
spacing: 12
model: MessageEventModel{
id: messageEventModel
room: currentRoom
onModelReset: currentRoom.getPreviousContent(50)
}
delegate: Row {
readonly property bool sentByMe: author === currentRoom.localUser
@ -102,18 +105,28 @@ Item {
anchors.right: sentByMe ? parent.right : undefined
spacing: 6
Image {
ImageStatus {
id: avatar
width: height
height: 40
mipmap: true
round: false
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 {
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
color: sentByMe ? "lightgrey" : Material.accent
@ -121,6 +134,7 @@ Item {
id: messageText
text: display
color: sentByMe ? "black" : "white"
linkColor: sentByMe ? Material.accent : "white"
anchors.fill: parent
anchors.margins: 12
wrapMode: Label.Wrap

View File

@ -1,6 +1,6 @@
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.4
import QtQuick.Layouts 1.11
import QtQuick.Controls.Material 2.4
import QtGraphicalEffects 1.0
import Qt.labs.settings 1.0
@ -28,16 +28,16 @@ ApplicationWindow {
property alias token: matriqueController.token
}
// Platform.SystemTrayIcon {
// visible: true
// iconSource: "qrc:/asset/img/icon.png"
// Platform.SystemTrayIcon {
// visible: true
// iconSource: "qrc:/asset/img/icon.png"
// onActivated: {
// window.show()
// window.raise()
// window.requestActivate()
// }
// }
// onActivated: {
// window.show()
// window.raise()
// window.requestActivate()
// }
// }
Controller {
id: matriqueController
@ -118,7 +118,8 @@ ApplicationWindow {
anchors.fill: parent
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
}
@ -158,7 +159,7 @@ ApplicationWindow {
imageProvider.connection = matriqueController.connection
console.log(matriqueController.homeserver, matriqueController.userID, matriqueController.token)
if (matriqueController.userID != "" && matriqueController.token != "") {
if (matriqueController.userID && matriqueController.token) {
console.log("Perform auto-login.");
matriqueController.login();
} else {