Apply Clang Format

This commit is contained in:
Carl Schwan
2020-11-02 16:11:24 +01:00
parent 9a2b7c0c83
commit bea870ad75
35 changed files with 5053 additions and 6370 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
build build
.clang-format
.DS_Store .DS_Store

View File

@@ -7,6 +7,8 @@ find_package(ECM ${REQUIRED_KF5_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${ECM_MODULE_PATH}) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${ECM_MODULE_PATH})
include(KDEClangFormat)
set(IDENTIFIER "org.kde.neochat") set(IDENTIFIER "org.kde.neochat")
set(COPYRIGHT "Copyright © 2018-2019 bhat@encom.eu.org, 2020 KDE Community") set(COPYRIGHT "Copyright © 2018-2019 bhat@encom.eu.org, 2020 KDE Community")
@@ -255,3 +257,7 @@ if(LINUX)
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons DESTINATION ${CMAKE_INSTALL_DATADIR}/icons
) )
endif(LINUX) endif(LINUX)
# add clang-format target for all our real source files
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})

View File

@@ -8,9 +8,12 @@
#include "room.h" #include "room.h"
AccountListModel::AccountListModel(QObject *parent) AccountListModel::AccountListModel(QObject *parent)
: QAbstractListModel(parent) {} : QAbstractListModel(parent)
{
}
void AccountListModel::setController(Controller* value) { void AccountListModel::setController(Controller *value)
{
if (m_controller == value) { if (m_controller == value) {
return; return;
} }
@@ -21,26 +24,22 @@ void AccountListModel::setController(Controller* value) {
m_controller = value; m_controller = value;
m_connections += m_controller->connections(); m_connections += m_controller->connections();
connect(m_controller, &Controller::connectionAdded, this, connect(m_controller, &Controller::connectionAdded, this, [=](Connection *conn) {
[=](Connection* conn) {
if (!conn) { if (!conn) {
return; return;
} }
beginInsertRows(QModelIndex(), m_connections.count(), beginInsertRows(QModelIndex(), m_connections.count(), m_connections.count());
m_connections.count());
m_connections.append(conn); m_connections.append(conn);
endInsertRows(); endInsertRows();
}); });
connect(m_controller, &Controller::connectionDropped, this, connect(m_controller, &Controller::connectionDropped, this, [=](Connection *conn) {
[=](Connection* conn) {
qDebug() << "Dropping connection" << conn->userId(); qDebug() << "Dropping connection" << conn->userId();
if (!conn) { if (!conn) {
qDebug() << "Trying to remove null connection"; qDebug() << "Trying to remove null connection";
return; return;
} }
conn->disconnect(this); conn->disconnect(this);
const auto it = const auto it = std::find(m_connections.begin(), m_connections.end(), conn);
std::find(m_connections.begin(), m_connections.end(), conn);
if (it == m_connections.end()) if (it == m_connections.end())
return; // Already deleted, nothing to do return; // Already deleted, nothing to do
const int row = it - m_connections.begin(); const int row = it - m_connections.begin();
@@ -51,7 +50,8 @@ void AccountListModel::setController(Controller* value) {
emit controllerChanged(); emit controllerChanged();
} }
QVariant AccountListModel::data(const QModelIndex& index, int role) const { QVariant AccountListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) { if (!index.isValid()) {
return {}; return {};
} }
@@ -74,7 +74,8 @@ QVariant AccountListModel::data(const QModelIndex& index, int role) const {
return {}; return {};
} }
int AccountListModel::rowCount(const QModelIndex& parent) const { int AccountListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) { if (parent.isValid()) {
return 0; return 0;
} }
@@ -82,7 +83,8 @@ int AccountListModel::rowCount(const QModelIndex& parent) const {
return m_connections.count(); return m_connections.count();
} }
QHash<int, QByteArray> AccountListModel::roleNames() const { QHash<int, QByteArray> AccountListModel::roleNames() const
{
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[UserRole] = "user"; roles[UserRole] = "user";

View File

@@ -11,10 +11,10 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QObject> #include <QObject>
class AccountListModel : public QAbstractListModel { class AccountListModel : public QAbstractListModel
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(Controller* controller READ controller WRITE setController NOTIFY Q_PROPERTY(Controller *controller READ controller WRITE setController NOTIFY controllerChanged)
controllerChanged)
public: public:
enum EventRoles { UserRole = Qt::UserRole + 1, ConnectionRole }; enum EventRoles { UserRole = Qt::UserRole + 1, ConnectionRole };
@@ -25,7 +25,10 @@ class AccountListModel : public QAbstractListModel {
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
Controller* controller() const { return m_controller; } Controller *controller() const
{
return m_controller;
}
void setController(Controller *value); void setController(Controller *value);
private: private:

View File

@@ -37,45 +37,44 @@
#include "spectraluser.h" #include "spectraluser.h"
#include "utils.h" #include "utils.h"
Controller::Controller(QObject* parent) : QObject(parent) { Controller::Controller(QObject *parent)
: QObject(parent)
{
QApplication::setQuitOnLastWindowClosed(false); QApplication::setQuitOnLastWindowClosed(false);
Connection::setRoomType<SpectralRoom>(); Connection::setRoomType<SpectralRoom>();
Connection::setUserType<SpectralUser>(); Connection::setUserType<SpectralUser>();
connect(&m_ncm, &QNetworkConfigurationManager::onlineStateChanged, this, connect(&m_ncm, &QNetworkConfigurationManager::onlineStateChanged, this, &Controller::isOnlineChanged);
&Controller::isOnlineChanged);
QTimer::singleShot(0, this, [=] { invokeLogin(); }); QTimer::singleShot(0, this, [=] {
invokeLogin();
});
} }
Controller::~Controller() { Controller::~Controller()
{
for (auto c : m_connections) { for (auto c : m_connections) {
c->stopSync(); c->stopSync();
c->saveState(); c->saveState();
} }
} }
inline QString accessTokenFileName(const AccountSettings& account) { inline QString accessTokenFileName(const AccountSettings &account)
{
QString fileName = account.userId(); QString fileName = account.userId();
fileName.replace(':', '_'); fileName.replace(':', '_');
return QStandardPaths::writableLocation( return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + '/' + fileName;
QStandardPaths::AppLocalDataLocation) +
'/' + fileName;
} }
void Controller::loginWithCredentials(QString serverAddr, void Controller::loginWithCredentials(QString serverAddr, QString user, QString pass, QString deviceName)
QString user, {
QString pass,
QString deviceName) {
if (user.isEmpty() || pass.isEmpty()) { if (user.isEmpty() || pass.isEmpty()) {
return; return;
} }
if (deviceName.isEmpty()) { if (deviceName.isEmpty()) {
deviceName = "Spectral " + QSysInfo::machineHostName() + " " + deviceName = "Spectral " + QSysInfo::machineHostName() + " " + QSysInfo::productType() + " " + QSysInfo::productVersion() + " " + QSysInfo::currentCpuArchitecture();
QSysInfo::productType() + " " + QSysInfo::productVersion() +
" " + QSysInfo::currentCpuArchitecture();
} }
QUrl serverUrl(serverAddr); QUrl serverUrl(serverAddr);
@@ -99,8 +98,7 @@ void Controller::loginWithCredentials(QString serverAddr,
addConnection(conn); addConnection(conn);
setConnection(conn); setConnection(conn);
}); });
connect(conn, &Connection::networkError, connect(conn, &Connection::networkError, [=](QString error, QString, int, int) {
[=](QString error, QString, int, int) {
emit errorOccured("Network Error", error); emit errorOccured("Network Error", error);
}); });
connect(conn, &Connection::loginError, [=](QString error, QString) { connect(conn, &Connection::loginError, [=](QString error, QString) {
@@ -108,10 +106,8 @@ void Controller::loginWithCredentials(QString serverAddr,
}); });
} }
void Controller::loginWithAccessToken(QString serverAddr, void Controller::loginWithAccessToken(QString serverAddr, QString user, QString token, QString deviceName)
QString user, {
QString token,
QString deviceName) {
if (user.isEmpty() || token.isEmpty()) { if (user.isEmpty() || token.isEmpty()) {
return; return;
} }
@@ -136,14 +132,14 @@ void Controller::loginWithAccessToken(QString serverAddr,
addConnection(conn); addConnection(conn);
setConnection(conn); setConnection(conn);
}); });
connect(conn, &Connection::networkError, connect(conn, &Connection::networkError, [=](QString error, QString, int, int) {
[=](QString error, QString, int, int) {
emit errorOccured("Network Error", error); emit errorOccured("Network Error", error);
}); });
conn->connectWithToken(user, token, deviceName); conn->connectWithToken(user, token, deviceName);
} }
void Controller::logout(Connection* conn) { void Controller::logout(Connection *conn)
{
if (!conn) { if (!conn) {
qCritical() << "Attempt to logout null connection"; qCritical() << "Attempt to logout null connection";
return; return;
@@ -156,8 +152,7 @@ void Controller::logout(Connection* conn) {
job.setAutoDelete(true); job.setAutoDelete(true);
job.setKey(conn->userId()); job.setKey(conn->userId());
QEventLoop loop; QEventLoop loop;
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
&QEventLoop::quit);
job.start(); job.start();
loop.exec(); loop.exec();
@@ -174,7 +169,8 @@ void Controller::logout(Connection* conn) {
}); });
} }
void Controller::addConnection(Connection* c) { void Controller::addConnection(Connection *c)
{
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection"); Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
m_connections += c; m_connections += c;
@@ -189,9 +185,10 @@ void Controller::addConnection(Connection* c) {
c->sync(30000); c->sync(30000);
c->saveState(); c->saveState();
}); });
connect(c, &Connection::loggedOut, this, [=] { dropConnection(c); }); connect(c, &Connection::loggedOut, this, [=] {
connect(&m_ncm, &QNetworkConfigurationManager::onlineStateChanged, dropConnection(c);
[=](bool status) { });
connect(&m_ncm, &QNetworkConfigurationManager::onlineStateChanged, [=](bool status) {
if (!status) { if (!status) {
return; return;
} }
@@ -209,7 +206,8 @@ void Controller::addConnection(Connection* c) {
emit connectionAdded(c); emit connectionAdded(c);
} }
void Controller::dropConnection(Connection* c) { void Controller::dropConnection(Connection *c)
{
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection"); Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
m_connections.removeOne(c); m_connections.removeOne(c);
@@ -217,7 +215,8 @@ void Controller::dropConnection(Connection* c) {
c->deleteLater(); c->deleteLater();
} }
void Controller::invokeLogin() { void Controller::invokeLogin()
{
using namespace Quotient; using namespace Quotient;
const auto accounts = SettingsGroup("Accounts").childGroups(); const auto accounts = SettingsGroup("Accounts").childGroups();
for (const auto &accountId : accounts) { for (const auto &accountId : accounts) {
@@ -235,8 +234,7 @@ void Controller::invokeLogin() {
emit errorOccured("Login Failed", error); emit errorOccured("Login Failed", error);
logout(c); logout(c);
}); });
connect(c, &Connection::networkError, connect(c, &Connection::networkError, [=](QString error, QString, int, int) {
[=](QString error, QString, int, int) {
emit errorOccured("Network Error", error); emit errorOccured("Network Error", error);
}); });
c->connectWithToken(account.userId(), accessToken, account.deviceId()); c->connectWithToken(account.userId(), accessToken, account.deviceId());
@@ -250,32 +248,28 @@ void Controller::invokeLogin() {
emit initiated(); emit initiated();
} }
QByteArray Controller::loadAccessTokenFromFile(const AccountSettings& account) { QByteArray Controller::loadAccessTokenFromFile(const AccountSettings &account)
{
QFile accountTokenFile {accessTokenFileName(account)}; QFile accountTokenFile {accessTokenFileName(account)};
if (accountTokenFile.open(QFile::ReadOnly)) { if (accountTokenFile.open(QFile::ReadOnly)) {
if (accountTokenFile.size() < 1024) if (accountTokenFile.size() < 1024)
return accountTokenFile.readAll(); return accountTokenFile.readAll();
qWarning() << "File" << accountTokenFile.fileName() << "is" qWarning() << "File" << accountTokenFile.fileName() << "is" << accountTokenFile.size() << "bytes long - too long for a token, ignoring it.";
<< accountTokenFile.size()
<< "bytes long - too long for a token, ignoring it.";
} }
qWarning() << "Could not open access token file" qWarning() << "Could not open access token file" << accountTokenFile.fileName();
<< accountTokenFile.fileName();
return {}; return {};
} }
QByteArray Controller::loadAccessTokenFromKeyChain( QByteArray Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
const AccountSettings& account) { {
qDebug() << "Read the access token from the keychain for " qDebug() << "Read the access token from the keychain for " << account.userId();
<< account.userId();
QKeychain::ReadPasswordJob job(qAppName()); QKeychain::ReadPasswordJob job(qAppName());
job.setAutoDelete(false); job.setAutoDelete(false);
job.setKey(account.userId()); job.setKey(account.userId());
QEventLoop loop; QEventLoop loop;
QKeychain::ReadPasswordJob::connect(&job, &QKeychain::Job::finished, &loop, QKeychain::ReadPasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
&QEventLoop::quit);
job.start(); job.start();
loop.exec(); loop.exec();
@@ -283,14 +277,12 @@ QByteArray Controller::loadAccessTokenFromKeyChain(
return job.binaryData(); return job.binaryData();
} }
qWarning() << "Could not read the access token from the keychain: " qWarning() << "Could not read the access token from the keychain: " << qPrintable(job.errorString());
<< qPrintable(job.errorString());
// no access token from the keychain, try token file // no access token from the keychain, try token file
auto accessToken = loadAccessTokenFromFile(account); auto accessToken = loadAccessTokenFromFile(account);
if (job.error() == QKeychain::Error::EntryNotFound) { if (job.error() == QKeychain::Error::EntryNotFound) {
if (!accessToken.isEmpty()) { if (!accessToken.isEmpty()) {
qDebug() << "Migrating the access token from file to the keychain for " qDebug() << "Migrating the access token from file to the keychain for " << account.userId();
<< account.userId();
bool removed = false; bool removed = false;
bool saved = saveAccessTokenToKeyChain(account, accessToken); bool saved = saveAccessTokenToKeyChain(account, accessToken);
if (saved) { if (saved) {
@@ -307,15 +299,14 @@ QByteArray Controller::loadAccessTokenFromKeyChain(
return accessToken; return accessToken;
} }
bool Controller::saveAccessTokenToFile(const AccountSettings& account, bool Controller::saveAccessTokenToFile(const AccountSettings &account, const QByteArray &accessToken)
const QByteArray& accessToken) { {
// (Re-)Make a dedicated file for access_token. // (Re-)Make a dedicated file for access_token.
QFile accountTokenFile {accessTokenFileName(account)}; QFile accountTokenFile {accessTokenFileName(account)};
accountTokenFile.remove(); // Just in case accountTokenFile.remove(); // Just in case
auto fileDir = QFileInfo(accountTokenFile).dir(); auto fileDir = QFileInfo(accountTokenFile).dir();
if (!((fileDir.exists() || fileDir.mkpath(".")) && if (!((fileDir.exists() || fileDir.mkpath(".")) && accountTokenFile.open(QFile::WriteOnly))) {
accountTokenFile.open(QFile::WriteOnly))) {
emit errorOccured("I/O Denied", "Cannot save access token."); emit errorOccured("I/O Denied", "Cannot save access token.");
} else { } else {
accountTokenFile.write(accessToken); accountTokenFile.write(accessToken);
@@ -324,29 +315,28 @@ bool Controller::saveAccessTokenToFile(const AccountSettings& account,
return false; return false;
} }
bool Controller::saveAccessTokenToKeyChain(const AccountSettings& account, bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
const QByteArray& accessToken) { {
qDebug() << "Save the access token to the keychain for " << account.userId(); qDebug() << "Save the access token to the keychain for " << account.userId();
QKeychain::WritePasswordJob job(qAppName()); QKeychain::WritePasswordJob job(qAppName());
job.setAutoDelete(false); job.setAutoDelete(false);
job.setKey(account.userId()); job.setKey(account.userId());
job.setBinaryData(accessToken); job.setBinaryData(accessToken);
QEventLoop loop; QEventLoop loop;
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
&QEventLoop::quit);
job.start(); job.start();
loop.exec(); loop.exec();
if (job.error()) { if (job.error()) {
qWarning() << "Could not save access token to the keychain: " qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString());
<< qPrintable(job.errorString());
return saveAccessTokenToFile(account, accessToken); return saveAccessTokenToFile(account, accessToken);
} }
return true; return true;
} }
void Controller::joinRoom(Connection* c, const QString& alias) { void Controller::joinRoom(Connection *c, const QString &alias)
{
if (!alias.contains(":")) if (!alias.contains(":"))
return; return;
@@ -357,32 +347,34 @@ void Controller::joinRoom(Connection* c, const QString& alias) {
}); });
} }
void Controller::createRoom(Connection* c, void Controller::createRoom(Connection *c, const QString &name, const QString &topic)
const QString& name, {
const QString& topic) { auto createRoomJob = c->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
auto createRoomJob =
c->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
createRoomJob->connect(createRoomJob, &CreateRoomJob::failure, [=] { createRoomJob->connect(createRoomJob, &CreateRoomJob::failure, [=] {
emit errorOccured("Create Room Failed", createRoomJob->errorString()); emit errorOccured("Create Room Failed", createRoomJob->errorString());
}); });
} }
void Controller::createDirectChat(Connection* c, const QString& userID) { void Controller::createDirectChat(Connection *c, const QString &userID)
{
auto createRoomJob = c->createDirectChat(userID); auto createRoomJob = c->createDirectChat(userID);
createRoomJob->connect(createRoomJob, &CreateRoomJob::failure, [=] { createRoomJob->connect(createRoomJob, &CreateRoomJob::failure, [=] {
emit errorOccured("Create Direct Chat Failed", emit errorOccured("Create Direct Chat Failed", createRoomJob->errorString());
createRoomJob->errorString());
}); });
} }
void Controller::playAudio(QUrl localFile) { void Controller::playAudio(QUrl localFile)
{
auto player = new QMediaPlayer; auto player = new QMediaPlayer;
player->setMedia(localFile); player->setMedia(localFile);
player->play(); player->play();
connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); }); connect(player, &QMediaPlayer::stateChanged, [=] {
player->deleteLater();
});
} }
void Controller::changeAvatar(Connection* conn, QUrl localFile) { void Controller::changeAvatar(Connection *conn, QUrl localFile)
{
auto job = conn->uploadFile(localFile.toLocalFile()); auto job = conn->uploadFile(localFile.toLocalFile());
if (isJobRunning(job)) { if (isJobRunning(job)) {
connect(job, &BaseJob::success, this, [conn, job] { connect(job, &BaseJob::success, this, [conn, job] {
@@ -391,7 +383,8 @@ void Controller::changeAvatar(Connection* conn, QUrl localFile) {
} }
} }
void Controller::markAllMessagesAsRead(Connection* conn) { void Controller::markAllMessagesAsRead(Connection *conn)
{
for (auto room : conn->allRooms()) { for (auto room : conn->allRooms()) {
room->markAllMessagesAsRead(); room->markAllMessagesAsRead();
} }

View File

@@ -22,14 +22,12 @@
using namespace Quotient; using namespace Quotient;
class Controller : public QObject { class Controller : public QObject
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(int accountCount READ accountCount NOTIFY connectionAdded NOTIFY Q_PROPERTY(int accountCount READ accountCount NOTIFY connectionAdded NOTIFY connectionDropped)
connectionDropped) Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
Q_PROPERTY(Connection* connection READ connection WRITE setConnection NOTIFY
connectionChanged)
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged) Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged) Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
@@ -40,19 +38,27 @@ class Controller : public QObject {
Q_INVOKABLE void loginWithCredentials(QString, QString, QString, QString); Q_INVOKABLE void loginWithCredentials(QString, QString, QString, QString);
Q_INVOKABLE void loginWithAccessToken(QString, QString, QString, QString); Q_INVOKABLE void loginWithAccessToken(QString, QString, QString, QString);
QVector<Connection*> connections() const { return m_connections; } QVector<Connection *> connections() const
{
return m_connections;
}
// All the non-Q_INVOKABLE functions. // All the non-Q_INVOKABLE functions.
void addConnection(Connection *c); void addConnection(Connection *c);
void dropConnection(Connection *c); void dropConnection(Connection *c);
// All the Q_PROPERTYs. // All the Q_PROPERTYs.
int accountCount() { return m_connections.count(); } int accountCount()
{
return m_connections.count();
}
bool quitOnLastWindowClosed() const { bool quitOnLastWindowClosed() const
{
return QApplication::quitOnLastWindowClosed(); return QApplication::quitOnLastWindowClosed();
} }
void setQuitOnLastWindowClosed(bool value) { void setQuitOnLastWindowClosed(bool value)
{
if (quitOnLastWindowClosed() != value) { if (quitOnLastWindowClosed() != value) {
QApplication::setQuitOnLastWindowClosed(value); QApplication::setQuitOnLastWindowClosed(value);
@@ -60,10 +66,17 @@ class Controller : public QObject {
} }
} }
bool isOnline() const { return m_ncm.isOnline(); } bool isOnline() const
{
return m_ncm.isOnline();
}
bool busy() const { return m_busy; } bool busy() const
void setBusy(bool busy) { {
return m_busy;
}
void setBusy(bool busy)
{
if (m_busy == busy) { if (m_busy == busy) {
return; return;
} }
@@ -71,14 +84,16 @@ class Controller : public QObject {
emit busyChanged(); emit busyChanged();
} }
Connection* connection() const { Connection *connection() const
{
if (m_connection.isNull()) if (m_connection.isNull())
return nullptr; return nullptr;
return m_connection; return m_connection;
} }
void setConnection(Connection* conn) { void setConnection(Connection *conn)
{
if (conn == m_connection) if (conn == m_connection)
return; return;
m_connection = conn; m_connection = conn;
@@ -94,10 +109,8 @@ class Controller : public QObject {
QByteArray loadAccessTokenFromFile(const AccountSettings &account); QByteArray loadAccessTokenFromFile(const AccountSettings &account);
QByteArray loadAccessTokenFromKeyChain(const AccountSettings &account); QByteArray loadAccessTokenFromKeyChain(const AccountSettings &account);
bool saveAccessTokenToFile(const AccountSettings& account, bool saveAccessTokenToFile(const AccountSettings &account, const QByteArray &accessToken);
const QByteArray& accessToken); bool saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken);
bool saveAccessTokenToKeyChain(const AccountSettings& account,
const QByteArray& accessToken);
void loadSettings(); void loadSettings();
void saveSettings() const; void saveSettings() const;

File diff suppressed because it is too large Load Diff

View File

@@ -12,16 +12,24 @@
#include <QVector> #include <QVector>
struct Emoji { struct Emoji {
Emoji(const QString& u, const QString& s) : unicode(u), shortname(s) {} Emoji(const QString &u, const QString &s)
Emoji() {} : unicode(u)
, shortname(s)
{
}
Emoji()
{
}
friend QDataStream& operator<<(QDataStream& arch, const Emoji& object) { friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
{
arch << object.unicode; arch << object.unicode;
arch << object.shortname; arch << object.shortname;
return arch; return arch;
} }
friend QDataStream& operator>>(QDataStream& arch, Emoji& object) { friend QDataStream &operator>>(QDataStream &arch, Emoji &object)
{
arch >> object.unicode; arch >> object.unicode;
arch >> object.shortname; arch >> object.shortname;
return arch; return arch;
@@ -37,7 +45,8 @@ struct Emoji {
Q_DECLARE_METATYPE(Emoji) Q_DECLARE_METATYPE(Emoji)
class EmojiModel : public QObject { class EmojiModel : public QObject
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged) Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
@@ -53,7 +62,10 @@ class EmojiModel : public QObject {
public: public:
explicit EmojiModel(QObject *parent = nullptr) explicit EmojiModel(QObject *parent = nullptr)
: QObject(parent), m_settings(new QSettings()) {} : QObject(parent)
, m_settings(new QSettings())
{
}
Q_INVOKABLE QVariantList history(); Q_INVOKABLE QVariantList history();
Q_INVOKABLE QVariantList filterModel(const QString &filter); Q_INVOKABLE QVariantList filterModel(const QString &filter);

View File

@@ -12,20 +12,24 @@
#include <QtDebug> #include <QtDebug>
ImageClipboard::ImageClipboard(QObject *parent) ImageClipboard::ImageClipboard(QObject *parent)
: QObject(parent), m_clipboard(QGuiApplication::clipboard()) { : QObject(parent)
connect(m_clipboard, &QClipboard::changed, this, , m_clipboard(QGuiApplication::clipboard())
&ImageClipboard::imageChanged); {
connect(m_clipboard, &QClipboard::changed, this, &ImageClipboard::imageChanged);
} }
bool ImageClipboard::hasImage() const { bool ImageClipboard::hasImage() const
{
return !image().isNull(); return !image().isNull();
} }
QImage ImageClipboard::image() const { QImage ImageClipboard::image() const
{
return m_clipboard->image(); return m_clipboard->image();
} }
bool ImageClipboard::saveImage(const QUrl& localPath) { bool ImageClipboard::saveImage(const QUrl &localPath)
{
if (!localPath.isLocalFile()) if (!localPath.isLocalFile())
return false; return false;

View File

@@ -10,7 +10,8 @@
#include <QImage> #include <QImage>
#include <QObject> #include <QObject>
class ImageClipboard : public QObject { class ImageClipboard : public QObject
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool hasImage READ hasImage NOTIFY imageChanged) Q_PROPERTY(bool hasImage READ hasImage NOTIFY imageChanged)
Q_PROPERTY(QImage image READ image NOTIFY imageChanged) Q_PROPERTY(QImage image READ image NOTIFY imageChanged)

View File

@@ -30,7 +30,8 @@
using namespace Quotient; using namespace Quotient;
int main(int argc, char* argv[]) { int main(int argc, char *argv[])
{
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QNetworkProxyFactory::setUseSystemConfiguration(true); QNetworkProxyFactory::setUseSystemConfiguration(true);
@@ -48,15 +49,12 @@ int main(int argc, char* argv[]) {
qmlRegisterType<UserListModel>("Spectral", 0, 1, "UserListModel"); qmlRegisterType<UserListModel>("Spectral", 0, 1, "UserListModel");
qmlRegisterType<MessageEventModel>("Spectral", 0, 1, "MessageEventModel"); qmlRegisterType<MessageEventModel>("Spectral", 0, 1, "MessageEventModel");
qmlRegisterType<PublicRoomListModel>("Spectral", 0, 1, "PublicRoomListModel"); qmlRegisterType<PublicRoomListModel>("Spectral", 0, 1, "PublicRoomListModel");
qmlRegisterType<UserDirectoryListModel>("Spectral", 0, 1, qmlRegisterType<UserDirectoryListModel>("Spectral", 0, 1, "UserDirectoryListModel");
"UserDirectoryListModel");
qmlRegisterType<EmojiModel>("Spectral", 0, 1, "EmojiModel"); qmlRegisterType<EmojiModel>("Spectral", 0, 1, "EmojiModel");
qmlRegisterType<NotificationsManager>("Spectral", 0, 1, qmlRegisterType<NotificationsManager>("Spectral", 0, 1, "NotificationsManager");
"NotificationsManager");
qmlRegisterType<TrayIcon>("Spectral", 0, 1, "TrayIcon"); qmlRegisterType<TrayIcon>("Spectral", 0, 1, "TrayIcon");
qmlRegisterType<ImageClipboard>("Spectral", 0, 1, "ImageClipboard"); qmlRegisterType<ImageClipboard>("Spectral", 0, 1, "ImageClipboard");
qmlRegisterUncreatableType<RoomMessageEvent>("Spectral", 0, 1, qmlRegisterUncreatableType<RoomMessageEvent>("Spectral", 0, 1, "RoomMessageEvent", "ENUM");
"RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM"); qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM");
qmlRegisterUncreatableType<UserType>("Spectral", 0, 1, "UserType", "ENUM"); qmlRegisterUncreatableType<UserType>("Spectral", 0, 1, "UserType", "ENUM");
@@ -76,8 +74,7 @@ int main(int argc, char* argv[]) {
engine.addImportPath("qrc:/imports"); engine.addImportPath("qrc:/imports");
MatrixImageProvider *matrixImageProvider = new MatrixImageProvider(); MatrixImageProvider *matrixImageProvider = new MatrixImageProvider();
engine.rootContext()->setContextProperty("imageProvider", engine.rootContext()->setContextProperty("imageProvider", matrixImageProvider);
matrixImageProvider);
engine.addImageProvider(QLatin1String("mxc"), matrixImageProvider); engine.addImageProvider(QLatin1String("mxc"), matrixImageProvider);
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));

View File

@@ -14,27 +14,20 @@
using Quotient::BaseJob; using Quotient::BaseJob;
ThumbnailResponse::ThumbnailResponse(Quotient::Connection* c, ThumbnailResponse::ThumbnailResponse(Quotient::Connection *c, QString id, const QSize &size)
QString id, : c(c)
const QSize& size) , mediaId(std::move(id))
: c(c), , requestedSize(size)
mediaId(std::move(id)), , localFile(QStringLiteral("%1/image_provider/%2-%3x%4.png").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), mediaId, QString::number(requestedSize.width()), QString::number(requestedSize.height())))
requestedSize(size), , errorStr("Image request hasn't started")
localFile(QStringLiteral("%1/image_provider/%2-%3x%4.png") {
.arg(QStandardPaths::writableLocation(
QStandardPaths::CacheLocation),
mediaId,
QString::number(requestedSize.width()),
QString::number(requestedSize.height()))),
errorStr("Image request hasn't started") {
if (requestedSize.isEmpty()) { if (requestedSize.isEmpty()) {
errorStr.clear(); errorStr.clear();
emit finished(); emit finished();
return; return;
} }
if (mediaId.count('/') != 1) { if (mediaId.count('/') != 1) {
errorStr = errorStr = tr("Media id '%1' doesn't follow server/mediaId pattern").arg(mediaId);
tr("Media id '%1' doesn't follow server/mediaId pattern").arg(mediaId);
emit finished(); emit finished();
return; return;
} }
@@ -54,11 +47,11 @@ ThumbnailResponse::ThumbnailResponse(Quotient::Connection* c,
// Execute a request on the main thread asynchronously // Execute a request on the main thread asynchronously
moveToThread(c->thread()); moveToThread(c->thread());
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest, QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest, Qt::QueuedConnection);
Qt::QueuedConnection);
} }
void ThumbnailResponse::startRequest() { void ThumbnailResponse::startRequest()
{
// Runs in the main thread, not QML thread // Runs in the main thread, not QML thread
Q_ASSERT(QThread::currentThread() == c->thread()); Q_ASSERT(QThread::currentThread() == c->thread());
job = c->getThumbnail(mediaId, requestedSize); job = c->getThumbnail(mediaId, requestedSize);
@@ -67,7 +60,8 @@ void ThumbnailResponse::startRequest() {
connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult); connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult);
} }
void ThumbnailResponse::prepareResult() { void ThumbnailResponse::prepareResult()
{
Q_ASSERT(QThread::currentThread() == job->thread()); Q_ASSERT(QThread::currentThread() == job->thread());
Q_ASSERT(job->error() != BaseJob::Pending); Q_ASSERT(job->error() != BaseJob::Pending);
{ {
@@ -88,15 +82,15 @@ void ThumbnailResponse::prepareResult() {
qDebug() << "ThumbnailResponse: cancelled for" << mediaId; qDebug() << "ThumbnailResponse: cancelled for" << mediaId;
} else { } else {
errorStr = job->errorString(); errorStr = job->errorString();
qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-" qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-" << errorStr;
<< errorStr;
} }
job = nullptr; job = nullptr;
} }
emit finished(); emit finished();
} }
void ThumbnailResponse::doCancel() { void ThumbnailResponse::doCancel()
{
// Runs in the main thread, not QML thread // Runs in the main thread, not QML thread
if (job) { if (job) {
Q_ASSERT(QThread::currentThread() == job->thread()); Q_ASSERT(QThread::currentThread() == job->thread());
@@ -104,23 +98,24 @@ void ThumbnailResponse::doCancel() {
} }
} }
QQuickTextureFactory* ThumbnailResponse::textureFactory() const { QQuickTextureFactory *ThumbnailResponse::textureFactory() const
{
QReadLocker _(&lock); QReadLocker _(&lock);
return QQuickTextureFactory::textureFactoryForImage(image); return QQuickTextureFactory::textureFactoryForImage(image);
} }
QString ThumbnailResponse::errorString() const { QString ThumbnailResponse::errorString() const
{
QReadLocker _(&lock); QReadLocker _(&lock);
return errorStr; return errorStr;
} }
void ThumbnailResponse::cancel() { void ThumbnailResponse::cancel()
QMetaObject::invokeMethod(this, &ThumbnailResponse::doCancel, {
Qt::QueuedConnection); QMetaObject::invokeMethod(this, &ThumbnailResponse::doCancel, Qt::QueuedConnection);
} }
QQuickImageResponse* MatrixImageProvider::requestImageResponse( QQuickImageResponse *MatrixImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
const QString& id, {
const QSize& requestedSize) {
return new ThumbnailResponse(m_connection.loadRelaxed(), id, requestedSize); return new ThumbnailResponse(m_connection.loadRelaxed(), id, requestedSize);
} }

View File

@@ -16,16 +16,16 @@
#include <QtCore/QAtomicPointer> #include <QtCore/QAtomicPointer>
#include <QtCore/QReadWriteLock> #include <QtCore/QReadWriteLock>
namespace Quotient { namespace Quotient
{
class Connection; class Connection;
} }
class ThumbnailResponse : public QQuickImageResponse { class ThumbnailResponse : public QQuickImageResponse
{
Q_OBJECT Q_OBJECT
public: public:
ThumbnailResponse(Quotient::Connection* c, ThumbnailResponse(Quotient::Connection *c, QString mediaId, const QSize &requestedSize);
QString mediaId,
const QSize& requestedSize);
~ThumbnailResponse() override = default; ~ThumbnailResponse() override = default;
private slots: private slots:
@@ -49,19 +49,21 @@ class ThumbnailResponse : public QQuickImageResponse {
void cancel() override; void cancel() override;
}; };
class MatrixImageProvider : public QObject, public QQuickAsyncImageProvider { class MatrixImageProvider : public QObject, public QQuickAsyncImageProvider
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(Quotient::Connection* connection READ connection WRITE Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
setConnection NOTIFY connectionChanged)
public: public:
explicit MatrixImageProvider() = default; explicit MatrixImageProvider() = default;
QQuickImageResponse* requestImageResponse( QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
const QString& id,
const QSize& requestedSize) override;
Quotient::Connection* connection() { return m_connection; } Quotient::Connection *connection()
void setConnection(Quotient::Connection* connection) { {
return m_connection;
}
void setConnection(Quotient::Connection *connection)
{
m_connection.storeRelaxed(connection); m_connection.storeRelaxed(connection);
emit connectionChanged(); emit connectionChanged();
} }

View File

@@ -14,7 +14,8 @@
#include "utils.h" #include "utils.h"
QHash<int, QByteArray> MessageEventModel::roleNames() const { QHash<int, QByteArray> MessageEventModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[EventTypeRole] = "eventType"; roles[EventTypeRole] = "eventType";
roles[MessageRole] = "message"; roles[MessageRole] = "message";
@@ -39,17 +40,21 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const {
} }
MessageEventModel::MessageEventModel(QObject *parent) MessageEventModel::MessageEventModel(QObject *parent)
: QAbstractListModel(parent), m_currentRoom(nullptr) { : QAbstractListModel(parent)
, m_currentRoom(nullptr)
{
using namespace Quotient; using namespace Quotient;
qmlRegisterType<FileTransferInfo>(); qmlRegisterType<FileTransferInfo>();
qRegisterMetaType<FileTransferInfo>(); qRegisterMetaType<FileTransferInfo>();
qmlRegisterUncreatableType<EventStatus>( qmlRegisterUncreatableType<EventStatus>("Spectral", 0, 1, "EventStatus", "EventStatus is not an creatable type");
"Spectral", 0, 1, "EventStatus", "EventStatus is not an creatable type");
} }
MessageEventModel::~MessageEventModel() {} MessageEventModel::~MessageEventModel()
{
}
void MessageEventModel::setRoom(SpectralRoom* room) { void MessageEventModel::setRoom(SpectralRoom *room)
{
if (room == m_currentRoom) if (room == m_currentRoom)
return; return;
@@ -63,36 +68,28 @@ void MessageEventModel::setRoom(SpectralRoom* room) {
lastReadEventId = room->readMarkerEventId(); lastReadEventId = room->readMarkerEventId();
using namespace Quotient; using namespace Quotient;
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [=](RoomEventsRange events) {
[=](RoomEventsRange events) { beginInsertRows({}, timelineBaseIndex(), timelineBaseIndex() + int(events.size()) - 1);
beginInsertRows({}, timelineBaseIndex(),
timelineBaseIndex() + int(events.size()) - 1);
}); });
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [=](RoomEventsRange events) {
[=](RoomEventsRange events) {
if (rowCount() > 0) if (rowCount() > 0)
rowBelowInserted = rowCount() - 1; // See #312 rowBelowInserted = rowCount() - 1; // See #312
beginInsertRows({}, rowCount(), beginInsertRows({}, rowCount(), rowCount() + int(events.size()) - 1);
rowCount() + int(events.size()) - 1);
}); });
connect(m_currentRoom, &Room::addedMessages, this, connect(m_currentRoom, &Room::addedMessages, this, [=](int lowest, int biggest) {
[=](int lowest, int biggest) {
endInsertRows(); endInsertRows();
if (biggest < m_currentRoom->maxTimelineIndex()) { if (biggest < m_currentRoom->maxTimelineIndex()) {
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1;
biggest + timelineBaseIndex() - 1; refreshEventRoles(rowBelowInserted, {ShowAuthorRole});
refreshEventRoles(rowBelowInserted,
{ShowAuthorRole});
} }
for (auto i = m_currentRoom->maxTimelineIndex() - biggest; for (auto i = m_currentRoom->maxTimelineIndex() - biggest; i <= m_currentRoom->maxTimelineIndex() - lowest; ++i)
i <= m_currentRoom->maxTimelineIndex() - lowest; ++i)
refreshLastUserEvents(i); refreshLastUserEvents(i);
}); });
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] {
[this] { beginInsertRows({}, 0, 0); }); beginInsertRows({}, 0, 0);
});
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows); connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this](RoomEvent *, int i) {
[this](RoomEvent*, int i) {
if (i == 0) if (i == 0)
return; // No need to move anything, just refresh return; // No need to move anything, just refresh
@@ -111,77 +108,67 @@ void MessageEventModel::setRoom(SpectralRoom* room) {
if (m_currentRoom->timelineSize() > 1) // Refresh above if (m_currentRoom->timelineSize() > 1) // Refresh above
refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole}); refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole});
if (timelineBaseIndex() > 0) // Refresh below, see #312 if (timelineBaseIndex() > 0) // Refresh below, see #312
refreshEventRoles(timelineBaseIndex() - 1, refreshEventRoles(timelineBaseIndex() - 1, {ShowAuthorRole});
{ShowAuthorRole});
}); });
connect(m_currentRoom, &Room::pendingEventChanged, this, connect(m_currentRoom, &Room::pendingEventChanged, this, &MessageEventModel::refreshRow);
&MessageEventModel::refreshRow); connect(m_currentRoom, &Room::pendingEventAboutToDiscard, this, [this](int i) {
connect(m_currentRoom, &Room::pendingEventAboutToDiscard, this, beginRemoveRows({}, i, i);
[this](int i) { beginRemoveRows({}, i, i); }); });
connect(m_currentRoom, &Room::pendingEventDiscarded, this, connect(m_currentRoom, &Room::pendingEventDiscarded, this, &MessageEventModel::endRemoveRows);
&MessageEventModel::endRemoveRows);
connect(m_currentRoom, &Room::readMarkerMoved, this, [this] { connect(m_currentRoom, &Room::readMarkerMoved, this, [this] {
refreshEventRoles( refreshEventRoles(std::exchange(lastReadEventId, m_currentRoom->readMarkerEventId()), {ReadMarkerRole});
std::exchange(lastReadEventId, m_currentRoom->readMarkerEventId()),
{ReadMarkerRole});
refreshEventRoles(lastReadEventId, {ReadMarkerRole}); refreshEventRoles(lastReadEventId, {ReadMarkerRole});
}); });
connect(m_currentRoom, &Room::replacedEvent, this, connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
[this](const RoomEvent* newEvent) { refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
refreshLastUserEvents(refreshEvent(newEvent->id()) -
timelineBaseIndex());
}); });
connect(m_currentRoom, &Room::updatedEvent, this, connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString &eventId) {
[this](const QString& eventId) {
if (eventId.isEmpty()) { // How did we get here? if (eventId.isEmpty()) { // How did we get here?
return; return;
} }
refreshEventRoles(eventId, {ReactionRole, Qt::DisplayRole}); refreshEventRoles(eventId, {ReactionRole, Qt::DisplayRole});
}); });
connect(m_currentRoom, &Room::fileTransferProgress, this, connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent);
&MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent);
connect(m_currentRoom, &Room::fileTransferCompleted, this, connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent);
&MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::fileTransferCancelled, this, &MessageEventModel::refreshEvent);
connect(m_currentRoom, &Room::fileTransferFailed, this, connect(m_currentRoom, &Room::readMarkerForUserMoved, this, [=](User *, QString fromEventId, QString toEventId) {
&MessageEventModel::refreshEvent);
connect(m_currentRoom, &Room::fileTransferCancelled, this,
&MessageEventModel::refreshEvent);
connect(m_currentRoom, &Room::readMarkerForUserMoved, this,
[=](User*, QString fromEventId, QString toEventId) {
refreshEventRoles(fromEventId, {UserMarkerRole}); refreshEventRoles(fromEventId, {UserMarkerRole});
refreshEventRoles(toEventId, {UserMarkerRole}); refreshEventRoles(toEventId, {UserMarkerRole});
}); });
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [=] {
this, [=] {
beginResetModel(); beginResetModel();
endResetModel(); endResetModel();
}); });
qDebug() << "Connected to room" << room->id() << "as" qDebug() << "Connected to room" << room->id() << "as" << room->localUser()->id();
<< room->localUser()->id();
} else } else
lastReadEventId.clear(); lastReadEventId.clear();
endResetModel(); endResetModel();
} }
int MessageEventModel::refreshEvent(const QString& eventId) { int MessageEventModel::refreshEvent(const QString &eventId)
{
return refreshEventRoles(eventId); return refreshEventRoles(eventId);
} }
void MessageEventModel::refreshRow(int row) { void MessageEventModel::refreshRow(int row)
{
refreshEventRoles(row); refreshEventRoles(row);
} }
int MessageEventModel::timelineBaseIndex() const { int MessageEventModel::timelineBaseIndex() const
{
return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0; return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0;
} }
void MessageEventModel::refreshEventRoles(int row, const QVector<int>& roles) { void MessageEventModel::refreshEventRoles(int row, const QVector<int> &roles)
{
const auto idx = index(row); const auto idx = index(row);
emit dataChanged(idx, idx, roles); emit dataChanged(idx, idx, roles);
} }
int MessageEventModel::refreshEventRoles(const QString& id, int MessageEventModel::refreshEventRoles(const QString &id, const QVector<int> &roles)
const QVector<int>& roles) { {
// On 64-bit platforms, difference_type for std containers is long long // On 64-bit platforms, difference_type for std containers is long long
// but Qt uses int throughout its interfaces; hence casting to int below. // but Qt uses int throughout its interfaces; hence casting to int below.
int row = -1; int row = -1;
@@ -195,19 +182,19 @@ int MessageEventModel::refreshEventRoles(const QString& id,
qWarning() << "Trying to refresh inexistent event:" << id; qWarning() << "Trying to refresh inexistent event:" << id;
return -1; return -1;
} }
row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
timelineBaseIndex();
} }
refreshEventRoles(row, roles); refreshEventRoles(row, roles);
return row; return row;
} }
inline bool hasValidTimestamp(const Quotient::TimelineItem& ti) { inline bool hasValidTimestamp(const Quotient::TimelineItem &ti)
{
return ti->originTimestamp().isValid(); return ti->originTimestamp().isValid();
} }
QDateTime MessageEventModel::makeMessageTimestamp( QDateTime MessageEventModel::makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const
const Quotient::Room::rev_iter_t& baseIt) const { {
const auto &timeline = m_currentRoom->messageEvents(); const auto &timeline = m_currentRoom->messageEvents();
auto ts = baseIt->event()->originTimestamp(); auto ts = baseIt->event()->originTimestamp();
if (ts.isValid()) if (ts.isValid())
@@ -228,7 +215,8 @@ QDateTime MessageEventModel::makeMessageTimestamp(
return {}; return {};
} }
QString MessageEventModel::renderDate(QDateTime timestamp) const { QString MessageEventModel::renderDate(QDateTime timestamp) const
{
auto date = timestamp.toLocalTime().date(); auto date = timestamp.toLocalTime().date();
if (date == QDate::currentDate()) if (date == QDate::currentDate())
return tr("Today"); return tr("Today");
@@ -242,16 +230,15 @@ QString MessageEventModel::renderDate(QDateTime timestamp) const {
return QLocale::system().toString(date, QLocale::ShortFormat); return QLocale::system().toString(date, QLocale::ShortFormat);
} }
void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) { void MessageEventModel::refreshLastUserEvents(int baseTimelineRow)
{
if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow) if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow)
return; return;
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin(); const auto &timelineBottom = m_currentRoom->messageEvents().rbegin();
const auto &lastSender = (*(timelineBottom + baseTimelineRow))->senderId(); const auto &lastSender = (*(timelineBottom + baseTimelineRow))->senderId();
const auto limit = timelineBottom + std::min(baseTimelineRow + 10, const auto limit = timelineBottom + std::min(baseTimelineRow + 10, m_currentRoom->timelineSize());
m_currentRoom->timelineSize()); for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0); it != limit; ++it) {
for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0);
it != limit; ++it) {
if ((*it)->senderId() == lastSender) { if ((*it)->senderId() == lastSender) {
auto idx = index(it - timelineBottom); auto idx = index(it - timelineBottom);
emit dataChanged(idx, idx); emit dataChanged(idx, idx);
@@ -259,15 +246,15 @@ void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) {
} }
} }
int MessageEventModel::rowCount(const QModelIndex& parent) const { int MessageEventModel::rowCount(const QModelIndex &parent) const
{
if (!m_currentRoom || parent.isValid()) if (!m_currentRoom || parent.isValid())
return 0; return 0;
return m_currentRoom->timelineSize(); return m_currentRoom->timelineSize();
} }
inline QVariantMap userAtEvent(SpectralUser* user, inline QVariantMap userAtEvent(SpectralUser *user, SpectralRoom *room, const RoomEvent &evt)
SpectralRoom* room, {
const RoomEvent& evt) {
return QVariantMap { return QVariantMap {
{"isLocalUser", user->id() == room->localUser()->id()}, {"isLocalUser", user->id() == room->localUser()->id()},
{"id", user->id()}, {"id", user->id()},
@@ -279,19 +266,16 @@ inline QVariantMap userAtEvent(SpectralUser* user,
}; };
} }
QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
{
const auto row = idx.row(); const auto row = idx.row();
if (!m_currentRoom || row < 0 || if (!m_currentRoom || row < 0 || row >= int(m_currentRoom->pendingEvents().size()) + m_currentRoom->timelineSize())
row >= int(m_currentRoom->pendingEvents().size()) +
m_currentRoom->timelineSize())
return {}; return {};
bool isPending = row < timelineBaseIndex(); bool isPending = row < timelineBaseIndex();
const auto timelineIt = m_currentRoom->messageEvents().crbegin() + const auto timelineIt = m_currentRoom->messageEvents().crbegin() + std::max(0, row - timelineBaseIndex());
std::max(0, row - timelineBaseIndex()); const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex());
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() +
std::min(row, timelineBaseIndex());
const auto &evt = isPending ? **pendingIt : **timelineIt; const auto &evt = isPending ? **pendingIt : **timelineIt;
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
@@ -338,17 +322,14 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
return EventTypeRegistry::getMatrixType(evt.type()); return EventTypeRegistry::getMatrixType(evt.type());
if (role == AuthorRole) { if (role == AuthorRole) {
auto author = static_cast<SpectralUser*>( auto author = static_cast<SpectralUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
isPending ? m_currentRoom->localUser()
: m_currentRoom->user(evt.senderId()));
return userAtEvent(author, m_currentRoom, evt); return userAtEvent(author, m_currentRoom, evt);
} }
if (role == ContentTypeRole) { if (role == ContentTypeRole) {
if (auto e = eventCast<const RoomMessageEvent>(&evt)) { if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
const auto &contentType = e->mimeType().name(); const auto &contentType = e->mimeType().name();
return contentType == "text/plain" ? QStringLiteral("text/html") return contentType == "text/plain" ? QStringLiteral("text/html") : contentType;
: contentType;
} }
return QStringLiteral("text/plain"); return QStringLiteral("text/plain");
} }
@@ -356,18 +337,14 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
if (role == ContentRole) { if (role == ContentRole) {
if (evt.isRedacted()) { if (evt.isRedacted()) {
auto reason = evt.redactedBecause()->reason(); auto reason = evt.redactedBecause()->reason();
return (reason.isEmpty()) return (reason.isEmpty()) ? tr("[REDACTED]") : tr("[REDACTED: %1]").arg(evt.redactedBecause()->reason());
? tr("[REDACTED]")
: tr("[REDACTED: %1]").arg(evt.redactedBecause()->reason());
} }
if (auto e = eventCast<const RoomMessageEvent>(&evt)) { if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
// Cannot use e.contentJson() here because some // Cannot use e.contentJson() here because some
// EventContent classes inject values into the copy of the // EventContent classes inject values into the copy of the
// content JSON stored in EventContent::Base // content JSON stored in EventContent::Base
return e->hasFileContent() return e->hasFileContent() ? QVariant::fromValue(e->content()->originalJson) : QVariant();
? QVariant::fromValue(e->content()->originalJson)
: QVariant();
}; };
} }
@@ -383,8 +360,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
auto *memberEvent = timelineIt->viewAs<RoomMemberEvent>(); auto *memberEvent = timelineIt->viewAs<RoomMemberEvent>();
if (memberEvent) { if (memberEvent) {
if ((memberEvent->isJoin() || memberEvent->isLeave()) && if ((memberEvent->isJoin() || memberEvent->isLeave()) && !Settings().value("UI/show_joinleave", true).toBool())
!Settings().value("UI/show_joinleave", true).toBool())
return EventStatus::Hidden; return EventStatus::Hidden;
} }
@@ -393,8 +369,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
if (evt.isRedacted()) if (evt.isRedacted())
return EventStatus::Hidden; return EventStatus::Hidden;
if (evt.isStateEvent() && if (evt.isStateEvent() && static_cast<const StateEventBase &>(evt).repeatsState())
static_cast<const StateEventBase&>(evt).repeatsState())
return EventStatus::Hidden; return EventStatus::Hidden;
if (auto e = eventCast<const RoomMessageEvent>(&evt)) { if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
@@ -403,8 +378,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
} }
} }
if (m_currentRoom->connection()->isIgnored( if (m_currentRoom->connection()->isIgnored(m_currentRoom->user(evt.senderId())))
m_currentRoom->user(evt.senderId())))
return EventStatus::Hidden; return EventStatus::Hidden;
return EventStatus::Normal; return EventStatus::Normal;
@@ -424,8 +398,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
return pendingIt->annotation(); return pendingIt->annotation();
if (role == TimeRole || role == SectionRole) { if (role == TimeRole || role == SectionRole) {
auto ts = auto ts = isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
return role == TimeRole ? QVariant(ts) : renderDate(ts); return role == TimeRole ? QVariant(ts) : renderDate(ts);
} }
@@ -440,10 +413,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
} }
if (role == ReplyRole) { if (role == ReplyRole) {
const QString& replyEventId = evt.contentJson()["m.relates_to"] const QString &replyEventId = evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
.toObject()["m.in_reply_to"]
.toObject()["event_id"]
.toString();
if (replyEventId.isEmpty()) if (replyEventId.isEmpty())
return {}; return {};
const auto replyIt = m_currentRoom->findInTimeline(replyEventId); const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
@@ -451,23 +421,14 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
return {}; return {};
const auto &replyEvt = **replyIt; const auto &replyEvt = **replyIt;
return QVariantMap{ return QVariantMap {{"eventId", replyEventId}, {"display", m_currentRoom->eventToString(replyEvt, Qt::RichText)}, {"author", userAtEvent(static_cast<SpectralUser *>(m_currentRoom->user(replyEvt.senderId())), m_currentRoom, evt)}};
{"eventId", replyEventId},
{"display", m_currentRoom->eventToString(replyEvt, Qt::RichText)},
{"author", userAtEvent(static_cast<SpectralUser*>(
m_currentRoom->user(replyEvt.senderId())),
m_currentRoom, evt)}};
} }
if (role == ShowAuthorRole) { if (role == ShowAuthorRole) {
for (auto r = row + 1; r < rowCount(); ++r) { for (auto r = row + 1; r < rowCount(); ++r) {
auto i = index(r); auto i = index(r);
if (data(i, SpecialMarksRole) != EventStatus::Hidden) { if (data(i, SpecialMarksRole) != EventStatus::Hidden) {
return data(i, AuthorRole) != data(idx, AuthorRole) || return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, EventTypeRole) != data(idx, EventTypeRole) || data(idx, TimeRole).toDateTime().msecsTo(data(i, TimeRole).toDateTime()) > 600000;
data(i, EventTypeRole) != data(idx, EventTypeRole) ||
data(idx, TimeRole)
.toDateTime()
.msecsTo(data(i, TimeRole).toDateTime()) > 600000;
} }
} }
@@ -478,9 +439,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
for (auto r = row + 1; r < rowCount(); ++r) { for (auto r = row + 1; r < rowCount(); ++r) {
auto i = index(r); auto i = index(r);
if (data(i, SpecialMarksRole) != EventStatus::Hidden) { if (data(i, SpecialMarksRole) != EventStatus::Hidden) {
return data(i, TimeRole) return data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000;
.toDateTime()
.msecsTo(data(idx, TimeRole).toDateTime()) > 600000;
} }
} }
@@ -488,8 +447,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
} }
if (role == ReactionRole) { if (role == ReactionRole) {
const auto& annotations = const auto &annotations = m_currentRoom->relatedEvents(evt, EventRelation::Annotation());
m_currentRoom->relatedEvents(evt, EventRelation::Annotation());
if (annotations.isEmpty()) if (annotations.isEmpty())
return {}; return {};
QMap<QString, QList<SpectralUser *>> reactions = {}; QMap<QString, QList<SpectralUser *>> reactions = {};
@@ -497,8 +455,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
if (a->isRedacted()) // Just in case? if (a->isRedacted()) // Just in case?
continue; continue;
if (auto e = eventCast<const ReactionEvent>(a)) if (auto e = eventCast<const ReactionEvent>(a))
reactions[e->relation().key].append( reactions[e->relation().key].append(static_cast<SpectralUser *>(m_currentRoom->user(e->senderId())));
static_cast<SpectralUser*>(m_currentRoom->user(e->senderId())));
} }
if (reactions.isEmpty()) { if (reactions.isEmpty()) {
@@ -512,12 +469,8 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
for (auto author : i.value()) { for (auto author : i.value()) {
authors.append(userAtEvent(author, m_currentRoom, evt)); authors.append(userAtEvent(author, m_currentRoom, evt));
} }
bool hasLocalUser = i.value().contains( bool hasLocalUser = i.value().contains(static_cast<SpectralUser *>(m_currentRoom->localUser()));
static_cast<SpectralUser*>(m_currentRoom->localUser())); res.append(QVariantMap {{"reaction", i.key()}, {"count", i.value().count()}, {"authors", authors}, {"hasLocalUser", hasLocalUser}});
res.append(QVariantMap{{"reaction", i.key()},
{"count", i.value().count()},
{"authors", authors},
{"hasLocalUser", hasLocalUser}});
++i; ++i;
} }
@@ -527,7 +480,8 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
return {}; return {};
} }
int MessageEventModel::eventIDToIndex(const QString& eventID) const { int MessageEventModel::eventIDToIndex(const QString &eventID) const
{
const auto it = m_currentRoom->findInTimeline(eventID); const auto it = m_currentRoom->findInTimeline(eventID);
if (it == m_currentRoom->timelineEdge()) { if (it == m_currentRoom->timelineEdge()) {
qWarning() << "Trying to find inexistent event:" << eventID; qWarning() << "Trying to find inexistent event:" << eventID;

View File

@@ -6,7 +6,8 @@
#include "room.h" #include "room.h"
#include "spectralroom.h" #include "spectralroom.h"
class MessageEventModel : public QAbstractListModel { class MessageEventModel : public QAbstractListModel
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(SpectralRoom *room READ room WRITE setRoom NOTIFY roomChanged) Q_PROPERTY(SpectralRoom *room READ room WRITE setRoom NOTIFY roomChanged)
@@ -49,12 +50,14 @@ class MessageEventModel : public QAbstractListModel {
explicit MessageEventModel(QObject *parent = nullptr); explicit MessageEventModel(QObject *parent = nullptr);
~MessageEventModel() override; ~MessageEventModel() override;
SpectralRoom* room() const { return m_currentRoom; } SpectralRoom *room() const
{
return m_currentRoom;
}
void setRoom(SpectralRoom *room); void setRoom(SpectralRoom *room);
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE int eventIDToIndex(const QString &eventID) const; Q_INVOKABLE int eventIDToIndex(const QString &eventID) const;
@@ -70,8 +73,7 @@ class MessageEventModel : public QAbstractListModel {
bool movingEvent = 0; bool movingEvent = 0;
int timelineBaseIndex() const; int timelineBaseIndex() const;
QDateTime makeMessageTimestamp( QDateTime makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const;
const Quotient::Room::rev_iter_t& baseIt) const;
QString renderDate(QDateTime timestamp) const; QString renderDate(QDateTime timestamp) const;
void refreshLastUserEvents(int baseRow); void refreshLastUserEvents(int baseRow);

View File

@@ -16,7 +16,8 @@ struct roomEventId {
QString eventId; QString eventId;
}; };
class NotificationsManager : public QObject { class NotificationsManager : public QObject
{
Q_OBJECT Q_OBJECT
public: public:
NotificationsManager(QObject *parent = nullptr); NotificationsManager(QObject *parent = nullptr);
@@ -28,9 +29,7 @@ class NotificationsManager : public QObject {
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
QDBusInterface dbus; QDBusInterface dbus;
bool serverSupportsHtml = false; bool serverSupportsHtml = false;
uint showNotification(const QString summary, uint showNotification(const QString summary, const QString text, const QImage image);
const QString text,
const QImage image);
#endif #endif
// notification ID to (room ID, event ID) // notification ID to (room ID, event ID)
@@ -42,12 +41,7 @@ class NotificationsManager : public QObject {
void actionInvoked(uint id, QString action); void actionInvoked(uint id, QString action);
void notificationClosed(uint id, uint reason); void notificationClosed(uint id, uint reason);
void postNotification(const QString& roomId, void postNotification(const QString &roomId, const QString &eventId, const QString &roomName, const QString &senderName, const QString &text, const QImage &icon);
const QString& eventId,
const QString& roomName,
const QString& senderName,
const QString& text,
const QImage& icon);
}; };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)

View File

@@ -8,10 +8,9 @@
#include <QtDBus/QDBusReply> #include <QtDBus/QDBusReply>
NotificationsManager::NotificationsManager(QObject *parent) NotificationsManager::NotificationsManager(QObject *parent)
: QObject(parent), : QObject(parent)
dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", , dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus(), this)
"org.freedesktop.Notifications", QDBusConnection::sessionBus(), {
this) {
qDBusRegisterMetaType<QImage>(); qDBusRegisterMetaType<QImage>();
const QDBusReply<QStringList> capabilitiesReply = dbus.call("GetCapabilities"); const QDBusReply<QStringList> capabilitiesReply = dbus.call("GetCapabilities");
@@ -23,19 +22,12 @@ NotificationsManager::NotificationsManager(QObject *parent)
qWarning() << "Could not get notification server capabilities" << capabilitiesReply.error(); qWarning() << "Could not get notification server capabilities" << capabilitiesReply.error();
} }
QDBusConnection::sessionBus().connect( QDBusConnection::sessionBus().connect("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "ActionInvoked", this, SLOT(actionInvoked(uint, QString)));
"org.freedesktop.Notifications", "/org/freedesktop/Notifications", QDBusConnection::sessionBus().connect("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "NotificationClosed", this, SLOT(notificationClosed(uint, uint)));
"org.freedesktop.Notifications", "ActionInvoked", this,
SLOT(actionInvoked(uint, QString)));
QDBusConnection::sessionBus().connect(
"org.freedesktop.Notifications", "/org/freedesktop/Notifications",
"org.freedesktop.Notifications", "NotificationClosed", this,
SLOT(notificationClosed(uint, uint)));
} }
void NotificationsManager::postNotification( void NotificationsManager::postNotification(const QString &roomid, const QString &eventid, const QString &roomname, const QString &sender, const QString &text, const QImage &icon)
const QString &roomid, const QString &eventid, const QString &roomname, {
const QString &sender, const QString &text, const QImage &icon) {
uint id = showNotification(sender + " (" + roomname + ")", text, icon); uint id = showNotification(sender + " (" + roomname + ")", text, icon);
notificationIds[id] = roomEventId {roomid, eventid}; notificationIds[id] = roomEventId {roomid, eventid};
} }
@@ -45,19 +37,16 @@ void NotificationsManager::postNotification(
* Copyright (C) 2012 Roland Hieber <rohieb@rohieb.name> * Copyright (C) 2012 Roland Hieber <rohieb@rohieb.name>
* Licensed under the GNU General Public License, version 3 * Licensed under the GNU General Public License, version 3
*/ */
uint NotificationsManager::showNotification(const QString summary, uint NotificationsManager::showNotification(const QString summary, const QString text, const QImage image)
const QString text, {
const QImage image) {
QImage croppedImage; QImage croppedImage;
QRect rect = image.rect(); QRect rect = image.rect();
if (rect.width() != rect.height()) { if (rect.width() != rect.height()) {
if (rect.width() > rect.height()) { if (rect.width() > rect.height()) {
QRect crop((rect.width() - rect.height()) / 2, 0, rect.height(), QRect crop((rect.width() - rect.height()) / 2, 0, rect.height(), rect.height());
rect.height());
croppedImage = image.copy(crop); croppedImage = image.copy(crop);
} else { } else {
QRect crop(0, (rect.height() - rect.width()) / 2, rect.width(), QRect crop(0, (rect.height() - rect.width()) / 2, rect.width(), rect.width());
rect.width());
croppedImage = image.copy(crop); croppedImage = image.copy(crop);
} }
} else { } else {
@@ -79,11 +68,8 @@ uint NotificationsManager::showNotification(const QString summary,
argumentList << hints; // hints argumentList << hints; // hints
argumentList << int(-1); // timeout in ms argumentList << int(-1); // timeout in ms
static QDBusInterface notifyApp("org.freedesktop.Notifications", static QDBusInterface notifyApp("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications");
"/org/freedesktop/Notifications", QDBusMessage reply = notifyApp.callWithArgumentList(QDBus::AutoDetect, "Notify", argumentList);
"org.freedesktop.Notifications");
QDBusMessage reply =
notifyApp.callWithArgumentList(QDBus::AutoDetect, "Notify", argumentList);
if (reply.type() == QDBusMessage::ErrorMessage) { if (reply.type() == QDBusMessage::ErrorMessage) {
qDebug() << "D-Bus Error:" << reply.errorMessage(); qDebug() << "D-Bus Error:" << reply.errorMessage();
return 0; return 0;
@@ -92,14 +78,16 @@ uint NotificationsManager::showNotification(const QString summary,
} }
} }
void NotificationsManager::actionInvoked(uint id, QString action) { void NotificationsManager::actionInvoked(uint id, QString action)
{
if (action == "default" && notificationIds.contains(id)) { if (action == "default" && notificationIds.contains(id)) {
roomEventId idEntry = notificationIds[id]; roomEventId idEntry = notificationIds[id];
emit notificationClicked(idEntry.roomId, idEntry.eventId); emit notificationClicked(idEntry.roomId, idEntry.eventId);
} }
} }
void NotificationsManager::notificationClosed(uint id, uint reason) { void NotificationsManager::notificationClosed(uint id, uint reason)
{
Q_UNUSED(reason); Q_UNUSED(reason);
notificationIds.remove(id); notificationIds.remove(id);
} }
@@ -113,7 +101,8 @@ void NotificationsManager::notificationClosed(uint id, uint reason) {
* *
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
*/ */
QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image) { QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image)
{
if (image.isNull()) { if (image.isNull()) {
arg.beginStructure(); arg.beginStructure();
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray(); arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
@@ -155,7 +144,8 @@ QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image) {
return arg; return arg;
} }
const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &) { const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &)
{
// This is needed to link but shouldn't be called. // This is needed to link but shouldn't be called.
Q_ASSERT(0); Q_ASSERT(0);
return arg; return arg;

View File

@@ -5,20 +5,28 @@
using namespace WinToastLib; using namespace WinToastLib;
class CustomHandler : public IWinToastHandler { class CustomHandler : public IWinToastHandler
{
public: public:
CustomHandler(uint id, NotificationsManager *parent) CustomHandler(uint id, NotificationsManager *parent)
: notificationID(id), notificationsManager(parent) {} : notificationID(id)
void toastActivated() { , notificationsManager(parent)
{
}
void toastActivated()
{
notificationsManager->actionInvoked(notificationID, ""); notificationsManager->actionInvoked(notificationID, "");
} }
void toastActivated(int) { void toastActivated(int)
{
notificationsManager->actionInvoked(notificationID, ""); notificationsManager->actionInvoked(notificationID, "");
} }
void toastFailed() { void toastFailed()
{
std::wcout << L"Error showing current toast" << std::endl; std::wcout << L"Error showing current toast" << std::endl;
} }
void toastDismissed(WinToastDismissalReason) { void toastDismissed(WinToastDismissalReason)
{
notificationsManager->notificationClosed(notificationID, 0); notificationsManager->notificationClosed(notificationID, 0);
} }
@@ -27,42 +35,42 @@ class CustomHandler : public IWinToastHandler {
NotificationsManager *notificationsManager; NotificationsManager *notificationsManager;
}; };
namespace { namespace
{
bool isInitialized = false; bool isInitialized = false;
uint count = 0; uint count = 0;
void init() { void init()
{
isInitialized = true; isInitialized = true;
WinToast::instance()->setAppName(L"Spectral"); WinToast::instance()->setAppName(L"Spectral");
WinToast::instance()->setAppUserModelId( WinToast::instance()->setAppUserModelId(WinToast::configureAUMI(L"Spectral", L"Spectral"));
WinToast::configureAUMI(L"Spectral", L"Spectral"));
if (!WinToast::instance()->initialize()) if (!WinToast::instance()->initialize())
std::wcout << "Your system in not compatible with toast notifications\n"; std::wcout << "Your system in not compatible with toast notifications\n";
} }
} // namespace } // namespace
NotificationsManager::NotificationsManager(QObject *parent) : QObject(parent) {} NotificationsManager::NotificationsManager(QObject *parent)
: QObject(parent)
{
}
void NotificationsManager::postNotification( void NotificationsManager::postNotification(const QString &room_id, const QString &event_id, const QString &room_name, const QString &sender, const QString &text, const QImage &icon)
const QString &room_id, const QString &event_id, const QString &room_name, {
const QString &sender, const QString &text, const QImage &icon) {
Q_UNUSED(room_id) Q_UNUSED(room_id)
Q_UNUSED(event_id) Q_UNUSED(event_id)
Q_UNUSED(icon) Q_UNUSED(icon)
if (!isInitialized) init(); if (!isInitialized)
init();
auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02); auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
if (room_name != sender) if (room_name != sender)
templ.setTextField( templ.setTextField(QString("%1 - %2").arg(sender).arg(room_name).toStdWString(), WinToastTemplate::FirstLine);
QString("%1 - %2").arg(sender).arg(room_name).toStdWString(),
WinToastTemplate::FirstLine);
else else
templ.setTextField(QString("%1").arg(sender).toStdWString(), templ.setTextField(QString("%1").arg(sender).toStdWString(), WinToastTemplate::FirstLine);
WinToastTemplate::FirstLine); templ.setTextField(QString("%1").arg(text).toStdWString(), WinToastTemplate::SecondLine);
templ.setTextField(QString("%1").arg(text).toStdWString(),
WinToastTemplate::SecondLine);
count++; count++;
CustomHandler *customHandler = new CustomHandler(count, this); CustomHandler *customHandler = new CustomHandler(count, this);
@@ -71,13 +79,15 @@ void NotificationsManager::postNotification(
WinToast::instance()->showToast(templ, customHandler); WinToast::instance()->showToast(templ, customHandler);
} }
void NotificationsManager::actionInvoked(uint id, QString action) { void NotificationsManager::actionInvoked(uint id, QString action)
{
if (notificationIds.contains(id)) { if (notificationIds.contains(id)) {
roomEventId idEntry = notificationIds[id]; roomEventId idEntry = notificationIds[id];
emit notificationClicked(idEntry.roomId, idEntry.eventId); emit notificationClicked(idEntry.roomId, idEntry.eventId);
} }
} }
void NotificationsManager::notificationClosed(uint id, uint reason) { void NotificationsManager::notificationClosed(uint id, uint reason)
{
notificationIds.remove(id); notificationIds.remove(id);
} }

View File

@@ -1,14 +1,19 @@
#include "wintoastlib.h" #include "wintoastlib.h"
#include <memory>
#include <assert.h> #include <assert.h>
#include <memory>
#pragma comment(lib, "shlwapi") #pragma comment(lib, "shlwapi")
#pragma comment(lib, "user32") #pragma comment(lib, "user32")
#ifdef NDEBUG #ifdef NDEBUG
#define DEBUG_MSG(str) do { } while ( false ) #define DEBUG_MSG(str) \
do { \
} while (false)
#else #else
#define DEBUG_MSG(str) do { std::wcout << str << std::endl; } while( false ) #define DEBUG_MSG(str) \
do { \
std::wcout << str << std::endl; \
} while (false)
#endif #endif
// Thanks: https://stackoverflow.com/a/36545162/4297146 // Thanks: https://stackoverflow.com/a/36545162/4297146
@@ -19,7 +24,8 @@ typedef LONG NTSTATUS, *PNTSTATUS;
typedef NTSTATUS(WINAPI *RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); typedef NTSTATUS(WINAPI *RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
RTL_OSVERSIONINFOW GetRealOSVersion() { RTL_OSVERSIONINFOW GetRealOSVersion()
{
HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll"); HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
if (hMod) { if (hMod) {
RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion"); RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion");
@@ -39,11 +45,11 @@ RTL_OSVERSIONINFOW GetRealOSVersion() {
// https://blogs.msdn.microsoft.com/tiles_and_toasts/2015/10/16/quickstart-handling-toast-activations-from-win32-apps-in-windows-10/ // https://blogs.msdn.microsoft.com/tiles_and_toasts/2015/10/16/quickstart-handling-toast-activations-from-win32-apps-in-windows-10/
using namespace WinToastLib; using namespace WinToastLib;
namespace DllImporter { namespace DllImporter
{
// Function load a function from library // Function load a function from library
template <typename Function> template<typename Function> HRESULT loadFunctionFromLibrary(HINSTANCE library, LPCSTR name, Function &func)
HRESULT loadFunctionFromLibrary(HINSTANCE library, LPCSTR name, Function &func) { {
if (!library) { if (!library) {
return E_INVALIDARG; return E_INVALIDARG;
} }
@@ -65,18 +71,18 @@ namespace DllImporter {
static f_WindowsGetStringRawBuffer WindowsGetStringRawBuffer; static f_WindowsGetStringRawBuffer WindowsGetStringRawBuffer;
static f_WindowsDeleteString WindowsDeleteString; static f_WindowsDeleteString WindowsDeleteString;
template<class T> _Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T **factory)
template<class T> {
_Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T** factory) {
return RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory)); return RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory));
} }
template<typename T> template<typename T> inline HRESULT Wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef<T> factory) throw()
inline HRESULT Wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef<T> factory) throw() { {
return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf()); return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf());
} }
inline HRESULT initialize() { inline HRESULT initialize()
{
HINSTANCE LibShell32 = LoadLibraryW(L"SHELL32.DLL"); HINSTANCE LibShell32 = LoadLibraryW(L"SHELL32.DLL");
HRESULT hr = loadFunctionFromLibrary(LibShell32, "SetCurrentProcessExplicitAppUserModelID", SetCurrentProcessExplicitAppUserModelID); HRESULT hr = loadFunctionFromLibrary(LibShell32, "SetCurrentProcessExplicitAppUserModelID", SetCurrentProcessExplicitAppUserModelID);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -84,10 +90,9 @@ namespace DllImporter {
hr = loadFunctionFromLibrary(LibPropSys, "PropVariantToString", PropVariantToString); hr = loadFunctionFromLibrary(LibPropSys, "PropVariantToString", PropVariantToString);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
HINSTANCE LibComBase = LoadLibraryW(L"COMBASE.DLL"); HINSTANCE LibComBase = LoadLibraryW(L"COMBASE.DLL");
const bool succeded = SUCCEEDED(loadFunctionFromLibrary(LibComBase, "RoGetActivationFactory", RoGetActivationFactory)) const bool succeded = SUCCEEDED(loadFunctionFromLibrary(LibComBase, "RoGetActivationFactory", RoGetActivationFactory)) &&
&& SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsCreateStringReference", WindowsCreateStringReference)) SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsCreateStringReference", WindowsCreateStringReference)) && SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsGetStringRawBuffer", WindowsGetStringRawBuffer)) &&
&& SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsGetStringRawBuffer", WindowsGetStringRawBuffer)) SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsDeleteString", WindowsDeleteString));
&& SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsDeleteString", WindowsDeleteString));
return succeded ? S_OK : E_FAIL; return succeded ? S_OK : E_FAIL;
} }
} }
@@ -95,28 +100,35 @@ namespace DllImporter {
} }
} }
class WinToastStringWrapper { class WinToastStringWrapper
{
public: public:
WinToastStringWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) throw() { WinToastStringWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) throw()
{
HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef, length, &_header, &_hstring); HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef, length, &_header, &_hstring);
if (!SUCCEEDED(hr)) { if (!SUCCEEDED(hr)) {
RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
} }
} }
WinToastStringWrapper(_In_ const std::wstring &stringRef) throw() { WinToastStringWrapper(_In_ const std::wstring &stringRef) throw()
{
HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef.c_str(), static_cast<UINT32>(stringRef.length()), &_header, &_hstring); HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef.c_str(), static_cast<UINT32>(stringRef.length()), &_header, &_hstring);
if (FAILED(hr)) { if (FAILED(hr)) {
RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
} }
} }
~WinToastStringWrapper() { ~WinToastStringWrapper()
{
DllImporter::WindowsDeleteString(_hstring); DllImporter::WindowsDeleteString(_hstring);
} }
inline HSTRING Get() const throw() { return _hstring; } inline HSTRING Get() const throw()
{
return _hstring;
}
private: private:
HSTRING _hstring; HSTRING _hstring;
HSTRING_HEADER _header; HSTRING_HEADER _header;
}; };
class MyDateTime : public IReference<DateTime> class MyDateTime : public IReference<DateTime>
@@ -125,28 +137,36 @@ protected:
DateTime _dateTime; DateTime _dateTime;
public: public:
static INT64 Now() { static INT64 Now()
{
FILETIME now; FILETIME now;
GetSystemTimeAsFileTime(&now); GetSystemTimeAsFileTime(&now);
return ((((INT64)now.dwHighDateTime) << 32) | now.dwLowDateTime); return ((((INT64)now.dwHighDateTime) << 32) | now.dwLowDateTime);
} }
MyDateTime(DateTime dateTime) : _dateTime(dateTime) {} MyDateTime(DateTime dateTime)
: _dateTime(dateTime)
{
}
MyDateTime(INT64 millisecondsFromNow) { MyDateTime(INT64 millisecondsFromNow)
{
_dateTime.UniversalTime = Now() + millisecondsFromNow * 10000; _dateTime.UniversalTime = Now() + millisecondsFromNow * 10000;
} }
operator INT64() { operator INT64()
{
return _dateTime.UniversalTime; return _dateTime.UniversalTime;
} }
HRESULT STDMETHODCALLTYPE get_Value(DateTime *dateTime) { HRESULT STDMETHODCALLTYPE get_Value(DateTime *dateTime)
{
*dateTime = _dateTime; *dateTime = _dateTime;
return S_OK; return S_OK;
} }
HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid, void** ppvObject) { HRESULT STDMETHODCALLTYPE QueryInterface(const IID &riid, void **ppvObject)
{
if (!ppvObject) { if (!ppvObject) {
return E_POINTER; return E_POINTER;
} }
@@ -157,36 +177,43 @@ public:
return E_NOINTERFACE; return E_NOINTERFACE;
} }
ULONG STDMETHODCALLTYPE Release() { ULONG STDMETHODCALLTYPE Release()
{
return 1; return 1;
} }
ULONG STDMETHODCALLTYPE AddRef() { ULONG STDMETHODCALLTYPE AddRef()
{
return 2; return 2;
} }
HRESULT STDMETHODCALLTYPE GetIids(ULONG*, IID**) { HRESULT STDMETHODCALLTYPE GetIids(ULONG *, IID **)
{
return E_NOTIMPL; return E_NOTIMPL;
} }
HRESULT STDMETHODCALLTYPE GetRuntimeClassName(HSTRING*) { HRESULT STDMETHODCALLTYPE GetRuntimeClassName(HSTRING *)
{
return E_NOTIMPL; return E_NOTIMPL;
} }
HRESULT STDMETHODCALLTYPE GetTrustLevel(TrustLevel*) { HRESULT STDMETHODCALLTYPE GetTrustLevel(TrustLevel *)
{
return E_NOTIMPL; return E_NOTIMPL;
} }
}; };
namespace Util { namespace Util
inline HRESULT defaultExecutablePath(_In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) { {
inline HRESULT defaultExecutablePath(_In_ WCHAR *path, _In_ DWORD nSize = MAX_PATH)
{
DWORD written = GetModuleFileNameExW(GetCurrentProcess(), nullptr, path, nSize); DWORD written = GetModuleFileNameExW(GetCurrentProcess(), nullptr, path, nSize);
DEBUG_MSG("Default executable path: " << path); DEBUG_MSG("Default executable path: " << path);
return (written > 0) ? S_OK : E_FAIL; return (written > 0) ? S_OK : E_FAIL;
} }
inline HRESULT defaultShellLinksDirectory(_In_ WCHAR *path, _In_ DWORD nSize = MAX_PATH)
inline HRESULT defaultShellLinksDirectory(_In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) { {
DWORD written = GetEnvironmentVariableW(L"APPDATA", path, nSize); DWORD written = GetEnvironmentVariableW(L"APPDATA", path, nSize);
HRESULT hr = written > 0 ? S_OK : E_INVALIDARG; HRESULT hr = written > 0 ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -197,7 +224,8 @@ namespace Util {
return hr; return hr;
} }
inline HRESULT defaultShellLinkPath(const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) { inline HRESULT defaultShellLinkPath(const std::wstring &appname, _In_ WCHAR *path, _In_ DWORD nSize = MAX_PATH)
{
HRESULT hr = defaultShellLinksDirectory(path, nSize); HRESULT hr = defaultShellLinksDirectory(path, nSize);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
const std::wstring appLink(appname + DEFAULT_LINK_FORMAT); const std::wstring appLink(appname + DEFAULT_LINK_FORMAT);
@@ -208,8 +236,8 @@ namespace Util {
return hr; return hr;
} }
inline PCWSTR AsString(ComPtr<IXmlDocument> &xmlDocument)
inline PCWSTR AsString(ComPtr<IXmlDocument> &xmlDocument) { {
HSTRING xml; HSTRING xml;
ComPtr<IXmlNodeSerializer> ser; ComPtr<IXmlNodeSerializer> ser;
HRESULT hr = xmlDocument.As<IXmlNodeSerializer>(&ser); HRESULT hr = xmlDocument.As<IXmlNodeSerializer>(&ser);
@@ -219,11 +247,13 @@ namespace Util {
return NULL; return NULL;
} }
inline PCWSTR AsString(HSTRING hstring) { inline PCWSTR AsString(HSTRING hstring)
{
return DllImporter::WindowsGetStringRawBuffer(hstring, NULL); return DllImporter::WindowsGetStringRawBuffer(hstring, NULL);
} }
inline HRESULT setNodeStringValue(const std::wstring& string, IXmlNode *node, IXmlDocument *xml) { inline HRESULT setNodeStringValue(const std::wstring &string, IXmlNode *node, IXmlDocument *xml)
{
ComPtr<IXmlText> textNode; ComPtr<IXmlText> textNode;
HRESULT hr = xml->CreateTextNode(WinToastStringWrapper(string).Get(), &textNode); HRESULT hr = xml->CreateTextNode(WinToastStringWrapper(string).Get(), &textNode);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -237,13 +267,10 @@ namespace Util {
return hr; return hr;
} }
inline HRESULT setEventHandlers(_In_ IToastNotification* notification, _In_ std::shared_ptr<IWinToastHandler> eventHandler, _In_ INT64 expirationTime) { inline HRESULT setEventHandlers(_In_ IToastNotification *notification, _In_ std::shared_ptr<IWinToastHandler> eventHandler, _In_ INT64 expirationTime)
EventRegistrationToken activatedToken, dismissedToken, failedToken;
HRESULT hr = notification->add_Activated(
Callback < Implements < RuntimeClassFlags<ClassicCom>,
ITypedEventHandler<ToastNotification*, IInspectable* >> >(
[eventHandler](IToastNotification*, IInspectable* inspectable)
{ {
EventRegistrationToken activatedToken, dismissedToken, failedToken;
HRESULT hr = notification->add_Activated(Callback<Implements<RuntimeClassFlags<ClassicCom>, ITypedEventHandler<ToastNotification *, IInspectable *>>>([eventHandler](IToastNotification *, IInspectable *inspectable) {
IToastActivatedEventArgs *activatedEventArgs; IToastActivatedEventArgs *activatedEventArgs;
HRESULT hr = inspectable->QueryInterface(&activatedEventArgs); HRESULT hr = inspectable->QueryInterface(&activatedEventArgs);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -259,36 +286,34 @@ namespace Util {
} }
eventHandler->toastActivated(); eventHandler->toastActivated();
return S_OK; return S_OK;
}).Get(), &activatedToken); }).Get(),
&activatedToken);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
hr = notification->add_Dismissed(Callback < Implements < RuntimeClassFlags<ClassicCom>, hr = notification->add_Dismissed(
ITypedEventHandler<ToastNotification*, ToastDismissedEventArgs* >> >( Callback<Implements<RuntimeClassFlags<ClassicCom>, ITypedEventHandler<ToastNotification *, ToastDismissedEventArgs *>>>([eventHandler, expirationTime](IToastNotification *, IToastDismissedEventArgs *e) {
[eventHandler, expirationTime](IToastNotification*, IToastDismissedEventArgs* e)
{
ToastDismissalReason reason; ToastDismissalReason reason;
if (SUCCEEDED(e->get_Reason(&reason))) if (SUCCEEDED(e->get_Reason(&reason))) {
{
if (reason == ToastDismissalReason_UserCanceled && expirationTime && MyDateTime::Now() >= expirationTime) if (reason == ToastDismissalReason_UserCanceled && expirationTime && MyDateTime::Now() >= expirationTime)
reason = ToastDismissalReason_TimedOut; reason = ToastDismissalReason_TimedOut;
eventHandler->toastDismissed(static_cast<IWinToastHandler::WinToastDismissalReason>(reason)); eventHandler->toastDismissed(static_cast<IWinToastHandler::WinToastDismissalReason>(reason));
} }
return S_OK; return S_OK;
}).Get(), &dismissedToken); }).Get(),
&dismissedToken);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
hr = notification->add_Failed(Callback < Implements < RuntimeClassFlags<ClassicCom>, hr = notification->add_Failed(Callback<Implements<RuntimeClassFlags<ClassicCom>, ITypedEventHandler<ToastNotification *, ToastFailedEventArgs *>>>([eventHandler](IToastNotification *, IToastFailedEventArgs *) {
ITypedEventHandler<ToastNotification*, ToastFailedEventArgs* >> >(
[eventHandler](IToastNotification*, IToastFailedEventArgs*)
{
eventHandler->toastFailed(); eventHandler->toastFailed();
return S_OK; return S_OK;
}).Get(), &failedToken); }).Get(),
&failedToken);
} }
} }
return hr; return hr;
} }
inline HRESULT addAttribute(_In_ IXmlDocument *xml, const std::wstring &name, IXmlNamedNodeMap *attributeMap) { inline HRESULT addAttribute(_In_ IXmlDocument *xml, const std::wstring &name, IXmlNamedNodeMap *attributeMap)
{
ComPtr<ABI::Windows::Data::Xml::Dom::IXmlAttribute> srcAttribute; ComPtr<ABI::Windows::Data::Xml::Dom::IXmlAttribute> srcAttribute;
HRESULT hr = xml->CreateAttribute(WinToastStringWrapper(name).Get(), &srcAttribute); HRESULT hr = xml->CreateAttribute(WinToastStringWrapper(name).Get(), &srcAttribute);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -302,7 +327,8 @@ namespace Util {
return hr; return hr;
} }
inline HRESULT createElement(_In_ IXmlDocument *xml, _In_ const std::wstring& root_node, _In_ const std::wstring& element_name, _In_ const std::vector<std::wstring>& attribute_names) { inline HRESULT createElement(_In_ IXmlDocument *xml, _In_ const std::wstring &root_node, _In_ const std::wstring &element_name, _In_ const std::vector<std::wstring> &attribute_names)
{
ComPtr<IXmlNodeList> rootList; ComPtr<IXmlNodeList> rootList;
HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(root_node).Get(), &rootList); HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(root_node).Get(), &rootList);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -334,54 +360,52 @@ namespace Util {
} }
} }
WinToast* WinToast::instance() { WinToast *WinToast::instance()
{
static WinToast instance; static WinToast instance;
return &instance; return &instance;
} }
WinToast::WinToast() : WinToast::WinToast()
_isInitialized(false), : _isInitialized(false)
_hasCoInitialized(false) , _hasCoInitialized(false)
{ {
if (!isCompatible()) { if (!isCompatible()) {
DEBUG_MSG(L"Warning: Your system is not compatible with this library "); DEBUG_MSG(L"Warning: Your system is not compatible with this library ");
} }
} }
WinToast::~WinToast() { WinToast::~WinToast()
{
if (_hasCoInitialized) { if (_hasCoInitialized) {
CoUninitialize(); CoUninitialize();
} }
} }
void WinToast::setAppName(_In_ const std::wstring& appName) { void WinToast::setAppName(_In_ const std::wstring &appName)
{
_appName = appName; _appName = appName;
} }
void WinToast::setAppUserModelId(_In_ const std::wstring &aumi)
void WinToast::setAppUserModelId(_In_ const std::wstring& aumi) { {
_aumi = aumi; _aumi = aumi;
DEBUG_MSG(L"Default App User Model Id: " << _aumi.c_str()); DEBUG_MSG(L"Default App User Model Id: " << _aumi.c_str());
} }
bool WinToast::isCompatible() { bool WinToast::isCompatible()
{
DllImporter::initialize(); DllImporter::initialize();
return !((DllImporter::SetCurrentProcessExplicitAppUserModelID == nullptr) return !((DllImporter::SetCurrentProcessExplicitAppUserModelID == nullptr) || (DllImporter::PropVariantToString == nullptr) || (DllImporter::RoGetActivationFactory == nullptr) || (DllImporter::WindowsCreateStringReference == nullptr) ||
|| (DllImporter::PropVariantToString == nullptr) (DllImporter::WindowsDeleteString == nullptr));
|| (DllImporter::RoGetActivationFactory == nullptr)
|| (DllImporter::WindowsCreateStringReference == nullptr)
|| (DllImporter::WindowsDeleteString == nullptr));
} }
bool WinToastLib::WinToast::isSupportingModernFeatures() { bool WinToastLib::WinToast::isSupportingModernFeatures()
{
RTL_OSVERSIONINFOW tmp = GetRealOSVersion(); RTL_OSVERSIONINFOW tmp = GetRealOSVersion();
return tmp.dwMajorVersion > 6; return tmp.dwMajorVersion > 6;
} }
std::wstring WinToast::configureAUMI(_In_ const std::wstring &companyName, std::wstring WinToast::configureAUMI(_In_ const std::wstring &companyName, _In_ const std::wstring &productName, _In_ const std::wstring &subProduct, _In_ const std::wstring &versionInformation)
_In_ const std::wstring &productName,
_In_ const std::wstring &subProduct,
_In_ const std::wstring &versionInformation)
{ {
std::wstring aumi = companyName; std::wstring aumi = companyName;
aumi += L"." + productName; aumi += L"." + productName;
@@ -398,8 +422,8 @@ std::wstring WinToast::configureAUMI(_In_ const std::wstring &companyName,
return aumi; return aumi;
} }
enum WinToast::ShortcutResult WinToast::createShortcut()
enum WinToast::ShortcutResult WinToast::createShortcut() { {
if (_aumi.empty() || _appName.empty()) { if (_aumi.empty() || _appName.empty()) {
DEBUG_MSG(L"Error: App User Model Id or Appname is empty!"); DEBUG_MSG(L"Error: App User Model Id or Appname is empty!");
return SHORTCUT_MISSING_PARAMETERS; return SHORTCUT_MISSING_PARAMETERS;
@@ -416,8 +440,7 @@ enum WinToast::ShortcutResult WinToast::createShortcut() {
if (FAILED(initHr) && initHr != S_FALSE) { if (FAILED(initHr) && initHr != S_FALSE) {
DEBUG_MSG(L"Error on COM library initialization!"); DEBUG_MSG(L"Error on COM library initialization!");
return SHORTCUT_COM_INIT_FAILURE; return SHORTCUT_COM_INIT_FAILURE;
} } else {
else {
_hasCoInitialized = true; _hasCoInitialized = true;
} }
} }
@@ -432,7 +455,8 @@ enum WinToast::ShortcutResult WinToast::createShortcut() {
return SUCCEEDED(hr) ? SHORTCUT_WAS_CREATED : SHORTCUT_CREATE_FAILED; return SUCCEEDED(hr) ? SHORTCUT_WAS_CREATED : SHORTCUT_CREATE_FAILED;
} }
bool WinToast::initialize(_Out_ WinToastError* error) { bool WinToast::initialize(_Out_ WinToastError *error)
{
_isInitialized = false; _isInitialized = false;
setError(error, WinToastError::NoError); setError(error, WinToastError::NoError);
@@ -442,7 +466,6 @@ bool WinToast::initialize(_Out_ WinToastError* error) {
return false; return false;
} }
if (_aumi.empty() || _appName.empty()) { if (_aumi.empty() || _appName.empty()) {
setError(error, WinToastError::InvalidParameters); setError(error, WinToastError::InvalidParameters);
DEBUG_MSG(L"Error while initializing, did you set up a valid AUMI and App name?"); DEBUG_MSG(L"Error while initializing, did you set up a valid AUMI and App name?");
@@ -465,20 +488,23 @@ bool WinToast::initialize(_Out_ WinToastError* error) {
return _isInitialized; return _isInitialized;
} }
bool WinToast::isInitialized() const { bool WinToast::isInitialized() const
{
return _isInitialized; return _isInitialized;
} }
const std::wstring& WinToast::appName() const { const std::wstring &WinToast::appName() const
{
return _appName; return _appName;
} }
const std::wstring& WinToast::appUserModelId() const { const std::wstring &WinToast::appUserModelId() const
{
return _aumi; return _aumi;
} }
HRESULT WinToast::validateShellLinkHelper(_Out_ bool &wasChanged)
HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) { {
WCHAR path[MAX_PATH] = {L'\0'}; WCHAR path[MAX_PATH] = {L'\0'};
Util::defaultShellLinkPath(_appName, path); Util::defaultShellLinkPath(_appName, path);
// Check if the file exist // Check if the file exist
@@ -535,9 +561,8 @@ HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) {
return hr; return hr;
} }
HRESULT WinToast::createShellLinkHelper()
{
HRESULT WinToast::createShellLinkHelper() {
WCHAR exePath[MAX_PATH] {L'\0'}; WCHAR exePath[MAX_PATH] {L'\0'};
WCHAR slPath[MAX_PATH] {L'\0'}; WCHAR slPath[MAX_PATH] {L'\0'};
Util::defaultShellLinkPath(_appName, slPath); Util::defaultShellLinkPath(_appName, slPath);
@@ -578,7 +603,8 @@ HRESULT WinToast::createShellLinkHelper() {
return hr; return hr;
} }
INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error) { INT64 WinToast::showToast(_In_ const WinToastTemplate &toast, _In_ IWinToastHandler *handler, _Out_ WinToastError *error)
{
setError(error, WinToastError::NoError); setError(error, WinToastError::NoError);
INT64 id = -1; INT64 id = -1;
if (!isInitialized()) { if (!isInitialized()) {
@@ -611,11 +637,9 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan
// Modern feature are supported Windows > Windows 10 // Modern feature are supported Windows > Windows 10
if (SUCCEEDED(hr) && isSupportingModernFeatures()) { if (SUCCEEDED(hr) && isSupportingModernFeatures()) {
// Note that we do this *after* using toast.textFieldsCount() to // Note that we do this *after* using toast.textFieldsCount() to
// iterate/fill the template's text fields, since we're adding yet another text field. // iterate/fill the template's text fields, since we're adding yet another text field.
if (SUCCEEDED(hr) if (SUCCEEDED(hr) && !toast.attributionText().empty()) {
&& !toast.attributionText().empty()) {
hr = setAttributionTextFieldHelper(xmlDocument.Get(), toast.attributionText()); hr = setAttributionTextFieldHelper(xmlDocument.Get(), toast.attributionText());
} }
@@ -627,13 +651,11 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan
} }
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
hr = (toast.audioPath().empty() && toast.audioOption() == WinToastTemplate::AudioOption::Default) hr = (toast.audioPath().empty() && toast.audioOption() == WinToastTemplate::AudioOption::Default) ? hr : setAudioFieldHelper(xmlDocument.Get(), toast.audioPath(), toast.audioOption());
? hr : setAudioFieldHelper(xmlDocument.Get(), toast.audioPath(), toast.audioOption());
} }
if (SUCCEEDED(hr) && toast.duration() != WinToastTemplate::Duration::System) { if (SUCCEEDED(hr) && toast.duration() != WinToastTemplate::Duration::System) {
hr = addDurationHelper(xmlDocument.Get(), hr = addDurationHelper(xmlDocument.Get(), (toast.duration() == WinToastTemplate::Duration::Short) ? L"short" : L"long");
(toast.duration() == WinToastTemplate::Duration::Short) ? L"short" : L"long");
} }
} else { } else {
@@ -683,7 +705,8 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan
return FAILED(hr) ? -1 : id; return FAILED(hr) ? -1 : id;
} }
ComPtr<IToastNotifier> WinToast::notifier(_In_ bool* succeded) const { ComPtr<IToastNotifier> WinToast::notifier(_In_ bool *succeded) const
{
ComPtr<IToastNotificationManagerStatics> notificationManager; ComPtr<IToastNotificationManagerStatics> notificationManager;
ComPtr<IToastNotifier> notifier; ComPtr<IToastNotifier> notifier;
HRESULT hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &notificationManager); HRESULT hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &notificationManager);
@@ -694,7 +717,8 @@ ComPtr<IToastNotifier> WinToast::notifier(_In_ bool* succeded) const {
return notifier; return notifier;
} }
bool WinToast::hideToast(_In_ INT64 id) { bool WinToast::hideToast(_In_ INT64 id)
{
if (!isInitialized()) { if (!isInitialized()) {
DEBUG_MSG("Error when hiding the toast. WinToast is not initialized."); DEBUG_MSG("Error when hiding the toast. WinToast is not initialized.");
return false; return false;
@@ -711,7 +735,8 @@ bool WinToast::hideToast(_In_ INT64 id) {
return find; return find;
} }
void WinToast::clear() { void WinToast::clear()
{
bool succeded = false; bool succeded = false;
ComPtr<IToastNotifier> notify = notifier(&succeded); ComPtr<IToastNotifier> notify = notifier(&succeded);
if (succeded) { if (succeded) {
@@ -730,7 +755,8 @@ void WinToast::clear() {
// NOTE: This will add a new text field, so be aware when iterating over // NOTE: This will add a new text field, so be aware when iterating over
// the toast's text fields or getting a count of them. // the toast's text fields or getting a count of them.
// //
HRESULT WinToast::setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text) { HRESULT WinToast::setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &text)
{
Util::createElement(xml, L"binding", L"text", {L"placement"}); Util::createElement(xml, L"binding", L"text", {L"placement"});
ComPtr<IXmlNodeList> nodeList; ComPtr<IXmlNodeList> nodeList;
HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList); HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList);
@@ -764,7 +790,8 @@ HRESULT WinToast::setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ con
return hr; return hr;
} }
HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration) { HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &duration)
{
ComPtr<IXmlNodeList> nodeList; ComPtr<IXmlNodeList> nodeList;
HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList); HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -777,8 +804,7 @@ HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstr
ComPtr<IXmlElement> toastElement; ComPtr<IXmlElement> toastElement;
hr = toastNode.As(&toastElement); hr = toastNode.As(&toastElement);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(), hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(), WinToastStringWrapper(duration).Get());
WinToastStringWrapper(duration).Get());
} }
} }
} }
@@ -786,7 +812,8 @@ HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstr
return hr; return hr;
} }
HRESULT WinToast::setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ int pos) { HRESULT WinToast::setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &text, _In_ int pos)
{
ComPtr<IXmlNodeList> nodeList; ComPtr<IXmlNodeList> nodeList;
HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList); HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -799,8 +826,8 @@ HRESULT WinToast::setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wst
return hr; return hr;
} }
HRESULT WinToast::setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &path)
HRESULT WinToast::setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path) { {
wchar_t imagePath[MAX_PATH] = L"file:///"; wchar_t imagePath[MAX_PATH] = L"file:///";
HRESULT hr = StringCchCatW(imagePath, MAX_PATH, path.c_str()); HRESULT hr = StringCchCatW(imagePath, MAX_PATH, path.c_str());
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -825,11 +852,15 @@ HRESULT WinToast::setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::ws
return hr; return hr;
} }
HRESULT WinToast::setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option) { HRESULT WinToast::setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &path, _In_opt_ WinToastTemplate::AudioOption option)
{
std::vector<std::wstring> attrs; std::vector<std::wstring> attrs;
if (!path.empty()) attrs.push_back(L"src"); if (!path.empty())
if (option == WinToastTemplate::AudioOption::Loop) attrs.push_back(L"loop"); attrs.push_back(L"src");
if (option == WinToastTemplate::AudioOption::Silent) attrs.push_back(L"silent"); if (option == WinToastTemplate::AudioOption::Loop)
attrs.push_back(L"loop");
if (option == WinToastTemplate::AudioOption::Silent)
attrs.push_back(L"silent");
Util::createElement(xml, L"toast", L"audio", attrs); Util::createElement(xml, L"toast", L"audio", attrs);
ComPtr<IXmlNodeList> nodeList; ComPtr<IXmlNodeList> nodeList;
@@ -874,7 +905,8 @@ HRESULT WinToast::setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::ws
return hr; return hr;
} }
HRESULT WinToast::addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& content, _In_ const std::wstring& arguments) { HRESULT WinToast::addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &content, _In_ const std::wstring &arguments)
{
ComPtr<IXmlNodeList> nodeList; ComPtr<IXmlNodeList> nodeList;
HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"actions").Get(), &nodeList); HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"actions").Get(), &nodeList);
if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) {
@@ -934,46 +966,57 @@ HRESULT WinToast::addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstrin
return hr; return hr;
} }
void WinToast::setError(_Out_ WinToastError* error, _In_ WinToastError value) { void WinToast::setError(_Out_ WinToastError *error, _In_ WinToastError value)
{
if (error) { if (error) {
*error = value; *error = value;
} }
} }
WinToastTemplate::WinToastTemplate(_In_ WinToastTemplateType type) : _type(type) { WinToastTemplate::WinToastTemplate(_In_ WinToastTemplateType type)
: _type(type)
{
static const std::size_t TextFieldsCount[] = {1, 2, 2, 3, 1, 2, 2, 3}; static const std::size_t TextFieldsCount[] = {1, 2, 2, 3, 1, 2, 2, 3};
_textFields = std::vector<std::wstring>(TextFieldsCount[type], L""); _textFields = std::vector<std::wstring>(TextFieldsCount[type], L"");
} }
WinToastTemplate::~WinToastTemplate() { WinToastTemplate::~WinToastTemplate()
{
_textFields.clear(); _textFields.clear();
} }
void WinToastTemplate::setTextField(_In_ const std::wstring& txt, _In_ WinToastTemplate::TextField pos) { void WinToastTemplate::setTextField(_In_ const std::wstring &txt, _In_ WinToastTemplate::TextField pos)
{
_textFields[pos] = txt; _textFields[pos] = txt;
} }
void WinToastTemplate::setImagePath(_In_ const std::wstring& imgPath) { void WinToastTemplate::setImagePath(_In_ const std::wstring &imgPath)
{
_imagePath = imgPath; _imagePath = imgPath;
} }
void WinToastTemplate::setAudioPath(_In_ const std::wstring& audioPath) { void WinToastTemplate::setAudioPath(_In_ const std::wstring &audioPath)
{
_audioPath = audioPath; _audioPath = audioPath;
} }
void WinToastTemplate::setAudioOption(_In_ WinToastTemplate::AudioOption audioOption) { void WinToastTemplate::setAudioOption(_In_ WinToastTemplate::AudioOption audioOption)
{
_audioOption = audioOption; _audioOption = audioOption;
} }
void WinToastTemplate::setDuration(_In_ Duration duration) { void WinToastTemplate::setDuration(_In_ Duration duration)
{
_duration = duration; _duration = duration;
} }
void WinToastTemplate::setExpiration(_In_ INT64 millisecondsFromNow) { void WinToastTemplate::setExpiration(_In_ INT64 millisecondsFromNow)
{
_expiration = millisecondsFromNow; _expiration = millisecondsFromNow;
} }
void WinToastTemplate::setAttributionText(_In_ const std::wstring& attributionText) { void WinToastTemplate::setAttributionText(_In_ const std::wstring &attributionText)
{
_attributionText = attributionText; _attributionText = attributionText;
} }
@@ -982,54 +1025,67 @@ void WinToastTemplate::addAction(_In_ const std::wstring & label)
_actions.push_back(label); _actions.push_back(label);
} }
std::size_t WinToastTemplate::textFieldsCount() const { std::size_t WinToastTemplate::textFieldsCount() const
{
return _textFields.size(); return _textFields.size();
} }
std::size_t WinToastTemplate::actionsCount() const { std::size_t WinToastTemplate::actionsCount() const
{
return _actions.size(); return _actions.size();
} }
bool WinToastTemplate::hasImage() const { bool WinToastTemplate::hasImage() const
{
return _type < WinToastTemplateType::Text01; return _type < WinToastTemplateType::Text01;
} }
const std::vector<std::wstring>& WinToastTemplate::textFields() const { const std::vector<std::wstring> &WinToastTemplate::textFields() const
{
return _textFields; return _textFields;
} }
const std::wstring& WinToastTemplate::textField(_In_ TextField pos) const { const std::wstring &WinToastTemplate::textField(_In_ TextField pos) const
{
return _textFields[pos]; return _textFields[pos];
} }
const std::wstring& WinToastTemplate::actionLabel(_In_ int pos) const { const std::wstring &WinToastTemplate::actionLabel(_In_ int pos) const
{
return _actions[pos]; return _actions[pos];
} }
const std::wstring& WinToastTemplate::imagePath() const { const std::wstring &WinToastTemplate::imagePath() const
{
return _imagePath; return _imagePath;
} }
const std::wstring& WinToastTemplate::audioPath() const { const std::wstring &WinToastTemplate::audioPath() const
{
return _audioPath; return _audioPath;
} }
const std::wstring& WinToastTemplate::attributionText() const { const std::wstring &WinToastTemplate::attributionText() const
{
return _attributionText; return _attributionText;
} }
INT64 WinToastTemplate::expiration() const { INT64 WinToastTemplate::expiration() const
{
return _expiration; return _expiration;
} }
WinToastTemplate::WinToastTemplateType WinToastTemplate::type() const { WinToastTemplate::WinToastTemplateType WinToastTemplate::type() const
{
return _type; return _type;
} }
WinToastTemplate::AudioOption WinToastTemplate::audioOption() const { WinToastTemplate::AudioOption WinToastTemplate::audioOption() const
{
return _audioOption; return _audioOption;
} }
WinToastTemplate::Duration WinToastTemplate::duration() const { WinToastTemplate::Duration WinToastTemplate::duration() const
{
return _duration; return _duration;
} }

View File

@@ -1,23 +1,23 @@
#ifndef WINTOASTLIB_H #ifndef WINTOASTLIB_H
#define WINTOASTLIB_H #define WINTOASTLIB_H
#include <Windows.h>
#include <sdkddkver.h>
#include <WinUser.h>
#include <ShObjIdl.h>
#include <wrl/implements.h>
#include <wrl/event.h>
#include <windows.ui.notifications.h>
#include <strsafe.h>
#include <Psapi.h> #include <Psapi.h>
#include <ShObjIdl.h>
#include <ShlObj.h> #include <ShlObj.h>
#include <roapi.h> #include <WinUser.h>
#include <propvarutil.h> #include <Windows.h>
#include <functiondiscoverykeys.h> #include <functiondiscoverykeys.h>
#include <iostream> #include <iostream>
#include <winstring.h>
#include <string.h>
#include <vector>
#include <map> #include <map>
#include <propvarutil.h>
#include <roapi.h>
#include <sdkddkver.h>
#include <string.h>
#include <strsafe.h>
#include <vector>
#include <windows.ui.notifications.h>
#include <winstring.h>
#include <wrl/event.h>
#include <wrl/implements.h>
using namespace Microsoft::WRL; using namespace Microsoft::WRL;
using namespace ABI::Windows::Data::Xml::Dom; using namespace ABI::Windows::Data::Xml::Dom;
using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Foundation;
@@ -26,23 +26,27 @@ using namespace Windows::Foundation;
#define DEFAULT_SHELL_LINKS_PATH L"\\Microsoft\\Windows\\Start Menu\\Programs\\" #define DEFAULT_SHELL_LINKS_PATH L"\\Microsoft\\Windows\\Start Menu\\Programs\\"
#define DEFAULT_LINK_FORMAT L".lnk" #define DEFAULT_LINK_FORMAT L".lnk"
namespace WinToastLib { namespace WinToastLib
{
class IWinToastHandler { class IWinToastHandler
{
public: public:
enum WinToastDismissalReason { enum WinToastDismissalReason {
UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled, UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled,
ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden, ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden,
TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut
}; };
virtual ~IWinToastHandler() {} virtual ~IWinToastHandler()
{
}
virtual void toastActivated() = 0; virtual void toastActivated() = 0;
virtual void toastActivated(int actionIndex) = 0; virtual void toastActivated(int actionIndex) = 0;
virtual void toastDismissed(WinToastDismissalReason state) = 0; virtual void toastDismissed(WinToastDismissalReason state) = 0;
virtual void toastFailed() = 0; virtual void toastFailed() = 0;
}; };
class WinToastTemplate { class WinToastTemplate
{
public: public:
enum Duration { System, Short, Long }; enum Duration { System, Short, Long };
enum AudioOption { Default = 0, Silent = 1, Loop = 2 }; enum AudioOption { Default = 0, Silent = 1, Loop = 2 };
@@ -83,6 +87,7 @@ namespace WinToastLib {
WinToastTemplateType type() const; WinToastTemplateType type() const;
WinToastTemplate::AudioOption audioOption() const; WinToastTemplate::AudioOption audioOption() const;
Duration duration() const; Duration duration() const;
private: private:
std::vector<std::wstring> _textFields; std::vector<std::wstring> _textFields;
std::vector<std::wstring> _actions; std::vector<std::wstring> _actions;
@@ -95,19 +100,10 @@ namespace WinToastLib {
Duration _duration = Duration::System; Duration _duration = Duration::System;
}; };
class WinToast { class WinToast
{
public: public:
enum WinToastError { enum WinToastError { NoError = 0, NotInitialized, SystemNotSupported, ShellLinkNotCreated, InvalidAppUserModelID, InvalidParameters, InvalidHandler, NotDisplayed, UnknownError };
NoError = 0,
NotInitialized,
SystemNotSupported,
ShellLinkNotCreated,
InvalidAppUserModelID,
InvalidParameters,
InvalidHandler,
NotDisplayed,
UnknownError
};
enum ShortcutResult { enum ShortcutResult {
SHORTCUT_UNCHANGED = 0, SHORTCUT_UNCHANGED = 0,
@@ -125,11 +121,7 @@ namespace WinToastLib {
static WinToast *instance(); static WinToast *instance();
static bool isCompatible(); static bool isCompatible();
static bool isSupportingModernFeatures(); static bool isSupportingModernFeatures();
static std::wstring configureAUMI(_In_ const std::wstring& companyName, static std::wstring configureAUMI(_In_ const std::wstring &companyName, _In_ const std::wstring &productName, _In_ const std::wstring &subProduct = std::wstring(), _In_ const std::wstring &versionInformation = std::wstring());
_In_ const std::wstring& productName,
_In_ const std::wstring& subProduct = std::wstring(),
_In_ const std::wstring& versionInformation = std::wstring()
);
virtual bool initialize(_Out_ WinToastError *error = nullptr); virtual bool initialize(_Out_ WinToastError *error = nullptr);
virtual bool isInitialized() const; virtual bool isInitialized() const;
virtual bool hideToast(_In_ INT64 id); virtual bool hideToast(_In_ INT64 id);

View File

@@ -1,9 +1,12 @@
#include "publicroomlistmodel.h" #include "publicroomlistmodel.h"
PublicRoomListModel::PublicRoomListModel(QObject *parent) PublicRoomListModel::PublicRoomListModel(QObject *parent)
: QAbstractListModel(parent) {} : QAbstractListModel(parent)
{
}
void PublicRoomListModel::setConnection(Connection* conn) { void PublicRoomListModel::setConnection(Connection *conn)
{
if (m_connection == conn) if (m_connection == conn)
return; return;
@@ -36,7 +39,8 @@ void PublicRoomListModel::setConnection(Connection* conn) {
emit hasMoreChanged(); emit hasMoreChanged();
} }
void PublicRoomListModel::setServer(const QString& value) { void PublicRoomListModel::setServer(const QString &value)
{
if (m_server == value) if (m_server == value)
return; return;
@@ -63,7 +67,8 @@ void PublicRoomListModel::setServer(const QString& value) {
emit hasMoreChanged(); emit hasMoreChanged();
} }
void PublicRoomListModel::setKeyword(const QString& value) { void PublicRoomListModel::setKeyword(const QString &value)
{
if (m_keyword == value) if (m_keyword == value)
return; return;
@@ -90,7 +95,8 @@ void PublicRoomListModel::setKeyword(const QString& value) {
emit hasMoreChanged(); emit hasMoreChanged();
} }
void PublicRoomListModel::next(int count) { void PublicRoomListModel::next(int count)
{
if (count < 1) if (count < 1)
return; return;
@@ -103,8 +109,7 @@ void PublicRoomListModel::next(int count) {
if (!hasMore()) if (!hasMore())
return; return;
job = m_connection->callApi<QueryPublicRoomsJob>( job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter {m_keyword});
m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword});
connect(job, &BaseJob::finished, this, [=] { connect(job, &BaseJob::finished, this, [=] {
attempted = true; attempted = true;
@@ -112,8 +117,7 @@ void PublicRoomListModel::next(int count) {
if (job->status() == BaseJob::Success) { if (job->status() == BaseJob::Success) {
nextBatch = job->nextBatch(); nextBatch = job->nextBatch();
this->beginInsertRows({}, rooms.count(), this->beginInsertRows({}, rooms.count(), rooms.count() + job->chunk().count() - 1);
rooms.count() + job->chunk().count() - 1);
rooms.append(job->chunk()); rooms.append(job->chunk());
this->endInsertRows(); this->endInsertRows();
@@ -126,7 +130,8 @@ void PublicRoomListModel::next(int count) {
}); });
} }
QVariant PublicRoomListModel::data(const QModelIndex& index, int role) const { QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) if (!index.isValid())
return QVariant(); return QVariant();
@@ -191,7 +196,8 @@ QVariant PublicRoomListModel::data(const QModelIndex& index, int role) const {
return {}; return {};
} }
QHash<int, QByteArray> PublicRoomListModel::roleNames() const { QHash<int, QByteArray> PublicRoomListModel::roleNames() const
{
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[NameRole] = "name"; roles[NameRole] = "name";
@@ -206,13 +212,15 @@ QHash<int, QByteArray> PublicRoomListModel::roleNames() const {
return roles; return roles;
} }
int PublicRoomListModel::rowCount(const QModelIndex& parent) const { int PublicRoomListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) if (parent.isValid())
return 0; return 0;
return rooms.count(); return rooms.count();
} }
bool PublicRoomListModel::hasMore() const { bool PublicRoomListModel::hasMore() const
{
return !(attempted && nextBatch.isEmpty()); return !(attempted && nextBatch.isEmpty());
} }

View File

@@ -10,13 +10,12 @@
using namespace Quotient; using namespace Quotient;
class PublicRoomListModel : public QAbstractListModel { class PublicRoomListModel : public QAbstractListModel
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(Connection* connection READ connection WRITE setConnection NOTIFY Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
connectionChanged)
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged) Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged)
Q_PROPERTY( Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
Q_PROPERTY(bool hasMore READ hasMore NOTIFY hasMoreChanged) Q_PROPERTY(bool hasMore READ hasMore NOTIFY hasMoreChanged)
public: public:
@@ -38,13 +37,22 @@ class PublicRoomListModel : public QAbstractListModel {
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
Connection* connection() const { return m_connection; } Connection *connection() const
{
return m_connection;
}
void setConnection(Connection *value); void setConnection(Connection *value);
QString server() const { return m_server; } QString server() const
{
return m_server;
}
void setServer(const QString &value); void setServer(const QString &value);
QString keyword() const { return m_keyword; } QString keyword() const
{
return m_keyword;
}
void setKeyword(const QString &value); void setKeyword(const QString &value);
bool hasMore() const; bool hasMore() const;

View File

@@ -11,11 +11,17 @@
#include <QtGui/QColor> #include <QtGui/QColor>
#include <QtQuick> #include <QtQuick>
RoomListModel::RoomListModel(QObject* parent) : QAbstractListModel(parent) {} RoomListModel::RoomListModel(QObject *parent)
: QAbstractListModel(parent)
{
}
RoomListModel::~RoomListModel() {} RoomListModel::~RoomListModel()
{
}
void RoomListModel::setConnection(Connection* connection) { void RoomListModel::setConnection(Connection *connection)
{
if (connection == m_connection) if (connection == m_connection)
return; return;
if (m_connection) if (m_connection)
@@ -34,18 +40,12 @@ void RoomListModel::setConnection(Connection* connection) {
for (SpectralRoom *room : m_rooms) for (SpectralRoom *room : m_rooms)
room->disconnect(this); room->disconnect(this);
connect(connection, &Connection::connected, this, connect(connection, &Connection::connected, this, &RoomListModel::doResetModel);
&RoomListModel::doResetModel); connect(connection, &Connection::invitedRoom, this, &RoomListModel::updateRoom);
connect(connection, &Connection::invitedRoom, this, connect(connection, &Connection::joinedRoom, this, &RoomListModel::updateRoom);
&RoomListModel::updateRoom);
connect(connection, &Connection::joinedRoom, this,
&RoomListModel::updateRoom);
connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom); connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom);
connect(connection, &Connection::aboutToDeleteRoom, this, connect(connection, &Connection::aboutToDeleteRoom, this, &RoomListModel::deleteRoom);
&RoomListModel::deleteRoom); connect(connection, &Connection::directChatsListChanged, this, [=](Quotient::DirectChatsMap additions, Quotient::DirectChatsMap removals) {
connect(connection, &Connection::directChatsListChanged, this,
[=](Quotient::DirectChatsMap additions,
Quotient::DirectChatsMap removals) {
for (QString roomID : additions.values() + removals.values()) { for (QString roomID : additions.values() + removals.values()) {
auto room = connection->room(roomID); auto room = connection->room(roomID);
if (room) if (room)
@@ -56,7 +56,8 @@ void RoomListModel::setConnection(Connection* connection) {
doResetModel(); doResetModel();
} }
void RoomListModel::doResetModel() { void RoomListModel::doResetModel()
{
beginResetModel(); beginResetModel();
m_rooms.clear(); m_rooms.clear();
for (auto r : m_connection->allRooms()) { for (auto r : m_connection->allRooms()) {
@@ -66,11 +67,13 @@ void RoomListModel::doResetModel() {
refreshNotificationCount(); refreshNotificationCount();
} }
SpectralRoom* RoomListModel::roomAt(int row) const { SpectralRoom *RoomListModel::roomAt(int row) const
{
return m_rooms.at(row); return m_rooms.at(row);
} }
void RoomListModel::doAddRoom(Room* r) { void RoomListModel::doAddRoom(Room *r)
{
if (auto room = static_cast<SpectralRoom *>(r)) { if (auto room = static_cast<SpectralRoom *>(r)) {
m_rooms.append(room); m_rooms.append(room);
connectRoomSignals(room); connectRoomSignals(room);
@@ -81,16 +84,29 @@ void RoomListModel::doAddRoom(Room* r) {
} }
} }
void RoomListModel::connectRoomSignals(SpectralRoom* room) { void RoomListModel::connectRoomSignals(SpectralRoom *room)
connect(room, &Room::displaynameChanged, this, [=] { refresh(room); }); {
connect(room, &Room::unreadMessagesChanged, this, [=] { refresh(room); }); connect(room, &Room::displaynameChanged, this, [=] {
connect(room, &Room::notificationCountChanged, this, [=] { refresh(room); }); refresh(room);
connect(room, &Room::avatarChanged, this, });
[this, room] { refresh(room, {AvatarRole}); }); connect(room, &Room::unreadMessagesChanged, this, [=] {
connect(room, &Room::tagsChanged, this, [=] { refresh(room); }); refresh(room);
connect(room, &Room::joinStateChanged, this, [=] { refresh(room); }); });
connect(room, &Room::addedMessages, this, connect(room, &Room::notificationCountChanged, this, [=] {
[=] { refresh(room, {LastEventRole}); }); refresh(room);
});
connect(room, &Room::avatarChanged, this, [this, room] {
refresh(room, {AvatarRole});
});
connect(room, &Room::tagsChanged, this, [=] {
refresh(room);
});
connect(room, &Room::joinStateChanged, this, [=] {
refresh(room);
});
connect(room, &Room::addedMessages, this, [=] {
refresh(room, {LastEventRole});
});
connect(room, &Room::notificationCountChanged, this, [=] { connect(room, &Room::notificationCountChanged, this, [=] {
if (room->notificationCount() == 0) if (room->notificationCount() == 0)
return; return;
@@ -102,9 +118,7 @@ void RoomListModel::connectRoomSignals(SpectralRoom* room) {
User *sender = room->user(lastEvent->senderId()); User *sender = room->user(lastEvent->senderId());
if (sender == room->localUser()) if (sender == room->localUser())
return; return;
emit newMessage(room->id(), lastEvent->id(), room->displayName(), emit newMessage(room->id(), lastEvent->id(), room->displayName(), sender->displayname(), room->eventToString(*lastEvent), room->avatar(128));
sender->displayname(), room->eventToString(*lastEvent),
room->avatar(128));
}); });
connect(room, &Room::highlightCountChanged, this, [=] { connect(room, &Room::highlightCountChanged, this, [=] {
if (room->highlightCount() == 0) if (room->highlightCount() == 0)
@@ -117,15 +131,13 @@ void RoomListModel::connectRoomSignals(SpectralRoom* room) {
User *sender = room->user(lastEvent->senderId()); User *sender = room->user(lastEvent->senderId());
if (sender == room->localUser()) if (sender == room->localUser())
return; return;
emit newHighlight(room->id(), lastEvent->id(), room->displayName(), emit newHighlight(room->id(), lastEvent->id(), room->displayName(), sender->displayname(), room->eventToString(*lastEvent), room->avatar(128));
sender->displayname(), room->eventToString(*lastEvent),
room->avatar(128));
}); });
connect(room, &Room::notificationCountChanged, this, connect(room, &Room::notificationCountChanged, this, &RoomListModel::refreshNotificationCount);
&RoomListModel::refreshNotificationCount);
} }
void RoomListModel::refreshNotificationCount() { void RoomListModel::refreshNotificationCount()
{
int count = 0; int count = 0;
for (auto room : m_rooms) { for (auto room : m_rooms) {
count += room->notificationCount(); count += room->notificationCount();
@@ -134,7 +146,8 @@ void RoomListModel::refreshNotificationCount() {
emit notificationCountChanged(); emit notificationCountChanged();
} }
void RoomListModel::updateRoom(Room* room, Room* prev) { void RoomListModel::updateRoom(Room *room, Room *prev)
{
// There are two cases when this method is called: // There are two cases when this method is called:
// 1. (prev == nullptr) adding a new room to the room list // 1. (prev == nullptr) adding a new room to the room list
// 2. (prev != nullptr) accepting/rejecting an invitation or inviting to // 2. (prev != nullptr) accepting/rejecting an invitation or inviting to
@@ -145,15 +158,14 @@ void RoomListModel::updateRoom(Room* room, Room* prev) {
return; return;
} }
if (prev && room->id() != prev->id()) { if (prev && room->id() != prev->id()) {
qCritical() << "RoomListModel::updateRoom: attempt to update room" qCritical() << "RoomListModel::updateRoom: attempt to update room" << room->id() << "to" << prev->id();
<< room->id() << "to" << prev->id();
// That doesn't look right but technically we still can do it. // That doesn't look right but technically we still can do it.
} }
// Ok, we're through with pre-checks, now for the real thing. // Ok, we're through with pre-checks, now for the real thing.
auto newRoom = static_cast<SpectralRoom *>(room); auto newRoom = static_cast<SpectralRoom *>(room);
const auto it = std::find_if( const auto it = std::find_if(m_rooms.begin(), m_rooms.end(), [=](const SpectralRoom *r) {
m_rooms.begin(), m_rooms.end(), return r == prev || r == newRoom;
[=](const SpectralRoom* r) { return r == prev || r == newRoom; }); });
if (it != m_rooms.end()) { if (it != m_rooms.end()) {
const int row = it - m_rooms.begin(); const int row = it - m_rooms.begin();
// There's no guarantee that prev != newRoom // There's no guarantee that prev != newRoom
@@ -170,7 +182,8 @@ void RoomListModel::updateRoom(Room* room, Room* prev) {
} }
} }
void RoomListModel::deleteRoom(Room* room) { void RoomListModel::deleteRoom(Room *room)
{
qDebug() << "Deleting room" << room->id(); qDebug() << "Deleting room" << room->id();
const auto it = std::find(m_rooms.begin(), m_rooms.end(), room); const auto it = std::find(m_rooms.begin(), m_rooms.end(), room);
if (it == m_rooms.end()) if (it == m_rooms.end())
@@ -182,13 +195,15 @@ void RoomListModel::deleteRoom(Room* room) {
endRemoveRows(); endRemoveRows();
} }
int RoomListModel::rowCount(const QModelIndex& parent) const { int RoomListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) if (parent.isValid())
return 0; return 0;
return m_rooms.count(); return m_rooms.count();
} }
QVariant RoomListModel::data(const QModelIndex& index, int role) const { QVariant RoomListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) if (!index.isValid())
return QVariant(); return QVariant();
@@ -234,7 +249,8 @@ QVariant RoomListModel::data(const QModelIndex& index, int role) const {
return QVariant(); return QVariant();
} }
void RoomListModel::refresh(SpectralRoom* room, const QVector<int>& roles) { void RoomListModel::refresh(SpectralRoom *room, const QVector<int> &roles)
{
const auto it = std::find(m_rooms.begin(), m_rooms.end(), room); const auto it = std::find(m_rooms.begin(), m_rooms.end(), room);
if (it == m_rooms.end()) { if (it == m_rooms.end()) {
qCritical() << "Room" << room->id() << "not found in the room list"; qCritical() << "Room" << room->id() << "not found in the room list";
@@ -244,7 +260,8 @@ void RoomListModel::refresh(SpectralRoom* room, const QVector<int>& roles) {
emit dataChanged(idx, idx, roles); emit dataChanged(idx, idx, roles);
} }
QHash<int, QByteArray> RoomListModel::roleNames() const { QHash<int, QByteArray> RoomListModel::roleNames() const
{
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[NameRole] = "name"; roles[NameRole] = "name";
roles[AvatarRole] = "avatar"; roles[AvatarRole] = "avatar";

View File

@@ -10,7 +10,8 @@
using namespace Quotient; using namespace Quotient;
class RoomType : public QObject { class RoomType : public QObject
{
Q_OBJECT Q_OBJECT
public: public:
@@ -24,11 +25,11 @@ class RoomType : public QObject {
Q_ENUMS(Types) Q_ENUMS(Types)
}; };
class RoomListModel : public QAbstractListModel { class RoomListModel : public QAbstractListModel
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(Connection *connection READ connection WRITE setConnection) Q_PROPERTY(Connection *connection READ connection WRITE setConnection)
Q_PROPERTY(int notificationCount READ notificationCount NOTIFY Q_PROPERTY(int notificationCount READ notificationCount NOTIFY notificationCountChanged)
notificationCountChanged)
public: public:
enum EventRoles { enum EventRoles {
@@ -49,20 +50,24 @@ class RoomListModel : public QAbstractListModel {
RoomListModel(QObject *parent = nullptr); RoomListModel(QObject *parent = nullptr);
virtual ~RoomListModel() override; virtual ~RoomListModel() override;
Connection* connection() const { return m_connection; } Connection *connection() const
{
return m_connection;
}
void setConnection(Connection *connection); void setConnection(Connection *connection);
void doResetModel(); void doResetModel();
Q_INVOKABLE SpectralRoom *roomAt(int row) const; Q_INVOKABLE SpectralRoom *roomAt(int row) const;
QVariant data(const QModelIndex& index, QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int role = Qt::DisplayRole) const override; Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override;
Q_INVOKABLE int rowCount(
const QModelIndex& parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
int notificationCount() const { return m_notificationCount; } int notificationCount() const
{
return m_notificationCount;
}
private slots: private slots:
void doAddRoom(Room *room); void doAddRoom(Room *room);
@@ -84,18 +89,8 @@ class RoomListModel : public QAbstractListModel {
void notificationCountChanged(); void notificationCountChanged();
void roomAdded(SpectralRoom *room); void roomAdded(SpectralRoom *room);
void newMessage(const QString& roomId, void newMessage(const QString &roomId, const QString &eventId, const QString &roomName, const QString &senderName, const QString &text, const QImage &icon);
const QString& eventId, void newHighlight(const QString &roomId, const QString &eventId, const QString &roomName, const QString &senderName, const QString &text, const QImage &icon);
const QString& roomName,
const QString& senderName,
const QString& text,
const QImage& icon);
void newHighlight(const QString& roomId,
const QString& eventId,
const QString& roomName,
const QString& senderName,
const QString& text,
const QImage& icon);
}; };
#endif // ROOMLISTMODEL_H #endif // ROOMLISTMODEL_H

View File

@@ -19,36 +19,33 @@
#include "csapi/typing.h" #include "csapi/typing.h"
#include "events/accountdataevents.h" #include "events/accountdataevents.h"
#include "events/reactionevent.h" #include "events/reactionevent.h"
#include "events/roommessageevent.h"
#include "events/typingevent.h"
#include "events/roompowerlevelsevent.h"
#include "events/roomcanonicalaliasevent.h" #include "events/roomcanonicalaliasevent.h"
#include "events/roommessageevent.h"
#include "events/roompowerlevelsevent.h"
#include "events/typingevent.h"
#include "jobs/downloadfilejob.h" #include "jobs/downloadfilejob.h"
#include "user.h" #include "user.h"
#include "utils.h" #include "utils.h"
SpectralRoom::SpectralRoom(Connection* connection, SpectralRoom::SpectralRoom(Connection *connection, QString roomId, JoinState joinState)
QString roomId, : Room(connection, std::move(roomId), joinState)
JoinState joinState) {
: Room(connection, std::move(roomId), joinState) { connect(this, &SpectralRoom::notificationCountChanged, this, &SpectralRoom::countChanged);
connect(this, &SpectralRoom::notificationCountChanged, this, connect(this, &SpectralRoom::highlightCountChanged, this, &SpectralRoom::countChanged);
&SpectralRoom::countChanged);
connect(this, &SpectralRoom::highlightCountChanged, this,
&SpectralRoom::countChanged);
connect(this, &Room::fileTransferCompleted, this, [=] { connect(this, &Room::fileTransferCompleted, this, [=] {
setFileUploadingProgress(0); setFileUploadingProgress(0);
setHasFileUploading(false); setHasFileUploading(false);
}); });
} }
void SpectralRoom::uploadFile(const QUrl& url, const QString& body) { void SpectralRoom::uploadFile(const QUrl &url, const QString &body)
{
if (url.isEmpty()) if (url.isEmpty())
return; return;
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, url, false); QString txnId = postFile(body.isEmpty() ? url.fileName() : body, url, false);
setHasFileUploading(true); setHasFileUploading(true);
connect(this, &Room::fileTransferCompleted, connect(this, &Room::fileTransferCompleted, [=](QString id, QUrl /*localFile*/, QUrl /*mxcUrl*/) {
[=](QString id, QUrl /*localFile*/, QUrl /*mxcUrl*/) {
if (id == txnId) { if (id == txnId) {
setFileUploadingProgress(0); setFileUploadingProgress(0);
setHasFileUploading(false); setHasFileUploading(false);
@@ -60,9 +57,7 @@ void SpectralRoom::uploadFile(const QUrl& url, const QString& body) {
setHasFileUploading(false); setHasFileUploading(false);
} }
}); });
connect( connect(this, &Room::fileTransferProgress, [=](QString id, qint64 progress, qint64 total) {
this, &Room::fileTransferProgress,
[=](QString id, qint64 progress, qint64 total) {
if (id == txnId) { if (id == txnId) {
qDebug() << "Progress:" << progress << total; qDebug() << "Progress:" << progress << total;
setFileUploadingProgress(int(float(progress) / float(total) * 100)); setFileUploadingProgress(int(float(progress) / float(total) * 100));
@@ -70,15 +65,18 @@ void SpectralRoom::uploadFile(const QUrl& url, const QString& body) {
}); });
} }
void SpectralRoom::acceptInvitation() { void SpectralRoom::acceptInvitation()
{
connection()->joinRoom(id()); connection()->joinRoom(id());
} }
void SpectralRoom::forget() { void SpectralRoom::forget()
{
connection()->forgetRoom(id()); connection()->forgetRoom(id());
} }
QVariantList SpectralRoom::getUsersTyping() const { QVariantList SpectralRoom::getUsersTyping() const
{
auto users = usersTyping(); auto users = usersTyping();
users.removeAll(localUser()); users.removeAll(localUser());
QVariantList userVariants; QVariantList userVariants;
@@ -88,12 +86,13 @@ QVariantList SpectralRoom::getUsersTyping() const {
return userVariants; return userVariants;
} }
void SpectralRoom::sendTypingNotification(bool isTyping) { void SpectralRoom::sendTypingNotification(bool isTyping)
connection()->callApi<SetTypingJob>(BackgroundRequest, localUser()->id(), {
id(), isTyping, 10000); connection()->callApi<SetTypingJob>(BackgroundRequest, localUser()->id(), id(), isTyping, 10000);
} }
QString SpectralRoom::lastEvent() const { QString SpectralRoom::lastEvent() const
{
for (auto i = messageEvents().rbegin(); i < messageEvents().rend(); i++) { for (auto i = messageEvents().rbegin(); i < messageEvents().rend(); i++) {
const RoomEvent *evt = i->get(); const RoomEvent *evt = i->get();
@@ -102,8 +101,7 @@ QString SpectralRoom::lastEvent() const {
if (evt->isRedacted()) if (evt->isRedacted())
continue; continue;
if (evt->isStateEvent() && if (evt->isStateEvent() && static_cast<const StateEventBase &>(*evt).repeatsState())
static_cast<const StateEventBase&>(*evt).repeatsState())
continue; continue;
if (auto e = eventCast<const RoomMessageEvent>(evt)) { if (auto e = eventCast<const RoomMessageEvent>(evt)) {
@@ -115,77 +113,79 @@ QString SpectralRoom::lastEvent() const {
if (connection()->isIgnored(user(evt->senderId()))) if (connection()->isIgnored(user(evt->senderId())))
continue; continue;
return user(evt->senderId())->displayname() + return user(evt->senderId())->displayname() + (evt->isStateEvent() ? " " : ": ") + eventToString(*evt);
(evt->isStateEvent() ? " " : ": ") + eventToString(*evt);
} }
return ""; return "";
} }
bool SpectralRoom::isEventHighlighted(const RoomEvent* e) const { bool SpectralRoom::isEventHighlighted(const RoomEvent *e) const
{
return highlights.contains(e); return highlights.contains(e);
} }
void SpectralRoom::checkForHighlights(const Quotient::TimelineItem& ti) { void SpectralRoom::checkForHighlights(const Quotient::TimelineItem &ti)
{
auto localUserId = localUser()->id(); auto localUserId = localUser()->id();
if (ti->senderId() == localUserId) if (ti->senderId() == localUserId)
return; return;
if (auto *e = ti.viewAs<RoomMessageEvent>()) { if (auto *e = ti.viewAs<RoomMessageEvent>()) {
const auto &text = e->plainBody(); const auto &text = e->plainBody();
if (text.contains(localUserId) || if (text.contains(localUserId) || text.contains(roomMembername(localUserId)))
text.contains(roomMembername(localUserId)))
highlights.insert(e); highlights.insert(e);
} }
} }
void SpectralRoom::onAddNewTimelineEvents(timeline_iter_t from) { void SpectralRoom::onAddNewTimelineEvents(timeline_iter_t from)
std::for_each(from, messageEvents().cend(), {
[this](const TimelineItem& ti) { checkForHighlights(ti); }); std::for_each(from, messageEvents().cend(), [this](const TimelineItem &ti) {
checkForHighlights(ti);
});
} }
void SpectralRoom::onAddHistoricalTimelineEvents(rev_iter_t from) { void SpectralRoom::onAddHistoricalTimelineEvents(rev_iter_t from)
std::for_each(from, messageEvents().crend(), {
[this](const TimelineItem& ti) { checkForHighlights(ti); }); std::for_each(from, messageEvents().crend(), [this](const TimelineItem &ti) {
checkForHighlights(ti);
});
} }
void SpectralRoom::onRedaction(const RoomEvent& prevEvent, void SpectralRoom::onRedaction(const RoomEvent &prevEvent, const RoomEvent & /*after*/)
const RoomEvent& /*after*/) { {
if (const auto &e = eventCast<const ReactionEvent>(&prevEvent)) { if (const auto &e = eventCast<const ReactionEvent>(&prevEvent)) {
if (auto relatedEventId = e->relation().eventId; if (auto relatedEventId = e->relation().eventId; !relatedEventId.isEmpty()) {
!relatedEventId.isEmpty()) {
emit updatedEvent(relatedEventId); emit updatedEvent(relatedEventId);
} }
} }
} }
void SpectralRoom::countChanged() { void SpectralRoom::countChanged()
{
if (displayed() && !hasUnreadMessages()) { if (displayed() && !hasUnreadMessages()) {
resetNotificationCount(); resetNotificationCount();
resetHighlightCount(); resetHighlightCount();
} }
} }
QDateTime SpectralRoom::lastActiveTime() const { QDateTime SpectralRoom::lastActiveTime() const
{
if (timelineSize() == 0) if (timelineSize() == 0)
return QDateTime(); return QDateTime();
return messageEvents().rbegin()->get()->originTimestamp(); return messageEvents().rbegin()->get()->originTimestamp();
} }
int SpectralRoom::savedTopVisibleIndex() const { int SpectralRoom::savedTopVisibleIndex() const
return firstDisplayedMarker() == timelineEdge() {
? 0 return firstDisplayedMarker() == timelineEdge() ? 0 : int(firstDisplayedMarker() - messageEvents().rbegin());
: int(firstDisplayedMarker() - messageEvents().rbegin());
} }
int SpectralRoom::savedBottomVisibleIndex() const { int SpectralRoom::savedBottomVisibleIndex() const
return lastDisplayedMarker() == timelineEdge() {
? 0 return lastDisplayedMarker() == timelineEdge() ? 0 : int(lastDisplayedMarker() - messageEvents().rbegin());
: int(lastDisplayedMarker() - messageEvents().rbegin());
} }
void SpectralRoom::saveViewport(int topIndex, int bottomIndex) { void SpectralRoom::saveViewport(int topIndex, int bottomIndex)
if (topIndex == -1 || bottomIndex == -1 || {
(bottomIndex == savedBottomVisibleIndex() && if (topIndex == -1 || bottomIndex == -1 || (bottomIndex == savedBottomVisibleIndex() && (bottomIndex == 0 || topIndex == savedTopVisibleIndex())))
(bottomIndex == 0 || topIndex == savedTopVisibleIndex())))
return; return;
if (bottomIndex == 0) { if (bottomIndex == 0) {
setFirstDisplayedEventId({}); setFirstDisplayedEventId({});
@@ -196,7 +196,8 @@ void SpectralRoom::saveViewport(int topIndex, int bottomIndex) {
setLastDisplayedEvent(maxTimelineIndex() - bottomIndex); setLastDisplayedEvent(maxTimelineIndex() - bottomIndex);
} }
QVariantList SpectralRoom::getUsers(const QString& keyword) const { QVariantList SpectralRoom::getUsers(const QString &keyword) const
{
const auto userList = users(); const auto userList = users();
QVariantList matchedList; QVariantList matchedList;
for (const auto u : userList) for (const auto u : userList)
@@ -207,11 +208,13 @@ QVariantList SpectralRoom::getUsers(const QString& keyword) const {
return matchedList; return matchedList;
} }
QUrl SpectralRoom::urlToMxcUrl(QUrl mxcUrl) { QUrl SpectralRoom::urlToMxcUrl(QUrl mxcUrl)
{
return DownloadFileJob::makeRequestUrl(connection()->homeserver(), mxcUrl); return DownloadFileJob::makeRequestUrl(connection()->homeserver(), mxcUrl);
} }
QString SpectralRoom::avatarMediaId() const { QString SpectralRoom::avatarMediaId() const
{
if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) { if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
return avatar; return avatar;
} }
@@ -227,9 +230,8 @@ QString SpectralRoom::avatarMediaId() const {
return {}; return {};
} }
QString SpectralRoom::eventToString(const RoomEvent& evt, QString SpectralRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format, bool removeReply) const
Qt::TextFormat format, {
bool removeReply) const {
const bool prettyPrint = (format == Qt::RichText); const bool prettyPrint = (format == Qt::RichText);
using namespace Quotient; using namespace Quotient;
@@ -239,8 +241,7 @@ QString SpectralRoom::eventToString(const RoomEvent& evt,
using namespace MessageEventContent; using namespace MessageEventContent;
// 1. prettyPrint/HTML // 1. prettyPrint/HTML
if (prettyPrint && e.hasTextContent() && if (prettyPrint && e.hasTextContent() && e.mimeType().name() != "text/plain") {
e.mimeType().name() != "text/plain") {
auto htmlBody = static_cast<const TextContent *>(e.content())->body; auto htmlBody = static_cast<const TextContent *>(e.content())->body;
if (removeReply) { if (removeReply) {
htmlBody.remove(utils::removeRichReplyRegex); htmlBody.remove(utils::removeRichReplyRegex);
@@ -252,11 +253,9 @@ QString SpectralRoom::eventToString(const RoomEvent& evt,
} }
if (e.hasFileContent()) { if (e.hasFileContent()) {
auto fileCaption = auto fileCaption = e.content()->fileInfo()->originalName.toHtmlEscaped();
e.content()->fileInfo()->originalName.toHtmlEscaped();
if (fileCaption.isEmpty()) { if (fileCaption.isEmpty()) {
fileCaption = prettyPrint ? Quotient::prettyPrint(e.plainBody()) fileCaption = prettyPrint ? Quotient::prettyPrint(e.plainBody()) : e.plainBody();
: e.plainBody();
} else if (e.content()->fileInfo()->originalName != e.plainBody()) { } else if (e.content()->fileInfo()->originalName != e.plainBody()) {
fileCaption = e.plainBody() + " | " + fileCaption; fileCaption = e.plainBody() + " | " + fileCaption;
} }
@@ -293,19 +292,15 @@ QString SpectralRoom::eventToString(const RoomEvent& evt,
case MembershipType::Join: { case MembershipType::Join: {
if (e.repeatsState()) if (e.repeatsState())
return tr("joined the room (repeated)"); return tr("joined the room (repeated)");
if (!e.prevContent() || if (!e.prevContent() || e.membership() != e.prevContent()->membership) {
e.membership() != e.prevContent()->membership) { return e.membership() == MembershipType::Invite ? tr("invited %1 to the room").arg(subjectName) : tr("joined the room");
return e.membership() == MembershipType::Invite
? tr("invited %1 to the room").arg(subjectName)
: tr("joined the room");
} }
QString text {}; QString text {};
if (e.isRename()) { if (e.isRename()) {
if (e.displayName().isEmpty()) if (e.displayName().isEmpty())
text = tr("cleared their display name"); text = tr("cleared their display name");
else else
text = tr("changed their display name to %1") text = tr("changed their display name to %1").arg(e.displayName().toHtmlEscaped());
.arg(e.displayName().toHtmlEscaped());
} }
if (e.isAvatarUpdate()) { if (e.isAvatarUpdate()) {
if (!text.isEmpty()) if (!text.isEmpty())
@@ -320,32 +315,16 @@ QString SpectralRoom::eventToString(const RoomEvent& evt,
return text; return text;
} }
case MembershipType::Leave: case MembershipType::Leave:
if (e.prevContent() && if (e.prevContent() && e.prevContent()->membership == MembershipType::Invite) {
e.prevContent()->membership == MembershipType::Invite) { return (e.senderId() != e.userId()) ? tr("withdrew %1's invitation").arg(subjectName) : tr("rejected the invitation");
return (e.senderId() != e.userId())
? tr("withdrew %1's invitation").arg(subjectName)
: tr("rejected the invitation");
} }
if (e.prevContent() && if (e.prevContent() && e.prevContent()->membership == MembershipType::Ban) {
e.prevContent()->membership == MembershipType::Ban) { return (e.senderId() != e.userId()) ? tr("unbanned %1").arg(subjectName) : tr("self-unbanned");
return (e.senderId() != e.userId())
? tr("unbanned %1").arg(subjectName)
: tr("self-unbanned");
} }
return (e.senderId() != e.userId()) return (e.senderId() != e.userId()) ? tr("has put %1 out of the room: %2").arg(subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped()) : tr("left the room");
? tr("has put %1 out of the room: %2")
.arg(subjectName, e.contentJson()["reason"_ls]
.toString()
.toHtmlEscaped())
: tr("left the room");
case MembershipType::Ban: case MembershipType::Ban:
return (e.senderId() != e.userId()) return (e.senderId() != e.userId()) ? tr("banned %1 from the room: %2").arg(subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped()) : tr("self-banned from the room");
? tr("banned %1 from the room: %2")
.arg(subjectName, e.contentJson()["reason"_ls]
.toString()
.toHtmlEscaped())
: tr("self-banned from the room");
case MembershipType::Knock: case MembershipType::Knock:
return tr("knocked"); return tr("knocked");
default:; default:;
@@ -353,57 +332,44 @@ QString SpectralRoom::eventToString(const RoomEvent& evt,
return tr("made something unknown"); return tr("made something unknown");
}, },
[](const RoomCanonicalAliasEvent &e) { [](const RoomCanonicalAliasEvent &e) {
return (e.alias().isEmpty()) return (e.alias().isEmpty()) ? tr("cleared the room main alias") : tr("set the room main alias to: %1").arg(e.alias());
? tr("cleared the room main alias")
: tr("set the room main alias to: %1").arg(e.alias());
}, },
[](const RoomNameEvent &e) { [](const RoomNameEvent &e) {
return (e.name().isEmpty()) ? tr("cleared the room name") return (e.name().isEmpty()) ? tr("cleared the room name") : tr("set the room name to: %1").arg(e.name().toHtmlEscaped());
: tr("set the room name to: %1")
.arg(e.name().toHtmlEscaped());
}, },
[prettyPrint](const RoomTopicEvent &e) { [prettyPrint](const RoomTopicEvent &e) {
return (e.topic().isEmpty()) return (e.topic().isEmpty()) ? tr("cleared the topic") : tr("set the topic to: %1").arg(prettyPrint ? Quotient::prettyPrint(e.topic()) : e.topic());
? tr("cleared the topic") },
: tr("set the topic to: %1") [](const RoomAvatarEvent &) {
.arg(prettyPrint ? Quotient::prettyPrint(e.topic()) return tr("changed the room avatar");
: e.topic());
}, },
[](const RoomAvatarEvent&) { return tr("changed the room avatar"); },
[](const EncryptionEvent &) { [](const EncryptionEvent &) {
return tr("activated End-to-End Encryption"); return tr("activated End-to-End Encryption");
}, },
[](const RoomCreateEvent &e) { [](const RoomCreateEvent &e) {
return (e.isUpgrade() ? tr("upgraded the room to version %1") return (e.isUpgrade() ? tr("upgraded the room to version %1") : tr("created the room, version %1")).arg(e.version().isEmpty() ? "1" : e.version().toHtmlEscaped());
: tr("created the room, version %1"))
.arg(e.version().isEmpty() ? "1" : e.version().toHtmlEscaped());
}, },
[](const StateEventBase &e) { [](const StateEventBase &e) {
// A small hack for state events from TWIM bot // A small hack for state events from TWIM bot
return e.stateKey() == "twim" return e.stateKey() == "twim"
? tr("updated the database", "TWIM bot updated the database") ? tr("updated the database", "TWIM bot updated the database")
: e.stateKey().isEmpty() : e.stateKey().isEmpty() ? tr("updated %1 state", "%1 - Matrix event type").arg(e.matrixType()) : tr("updated %1 state for %2", "%1 - Matrix event type, %2 - state key").arg(e.matrixType(), e.stateKey().toHtmlEscaped());
? tr("updated %1 state", "%1 - Matrix event type")
.arg(e.matrixType())
: tr("updated %1 state for %2",
"%1 - Matrix event type, %2 - state key")
.arg(e.matrixType(),
e.stateKey().toHtmlEscaped());
}, },
tr("Unknown event")); tr("Unknown event"));
} }
void SpectralRoom::changeAvatar(QUrl localFile) { void SpectralRoom::changeAvatar(QUrl localFile)
{
const auto job = connection()->uploadFile(localFile.toLocalFile()); const auto job = connection()->uploadFile(localFile.toLocalFile());
if (isJobRunning(job)) { if (isJobRunning(job)) {
connect(job, &BaseJob::success, this, [this, job] { connect(job, &BaseJob::success, this, [this, job] {
connection()->callApi<SetRoomStateWithKeyJob>( connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar", localUser()->id(), QJsonObject {{"url", job->contentUri()}});
id(), "m.room.avatar", localUser()->id(), QJsonObject{{"url", job->contentUri()}});
}); });
} }
} }
void SpectralRoom::addLocalAlias(const QString& alias) { void SpectralRoom::addLocalAlias(const QString &alias)
{
auto a = aliases(); auto a = aliases();
if (a.contains(alias)) if (a.contains(alias))
return; return;
@@ -413,7 +379,8 @@ void SpectralRoom::addLocalAlias(const QString& alias) {
setLocalAliases(a); setLocalAliases(a);
} }
void SpectralRoom::removeLocalAlias(const QString& alias) { void SpectralRoom::removeLocalAlias(const QString &alias)
{
auto a = aliases(); auto a = aliases();
if (!a.contains(alias)) if (!a.contains(alias))
return; return;
@@ -423,10 +390,10 @@ void SpectralRoom::removeLocalAlias(const QString& alias) {
setLocalAliases(a); setLocalAliases(a);
} }
QString SpectralRoom::markdownToHTML(const QString& markdown) { QString SpectralRoom::markdownToHTML(const QString &markdown)
{
const auto str = markdown.toUtf8(); const auto str = markdown.toUtf8();
char* tmp_buf = char *tmp_buf = cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_DEFAULT);
cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_DEFAULT);
const std::string html(tmp_buf); const std::string html(tmp_buf);
@@ -440,9 +407,8 @@ QString SpectralRoom::markdownToHTML(const QString& markdown) {
return result; return result;
} }
void SpectralRoom::postArbitaryMessage(const QString& text, void SpectralRoom::postArbitaryMessage(const QString &text, MessageEventType type, const QString &replyEventId)
MessageEventType type, {
const QString& replyEventId) {
const auto parsedHTML = markdownToHTML(text); const auto parsedHTML = markdownToHTML(text);
const bool isRichText = Qt::mightBeRichText(parsedHTML); const bool isRichText = Qt::mightBeRichText(parsedHTML);
@@ -453,7 +419,8 @@ void SpectralRoom::postArbitaryMessage(const QString& text,
} }
} }
QString msgTypeToString(MessageEventType msgType) { QString msgTypeToString(MessageEventType msgType)
{
switch (msgType) { switch (msgType) {
case MessageEventType::Text: case MessageEventType::Text:
return "m.text"; return "m.text";
@@ -476,9 +443,8 @@ QString msgTypeToString(MessageEventType msgType) {
} }
} }
void SpectralRoom::postPlainMessage(const QString& text, void SpectralRoom::postPlainMessage(const QString &text, MessageEventType type, const QString &replyEventId)
MessageEventType type, {
const QString& replyEventId) {
bool isReply = !replyEventId.isEmpty(); bool isReply = !replyEventId.isEmpty();
const auto replyIt = findInTimeline(replyEventId); const auto replyIt = findInTimeline(replyEventId);
if (replyIt == timelineEdge()) if (replyIt == timelineEdge())
@@ -521,10 +487,8 @@ void SpectralRoom::postPlainMessage(const QString& text,
Room::postMessage(text, type); Room::postMessage(text, type);
} }
void SpectralRoom::postHtmlMessage(const QString& text, void SpectralRoom::postHtmlMessage(const QString &text, const QString &html, MessageEventType type, const QString &replyEventId)
const QString& html, {
MessageEventType type,
const QString& replyEventId) {
bool isReply = !replyEventId.isEmpty(); bool isReply = !replyEventId.isEmpty();
const auto replyIt = findInTimeline(replyEventId); const auto replyIt = findInTimeline(replyEventId);
if (replyIt == timelineEdge()) if (replyIt == timelineEdge())
@@ -567,8 +531,8 @@ void SpectralRoom::postHtmlMessage(const QString& text,
Room::postHtmlMessage(text, html, type); Room::postHtmlMessage(text, html, type);
} }
void SpectralRoom::toggleReaction(const QString& eventId, void SpectralRoom::toggleReaction(const QString &eventId, const QString &reaction)
const QString& reaction) { {
if (eventId.isEmpty() || reaction.isEmpty()) if (eventId.isEmpty() || reaction.isEmpty())
return; return;
@@ -604,7 +568,8 @@ void SpectralRoom::toggleReaction(const QString& eventId,
} }
} }
bool SpectralRoom::containsUser(QString userID) const { bool SpectralRoom::containsUser(QString userID) const
{
auto u = Room::user(userID); auto u = Room::user(userID);
if (!u) if (!u)
@@ -613,7 +578,8 @@ bool SpectralRoom::containsUser(QString userID) const {
return Room::memberJoinState(u) != JoinState::Leave; return Room::memberJoinState(u) != JoinState::Leave;
} }
bool SpectralRoom::canSendEvent(const QString& eventType) const { bool SpectralRoom::canSendEvent(const QString &eventType) const
{
auto plEvent = getCurrentState<RoomPowerLevelsEvent>(); auto plEvent = getCurrentState<RoomPowerLevelsEvent>();
auto pl = plEvent->powerLevelForEvent(eventType); auto pl = plEvent->powerLevelForEvent(eventType);
auto currentPl = plEvent->powerLevelForUser(localUser()->id()); auto currentPl = plEvent->powerLevelForUser(localUser()->id());
@@ -621,7 +587,8 @@ bool SpectralRoom::canSendEvent(const QString& eventType) const {
return currentPl >= pl; return currentPl >= pl;
} }
bool SpectralRoom::canSendState(const QString& eventType) const { bool SpectralRoom::canSendState(const QString &eventType) const
{
auto plEvent = getCurrentState<RoomPowerLevelsEvent>(); auto plEvent = getCurrentState<RoomPowerLevelsEvent>();
auto pl = plEvent->powerLevelForState(eventType); auto pl = plEvent->powerLevelForState(eventType);
auto currentPl = plEvent->powerLevelForUser(localUser()->id()); auto currentPl = plEvent->powerLevelForUser(localUser()->id());

View File

@@ -18,21 +18,17 @@
using namespace Quotient; using namespace Quotient;
class SpectralRoom : public Room { class SpectralRoom : public Room
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged) Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
Q_PROPERTY(QString cachedInput MEMBER m_cachedInput NOTIFY cachedInputChanged) Q_PROPERTY(QString cachedInput MEMBER m_cachedInput NOTIFY cachedInputChanged)
Q_PROPERTY(bool hasFileUploading READ hasFileUploading WRITE Q_PROPERTY(bool hasFileUploading READ hasFileUploading WRITE setHasFileUploading NOTIFY hasFileUploadingChanged)
setHasFileUploading NOTIFY hasFileUploadingChanged) Q_PROPERTY(int fileUploadingProgress READ fileUploadingProgress NOTIFY fileUploadingProgressChanged)
Q_PROPERTY(int fileUploadingProgress READ fileUploadingProgress NOTIFY Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
fileUploadingProgressChanged)
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged
STORED false)
public: public:
explicit SpectralRoom(Connection* connection, explicit SpectralRoom(Connection *connection, QString roomId, JoinState joinState = {});
QString roomId,
JoinState joinState = {});
QVariantList getUsersTyping() const; QVariantList getUsersTyping() const;
@@ -41,8 +37,12 @@ class SpectralRoom : public Room {
QDateTime lastActiveTime() const; QDateTime lastActiveTime() const;
bool hasFileUploading() const { return m_hasFileUploading; } bool hasFileUploading() const
void setHasFileUploading(bool value) { {
return m_hasFileUploading;
}
void setHasFileUploading(bool value)
{
if (value == m_hasFileUploading) { if (value == m_hasFileUploading) {
return; return;
} }
@@ -50,8 +50,12 @@ class SpectralRoom : public Room {
emit hasFileUploadingChanged(); emit hasFileUploadingChanged();
} }
int fileUploadingProgress() const { return m_fileUploadingProgress; } int fileUploadingProgress() const
void setFileUploadingProgress(int value) { {
return m_fileUploadingProgress;
}
void setFileUploadingProgress(int value)
{
if (m_fileUploadingProgress == value) { if (m_fileUploadingProgress == value) {
return; return;
} }
@@ -69,9 +73,7 @@ class SpectralRoom : public Room {
QString avatarMediaId() const; QString avatarMediaId() const;
QString eventToString(const RoomEvent& evt, QString eventToString(const RoomEvent &evt, Qt::TextFormat format = Qt::PlainText, bool removeReply = true) const;
Qt::TextFormat format = Qt::PlainText,
bool removeReply = true) const;
Q_INVOKABLE bool containsUser(QString userID) const; Q_INVOKABLE bool containsUser(QString userID) const;
@@ -108,16 +110,9 @@ class SpectralRoom : public Room {
void acceptInvitation(); void acceptInvitation();
void forget(); void forget();
void sendTypingNotification(bool isTyping); void sendTypingNotification(bool isTyping);
void postArbitaryMessage(const QString& text, void postArbitaryMessage(const QString &text, MessageEventType type = MessageEventType::Text, const QString &replyEventId = "");
MessageEventType type = MessageEventType::Text, void postPlainMessage(const QString &text, MessageEventType type = MessageEventType::Text, const QString &replyEventId = "");
const QString& replyEventId = ""); void postHtmlMessage(const QString &text, const QString &html, MessageEventType type = MessageEventType::Text, const QString &replyEventId = "");
void postPlainMessage(const QString& text,
MessageEventType type = MessageEventType::Text,
const QString& replyEventId = "");
void postHtmlMessage(const QString& text,
const QString& html,
MessageEventType type = MessageEventType::Text,
const QString& replyEventId = "");
void changeAvatar(QUrl localFile); void changeAvatar(QUrl localFile);
void addLocalAlias(const QString &alias); void addLocalAlias(const QString &alias);
void removeLocalAlias(const QString &alias); void removeLocalAlias(const QString &alias);

View File

@@ -1,5 +1,6 @@
#include "spectraluser.h" #include "spectraluser.h"
QColor SpectralUser::color() { QColor SpectralUser::color()
{
return QColor::fromHslF(hueF(), 0.7, 0.5, 1); return QColor::fromHslF(hueF(), 0.7, 0.5, 1);
} }

View File

@@ -8,12 +8,15 @@
using namespace Quotient; using namespace Quotient;
class SpectralUser : public User { class SpectralUser : public User
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(QColor color READ color CONSTANT) Q_PROPERTY(QColor color READ color CONSTANT)
public: public:
SpectralUser(QString userId, Connection *connection) SpectralUser(QString userId, Connection *connection)
: User(userId, connection) {} : User(userId, connection)
{
}
QColor color(); QColor color();
}; };

View File

@@ -13,14 +13,13 @@
#endif #endif
MsgCountComposedIcon::MsgCountComposedIcon(const QString &filename) MsgCountComposedIcon::MsgCountComposedIcon(const QString &filename)
: QIconEngine() { : QIconEngine()
{
icon_ = QIcon(filename); icon_ = QIcon(filename);
} }
void MsgCountComposedIcon::paint(QPainter* painter, void MsgCountComposedIcon::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
const QRect& rect, {
QIcon::Mode mode,
QIcon::State state) {
painter->setRenderHint(QPainter::TextAntialiasing); painter->setRenderHint(QPainter::TextAntialiasing);
painter->setRenderHint(QPainter::SmoothPixmapTransform); painter->setRenderHint(QPainter::SmoothPixmapTransform);
painter->setRenderHint(QPainter::Antialiasing); painter->setRenderHint(QPainter::Antialiasing);
@@ -41,8 +40,7 @@ void MsgCountComposedIcon::paint(QPainter* painter,
painter->setPen(Qt::NoPen); painter->setPen(Qt::NoPen);
painter->setFont(QFont("Open Sans", 8, QFont::Black)); painter->setFont(QFont("Open Sans", 8, QFont::Black));
QRectF bubble(rect.width() - BubbleDiameter, rect.height() - BubbleDiameter, QRectF bubble(rect.width() - BubbleDiameter, rect.height() - BubbleDiameter, BubbleDiameter, BubbleDiameter);
BubbleDiameter, BubbleDiameter);
painter->drawEllipse(bubble); painter->drawEllipse(bubble);
painter->setPen(QPen(textColor)); painter->setPen(QPen(textColor));
painter->setBrush(Qt::NoBrush); painter->setBrush(Qt::NoBrush);
@@ -55,12 +53,13 @@ void MsgCountComposedIcon::paint(QPainter* painter,
} }
} }
QIconEngine* MsgCountComposedIcon::clone() const { QIconEngine *MsgCountComposedIcon::clone() const
{
return new MsgCountComposedIcon(*this); return new MsgCountComposedIcon(*this);
} }
QList<QSize> MsgCountComposedIcon::availableSizes(QIcon::Mode mode, QList<QSize> MsgCountComposedIcon::availableSizes(QIcon::Mode mode, QIcon::State state) const
QIcon::State state) const { {
Q_UNUSED(mode) Q_UNUSED(mode)
Q_UNUSED(state) Q_UNUSED(state)
QList<QSize> sizes; QList<QSize> sizes;
@@ -73,9 +72,8 @@ QList<QSize> MsgCountComposedIcon::availableSizes(QIcon::Mode mode,
return sizes; return sizes;
} }
QPixmap MsgCountComposedIcon::pixmap(const QSize& size, QPixmap MsgCountComposedIcon::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
QIcon::Mode mode, {
QIcon::State state) {
QImage img(size, QImage::Format_ARGB32); QImage img(size, QImage::Format_ARGB32);
img.fill(qRgba(0, 0, 0, 0)); img.fill(qRgba(0, 0, 0, 0));
QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion); QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion);
@@ -86,7 +84,9 @@ QPixmap MsgCountComposedIcon::pixmap(const QSize& size,
return result; return result;
} }
TrayIcon::TrayIcon(QObject* parent) : QSystemTrayIcon(parent) { TrayIcon::TrayIcon(QObject *parent)
: QSystemTrayIcon(parent)
{
QMenu *menu = new QMenu(); QMenu *menu = new QMenu();
viewAction_ = new QAction(tr("Show"), parent); viewAction_ = new QAction(tr("Show"), parent);
quitAction_ = new QAction(tr("Quit"), parent); quitAction_ = new QAction(tr("Quit"), parent);
@@ -100,7 +100,8 @@ TrayIcon::TrayIcon(QObject* parent) : QSystemTrayIcon(parent) {
setContextMenu(menu); setContextMenu(menu);
} }
void TrayIcon::setNotificationCount(int count) { void TrayIcon::setNotificationCount(int count)
{
m_notificationCount = count; m_notificationCount = count;
// Use the native badge counter in MacOS. // Use the native badge counter in MacOS.
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
@@ -117,8 +118,7 @@ void TrayIcon::setNotificationCount(int count) {
return; return;
// Custom drawing on Linux. // Custom drawing on Linux.
MsgCountComposedIcon* tmp = MsgCountComposedIcon *tmp = static_cast<MsgCountComposedIcon *>(icon_->clone());
static_cast<MsgCountComposedIcon*>(icon_->clone());
tmp->msgCount = count; tmp->msgCount = count;
setIcon(QIcon(tmp)); setIcon(QIcon(tmp));
@@ -128,12 +128,12 @@ void TrayIcon::setNotificationCount(int count) {
emit notificationCountChanged(); emit notificationCountChanged();
} }
void TrayIcon::setIsOnline(bool online) { void TrayIcon::setIsOnline(bool online)
{
m_isOnline = online; m_isOnline = online;
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
if (online) { if (online) {
auto labelText = auto labelText = m_notificationCount == 0 ? "" : QString::number(m_notificationCount);
m_notificationCount == 0 ? "" : QString::number(m_notificationCount);
if (labelText == QtMac::badgeLabelText()) if (labelText == QtMac::badgeLabelText())
return; return;
@@ -154,8 +154,7 @@ void TrayIcon::setIsOnline(bool online) {
return; return;
// Custom drawing on Linux. // Custom drawing on Linux.
MsgCountComposedIcon* tmp = MsgCountComposedIcon *tmp = static_cast<MsgCountComposedIcon *>(icon_->clone());
static_cast<MsgCountComposedIcon*>(icon_->clone());
tmp->isOnline = online; tmp->isOnline = online;
setIcon(QIcon(tmp)); setIcon(QIcon(tmp));
@@ -165,7 +164,8 @@ void TrayIcon::setIsOnline(bool online) {
emit isOnlineChanged(); emit isOnlineChanged();
} }
void TrayIcon::setIconSource(const QString& source) { void TrayIcon::setIconSource(const QString &source)
{
m_iconSource = source; m_iconSource = source;
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) #if defined(Q_OS_MAC) || defined(Q_OS_WIN)
setIcon(QIcon(source)); setIcon(QIcon(source));

View File

@@ -10,20 +10,15 @@
#include <QRect> #include <QRect>
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
class MsgCountComposedIcon : public QIconEngine { class MsgCountComposedIcon : public QIconEngine
{
public: public:
MsgCountComposedIcon(const QString &filename); MsgCountComposedIcon(const QString &filename);
virtual void paint(QPainter* p, virtual void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state);
const QRect& rect,
QIcon::Mode mode,
QIcon::State state);
virtual QIconEngine *clone() const; virtual QIconEngine *clone() const;
virtual QList<QSize> availableSizes(QIcon::Mode mode, virtual QList<QSize> availableSizes(QIcon::Mode mode, QIcon::State state) const;
QIcon::State state) const; virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state);
virtual QPixmap pixmap(const QSize& size,
QIcon::Mode mode,
QIcon::State state);
int msgCount = 0; int msgCount = 0;
bool isOnline = true; // Default to false? bool isOnline = true; // Default to false?
@@ -34,23 +29,31 @@ class MsgCountComposedIcon : public QIconEngine {
QIcon icon_; QIcon icon_;
}; };
class TrayIcon : public QSystemTrayIcon { class TrayIcon : public QSystemTrayIcon
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString iconSource READ iconSource WRITE setIconSource NOTIFY Q_PROPERTY(QString iconSource READ iconSource WRITE setIconSource NOTIFY iconSourceChanged)
iconSourceChanged) Q_PROPERTY(int notificationCount READ notificationCount WRITE setNotificationCount NOTIFY notificationCountChanged)
Q_PROPERTY(int notificationCount READ notificationCount WRITE
setNotificationCount NOTIFY notificationCountChanged)
Q_PROPERTY(bool isOnline READ isOnline WRITE setIsOnline NOTIFY isOnlineChanged) Q_PROPERTY(bool isOnline READ isOnline WRITE setIsOnline NOTIFY isOnlineChanged)
public: public:
TrayIcon(QObject *parent = nullptr); TrayIcon(QObject *parent = nullptr);
QString iconSource() { return m_iconSource; } QString iconSource()
{
return m_iconSource;
}
void setIconSource(const QString &source); void setIconSource(const QString &source);
int notificationCount() { return m_notificationCount; } int notificationCount()
{
return m_notificationCount;
}
void setNotificationCount(int count); void setNotificationCount(int count);
bool isOnline() { return m_isOnline; } bool isOnline()
{
return m_isOnline;
}
void setIsOnline(bool online); void setIsOnline(bool online);
signals: signals:

View File

@@ -1,9 +1,12 @@
#include "userdirectorylistmodel.h" #include "userdirectorylistmodel.h"
UserDirectoryListModel::UserDirectoryListModel(QObject *parent) UserDirectoryListModel::UserDirectoryListModel(QObject *parent)
: QAbstractListModel(parent) {} : QAbstractListModel(parent)
{
}
void UserDirectoryListModel::setConnection(Connection* conn) { void UserDirectoryListModel::setConnection(Connection *conn)
{
if (m_connection == conn) if (m_connection == conn)
return; return;
@@ -30,7 +33,8 @@ void UserDirectoryListModel::setConnection(Connection* conn) {
emit limitedChanged(); emit limitedChanged();
} }
void UserDirectoryListModel::setKeyword(const QString& value) { void UserDirectoryListModel::setKeyword(const QString &value)
{
if (m_keyword == value) if (m_keyword == value)
return; return;
@@ -48,7 +52,8 @@ void UserDirectoryListModel::setKeyword(const QString& value) {
emit limitedChanged(); emit limitedChanged();
} }
void UserDirectoryListModel::search(int count) { void UserDirectoryListModel::search(int count)
{
if (count < 1) if (count < 1)
return; return;
@@ -83,8 +88,8 @@ void UserDirectoryListModel::search(int count) {
}); });
} }
QVariant UserDirectoryListModel::data(const QModelIndex& index, QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
int role) const { {
if (!index.isValid()) if (!index.isValid())
return QVariant(); return QVariant();
@@ -137,7 +142,8 @@ QVariant UserDirectoryListModel::data(const QModelIndex& index,
return {}; return {};
} }
QHash<int, QByteArray> UserDirectoryListModel::roleNames() const { QHash<int, QByteArray> UserDirectoryListModel::roleNames() const
{
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[NameRole] = "name"; roles[NameRole] = "name";
@@ -148,7 +154,8 @@ QHash<int, QByteArray> UserDirectoryListModel::roleNames() const {
return roles; return roles;
} }
int UserDirectoryListModel::rowCount(const QModelIndex& parent) const { int UserDirectoryListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) if (parent.isValid())
return 0; return 0;

View File

@@ -9,12 +9,11 @@
using namespace Quotient; using namespace Quotient;
class UserDirectoryListModel : public QAbstractListModel { class UserDirectoryListModel : public QAbstractListModel
{
Q_OBJECT Q_OBJECT
Q_PROPERTY(Connection* connection READ connection WRITE setConnection NOTIFY Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
connectionChanged) Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
Q_PROPERTY(
QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
Q_PROPERTY(bool limited READ limited NOTIFY limitedChanged) Q_PROPERTY(bool limited READ limited NOTIFY limitedChanged)
public: public:
@@ -32,13 +31,22 @@ class UserDirectoryListModel : public QAbstractListModel {
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
Connection* connection() const { return m_connection; } Connection *connection() const
{
return m_connection;
}
void setConnection(Connection *value); void setConnection(Connection *value);
QString keyword() const { return m_keyword; } QString keyword() const
{
return m_keyword;
}
void setKeyword(const QString &value); void setKeyword(const QString &value);
bool limited() const { return m_limited; } bool limited() const
{
return m_limited;
}
Q_INVOKABLE void search(int count = 50); Q_INVOKABLE void search(int count = 50);

View File

@@ -13,9 +13,13 @@
#include "spectraluser.h" #include "spectraluser.h"
UserListModel::UserListModel(QObject *parent) UserListModel::UserListModel(QObject *parent)
: QAbstractListModel(parent), m_currentRoom(nullptr) {} : QAbstractListModel(parent)
, m_currentRoom(nullptr)
{
}
void UserListModel::setRoom(Quotient::Room* room) { void UserListModel::setRoom(Quotient::Room *room)
{
if (m_currentRoom == room) if (m_currentRoom == room)
return; return;
@@ -31,40 +35,41 @@ void UserListModel::setRoom(Quotient::Room* room) {
m_currentRoom = room; m_currentRoom = room;
if (m_currentRoom) { if (m_currentRoom) {
connect(m_currentRoom, &Room::userAdded, this, &UserListModel::userAdded); connect(m_currentRoom, &Room::userAdded, this, &UserListModel::userAdded);
connect(m_currentRoom, &Room::userRemoved, this, connect(m_currentRoom, &Room::userRemoved, this, &UserListModel::userRemoved);
&UserListModel::userRemoved); connect(m_currentRoom, &Room::memberAboutToRename, this, &UserListModel::userRemoved);
connect(m_currentRoom, &Room::memberAboutToRename, this, connect(m_currentRoom, &Room::memberRenamed, this, &UserListModel::userAdded);
&UserListModel::userRemoved);
connect(m_currentRoom, &Room::memberRenamed, this,
&UserListModel::userAdded);
{ {
m_users = m_currentRoom->users(); m_users = m_currentRoom->users();
std::sort(m_users.begin(), m_users.end(), room->memberSorter()); std::sort(m_users.begin(), m_users.end(), room->memberSorter());
} }
for (User *user : m_users) { for (User *user : m_users) {
connect(user, &User::defaultAvatarChanged, this, [user, this](){avatarChanged(user);}); connect(user, &User::defaultAvatarChanged, this, [user, this]() {
avatarChanged(user);
});
} }
connect(m_currentRoom->connection(), &Connection::loggedOut, this, connect(m_currentRoom->connection(), &Connection::loggedOut, this, [=] {
[=] { setRoom(nullptr); }); setRoom(nullptr);
});
qDebug() << m_users.count() << "user(s) in the room"; qDebug() << m_users.count() << "user(s) in the room";
} }
endResetModel(); endResetModel();
emit roomChanged(); emit roomChanged();
} }
Quotient::User* UserListModel::userAt(QModelIndex index) const { Quotient::User *UserListModel::userAt(QModelIndex index) const
{
if (index.row() < 0 || index.row() >= m_users.size()) if (index.row() < 0 || index.row() >= m_users.size())
return nullptr; return nullptr;
return m_users.at(index.row()); return m_users.at(index.row());
} }
QVariant UserListModel::data(const QModelIndex& index, int role) const { QVariant UserListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) if (!index.isValid())
return QVariant(); return QVariant();
if (index.row() >= m_users.count()) { if (index.row() >= m_users.count()) {
qDebug() qDebug() << "UserListModel, something's wrong: index.row() >= m_users.count()";
<< "UserListModel, something's wrong: index.row() >= m_users.count()";
return {}; return {};
} }
auto user = m_users.at(index.row()); auto user = m_users.at(index.row());
@@ -122,22 +127,27 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const {
return {}; return {};
} }
int UserListModel::rowCount(const QModelIndex& parent) const { int UserListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) if (parent.isValid())
return 0; return 0;
return m_users.count(); return m_users.count();
} }
void UserListModel::userAdded(Quotient::User* user) { void UserListModel::userAdded(Quotient::User *user)
{
auto pos = findUserPos(user); auto pos = findUserPos(user);
beginInsertRows(QModelIndex(), pos, pos); beginInsertRows(QModelIndex(), pos, pos);
m_users.insert(pos, user); m_users.insert(pos, user);
endInsertRows(); endInsertRows();
connect(user, &Quotient::User::defaultAvatarChanged, this, [user, this](){avatarChanged(user);}); connect(user, &Quotient::User::defaultAvatarChanged, this, [user, this]() {
avatarChanged(user);
});
} }
void UserListModel::userRemoved(Quotient::User* user) { void UserListModel::userRemoved(Quotient::User *user)
{
auto pos = findUserPos(user); auto pos = findUserPos(user);
if (pos != m_users.size()) { if (pos != m_users.size()) {
beginRemoveRows(QModelIndex(), pos, pos); beginRemoveRows(QModelIndex(), pos, pos);
@@ -148,7 +158,8 @@ void UserListModel::userRemoved(Quotient::User* user) {
qWarning() << "Trying to remove a room member not in the user list"; qWarning() << "Trying to remove a room member not in the user list";
} }
void UserListModel::refresh(Quotient::User* user, QVector<int> roles) { void UserListModel::refresh(Quotient::User *user, QVector<int> roles)
{
auto pos = findUserPos(user); auto pos = findUserPos(user);
if (pos != m_users.size()) if (pos != m_users.size())
emit dataChanged(index(pos), index(pos), roles); emit dataChanged(index(pos), index(pos), roles);
@@ -156,19 +167,23 @@ void UserListModel::refresh(Quotient::User* user, QVector<int> roles) {
qWarning() << "Trying to access a room member not in the user list"; qWarning() << "Trying to access a room member not in the user list";
} }
void UserListModel::avatarChanged(Quotient::User* user) { void UserListModel::avatarChanged(Quotient::User *user)
{
refresh(user, {AvatarRole}); refresh(user, {AvatarRole});
} }
int UserListModel::findUserPos(User* user) const { int UserListModel::findUserPos(User *user) const
{
return findUserPos(m_currentRoom->roomMembername(user)); return findUserPos(m_currentRoom->roomMembername(user));
} }
int UserListModel::findUserPos(const QString& username) const { int UserListModel::findUserPos(const QString &username) const
{
return m_currentRoom->memberSorter().lowerBoundIndex(m_users, username); return m_currentRoom->memberSorter().lowerBoundIndex(m_users, username);
} }
QHash<int, QByteArray> UserListModel::roleNames() const { QHash<int, QByteArray> UserListModel::roleNames() const
{
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[NameRole] = "name"; roles[NameRole] = "name";

View File

@@ -6,13 +6,15 @@
#include <QObject> #include <QObject>
#include <QtCore/QAbstractListModel> #include <QtCore/QAbstractListModel>
namespace Quotient { namespace Quotient
{
class Connection; class Connection;
class Room; class Room;
class User; class User;
} // namespace Quotient } // namespace Quotient
class UserType : public QObject { class UserType : public QObject
{
Q_OBJECT Q_OBJECT
public: public:
@@ -26,10 +28,10 @@ class UserType : public QObject {
Q_ENUMS(Types) Q_ENUMS(Types)
}; };
class UserListModel : public QAbstractListModel { class UserListModel : public QAbstractListModel
{
Q_OBJECT Q_OBJECT
Q_PROPERTY( Q_PROPERTY(Quotient::Room *room READ room WRITE setRoom NOTIFY roomChanged)
Quotient::Room* room READ room WRITE setRoom NOTIFY roomChanged)
public: public:
enum EventRoles { enum EventRoles {
NameRole = Qt::UserRole + 1, NameRole = Qt::UserRole + 1,
@@ -41,7 +43,10 @@ class UserListModel : public QAbstractListModel {
UserListModel(QObject *parent = nullptr); UserListModel(QObject *parent = nullptr);
Quotient::Room* room() const { return m_currentRoom; } Quotient::Room *room() const
{
return m_currentRoom;
}
void setRoom(Quotient::Room *room); void setRoom(Quotient::Room *room);
Quotient::User *userAt(QModelIndex index) const; Quotient::User *userAt(QModelIndex index) const;

View File

@@ -13,18 +13,13 @@
#include <events/roommemberevent.h> #include <events/roommemberevent.h>
#include <events/simplestateevents.h> #include <events/simplestateevents.h>
namespace utils { namespace utils
static const QRegularExpression removeReplyRegex{ {
"> <.*?>.*?\\n\\n", QRegularExpression::DotMatchesEverythingOption}; static const QRegularExpression removeReplyRegex {"> <.*?>.*?\\n\\n", QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression removeRichReplyRegex{ static const QRegularExpression removeRichReplyRegex {"<mx-reply>.*?</mx-reply>", QRegularExpression::DotMatchesEverythingOption};
"<mx-reply>.*?</mx-reply>", QRegularExpression::DotMatchesEverythingOption}; static const QRegularExpression codePillRegExp {"<pre><code[^>]*>(.*?)</code></pre>", QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression codePillRegExp{ static const QRegularExpression userPillRegExp {"<a href=\"https://matrix.to/#/@.*?:.*?\">(.*?)</a>", QRegularExpression::DotMatchesEverythingOption};
"<pre><code[^>]*>(.*?)</code></pre>", QRegularExpression::DotMatchesEverythingOption}; static const QRegularExpression strikethroughRegExp {"<del>(.*?)</del>", QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression userPillRegExp{
"<a href=\"https://matrix.to/#/@.*?:.*?\">(.*?)</a>",
QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression strikethroughRegExp{
"<del>(.*?)</del>", QRegularExpression::DotMatchesEverythingOption};
} // namespace utils } // namespace utils
#endif #endif