Apply Clang Format
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
|||||||
build
|
build
|
||||||
|
.clang-format
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -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})
|
||||||
|
|||||||
@@ -7,86 +7,88 @@
|
|||||||
|
|
||||||
#include "room.h"
|
#include "room.h"
|
||||||
|
|
||||||
AccountListModel::AccountListModel(QObject* parent)
|
AccountListModel::AccountListModel(QObject *parent)
|
||||||
: QAbstractListModel(parent) {}
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
void AccountListModel::setController(Controller* value) {
|
|
||||||
if (m_controller == value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
|
|
||||||
m_connections.clear();
|
|
||||||
m_controller = value;
|
|
||||||
m_connections += m_controller->connections();
|
|
||||||
|
|
||||||
connect(m_controller, &Controller::connectionAdded, this,
|
|
||||||
[=](Connection* conn) {
|
|
||||||
if (!conn) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
beginInsertRows(QModelIndex(), m_connections.count(),
|
|
||||||
m_connections.count());
|
|
||||||
m_connections.append(conn);
|
|
||||||
endInsertRows();
|
|
||||||
});
|
|
||||||
connect(m_controller, &Controller::connectionDropped, this,
|
|
||||||
[=](Connection* conn) {
|
|
||||||
qDebug() << "Dropping connection" << conn->userId();
|
|
||||||
if (!conn) {
|
|
||||||
qDebug() << "Trying to remove null connection";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
conn->disconnect(this);
|
|
||||||
const auto it =
|
|
||||||
std::find(m_connections.begin(), m_connections.end(), conn);
|
|
||||||
if (it == m_connections.end())
|
|
||||||
return; // Already deleted, nothing to do
|
|
||||||
const int row = it - m_connections.begin();
|
|
||||||
beginRemoveRows(QModelIndex(), row, row);
|
|
||||||
m_connections.erase(it);
|
|
||||||
endRemoveRows();
|
|
||||||
});
|
|
||||||
emit controllerChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant AccountListModel::data(const QModelIndex& index, int role) const {
|
void AccountListModel::setController(Controller *value)
|
||||||
if (!index.isValid()) {
|
{
|
||||||
|
if (m_controller == value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
m_connections.clear();
|
||||||
|
m_controller = value;
|
||||||
|
m_connections += m_controller->connections();
|
||||||
|
|
||||||
|
connect(m_controller, &Controller::connectionAdded, this, [=](Connection *conn) {
|
||||||
|
if (!conn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
beginInsertRows(QModelIndex(), m_connections.count(), m_connections.count());
|
||||||
|
m_connections.append(conn);
|
||||||
|
endInsertRows();
|
||||||
|
});
|
||||||
|
connect(m_controller, &Controller::connectionDropped, this, [=](Connection *conn) {
|
||||||
|
qDebug() << "Dropping connection" << conn->userId();
|
||||||
|
if (!conn) {
|
||||||
|
qDebug() << "Trying to remove null connection";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
conn->disconnect(this);
|
||||||
|
const auto it = std::find(m_connections.begin(), m_connections.end(), conn);
|
||||||
|
if (it == m_connections.end())
|
||||||
|
return; // Already deleted, nothing to do
|
||||||
|
const int row = it - m_connections.begin();
|
||||||
|
beginRemoveRows(QModelIndex(), row, row);
|
||||||
|
m_connections.erase(it);
|
||||||
|
endRemoveRows();
|
||||||
|
});
|
||||||
|
emit controllerChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant AccountListModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index.row() >= m_connections.count()) {
|
||||||
|
qDebug() << "AccountListModel, something's wrong: index.row() >= "
|
||||||
|
"m_users.count()";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto m_connection = m_connections.at(index.row());
|
||||||
|
|
||||||
|
if (role == UserRole) {
|
||||||
|
return QVariant::fromValue(m_connection->user());
|
||||||
|
}
|
||||||
|
if (role == ConnectionRole) {
|
||||||
|
return QVariant::fromValue(m_connection);
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
|
||||||
|
|
||||||
if (index.row() >= m_connections.count()) {
|
|
||||||
qDebug() << "AccountListModel, something's wrong: index.row() >= "
|
|
||||||
"m_users.count()";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto m_connection = m_connections.at(index.row());
|
|
||||||
|
|
||||||
if (role == UserRole) {
|
|
||||||
return QVariant::fromValue(m_connection->user());
|
|
||||||
}
|
|
||||||
if (role == ConnectionRole) {
|
|
||||||
return QVariant::fromValue(m_connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int AccountListModel::rowCount(const QModelIndex& parent) const {
|
int AccountListModel::rowCount(const QModelIndex &parent) const
|
||||||
if (parent.isValid()) {
|
{
|
||||||
return 0;
|
if (parent.isValid()) {
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
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";
|
||||||
roles[ConnectionRole] = "connection";
|
roles[ConnectionRole] = "connection";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,29 +11,32 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
class AccountListModel : public QAbstractListModel {
|
class AccountListModel : public QAbstractListModel
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(Controller* controller READ controller WRITE setController NOTIFY
|
Q_OBJECT
|
||||||
controllerChanged)
|
Q_PROPERTY(Controller *controller READ controller WRITE setController NOTIFY controllerChanged)
|
||||||
public:
|
public:
|
||||||
enum EventRoles { UserRole = Qt::UserRole + 1, ConnectionRole };
|
enum EventRoles { UserRole = Qt::UserRole + 1, ConnectionRole };
|
||||||
|
|
||||||
AccountListModel(QObject* parent = nullptr);
|
AccountListModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
QVariant data(const QModelIndex& index, int role = UserRole) const override;
|
QVariant data(const QModelIndex &index, int role = UserRole) const override;
|
||||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Controller* controller() const { return m_controller; }
|
Controller *controller() const
|
||||||
void setController(Controller* value);
|
{
|
||||||
|
return m_controller;
|
||||||
|
}
|
||||||
|
void setController(Controller *value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Controller* m_controller = nullptr;
|
Controller *m_controller = nullptr;
|
||||||
QVector<Connection*> m_connections;
|
QVector<Connection *> m_connections;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void controllerChanged();
|
void controllerChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ACCOUNTLISTMODEL_H
|
#endif // ACCOUNTLISTMODEL_H
|
||||||
|
|||||||
@@ -37,362 +37,355 @@
|
|||||||
#include "spectraluser.h"
|
#include "spectraluser.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
Controller::Controller(QObject* parent) : QObject(parent) {
|
Controller::Controller(QObject *parent)
|
||||||
QApplication::setQuitOnLastWindowClosed(false);
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
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() {
|
|
||||||
for (auto c : m_connections) {
|
|
||||||
c->stopSync();
|
|
||||||
c->saveState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline QString accessTokenFileName(const AccountSettings& account) {
|
|
||||||
QString fileName = account.userId();
|
|
||||||
fileName.replace(':', '_');
|
|
||||||
return QStandardPaths::writableLocation(
|
|
||||||
QStandardPaths::AppLocalDataLocation) +
|
|
||||||
'/' + fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::loginWithCredentials(QString serverAddr,
|
|
||||||
QString user,
|
|
||||||
QString pass,
|
|
||||||
QString deviceName) {
|
|
||||||
if (user.isEmpty() || pass.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deviceName.isEmpty()) {
|
|
||||||
deviceName = "Spectral " + QSysInfo::machineHostName() + " " +
|
|
||||||
QSysInfo::productType() + " " + QSysInfo::productVersion() +
|
|
||||||
" " + QSysInfo::currentCpuArchitecture();
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl serverUrl(serverAddr);
|
|
||||||
|
|
||||||
auto conn = new Connection(this);
|
|
||||||
if (serverUrl.isValid()) {
|
|
||||||
conn->setHomeserver(serverUrl);
|
|
||||||
}
|
|
||||||
conn->connectToServer(user, pass, deviceName, "");
|
|
||||||
|
|
||||||
connect(conn, &Connection::connected, [=] {
|
|
||||||
AccountSettings account(conn->userId());
|
|
||||||
account.setKeepLoggedIn(true);
|
|
||||||
account.clearAccessToken(); // Drop the legacy - just in case
|
|
||||||
account.setHomeserver(conn->homeserver());
|
|
||||||
account.setDeviceId(conn->deviceId());
|
|
||||||
account.setDeviceName(deviceName);
|
|
||||||
if (!saveAccessTokenToKeyChain(account, conn->accessToken()))
|
|
||||||
qWarning() << "Couldn't save access token";
|
|
||||||
account.sync();
|
|
||||||
addConnection(conn);
|
|
||||||
setConnection(conn);
|
|
||||||
});
|
|
||||||
connect(conn, &Connection::networkError,
|
|
||||||
[=](QString error, QString, int, int) {
|
|
||||||
emit errorOccured("Network Error", error);
|
|
||||||
});
|
|
||||||
connect(conn, &Connection::loginError, [=](QString error, QString) {
|
|
||||||
emit errorOccured("Login Failed", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::loginWithAccessToken(QString serverAddr,
|
|
||||||
QString user,
|
|
||||||
QString token,
|
|
||||||
QString deviceName) {
|
|
||||||
if (user.isEmpty() || token.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl serverUrl(serverAddr);
|
|
||||||
|
|
||||||
auto conn = new Connection(this);
|
|
||||||
if (serverUrl.isValid()) {
|
|
||||||
conn->setHomeserver(serverUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(conn, &Connection::connected, [=] {
|
|
||||||
AccountSettings account(conn->userId());
|
|
||||||
account.setKeepLoggedIn(true);
|
|
||||||
account.clearAccessToken(); // Drop the legacy - just in case
|
|
||||||
account.setHomeserver(conn->homeserver());
|
|
||||||
account.setDeviceId(conn->deviceId());
|
|
||||||
account.setDeviceName(deviceName);
|
|
||||||
if (!saveAccessTokenToKeyChain(account, conn->accessToken()))
|
|
||||||
qWarning() << "Couldn't save access token";
|
|
||||||
account.sync();
|
|
||||||
addConnection(conn);
|
|
||||||
setConnection(conn);
|
|
||||||
});
|
|
||||||
connect(conn, &Connection::networkError,
|
|
||||||
[=](QString error, QString, int, int) {
|
|
||||||
emit errorOccured("Network Error", error);
|
|
||||||
});
|
|
||||||
conn->connectWithToken(user, token, deviceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::logout(Connection* conn) {
|
|
||||||
if (!conn) {
|
|
||||||
qCritical() << "Attempt to logout null connection";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsGroup("Accounts").remove(conn->userId());
|
|
||||||
QFile(accessTokenFileName(AccountSettings(conn->userId()))).remove();
|
|
||||||
|
|
||||||
QKeychain::DeletePasswordJob job(qAppName());
|
|
||||||
job.setAutoDelete(true);
|
|
||||||
job.setKey(conn->userId());
|
|
||||||
QEventLoop loop;
|
|
||||||
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop,
|
|
||||||
&QEventLoop::quit);
|
|
||||||
job.start();
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
auto logoutJob = conn->callApi<LogoutJob>();
|
|
||||||
connect(logoutJob, &LogoutJob::finished, conn, [=] {
|
|
||||||
conn->stopSync();
|
|
||||||
emit conn->stateChanged();
|
|
||||||
emit conn->loggedOut();
|
|
||||||
if (!m_connections.isEmpty())
|
|
||||||
setConnection(m_connections[0]);
|
|
||||||
});
|
|
||||||
connect(logoutJob, &LogoutJob::failure, this, [=] {
|
|
||||||
emit errorOccured("Server-side Logout Failed", logoutJob->errorString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::addConnection(Connection* c) {
|
|
||||||
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
|
|
||||||
|
|
||||||
m_connections += c;
|
|
||||||
|
|
||||||
c->setLazyLoading(true);
|
|
||||||
|
|
||||||
connect(c, &Connection::syncDone, this, [=] {
|
|
||||||
setBusy(false);
|
|
||||||
|
|
||||||
emit syncDone();
|
|
||||||
|
|
||||||
c->sync(30000);
|
|
||||||
c->saveState();
|
|
||||||
});
|
|
||||||
connect(c, &Connection::loggedOut, this, [=] { dropConnection(c); });
|
|
||||||
connect(&m_ncm, &QNetworkConfigurationManager::onlineStateChanged,
|
|
||||||
[=](bool status) {
|
|
||||||
if (!status) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
c->stopSync();
|
|
||||||
c->sync(30000);
|
|
||||||
});
|
|
||||||
|
|
||||||
using namespace Quotient;
|
|
||||||
|
|
||||||
setBusy(true);
|
|
||||||
|
|
||||||
c->sync();
|
|
||||||
|
|
||||||
emit connectionAdded(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::dropConnection(Connection* c) {
|
|
||||||
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
|
|
||||||
m_connections.removeOne(c);
|
|
||||||
|
|
||||||
emit connectionDropped(c);
|
|
||||||
c->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::invokeLogin() {
|
|
||||||
using namespace Quotient;
|
|
||||||
const auto accounts = SettingsGroup("Accounts").childGroups();
|
|
||||||
for (const auto& accountId : accounts) {
|
|
||||||
AccountSettings account{accountId};
|
|
||||||
if (!account.homeserver().isEmpty()) {
|
|
||||||
auto accessToken = loadAccessTokenFromKeyChain(account);
|
|
||||||
|
|
||||||
auto c = new Connection(account.homeserver(), this);
|
|
||||||
auto deviceName = account.deviceName();
|
|
||||||
connect(c, &Connection::connected, this, [=] {
|
|
||||||
c->loadState();
|
|
||||||
addConnection(c);
|
|
||||||
});
|
|
||||||
connect(c, &Connection::loginError, [=](QString error, QString) {
|
|
||||||
emit errorOccured("Login Failed", error);
|
|
||||||
logout(c);
|
|
||||||
});
|
|
||||||
connect(c, &Connection::networkError,
|
|
||||||
[=](QString error, QString, int, int) {
|
|
||||||
emit errorOccured("Network Error", error);
|
|
||||||
});
|
|
||||||
c->connectWithToken(account.userId(), accessToken, account.deviceId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_connections.isEmpty()) {
|
|
||||||
setConnection(m_connections[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit initiated();
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Controller::loadAccessTokenFromFile(const AccountSettings& account) {
|
|
||||||
QFile accountTokenFile{accessTokenFileName(account)};
|
|
||||||
if (accountTokenFile.open(QFile::ReadOnly)) {
|
|
||||||
if (accountTokenFile.size() < 1024)
|
|
||||||
return accountTokenFile.readAll();
|
|
||||||
|
|
||||||
qWarning() << "File" << accountTokenFile.fileName() << "is"
|
|
||||||
<< accountTokenFile.size()
|
|
||||||
<< "bytes long - too long for a token, ignoring it.";
|
|
||||||
}
|
|
||||||
qWarning() << "Could not open access token file"
|
|
||||||
<< accountTokenFile.fileName();
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Controller::loadAccessTokenFromKeyChain(
|
|
||||||
const AccountSettings& account) {
|
|
||||||
qDebug() << "Read the access token from the keychain for "
|
|
||||||
<< account.userId();
|
|
||||||
QKeychain::ReadPasswordJob job(qAppName());
|
|
||||||
job.setAutoDelete(false);
|
|
||||||
job.setKey(account.userId());
|
|
||||||
QEventLoop loop;
|
|
||||||
QKeychain::ReadPasswordJob::connect(&job, &QKeychain::Job::finished, &loop,
|
|
||||||
&QEventLoop::quit);
|
|
||||||
job.start();
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (job.error() == QKeychain::Error::NoError) {
|
|
||||||
return job.binaryData();
|
|
||||||
}
|
|
||||||
|
|
||||||
qWarning() << "Could not read the access token from the keychain: "
|
|
||||||
<< qPrintable(job.errorString());
|
|
||||||
// no access token from the keychain, try token file
|
|
||||||
auto accessToken = loadAccessTokenFromFile(account);
|
|
||||||
if (job.error() == QKeychain::Error::EntryNotFound) {
|
|
||||||
if (!accessToken.isEmpty()) {
|
|
||||||
qDebug() << "Migrating the access token from file to the keychain for "
|
|
||||||
<< account.userId();
|
|
||||||
bool removed = false;
|
|
||||||
bool saved = saveAccessTokenToKeyChain(account, accessToken);
|
|
||||||
if (saved) {
|
|
||||||
QFile accountTokenFile{accessTokenFileName(account)};
|
|
||||||
removed = accountTokenFile.remove();
|
|
||||||
}
|
|
||||||
if (!(saved && removed)) {
|
|
||||||
qDebug() << "Migrating the access token from the file to the keychain "
|
|
||||||
"failed";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Controller::saveAccessTokenToFile(const AccountSettings& account,
|
|
||||||
const QByteArray& accessToken) {
|
|
||||||
// (Re-)Make a dedicated file for access_token.
|
|
||||||
QFile accountTokenFile{accessTokenFileName(account)};
|
|
||||||
accountTokenFile.remove(); // Just in case
|
|
||||||
|
|
||||||
auto fileDir = QFileInfo(accountTokenFile).dir();
|
|
||||||
if (!((fileDir.exists() || fileDir.mkpath(".")) &&
|
|
||||||
accountTokenFile.open(QFile::WriteOnly))) {
|
|
||||||
emit errorOccured("I/O Denied", "Cannot save access token.");
|
|
||||||
} else {
|
|
||||||
accountTokenFile.write(accessToken);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Controller::saveAccessTokenToKeyChain(const AccountSettings& account,
|
|
||||||
const QByteArray& accessToken) {
|
|
||||||
qDebug() << "Save the access token to the keychain for " << account.userId();
|
|
||||||
QKeychain::WritePasswordJob job(qAppName());
|
|
||||||
job.setAutoDelete(false);
|
|
||||||
job.setKey(account.userId());
|
|
||||||
job.setBinaryData(accessToken);
|
|
||||||
QEventLoop loop;
|
|
||||||
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop,
|
|
||||||
&QEventLoop::quit);
|
|
||||||
job.start();
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (job.error()) {
|
|
||||||
qWarning() << "Could not save access token to the keychain: "
|
|
||||||
<< qPrintable(job.errorString());
|
|
||||||
return saveAccessTokenToFile(account, accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::joinRoom(Connection* c, const QString& alias) {
|
|
||||||
if (!alias.contains(":"))
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto knownServer = alias.mid(alias.indexOf(":") + 1);
|
|
||||||
auto joinRoomJob = c->joinRoom(alias, QStringList{knownServer});
|
|
||||||
joinRoomJob->connect(joinRoomJob, &JoinRoomJob::failure, [=] {
|
|
||||||
emit errorOccured("Join Room Failed", joinRoomJob->errorString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::createRoom(Connection* c,
|
|
||||||
const QString& name,
|
|
||||||
const QString& topic) {
|
|
||||||
auto createRoomJob =
|
|
||||||
c->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
|
||||||
createRoomJob->connect(createRoomJob, &CreateRoomJob::failure, [=] {
|
|
||||||
emit errorOccured("Create Room Failed", createRoomJob->errorString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::createDirectChat(Connection* c, const QString& userID) {
|
|
||||||
auto createRoomJob = c->createDirectChat(userID);
|
|
||||||
createRoomJob->connect(createRoomJob, &CreateRoomJob::failure, [=] {
|
|
||||||
emit errorOccured("Create Direct Chat Failed",
|
|
||||||
createRoomJob->errorString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::playAudio(QUrl localFile) {
|
|
||||||
auto player = new QMediaPlayer;
|
|
||||||
player->setMedia(localFile);
|
|
||||||
player->play();
|
|
||||||
connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::changeAvatar(Connection* conn, QUrl localFile) {
|
|
||||||
auto job = conn->uploadFile(localFile.toLocalFile());
|
|
||||||
if (isJobRunning(job)) {
|
|
||||||
connect(job, &BaseJob::success, this, [conn, job] {
|
|
||||||
conn->callApi<SetAvatarUrlJob>(conn->userId(), job->contentUri());
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::markAllMessagesAsRead(Connection* conn) {
|
Controller::~Controller()
|
||||||
for (auto room : conn->allRooms()) {
|
{
|
||||||
room->markAllMessagesAsRead();
|
for (auto c : m_connections) {
|
||||||
}
|
c->stopSync();
|
||||||
|
c->saveState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QString accessTokenFileName(const AccountSettings &account)
|
||||||
|
{
|
||||||
|
QString fileName = account.userId();
|
||||||
|
fileName.replace(':', '_');
|
||||||
|
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + '/' + fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::loginWithCredentials(QString serverAddr, QString user, QString pass, QString deviceName)
|
||||||
|
{
|
||||||
|
if (user.isEmpty() || pass.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceName.isEmpty()) {
|
||||||
|
deviceName = "Spectral " + QSysInfo::machineHostName() + " " + QSysInfo::productType() + " " + QSysInfo::productVersion() + " " + QSysInfo::currentCpuArchitecture();
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl serverUrl(serverAddr);
|
||||||
|
|
||||||
|
auto conn = new Connection(this);
|
||||||
|
if (serverUrl.isValid()) {
|
||||||
|
conn->setHomeserver(serverUrl);
|
||||||
|
}
|
||||||
|
conn->connectToServer(user, pass, deviceName, "");
|
||||||
|
|
||||||
|
connect(conn, &Connection::connected, [=] {
|
||||||
|
AccountSettings account(conn->userId());
|
||||||
|
account.setKeepLoggedIn(true);
|
||||||
|
account.clearAccessToken(); // Drop the legacy - just in case
|
||||||
|
account.setHomeserver(conn->homeserver());
|
||||||
|
account.setDeviceId(conn->deviceId());
|
||||||
|
account.setDeviceName(deviceName);
|
||||||
|
if (!saveAccessTokenToKeyChain(account, conn->accessToken()))
|
||||||
|
qWarning() << "Couldn't save access token";
|
||||||
|
account.sync();
|
||||||
|
addConnection(conn);
|
||||||
|
setConnection(conn);
|
||||||
|
});
|
||||||
|
connect(conn, &Connection::networkError, [=](QString error, QString, int, int) {
|
||||||
|
emit errorOccured("Network Error", error);
|
||||||
|
});
|
||||||
|
connect(conn, &Connection::loginError, [=](QString error, QString) {
|
||||||
|
emit errorOccured("Login Failed", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::loginWithAccessToken(QString serverAddr, QString user, QString token, QString deviceName)
|
||||||
|
{
|
||||||
|
if (user.isEmpty() || token.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl serverUrl(serverAddr);
|
||||||
|
|
||||||
|
auto conn = new Connection(this);
|
||||||
|
if (serverUrl.isValid()) {
|
||||||
|
conn->setHomeserver(serverUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(conn, &Connection::connected, [=] {
|
||||||
|
AccountSettings account(conn->userId());
|
||||||
|
account.setKeepLoggedIn(true);
|
||||||
|
account.clearAccessToken(); // Drop the legacy - just in case
|
||||||
|
account.setHomeserver(conn->homeserver());
|
||||||
|
account.setDeviceId(conn->deviceId());
|
||||||
|
account.setDeviceName(deviceName);
|
||||||
|
if (!saveAccessTokenToKeyChain(account, conn->accessToken()))
|
||||||
|
qWarning() << "Couldn't save access token";
|
||||||
|
account.sync();
|
||||||
|
addConnection(conn);
|
||||||
|
setConnection(conn);
|
||||||
|
});
|
||||||
|
connect(conn, &Connection::networkError, [=](QString error, QString, int, int) {
|
||||||
|
emit errorOccured("Network Error", error);
|
||||||
|
});
|
||||||
|
conn->connectWithToken(user, token, deviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::logout(Connection *conn)
|
||||||
|
{
|
||||||
|
if (!conn) {
|
||||||
|
qCritical() << "Attempt to logout null connection";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsGroup("Accounts").remove(conn->userId());
|
||||||
|
QFile(accessTokenFileName(AccountSettings(conn->userId()))).remove();
|
||||||
|
|
||||||
|
QKeychain::DeletePasswordJob job(qAppName());
|
||||||
|
job.setAutoDelete(true);
|
||||||
|
job.setKey(conn->userId());
|
||||||
|
QEventLoop loop;
|
||||||
|
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||||
|
job.start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
auto logoutJob = conn->callApi<LogoutJob>();
|
||||||
|
connect(logoutJob, &LogoutJob::finished, conn, [=] {
|
||||||
|
conn->stopSync();
|
||||||
|
emit conn->stateChanged();
|
||||||
|
emit conn->loggedOut();
|
||||||
|
if (!m_connections.isEmpty())
|
||||||
|
setConnection(m_connections[0]);
|
||||||
|
});
|
||||||
|
connect(logoutJob, &LogoutJob::failure, this, [=] {
|
||||||
|
emit errorOccured("Server-side Logout Failed", logoutJob->errorString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::addConnection(Connection *c)
|
||||||
|
{
|
||||||
|
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
|
||||||
|
|
||||||
|
m_connections += c;
|
||||||
|
|
||||||
|
c->setLazyLoading(true);
|
||||||
|
|
||||||
|
connect(c, &Connection::syncDone, this, [=] {
|
||||||
|
setBusy(false);
|
||||||
|
|
||||||
|
emit syncDone();
|
||||||
|
|
||||||
|
c->sync(30000);
|
||||||
|
c->saveState();
|
||||||
|
});
|
||||||
|
connect(c, &Connection::loggedOut, this, [=] {
|
||||||
|
dropConnection(c);
|
||||||
|
});
|
||||||
|
connect(&m_ncm, &QNetworkConfigurationManager::onlineStateChanged, [=](bool status) {
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
c->stopSync();
|
||||||
|
c->sync(30000);
|
||||||
|
});
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
c->sync();
|
||||||
|
|
||||||
|
emit connectionAdded(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::dropConnection(Connection *c)
|
||||||
|
{
|
||||||
|
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
|
||||||
|
m_connections.removeOne(c);
|
||||||
|
|
||||||
|
emit connectionDropped(c);
|
||||||
|
c->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::invokeLogin()
|
||||||
|
{
|
||||||
|
using namespace Quotient;
|
||||||
|
const auto accounts = SettingsGroup("Accounts").childGroups();
|
||||||
|
for (const auto &accountId : accounts) {
|
||||||
|
AccountSettings account {accountId};
|
||||||
|
if (!account.homeserver().isEmpty()) {
|
||||||
|
auto accessToken = loadAccessTokenFromKeyChain(account);
|
||||||
|
|
||||||
|
auto c = new Connection(account.homeserver(), this);
|
||||||
|
auto deviceName = account.deviceName();
|
||||||
|
connect(c, &Connection::connected, this, [=] {
|
||||||
|
c->loadState();
|
||||||
|
addConnection(c);
|
||||||
|
});
|
||||||
|
connect(c, &Connection::loginError, [=](QString error, QString) {
|
||||||
|
emit errorOccured("Login Failed", error);
|
||||||
|
logout(c);
|
||||||
|
});
|
||||||
|
connect(c, &Connection::networkError, [=](QString error, QString, int, int) {
|
||||||
|
emit errorOccured("Network Error", error);
|
||||||
|
});
|
||||||
|
c->connectWithToken(account.userId(), accessToken, account.deviceId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_connections.isEmpty()) {
|
||||||
|
setConnection(m_connections[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit initiated();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray Controller::loadAccessTokenFromFile(const AccountSettings &account)
|
||||||
|
{
|
||||||
|
QFile accountTokenFile {accessTokenFileName(account)};
|
||||||
|
if (accountTokenFile.open(QFile::ReadOnly)) {
|
||||||
|
if (accountTokenFile.size() < 1024)
|
||||||
|
return accountTokenFile.readAll();
|
||||||
|
|
||||||
|
qWarning() << "File" << accountTokenFile.fileName() << "is" << accountTokenFile.size() << "bytes long - too long for a token, ignoring it.";
|
||||||
|
}
|
||||||
|
qWarning() << "Could not open access token file" << accountTokenFile.fileName();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
|
||||||
|
{
|
||||||
|
qDebug() << "Read the access token from the keychain for " << account.userId();
|
||||||
|
QKeychain::ReadPasswordJob job(qAppName());
|
||||||
|
job.setAutoDelete(false);
|
||||||
|
job.setKey(account.userId());
|
||||||
|
QEventLoop loop;
|
||||||
|
QKeychain::ReadPasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||||
|
job.start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
if (job.error() == QKeychain::Error::NoError) {
|
||||||
|
return job.binaryData();
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "Could not read the access token from the keychain: " << qPrintable(job.errorString());
|
||||||
|
// no access token from the keychain, try token file
|
||||||
|
auto accessToken = loadAccessTokenFromFile(account);
|
||||||
|
if (job.error() == QKeychain::Error::EntryNotFound) {
|
||||||
|
if (!accessToken.isEmpty()) {
|
||||||
|
qDebug() << "Migrating the access token from file to the keychain for " << account.userId();
|
||||||
|
bool removed = false;
|
||||||
|
bool saved = saveAccessTokenToKeyChain(account, accessToken);
|
||||||
|
if (saved) {
|
||||||
|
QFile accountTokenFile {accessTokenFileName(account)};
|
||||||
|
removed = accountTokenFile.remove();
|
||||||
|
}
|
||||||
|
if (!(saved && removed)) {
|
||||||
|
qDebug() << "Migrating the access token from the file to the keychain "
|
||||||
|
"failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Controller::saveAccessTokenToFile(const AccountSettings &account, const QByteArray &accessToken)
|
||||||
|
{
|
||||||
|
// (Re-)Make a dedicated file for access_token.
|
||||||
|
QFile accountTokenFile {accessTokenFileName(account)};
|
||||||
|
accountTokenFile.remove(); // Just in case
|
||||||
|
|
||||||
|
auto fileDir = QFileInfo(accountTokenFile).dir();
|
||||||
|
if (!((fileDir.exists() || fileDir.mkpath(".")) && accountTokenFile.open(QFile::WriteOnly))) {
|
||||||
|
emit errorOccured("I/O Denied", "Cannot save access token.");
|
||||||
|
} else {
|
||||||
|
accountTokenFile.write(accessToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
|
||||||
|
{
|
||||||
|
qDebug() << "Save the access token to the keychain for " << account.userId();
|
||||||
|
QKeychain::WritePasswordJob job(qAppName());
|
||||||
|
job.setAutoDelete(false);
|
||||||
|
job.setKey(account.userId());
|
||||||
|
job.setBinaryData(accessToken);
|
||||||
|
QEventLoop loop;
|
||||||
|
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||||
|
job.start();
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
if (job.error()) {
|
||||||
|
qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString());
|
||||||
|
return saveAccessTokenToFile(account, accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::joinRoom(Connection *c, const QString &alias)
|
||||||
|
{
|
||||||
|
if (!alias.contains(":"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto knownServer = alias.mid(alias.indexOf(":") + 1);
|
||||||
|
auto joinRoomJob = c->joinRoom(alias, QStringList {knownServer});
|
||||||
|
joinRoomJob->connect(joinRoomJob, &JoinRoomJob::failure, [=] {
|
||||||
|
emit errorOccured("Join Room Failed", joinRoomJob->errorString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::createRoom(Connection *c, const QString &name, const QString &topic)
|
||||||
|
{
|
||||||
|
auto createRoomJob = c->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
||||||
|
createRoomJob->connect(createRoomJob, &CreateRoomJob::failure, [=] {
|
||||||
|
emit errorOccured("Create Room Failed", createRoomJob->errorString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::createDirectChat(Connection *c, const QString &userID)
|
||||||
|
{
|
||||||
|
auto createRoomJob = c->createDirectChat(userID);
|
||||||
|
createRoomJob->connect(createRoomJob, &CreateRoomJob::failure, [=] {
|
||||||
|
emit errorOccured("Create Direct Chat Failed", createRoomJob->errorString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::playAudio(QUrl localFile)
|
||||||
|
{
|
||||||
|
auto player = new QMediaPlayer;
|
||||||
|
player->setMedia(localFile);
|
||||||
|
player->play();
|
||||||
|
connect(player, &QMediaPlayer::stateChanged, [=] {
|
||||||
|
player->deleteLater();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::changeAvatar(Connection *conn, QUrl localFile)
|
||||||
|
{
|
||||||
|
auto job = conn->uploadFile(localFile.toLocalFile());
|
||||||
|
if (isJobRunning(job)) {
|
||||||
|
connect(job, &BaseJob::success, this, [conn, job] {
|
||||||
|
conn->callApi<SetAvatarUrlJob>(conn->userId(), job->contentUri());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::markAllMessagesAsRead(Connection *conn)
|
||||||
|
{
|
||||||
|
for (auto room : conn->allRooms()) {
|
||||||
|
room->markAllMessagesAsRead();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
189
src/controller.h
189
src/controller.h
@@ -22,109 +22,122 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
class Controller : public QObject {
|
class Controller : public QObject
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(int accountCount READ accountCount NOTIFY connectionAdded NOTIFY
|
Q_OBJECT
|
||||||
connectionDropped)
|
Q_PROPERTY(int accountCount READ accountCount NOTIFY connectionAdded NOTIFY connectionDropped)
|
||||||
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE
|
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
|
||||||
setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
|
Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||||
Q_PROPERTY(Connection* connection READ connection WRITE setConnection NOTIFY
|
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
||||||
connectionChanged)
|
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
||||||
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
|
||||||
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Controller(QObject* parent = nullptr);
|
explicit Controller(QObject *parent = nullptr);
|
||||||
~Controller();
|
~Controller();
|
||||||
|
|
||||||
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
|
||||||
|
{
|
||||||
// All the non-Q_INVOKABLE functions.
|
return m_connections;
|
||||||
void addConnection(Connection* c);
|
|
||||||
void dropConnection(Connection* c);
|
|
||||||
|
|
||||||
// All the Q_PROPERTYs.
|
|
||||||
int accountCount() { return m_connections.count(); }
|
|
||||||
|
|
||||||
bool quitOnLastWindowClosed() const {
|
|
||||||
return QApplication::quitOnLastWindowClosed();
|
|
||||||
}
|
|
||||||
void setQuitOnLastWindowClosed(bool value) {
|
|
||||||
if (quitOnLastWindowClosed() != value) {
|
|
||||||
QApplication::setQuitOnLastWindowClosed(value);
|
|
||||||
|
|
||||||
emit quitOnLastWindowClosedChanged();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool isOnline() const { return m_ncm.isOnline(); }
|
// All the non-Q_INVOKABLE functions.
|
||||||
|
void addConnection(Connection *c);
|
||||||
|
void dropConnection(Connection *c);
|
||||||
|
|
||||||
bool busy() const { return m_busy; }
|
// All the Q_PROPERTYs.
|
||||||
void setBusy(bool busy) {
|
int accountCount()
|
||||||
if (m_busy == busy) {
|
{
|
||||||
return;
|
return m_connections.count();
|
||||||
}
|
}
|
||||||
m_busy = busy;
|
|
||||||
emit busyChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
Connection* connection() const {
|
bool quitOnLastWindowClosed() const
|
||||||
if (m_connection.isNull())
|
{
|
||||||
return nullptr;
|
return QApplication::quitOnLastWindowClosed();
|
||||||
|
}
|
||||||
|
void setQuitOnLastWindowClosed(bool value)
|
||||||
|
{
|
||||||
|
if (quitOnLastWindowClosed() != value) {
|
||||||
|
QApplication::setQuitOnLastWindowClosed(value);
|
||||||
|
|
||||||
return m_connection;
|
emit quitOnLastWindowClosedChanged();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setConnection(Connection* conn) {
|
bool isOnline() const
|
||||||
if (conn == m_connection)
|
{
|
||||||
return;
|
return m_ncm.isOnline();
|
||||||
m_connection = conn;
|
}
|
||||||
emit connectionChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
bool busy() const
|
||||||
QVector<Connection*> m_connections;
|
{
|
||||||
QPointer<Connection> m_connection;
|
return m_busy;
|
||||||
QNetworkConfigurationManager m_ncm;
|
}
|
||||||
bool m_busy = false;
|
void setBusy(bool busy)
|
||||||
|
{
|
||||||
|
if (m_busy == busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_busy = busy;
|
||||||
|
emit busyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray loadAccessTokenFromFile(const AccountSettings& account);
|
Connection *connection() const
|
||||||
QByteArray loadAccessTokenFromKeyChain(const AccountSettings& account);
|
{
|
||||||
|
if (m_connection.isNull())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
bool saveAccessTokenToFile(const AccountSettings& account,
|
return m_connection;
|
||||||
const QByteArray& accessToken);
|
}
|
||||||
bool saveAccessTokenToKeyChain(const AccountSettings& account,
|
|
||||||
const QByteArray& accessToken);
|
|
||||||
void loadSettings();
|
|
||||||
void saveSettings() const;
|
|
||||||
|
|
||||||
private slots:
|
void setConnection(Connection *conn)
|
||||||
void invokeLogin();
|
{
|
||||||
|
if (conn == m_connection)
|
||||||
|
return;
|
||||||
|
m_connection = conn;
|
||||||
|
emit connectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
signals:
|
private:
|
||||||
void busyChanged();
|
QVector<Connection *> m_connections;
|
||||||
void errorOccured(QString error, QString detail);
|
QPointer<Connection> m_connection;
|
||||||
void syncDone();
|
QNetworkConfigurationManager m_ncm;
|
||||||
void connectionAdded(Connection* conn);
|
bool m_busy = false;
|
||||||
void connectionDropped(Connection* conn);
|
|
||||||
void initiated();
|
|
||||||
void notificationClicked(const QString roomId, const QString eventId);
|
|
||||||
void quitOnLastWindowClosedChanged();
|
|
||||||
void unreadCountChanged();
|
|
||||||
void connectionChanged();
|
|
||||||
void isOnlineChanged();
|
|
||||||
|
|
||||||
public slots:
|
QByteArray loadAccessTokenFromFile(const AccountSettings &account);
|
||||||
void logout(Connection* conn);
|
QByteArray loadAccessTokenFromKeyChain(const AccountSettings &account);
|
||||||
void joinRoom(Connection* c, const QString& alias);
|
|
||||||
void createRoom(Connection* c, const QString& name, const QString& topic);
|
bool saveAccessTokenToFile(const AccountSettings &account, const QByteArray &accessToken);
|
||||||
void createDirectChat(Connection* c, const QString& userID);
|
bool saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken);
|
||||||
void playAudio(QUrl localFile);
|
void loadSettings();
|
||||||
void changeAvatar(Connection* conn, QUrl localFile);
|
void saveSettings() const;
|
||||||
void markAllMessagesAsRead(Connection* conn);
|
|
||||||
|
private slots:
|
||||||
|
void invokeLogin();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void busyChanged();
|
||||||
|
void errorOccured(QString error, QString detail);
|
||||||
|
void syncDone();
|
||||||
|
void connectionAdded(Connection *conn);
|
||||||
|
void connectionDropped(Connection *conn);
|
||||||
|
void initiated();
|
||||||
|
void notificationClicked(const QString roomId, const QString eventId);
|
||||||
|
void quitOnLastWindowClosedChanged();
|
||||||
|
void unreadCountChanged();
|
||||||
|
void connectionChanged();
|
||||||
|
void isOnlineChanged();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void logout(Connection *conn);
|
||||||
|
void joinRoom(Connection *c, const QString &alias);
|
||||||
|
void createRoom(Connection *c, const QString &name, const QString &topic);
|
||||||
|
void createDirectChat(Connection *c, const QString &userID);
|
||||||
|
void playAudio(QUrl localFile);
|
||||||
|
void changeAvatar(Connection *conn, QUrl localFile);
|
||||||
|
void markAllMessagesAsRead(Connection *conn);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONTROLLER_H
|
#endif // CONTROLLER_H
|
||||||
|
|||||||
4076
src/emojimodel.cpp
4076
src/emojimodel.cpp
File diff suppressed because it is too large
Load Diff
108
src/emojimodel.h
108
src/emojimodel.h
@@ -12,69 +12,81 @@
|
|||||||
#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.shortname;
|
arch << object.unicode;
|
||||||
return arch;
|
arch << object.shortname;
|
||||||
}
|
return arch;
|
||||||
|
}
|
||||||
|
|
||||||
friend QDataStream& operator>>(QDataStream& arch, Emoji& object) {
|
friend QDataStream &operator>>(QDataStream &arch, Emoji &object)
|
||||||
arch >> object.unicode;
|
{
|
||||||
arch >> object.shortname;
|
arch >> object.unicode;
|
||||||
return arch;
|
arch >> object.shortname;
|
||||||
}
|
return arch;
|
||||||
|
}
|
||||||
|
|
||||||
QString unicode;
|
QString unicode;
|
||||||
QString shortname;
|
QString shortname;
|
||||||
|
|
||||||
Q_GADGET
|
Q_GADGET
|
||||||
Q_PROPERTY(QString unicode MEMBER unicode)
|
Q_PROPERTY(QString unicode MEMBER unicode)
|
||||||
Q_PROPERTY(QString shortname MEMBER shortname)
|
Q_PROPERTY(QString shortname MEMBER shortname)
|
||||||
};
|
};
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
Q_PROPERTY(QVariantList people MEMBER people CONSTANT)
|
Q_PROPERTY(QVariantList people MEMBER people CONSTANT)
|
||||||
Q_PROPERTY(QVariantList nature MEMBER nature CONSTANT)
|
Q_PROPERTY(QVariantList nature MEMBER nature CONSTANT)
|
||||||
Q_PROPERTY(QVariantList food MEMBER food CONSTANT)
|
Q_PROPERTY(QVariantList food MEMBER food CONSTANT)
|
||||||
Q_PROPERTY(QVariantList activity MEMBER activity CONSTANT)
|
Q_PROPERTY(QVariantList activity MEMBER activity CONSTANT)
|
||||||
Q_PROPERTY(QVariantList travel MEMBER travel CONSTANT)
|
Q_PROPERTY(QVariantList travel MEMBER travel CONSTANT)
|
||||||
Q_PROPERTY(QVariantList objects MEMBER objects CONSTANT)
|
Q_PROPERTY(QVariantList objects MEMBER objects CONSTANT)
|
||||||
Q_PROPERTY(QVariantList symbols MEMBER symbols CONSTANT)
|
Q_PROPERTY(QVariantList symbols MEMBER symbols CONSTANT)
|
||||||
Q_PROPERTY(QVariantList flags MEMBER flags CONSTANT)
|
Q_PROPERTY(QVariantList flags MEMBER flags CONSTANT)
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void historyChanged();
|
void historyChanged();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void emojiUsed(QVariant modelData);
|
void emojiUsed(QVariant modelData);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const QVariantList people;
|
static const QVariantList people;
|
||||||
static const QVariantList nature;
|
static const QVariantList nature;
|
||||||
static const QVariantList food;
|
static const QVariantList food;
|
||||||
static const QVariantList activity;
|
static const QVariantList activity;
|
||||||
static const QVariantList travel;
|
static const QVariantList travel;
|
||||||
static const QVariantList objects;
|
static const QVariantList objects;
|
||||||
static const QVariantList symbols;
|
static const QVariantList symbols;
|
||||||
static const QVariantList flags;
|
static const QVariantList flags;
|
||||||
|
|
||||||
QSettings* m_settings;
|
QSettings *m_settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // EMOJIMODEL_H
|
#endif // EMOJIMODEL_H
|
||||||
|
|||||||
@@ -11,36 +11,40 @@
|
|||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#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())
|
{
|
||||||
return false;
|
if (!localPath.isLocalFile())
|
||||||
|
return false;
|
||||||
|
|
||||||
auto i = image();
|
auto i = image();
|
||||||
|
|
||||||
if (i.isNull())
|
if (i.isNull())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
QString path = QFileInfo(localPath.toLocalFile()).absolutePath();
|
QString path = QFileInfo(localPath.toLocalFile()).absolutePath();
|
||||||
QDir dir;
|
QDir dir;
|
||||||
if (!dir.exists(path)) {
|
if (!dir.exists(path)) {
|
||||||
dir.mkpath(path);
|
dir.mkpath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
i.save(localPath.toLocalFile());
|
i.save(localPath.toLocalFile());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,24 +10,25 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
class ImageClipboard : public QObject {
|
class ImageClipboard : public QObject
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(bool hasImage READ hasImage NOTIFY imageChanged)
|
Q_OBJECT
|
||||||
Q_PROPERTY(QImage image READ image NOTIFY imageChanged)
|
Q_PROPERTY(bool hasImage READ hasImage NOTIFY imageChanged)
|
||||||
|
Q_PROPERTY(QImage image READ image NOTIFY imageChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ImageClipboard(QObject* parent = nullptr);
|
explicit ImageClipboard(QObject *parent = nullptr);
|
||||||
|
|
||||||
bool hasImage() const;
|
bool hasImage() const;
|
||||||
QImage image() const;
|
QImage image() const;
|
||||||
|
|
||||||
Q_INVOKABLE bool saveImage(const QUrl& localPath);
|
Q_INVOKABLE bool saveImage(const QUrl &localPath);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QClipboard* m_clipboard;
|
QClipboard *m_clipboard;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void imageChanged();
|
void imageChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IMAGECLIPBOARD_H
|
#endif // IMAGECLIPBOARD_H
|
||||||
|
|||||||
87
src/main.cpp
87
src/main.cpp
@@ -30,59 +30,56 @@
|
|||||||
|
|
||||||
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);
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
app.setOrganizationName("ENCOM");
|
app.setOrganizationName("ENCOM");
|
||||||
app.setOrganizationDomain("encom.eu.org");
|
app.setOrganizationDomain("encom.eu.org");
|
||||||
app.setApplicationName("Spectral");
|
app.setApplicationName("Spectral");
|
||||||
app.setWindowIcon(QIcon(":/assets/img/icon.png"));
|
app.setWindowIcon(QIcon(":/assets/img/icon.png"));
|
||||||
|
|
||||||
qmlRegisterType<Controller>("Spectral", 0, 1, "Controller");
|
qmlRegisterType<Controller>("Spectral", 0, 1, "Controller");
|
||||||
qmlRegisterType<AccountListModel>("Spectral", 0, 1, "AccountListModel");
|
qmlRegisterType<AccountListModel>("Spectral", 0, 1, "AccountListModel");
|
||||||
qmlRegisterType<RoomListModel>("Spectral", 0, 1, "RoomListModel");
|
qmlRegisterType<RoomListModel>("Spectral", 0, 1, "RoomListModel");
|
||||||
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, "NotificationsManager");
|
||||||
qmlRegisterType<NotificationsManager>("Spectral", 0, 1,
|
qmlRegisterType<TrayIcon>("Spectral", 0, 1, "TrayIcon");
|
||||||
"NotificationsManager");
|
qmlRegisterType<ImageClipboard>("Spectral", 0, 1, "ImageClipboard");
|
||||||
qmlRegisterType<TrayIcon>("Spectral", 0, 1, "TrayIcon");
|
qmlRegisterUncreatableType<RoomMessageEvent>("Spectral", 0, 1, "RoomMessageEvent", "ENUM");
|
||||||
qmlRegisterType<ImageClipboard>("Spectral", 0, 1, "ImageClipboard");
|
qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM");
|
||||||
qmlRegisterUncreatableType<RoomMessageEvent>("Spectral", 0, 1,
|
qmlRegisterUncreatableType<UserType>("Spectral", 0, 1, "UserType", "ENUM");
|
||||||
"RoomMessageEvent", "ENUM");
|
|
||||||
qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM");
|
|
||||||
qmlRegisterUncreatableType<UserType>("Spectral", 0, 1, "UserType", "ENUM");
|
|
||||||
|
|
||||||
qRegisterMetaType<User*>("User*");
|
qRegisterMetaType<User *>("User*");
|
||||||
qRegisterMetaType<User*>("const User*");
|
qRegisterMetaType<User *>("const User*");
|
||||||
qRegisterMetaType<User*>("const Quotient::User*");
|
qRegisterMetaType<User *>("const Quotient::User*");
|
||||||
qRegisterMetaType<Room*>("Room*");
|
qRegisterMetaType<Room *>("Room*");
|
||||||
qRegisterMetaType<Connection*>("Connection*");
|
qRegisterMetaType<Connection *>("Connection*");
|
||||||
qRegisterMetaType<MessageEventType>("MessageEventType");
|
qRegisterMetaType<MessageEventType>("MessageEventType");
|
||||||
qRegisterMetaType<SpectralRoom*>("SpectralRoom*");
|
qRegisterMetaType<SpectralRoom *>("SpectralRoom*");
|
||||||
qRegisterMetaType<SpectralUser*>("SpectralUser*");
|
qRegisterMetaType<SpectralUser *>("SpectralUser*");
|
||||||
qRegisterMetaType<GetRoomEventsJob*>("GetRoomEventsJob*");
|
qRegisterMetaType<GetRoomEventsJob *>("GetRoomEventsJob*");
|
||||||
|
|
||||||
qRegisterMetaTypeStreamOperators<Emoji>();
|
qRegisterMetaTypeStreamOperators<Emoji>();
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
|
|
||||||
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")));
|
||||||
if (engine.rootObjects().isEmpty())
|
if (engine.rootObjects().isEmpty())
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,113 +14,108 @@
|
|||||||
|
|
||||||
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(
|
if (requestedSize.isEmpty()) {
|
||||||
QStandardPaths::CacheLocation),
|
errorStr.clear();
|
||||||
mediaId,
|
emit finished();
|
||||||
QString::number(requestedSize.width()),
|
return;
|
||||||
QString::number(requestedSize.height()))),
|
|
||||||
errorStr("Image request hasn't started") {
|
|
||||||
if (requestedSize.isEmpty()) {
|
|
||||||
errorStr.clear();
|
|
||||||
emit finished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mediaId.count('/') != 1) {
|
|
||||||
errorStr =
|
|
||||||
tr("Media id '%1' doesn't follow server/mediaId pattern").arg(mediaId);
|
|
||||||
emit finished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage cachedImage;
|
|
||||||
if (cachedImage.load(localFile)) {
|
|
||||||
image = cachedImage;
|
|
||||||
errorStr.clear();
|
|
||||||
emit finished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!c) {
|
|
||||||
qWarning() << "Current connection is null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute a request on the main thread asynchronously
|
|
||||||
moveToThread(c->thread());
|
|
||||||
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest,
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThumbnailResponse::startRequest() {
|
|
||||||
// Runs in the main thread, not QML thread
|
|
||||||
Q_ASSERT(QThread::currentThread() == c->thread());
|
|
||||||
job = c->getThumbnail(mediaId, requestedSize);
|
|
||||||
// Connect to any possible outcome including abandonment
|
|
||||||
// to make sure the QML thread is not left stuck forever.
|
|
||||||
connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThumbnailResponse::prepareResult() {
|
|
||||||
Q_ASSERT(QThread::currentThread() == job->thread());
|
|
||||||
Q_ASSERT(job->error() != BaseJob::Pending);
|
|
||||||
{
|
|
||||||
QWriteLocker _(&lock);
|
|
||||||
if (job->error() == BaseJob::Success) {
|
|
||||||
image = job->thumbnail();
|
|
||||||
|
|
||||||
QString localPath = QFileInfo(localFile).absolutePath();
|
|
||||||
QDir dir;
|
|
||||||
if (!dir.exists(localPath))
|
|
||||||
dir.mkpath(localPath);
|
|
||||||
|
|
||||||
image.save(localFile);
|
|
||||||
|
|
||||||
errorStr.clear();
|
|
||||||
} else if (job->error() == BaseJob::Abandoned) {
|
|
||||||
errorStr = tr("Image request has been cancelled");
|
|
||||||
qDebug() << "ThumbnailResponse: cancelled for" << mediaId;
|
|
||||||
} else {
|
|
||||||
errorStr = job->errorString();
|
|
||||||
qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-"
|
|
||||||
<< errorStr;
|
|
||||||
}
|
}
|
||||||
job = nullptr;
|
if (mediaId.count('/') != 1) {
|
||||||
}
|
errorStr = tr("Media id '%1' doesn't follow server/mediaId pattern").arg(mediaId);
|
||||||
emit finished();
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage cachedImage;
|
||||||
|
if (cachedImage.load(localFile)) {
|
||||||
|
image = cachedImage;
|
||||||
|
errorStr.clear();
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!c) {
|
||||||
|
qWarning() << "Current connection is null";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute a request on the main thread asynchronously
|
||||||
|
moveToThread(c->thread());
|
||||||
|
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThumbnailResponse::doCancel() {
|
void ThumbnailResponse::startRequest()
|
||||||
// Runs in the main thread, not QML thread
|
{
|
||||||
if (job) {
|
// Runs in the main thread, not QML thread
|
||||||
|
Q_ASSERT(QThread::currentThread() == c->thread());
|
||||||
|
job = c->getThumbnail(mediaId, requestedSize);
|
||||||
|
// Connect to any possible outcome including abandonment
|
||||||
|
// to make sure the QML thread is not left stuck forever.
|
||||||
|
connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThumbnailResponse::prepareResult()
|
||||||
|
{
|
||||||
Q_ASSERT(QThread::currentThread() == job->thread());
|
Q_ASSERT(QThread::currentThread() == job->thread());
|
||||||
job->abandon();
|
Q_ASSERT(job->error() != BaseJob::Pending);
|
||||||
}
|
{
|
||||||
|
QWriteLocker _(&lock);
|
||||||
|
if (job->error() == BaseJob::Success) {
|
||||||
|
image = job->thumbnail();
|
||||||
|
|
||||||
|
QString localPath = QFileInfo(localFile).absolutePath();
|
||||||
|
QDir dir;
|
||||||
|
if (!dir.exists(localPath))
|
||||||
|
dir.mkpath(localPath);
|
||||||
|
|
||||||
|
image.save(localFile);
|
||||||
|
|
||||||
|
errorStr.clear();
|
||||||
|
} else if (job->error() == BaseJob::Abandoned) {
|
||||||
|
errorStr = tr("Image request has been cancelled");
|
||||||
|
qDebug() << "ThumbnailResponse: cancelled for" << mediaId;
|
||||||
|
} else {
|
||||||
|
errorStr = job->errorString();
|
||||||
|
qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-" << errorStr;
|
||||||
|
}
|
||||||
|
job = nullptr;
|
||||||
|
}
|
||||||
|
emit finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickTextureFactory* ThumbnailResponse::textureFactory() const {
|
void ThumbnailResponse::doCancel()
|
||||||
QReadLocker _(&lock);
|
{
|
||||||
return QQuickTextureFactory::textureFactoryForImage(image);
|
// Runs in the main thread, not QML thread
|
||||||
|
if (job) {
|
||||||
|
Q_ASSERT(QThread::currentThread() == job->thread());
|
||||||
|
job->abandon();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ThumbnailResponse::errorString() const {
|
QQuickTextureFactory *ThumbnailResponse::textureFactory() const
|
||||||
QReadLocker _(&lock);
|
{
|
||||||
return errorStr;
|
QReadLocker _(&lock);
|
||||||
|
return QQuickTextureFactory::textureFactoryForImage(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThumbnailResponse::cancel() {
|
QString ThumbnailResponse::errorString() const
|
||||||
QMetaObject::invokeMethod(this, &ThumbnailResponse::doCancel,
|
{
|
||||||
Qt::QueuedConnection);
|
QReadLocker _(&lock);
|
||||||
|
return errorStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickImageResponse* MatrixImageProvider::requestImageResponse(
|
void ThumbnailResponse::cancel()
|
||||||
const QString& id,
|
{
|
||||||
const QSize& requestedSize) {
|
QMetaObject::invokeMethod(this, &ThumbnailResponse::doCancel, Qt::QueuedConnection);
|
||||||
return new ThumbnailResponse(m_connection.loadRelaxed(), id, requestedSize);
|
}
|
||||||
|
|
||||||
|
QQuickImageResponse *MatrixImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
||||||
|
{
|
||||||
|
return new ThumbnailResponse(m_connection.loadRelaxed(), id, requestedSize);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,61 +16,63 @@
|
|||||||
#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
|
{
|
||||||
public:
|
Q_OBJECT
|
||||||
ThumbnailResponse(Quotient::Connection* c,
|
public:
|
||||||
QString mediaId,
|
ThumbnailResponse(Quotient::Connection *c, QString mediaId, const QSize &requestedSize);
|
||||||
const QSize& requestedSize);
|
~ThumbnailResponse() override = default;
|
||||||
~ThumbnailResponse() override = default;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void startRequest();
|
void startRequest();
|
||||||
void prepareResult();
|
void prepareResult();
|
||||||
void doCancel();
|
void doCancel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Quotient::Connection* c;
|
Quotient::Connection *c;
|
||||||
const QString mediaId;
|
const QString mediaId;
|
||||||
const QSize requestedSize;
|
const QSize requestedSize;
|
||||||
const QString localFile;
|
const QString localFile;
|
||||||
Quotient::MediaThumbnailJob* job = nullptr;
|
Quotient::MediaThumbnailJob *job = nullptr;
|
||||||
|
|
||||||
QImage image;
|
QImage image;
|
||||||
QString errorStr;
|
QString errorStr;
|
||||||
mutable QReadWriteLock lock; // Guards ONLY these two members above
|
mutable QReadWriteLock lock; // Guards ONLY these two members above
|
||||||
|
|
||||||
QQuickTextureFactory* textureFactory() const override;
|
QQuickTextureFactory *textureFactory() const override;
|
||||||
QString errorString() const override;
|
QString errorString() const override;
|
||||||
void cancel() override;
|
void cancel() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MatrixImageProvider : public QObject, public QQuickAsyncImageProvider {
|
class MatrixImageProvider : public QObject, public QQuickAsyncImageProvider
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(Quotient::Connection* connection READ connection WRITE
|
Q_OBJECT
|
||||||
setConnection NOTIFY connectionChanged)
|
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE 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) {
|
{
|
||||||
m_connection.storeRelaxed(connection);
|
return m_connection;
|
||||||
emit connectionChanged();
|
}
|
||||||
}
|
void setConnection(Quotient::Connection *connection)
|
||||||
|
{
|
||||||
|
m_connection.storeRelaxed(connection);
|
||||||
|
emit connectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QAtomicPointer<Quotient::Connection> m_connection;
|
QAtomicPointer<Quotient::Connection> m_connection;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MatrixImageProvider_H
|
#endif // MatrixImageProvider_H
|
||||||
|
|||||||
@@ -10,528 +10,482 @@
|
|||||||
#include <user.h>
|
#include <user.h>
|
||||||
|
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QtQml> // for qmlRegisterType()
|
#include <QtQml> // for qmlRegisterType()
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
QHash<int, QByteArray> MessageEventModel::roleNames() const {
|
QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
{
|
||||||
roles[EventTypeRole] = "eventType";
|
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||||
roles[MessageRole] = "message";
|
roles[EventTypeRole] = "eventType";
|
||||||
roles[EventIdRole] = "eventId";
|
roles[MessageRole] = "message";
|
||||||
roles[TimeRole] = "time";
|
roles[EventIdRole] = "eventId";
|
||||||
roles[SectionRole] = "section";
|
roles[TimeRole] = "time";
|
||||||
roles[AuthorRole] = "author";
|
roles[SectionRole] = "section";
|
||||||
roles[ContentRole] = "content";
|
roles[AuthorRole] = "author";
|
||||||
roles[ContentTypeRole] = "contentType";
|
roles[ContentRole] = "content";
|
||||||
roles[HighlightRole] = "highlight";
|
roles[ContentTypeRole] = "contentType";
|
||||||
roles[ReadMarkerRole] = "readMarker";
|
roles[HighlightRole] = "highlight";
|
||||||
roles[SpecialMarksRole] = "marks";
|
roles[ReadMarkerRole] = "readMarker";
|
||||||
roles[LongOperationRole] = "progressInfo";
|
roles[SpecialMarksRole] = "marks";
|
||||||
roles[AnnotationRole] = "annotation";
|
roles[LongOperationRole] = "progressInfo";
|
||||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
roles[AnnotationRole] = "annotation";
|
||||||
roles[ReplyRole] = "reply";
|
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||||
roles[UserMarkerRole] = "userMarker";
|
roles[ReplyRole] = "reply";
|
||||||
roles[ShowAuthorRole] = "showAuthor";
|
roles[UserMarkerRole] = "userMarker";
|
||||||
roles[ShowSectionRole] = "showSection";
|
roles[ShowAuthorRole] = "showAuthor";
|
||||||
roles[ReactionRole] = "reaction";
|
roles[ShowSectionRole] = "showSection";
|
||||||
return roles;
|
roles[ReactionRole] = "reaction";
|
||||||
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageEventModel::MessageEventModel(QObject* parent)
|
MessageEventModel::MessageEventModel(QObject *parent)
|
||||||
: QAbstractListModel(parent), m_currentRoom(nullptr) {
|
: QAbstractListModel(parent)
|
||||||
using namespace Quotient;
|
, m_currentRoom(nullptr)
|
||||||
qmlRegisterType<FileTransferInfo>();
|
{
|
||||||
qRegisterMetaType<FileTransferInfo>();
|
|
||||||
qmlRegisterUncreatableType<EventStatus>(
|
|
||||||
"Spectral", 0, 1, "EventStatus", "EventStatus is not an creatable type");
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageEventModel::~MessageEventModel() {}
|
|
||||||
|
|
||||||
void MessageEventModel::setRoom(SpectralRoom* room) {
|
|
||||||
if (room == m_currentRoom)
|
|
||||||
return;
|
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
if (m_currentRoom) {
|
|
||||||
m_currentRoom->disconnect(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_currentRoom = room;
|
|
||||||
if (room) {
|
|
||||||
lastReadEventId = room->readMarkerEventId();
|
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
connect(m_currentRoom, &Room::aboutToAddNewMessages, this,
|
qmlRegisterType<FileTransferInfo>();
|
||||||
[=](RoomEventsRange events) {
|
qRegisterMetaType<FileTransferInfo>();
|
||||||
beginInsertRows({}, timelineBaseIndex(),
|
qmlRegisterUncreatableType<EventStatus>("Spectral", 0, 1, "EventStatus", "EventStatus is not an creatable type");
|
||||||
timelineBaseIndex() + int(events.size()) - 1);
|
}
|
||||||
});
|
|
||||||
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this,
|
MessageEventModel::~MessageEventModel()
|
||||||
[=](RoomEventsRange events) {
|
{
|
||||||
if (rowCount() > 0)
|
}
|
||||||
rowBelowInserted = rowCount() - 1; // See #312
|
|
||||||
beginInsertRows({}, rowCount(),
|
void MessageEventModel::setRoom(SpectralRoom *room)
|
||||||
rowCount() + int(events.size()) - 1);
|
{
|
||||||
});
|
if (room == m_currentRoom)
|
||||||
connect(m_currentRoom, &Room::addedMessages, this,
|
return;
|
||||||
[=](int lowest, int biggest) {
|
|
||||||
endInsertRows();
|
beginResetModel();
|
||||||
if (biggest < m_currentRoom->maxTimelineIndex()) {
|
if (m_currentRoom) {
|
||||||
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() -
|
m_currentRoom->disconnect(this);
|
||||||
biggest + timelineBaseIndex() - 1;
|
}
|
||||||
refreshEventRoles(rowBelowInserted,
|
|
||||||
{ShowAuthorRole});
|
m_currentRoom = room;
|
||||||
}
|
if (room) {
|
||||||
for (auto i = m_currentRoom->maxTimelineIndex() - biggest;
|
lastReadEventId = room->readMarkerEventId();
|
||||||
i <= m_currentRoom->maxTimelineIndex() - lowest; ++i)
|
|
||||||
|
using namespace Quotient;
|
||||||
|
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [=](RoomEventsRange events) {
|
||||||
|
beginInsertRows({}, timelineBaseIndex(), timelineBaseIndex() + int(events.size()) - 1);
|
||||||
|
});
|
||||||
|
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [=](RoomEventsRange events) {
|
||||||
|
if (rowCount() > 0)
|
||||||
|
rowBelowInserted = rowCount() - 1; // See #312
|
||||||
|
beginInsertRows({}, rowCount(), rowCount() + int(events.size()) - 1);
|
||||||
|
});
|
||||||
|
connect(m_currentRoom, &Room::addedMessages, this, [=](int lowest, int biggest) {
|
||||||
|
endInsertRows();
|
||||||
|
if (biggest < m_currentRoom->maxTimelineIndex()) {
|
||||||
|
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1;
|
||||||
|
refreshEventRoles(rowBelowInserted, {ShowAuthorRole});
|
||||||
|
}
|
||||||
|
for (auto i = m_currentRoom->maxTimelineIndex() - biggest; 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::pendingEventAboutToMerge, this,
|
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
|
||||||
[this](RoomEvent*, int i) {
|
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [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
|
||||||
|
|
||||||
movingEvent = true;
|
movingEvent = true;
|
||||||
// Reverse i because row 0 is bottommost in the model
|
// Reverse i because row 0 is bottommost in the model
|
||||||
const auto row = timelineBaseIndex() - i - 1;
|
const auto row = timelineBaseIndex() - i - 1;
|
||||||
Q_ASSERT(beginMoveRows({}, row, row, {}, timelineBaseIndex()));
|
Q_ASSERT(beginMoveRows({}, row, row, {}, timelineBaseIndex()));
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::pendingEventMerged, this, [this] {
|
connect(m_currentRoom, &Room::pendingEventMerged, this, [this] {
|
||||||
if (movingEvent) {
|
if (movingEvent) {
|
||||||
endMoveRows();
|
endMoveRows();
|
||||||
movingEvent = false;
|
movingEvent = false;
|
||||||
}
|
}
|
||||||
refreshRow(timelineBaseIndex()); // Refresh the looks
|
refreshRow(timelineBaseIndex()); // Refresh the looks
|
||||||
refreshLastUserEvents(0);
|
refreshLastUserEvents(0);
|
||||||
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, &MessageEventModel::refreshRow);
|
||||||
connect(m_currentRoom, &Room::pendingEventChanged, this,
|
connect(m_currentRoom, &Room::pendingEventAboutToDiscard, this, [this](int i) {
|
||||||
&MessageEventModel::refreshRow);
|
beginRemoveRows({}, i, i);
|
||||||
connect(m_currentRoom, &Room::pendingEventAboutToDiscard, this,
|
});
|
||||||
[this](int i) { beginRemoveRows({}, i, i); });
|
connect(m_currentRoom, &Room::pendingEventDiscarded, this, &MessageEventModel::endRemoveRows);
|
||||||
connect(m_currentRoom, &Room::pendingEventDiscarded, this,
|
connect(m_currentRoom, &Room::readMarkerMoved, this, [this] {
|
||||||
&MessageEventModel::endRemoveRows);
|
refreshEventRoles(std::exchange(lastReadEventId, m_currentRoom->readMarkerEventId()), {ReadMarkerRole});
|
||||||
connect(m_currentRoom, &Room::readMarkerMoved, this, [this] {
|
refreshEventRoles(lastReadEventId, {ReadMarkerRole});
|
||||||
refreshEventRoles(
|
});
|
||||||
std::exchange(lastReadEventId, m_currentRoom->readMarkerEventId()),
|
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
|
||||||
{ReadMarkerRole});
|
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
|
||||||
refreshEventRoles(lastReadEventId, {ReadMarkerRole});
|
});
|
||||||
});
|
connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString &eventId) {
|
||||||
connect(m_currentRoom, &Room::replacedEvent, this,
|
if (eventId.isEmpty()) { // How did we get here?
|
||||||
[this](const RoomEvent* newEvent) {
|
|
||||||
refreshLastUserEvents(refreshEvent(newEvent->id()) -
|
|
||||||
timelineBaseIndex());
|
|
||||||
});
|
|
||||||
connect(m_currentRoom, &Room::updatedEvent, this,
|
|
||||||
[this](const QString& eventId) {
|
|
||||||
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);
|
refreshEventRoles(fromEventId, {UserMarkerRole});
|
||||||
connect(m_currentRoom, &Room::fileTransferCancelled, this,
|
refreshEventRoles(toEventId, {UserMarkerRole});
|
||||||
&MessageEventModel::refreshEvent);
|
});
|
||||||
connect(m_currentRoom, &Room::readMarkerForUserMoved, this,
|
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [=] {
|
||||||
[=](User*, QString fromEventId, QString toEventId) {
|
beginResetModel();
|
||||||
refreshEventRoles(fromEventId, {UserMarkerRole});
|
endResetModel();
|
||||||
refreshEventRoles(toEventId, {UserMarkerRole});
|
});
|
||||||
});
|
qDebug() << "Connected to room" << room->id() << "as" << room->localUser()->id();
|
||||||
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged,
|
} else
|
||||||
this, [=] {
|
lastReadEventId.clear();
|
||||||
beginResetModel();
|
endResetModel();
|
||||||
endResetModel();
|
|
||||||
});
|
|
||||||
qDebug() << "Connected to room" << room->id() << "as"
|
|
||||||
<< room->localUser()->id();
|
|
||||||
} else
|
|
||||||
lastReadEventId.clear();
|
|
||||||
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);
|
{
|
||||||
emit dataChanged(idx, idx, roles);
|
const auto idx = index(row);
|
||||||
|
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;
|
||||||
// First try pendingEvents because it is almost always very short.
|
// First try pendingEvents because it is almost always very short.
|
||||||
const auto pendingIt = m_currentRoom->findPendingEvent(id);
|
const auto pendingIt = m_currentRoom->findPendingEvent(id);
|
||||||
if (pendingIt != m_currentRoom->pendingEvents().end())
|
if (pendingIt != m_currentRoom->pendingEvents().end())
|
||||||
row = int(pendingIt - m_currentRoom->pendingEvents().begin());
|
row = int(pendingIt - m_currentRoom->pendingEvents().begin());
|
||||||
else {
|
else {
|
||||||
const auto timelineIt = m_currentRoom->findInTimeline(id);
|
const auto timelineIt = m_currentRoom->findInTimeline(id);
|
||||||
if (timelineIt == m_currentRoom->timelineEdge()) {
|
if (timelineIt == m_currentRoom->timelineEdge()) {
|
||||||
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()) + timelineBaseIndex();
|
||||||
}
|
}
|
||||||
row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) +
|
refreshEventRoles(row, roles);
|
||||||
timelineBaseIndex();
|
return row;
|
||||||
}
|
|
||||||
refreshEventRoles(row, roles);
|
|
||||||
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())
|
||||||
return ts;
|
return ts;
|
||||||
|
|
||||||
// The event is most likely redacted or just invalid.
|
// The event is most likely redacted or just invalid.
|
||||||
// Look for the nearest date around and slap zero time to it.
|
// Look for the nearest date around and slap zero time to it.
|
||||||
using Quotient::TimelineItem;
|
using Quotient::TimelineItem;
|
||||||
auto rit = std::find_if(baseIt, timeline.rend(), hasValidTimestamp);
|
auto rit = std::find_if(baseIt, timeline.rend(), hasValidTimestamp);
|
||||||
if (rit != timeline.rend())
|
if (rit != timeline.rend())
|
||||||
return {rit->event()->originTimestamp().date(), {0, 0}, Qt::LocalTime};
|
return {rit->event()->originTimestamp().date(), {0, 0}, Qt::LocalTime};
|
||||||
auto it = std::find_if(baseIt.base(), timeline.end(), hasValidTimestamp);
|
auto it = std::find_if(baseIt.base(), timeline.end(), hasValidTimestamp);
|
||||||
if (it != timeline.end())
|
if (it != timeline.end())
|
||||||
return {it->event()->originTimestamp().date(), {0, 0}, Qt::LocalTime};
|
return {it->event()->originTimestamp().date(), {0, 0}, Qt::LocalTime};
|
||||||
|
|
||||||
// What kind of room is that?..
|
// What kind of room is that?..
|
||||||
qCritical() << "No valid timestamps in the room timeline!";
|
qCritical() << "No valid timestamps in the room timeline!";
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MessageEventModel::renderDate(QDateTime timestamp) const {
|
|
||||||
auto date = timestamp.toLocalTime().date();
|
|
||||||
if (date == QDate::currentDate())
|
|
||||||
return tr("Today");
|
|
||||||
if (date == QDate::currentDate().addDays(-1))
|
|
||||||
return tr("Yesterday");
|
|
||||||
if (date == QDate::currentDate().addDays(-2))
|
|
||||||
return tr("The day before yesterday");
|
|
||||||
if (date > QDate::currentDate().addDays(-7))
|
|
||||||
return date.toString("dddd");
|
|
||||||
|
|
||||||
return QLocale::system().toString(date, QLocale::ShortFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) {
|
|
||||||
if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto& timelineBottom = m_currentRoom->messageEvents().rbegin();
|
|
||||||
const auto& lastSender = (*(timelineBottom + baseTimelineRow))->senderId();
|
|
||||||
const auto limit = timelineBottom + std::min(baseTimelineRow + 10,
|
|
||||||
m_currentRoom->timelineSize());
|
|
||||||
for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0);
|
|
||||||
it != limit; ++it) {
|
|
||||||
if ((*it)->senderId() == lastSender) {
|
|
||||||
auto idx = index(it - timelineBottom);
|
|
||||||
emit dataChanged(idx, idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int MessageEventModel::rowCount(const QModelIndex& parent) const {
|
|
||||||
if (!m_currentRoom || parent.isValid())
|
|
||||||
return 0;
|
|
||||||
return m_currentRoom->timelineSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline QVariantMap userAtEvent(SpectralUser* user,
|
|
||||||
SpectralRoom* room,
|
|
||||||
const RoomEvent& evt) {
|
|
||||||
return QVariantMap{
|
|
||||||
{"isLocalUser", user->id() == room->localUser()->id()},
|
|
||||||
{"id", user->id()},
|
|
||||||
{"avatarMediaId", user->avatarMediaId(room)},
|
|
||||||
{"avatarUrl", user->avatarUrl(room)},
|
|
||||||
{"displayName", user->displayname(room)},
|
|
||||||
{"color", user->color()},
|
|
||||||
{"object", QVariant::fromValue(user)},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
|
|
||||||
const auto row = idx.row();
|
|
||||||
|
|
||||||
if (!m_currentRoom || row < 0 ||
|
|
||||||
row >= int(m_currentRoom->pendingEvents().size()) +
|
|
||||||
m_currentRoom->timelineSize())
|
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool isPending = row < timelineBaseIndex();
|
QString MessageEventModel::renderDate(QDateTime timestamp) const
|
||||||
const auto timelineIt = m_currentRoom->messageEvents().crbegin() +
|
{
|
||||||
std::max(0, row - timelineBaseIndex());
|
auto date = timestamp.toLocalTime().date();
|
||||||
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() +
|
if (date == QDate::currentDate())
|
||||||
std::min(row, timelineBaseIndex());
|
return tr("Today");
|
||||||
const auto& evt = isPending ? **pendingIt : **timelineIt;
|
if (date == QDate::currentDate().addDays(-1))
|
||||||
|
return tr("Yesterday");
|
||||||
|
if (date == QDate::currentDate().addDays(-2))
|
||||||
|
return tr("The day before yesterday");
|
||||||
|
if (date > QDate::currentDate().addDays(-7))
|
||||||
|
return date.toString("dddd");
|
||||||
|
|
||||||
if (role == Qt::DisplayRole) {
|
return QLocale::system().toString(date, QLocale::ShortFormat);
|
||||||
return m_currentRoom->eventToString(evt, Qt::RichText);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (role == MessageRole) {
|
void MessageEventModel::refreshLastUserEvents(int baseTimelineRow)
|
||||||
return m_currentRoom->eventToString(evt);
|
{
|
||||||
}
|
if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow)
|
||||||
|
return;
|
||||||
|
|
||||||
if (role == Qt::ToolTipRole) {
|
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin();
|
||||||
return evt.originalJson();
|
const auto &lastSender = (*(timelineBottom + baseTimelineRow))->senderId();
|
||||||
}
|
const auto limit = timelineBottom + std::min(baseTimelineRow + 10, m_currentRoom->timelineSize());
|
||||||
|
for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0); it != limit; ++it) {
|
||||||
if (role == EventTypeRole) {
|
if ((*it)->senderId() == lastSender) {
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
auto idx = index(it - timelineBottom);
|
||||||
switch (e->msgtype()) {
|
emit dataChanged(idx, idx);
|
||||||
case MessageEventType::Emote:
|
}
|
||||||
return "emote";
|
|
||||||
case MessageEventType::Notice:
|
|
||||||
return "notice";
|
|
||||||
case MessageEventType::Image:
|
|
||||||
return "image";
|
|
||||||
case MessageEventType::Audio:
|
|
||||||
return "audio";
|
|
||||||
case MessageEventType::Video:
|
|
||||||
return "video";
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (e->hasFileContent()) {
|
|
||||||
return "file";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "message";
|
|
||||||
}
|
}
|
||||||
if (evt.isStateEvent())
|
}
|
||||||
return "state";
|
|
||||||
|
|
||||||
return "other";
|
int MessageEventModel::rowCount(const QModelIndex &parent) const
|
||||||
}
|
{
|
||||||
|
if (!m_currentRoom || parent.isValid())
|
||||||
|
return 0;
|
||||||
|
return m_currentRoom->timelineSize();
|
||||||
|
}
|
||||||
|
|
||||||
if (role == EventResolvedTypeRole)
|
inline QVariantMap userAtEvent(SpectralUser *user, SpectralRoom *room, const RoomEvent &evt)
|
||||||
return EventTypeRegistry::getMatrixType(evt.type());
|
{
|
||||||
|
return QVariantMap {
|
||||||
if (role == AuthorRole) {
|
{"isLocalUser", user->id() == room->localUser()->id()},
|
||||||
auto author = static_cast<SpectralUser*>(
|
{"id", user->id()},
|
||||||
isPending ? m_currentRoom->localUser()
|
{"avatarMediaId", user->avatarMediaId(room)},
|
||||||
: m_currentRoom->user(evt.senderId()));
|
{"avatarUrl", user->avatarUrl(room)},
|
||||||
return userAtEvent(author, m_currentRoom, evt);
|
{"displayName", user->displayname(room)},
|
||||||
}
|
{"color", user->color()},
|
||||||
|
{"object", QVariant::fromValue(user)},
|
||||||
if (role == ContentTypeRole) {
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
|
||||||
const auto& contentType = e->mimeType().name();
|
|
||||||
return contentType == "text/plain" ? QStringLiteral("text/html")
|
|
||||||
: contentType;
|
|
||||||
}
|
|
||||||
return QStringLiteral("text/plain");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == ContentRole) {
|
|
||||||
if (evt.isRedacted()) {
|
|
||||||
auto reason = evt.redactedBecause()->reason();
|
|
||||||
return (reason.isEmpty())
|
|
||||||
? tr("[REDACTED]")
|
|
||||||
: tr("[REDACTED: %1]").arg(evt.redactedBecause()->reason());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
|
||||||
// Cannot use e.contentJson() here because some
|
|
||||||
// EventContent classes inject values into the copy of the
|
|
||||||
// content JSON stored in EventContent::Base
|
|
||||||
return e->hasFileContent()
|
|
||||||
? QVariant::fromValue(e->content()->originalJson)
|
|
||||||
: QVariant();
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (role == HighlightRole)
|
|
||||||
return m_currentRoom->isEventHighlighted(&evt);
|
|
||||||
|
|
||||||
if (role == ReadMarkerRole)
|
|
||||||
return evt.id() == lastReadEventId && row > timelineBaseIndex();
|
|
||||||
|
|
||||||
if (role == SpecialMarksRole) {
|
|
||||||
if (isPending)
|
|
||||||
return pendingIt->deliveryStatus();
|
|
||||||
|
|
||||||
auto* memberEvent = timelineIt->viewAs<RoomMemberEvent>();
|
|
||||||
if (memberEvent) {
|
|
||||||
if ((memberEvent->isJoin() || memberEvent->isLeave()) &&
|
|
||||||
!Settings().value("UI/show_joinleave", true).toBool())
|
|
||||||
return EventStatus::Hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is<RedactionEvent>(evt) || is<ReactionEvent>(evt))
|
|
||||||
return EventStatus::Hidden;
|
|
||||||
if (evt.isRedacted())
|
|
||||||
return EventStatus::Hidden;
|
|
||||||
|
|
||||||
if (evt.isStateEvent() &&
|
|
||||||
static_cast<const StateEventBase&>(evt).repeatsState())
|
|
||||||
return EventStatus::Hidden;
|
|
||||||
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
|
||||||
if (!e->replacedEvent().isEmpty() && e->replacedEvent() != e->id()) {
|
|
||||||
return EventStatus::Hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_currentRoom->connection()->isIgnored(
|
|
||||||
m_currentRoom->user(evt.senderId())))
|
|
||||||
return EventStatus::Hidden;
|
|
||||||
|
|
||||||
return EventStatus::Normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == EventIdRole)
|
|
||||||
return !evt.id().isEmpty() ? evt.id() : evt.transactionId();
|
|
||||||
|
|
||||||
if (role == LongOperationRole) {
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt))
|
|
||||||
if (e->hasFileContent())
|
|
||||||
return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == AnnotationRole)
|
|
||||||
if (isPending)
|
|
||||||
return pendingIt->annotation();
|
|
||||||
|
|
||||||
if (role == TimeRole || role == SectionRole) {
|
|
||||||
auto ts =
|
|
||||||
isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
|
|
||||||
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == UserMarkerRole) {
|
|
||||||
QVariantList variantList;
|
|
||||||
for (User* user : m_currentRoom->usersAtEventId(evt.id())) {
|
|
||||||
if (user == m_currentRoom->localUser())
|
|
||||||
continue;
|
|
||||||
variantList.append(QVariant::fromValue(user));
|
|
||||||
}
|
|
||||||
return variantList;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == ReplyRole) {
|
|
||||||
const QString& replyEventId = evt.contentJson()["m.relates_to"]
|
|
||||||
.toObject()["m.in_reply_to"]
|
|
||||||
.toObject()["event_id"]
|
|
||||||
.toString();
|
|
||||||
if (replyEventId.isEmpty())
|
|
||||||
return {};
|
|
||||||
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
|
||||||
if (replyIt == m_currentRoom->timelineEdge())
|
|
||||||
return {};
|
|
||||||
const auto& replyEvt = **replyIt;
|
|
||||||
|
|
||||||
return QVariantMap{
|
|
||||||
{"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) {
|
|
||||||
for (auto r = row + 1; r < rowCount(); ++r) {
|
|
||||||
auto i = index(r);
|
|
||||||
if (data(i, SpecialMarksRole) != EventStatus::Hidden) {
|
|
||||||
return data(i, AuthorRole) != data(idx, AuthorRole) ||
|
|
||||||
data(i, EventTypeRole) != data(idx, EventTypeRole) ||
|
|
||||||
data(idx, TimeRole)
|
|
||||||
.toDateTime()
|
|
||||||
.msecsTo(data(i, TimeRole).toDateTime()) > 600000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == ShowSectionRole) {
|
|
||||||
for (auto r = row + 1; r < rowCount(); ++r) {
|
|
||||||
auto i = index(r);
|
|
||||||
if (data(i, SpecialMarksRole) != EventStatus::Hidden) {
|
|
||||||
return data(i, TimeRole)
|
|
||||||
.toDateTime()
|
|
||||||
.msecsTo(data(idx, TimeRole).toDateTime()) > 600000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == ReactionRole) {
|
|
||||||
const auto& annotations =
|
|
||||||
m_currentRoom->relatedEvents(evt, EventRelation::Annotation());
|
|
||||||
if (annotations.isEmpty())
|
|
||||||
return {};
|
|
||||||
QMap<QString, QList<SpectralUser*>> reactions = {};
|
|
||||||
for (const auto& a : annotations) {
|
|
||||||
if (a->isRedacted()) // Just in case?
|
|
||||||
continue;
|
|
||||||
if (auto e = eventCast<const ReactionEvent>(a))
|
|
||||||
reactions[e->relation().key].append(
|
|
||||||
static_cast<SpectralUser*>(m_currentRoom->user(e->senderId())));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reactions.isEmpty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantList res = {};
|
|
||||||
auto i = reactions.constBegin();
|
|
||||||
while (i != reactions.constEnd()) {
|
|
||||||
QVariantList authors;
|
|
||||||
for (auto author : i.value()) {
|
|
||||||
authors.append(userAtEvent(author, m_currentRoom, evt));
|
|
||||||
}
|
|
||||||
bool hasLocalUser = i.value().contains(
|
|
||||||
static_cast<SpectralUser*>(m_currentRoom->localUser()));
|
|
||||||
res.append(QVariantMap{{"reaction", i.key()},
|
|
||||||
{"count", i.value().count()},
|
|
||||||
{"authors", authors},
|
|
||||||
{"hasLocalUser", hasLocalUser}});
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageEventModel::eventIDToIndex(const QString& eventID) const {
|
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||||
const auto it = m_currentRoom->findInTimeline(eventID);
|
{
|
||||||
if (it == m_currentRoom->timelineEdge()) {
|
const auto row = idx.row();
|
||||||
qWarning() << "Trying to find inexistent event:" << eventID;
|
|
||||||
return -1;
|
if (!m_currentRoom || row < 0 || row >= int(m_currentRoom->pendingEvents().size()) + m_currentRoom->timelineSize())
|
||||||
}
|
return {};
|
||||||
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
|
|
||||||
|
bool isPending = row < timelineBaseIndex();
|
||||||
|
const auto timelineIt = m_currentRoom->messageEvents().crbegin() + std::max(0, row - timelineBaseIndex());
|
||||||
|
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex());
|
||||||
|
const auto &evt = isPending ? **pendingIt : **timelineIt;
|
||||||
|
|
||||||
|
if (role == Qt::DisplayRole) {
|
||||||
|
return m_currentRoom->eventToString(evt, Qt::RichText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == MessageRole) {
|
||||||
|
return m_currentRoom->eventToString(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == Qt::ToolTipRole) {
|
||||||
|
return evt.originalJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == EventTypeRole) {
|
||||||
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
|
switch (e->msgtype()) {
|
||||||
|
case MessageEventType::Emote:
|
||||||
|
return "emote";
|
||||||
|
case MessageEventType::Notice:
|
||||||
|
return "notice";
|
||||||
|
case MessageEventType::Image:
|
||||||
|
return "image";
|
||||||
|
case MessageEventType::Audio:
|
||||||
|
return "audio";
|
||||||
|
case MessageEventType::Video:
|
||||||
|
return "video";
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (e->hasFileContent()) {
|
||||||
|
return "file";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "message";
|
||||||
|
}
|
||||||
|
if (evt.isStateEvent())
|
||||||
|
return "state";
|
||||||
|
|
||||||
|
return "other";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == EventResolvedTypeRole)
|
||||||
|
return EventTypeRegistry::getMatrixType(evt.type());
|
||||||
|
|
||||||
|
if (role == AuthorRole) {
|
||||||
|
auto author = static_cast<SpectralUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
|
||||||
|
return userAtEvent(author, m_currentRoom, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ContentTypeRole) {
|
||||||
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
|
const auto &contentType = e->mimeType().name();
|
||||||
|
return contentType == "text/plain" ? QStringLiteral("text/html") : contentType;
|
||||||
|
}
|
||||||
|
return QStringLiteral("text/plain");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ContentRole) {
|
||||||
|
if (evt.isRedacted()) {
|
||||||
|
auto reason = evt.redactedBecause()->reason();
|
||||||
|
return (reason.isEmpty()) ? tr("[REDACTED]") : tr("[REDACTED: %1]").arg(evt.redactedBecause()->reason());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
|
// Cannot use e.contentJson() here because some
|
||||||
|
// EventContent classes inject values into the copy of the
|
||||||
|
// content JSON stored in EventContent::Base
|
||||||
|
return e->hasFileContent() ? QVariant::fromValue(e->content()->originalJson) : QVariant();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == HighlightRole)
|
||||||
|
return m_currentRoom->isEventHighlighted(&evt);
|
||||||
|
|
||||||
|
if (role == ReadMarkerRole)
|
||||||
|
return evt.id() == lastReadEventId && row > timelineBaseIndex();
|
||||||
|
|
||||||
|
if (role == SpecialMarksRole) {
|
||||||
|
if (isPending)
|
||||||
|
return pendingIt->deliveryStatus();
|
||||||
|
|
||||||
|
auto *memberEvent = timelineIt->viewAs<RoomMemberEvent>();
|
||||||
|
if (memberEvent) {
|
||||||
|
if ((memberEvent->isJoin() || memberEvent->isLeave()) && !Settings().value("UI/show_joinleave", true).toBool())
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is<RedactionEvent>(evt) || is<ReactionEvent>(evt))
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
if (evt.isRedacted())
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
|
||||||
|
if (evt.isStateEvent() && static_cast<const StateEventBase &>(evt).repeatsState())
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
|
||||||
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
|
if (!e->replacedEvent().isEmpty() && e->replacedEvent() != e->id()) {
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_currentRoom->connection()->isIgnored(m_currentRoom->user(evt.senderId())))
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
|
||||||
|
return EventStatus::Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == EventIdRole)
|
||||||
|
return !evt.id().isEmpty() ? evt.id() : evt.transactionId();
|
||||||
|
|
||||||
|
if (role == LongOperationRole) {
|
||||||
|
if (auto e = eventCast<const RoomMessageEvent>(&evt))
|
||||||
|
if (e->hasFileContent())
|
||||||
|
return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == AnnotationRole)
|
||||||
|
if (isPending)
|
||||||
|
return pendingIt->annotation();
|
||||||
|
|
||||||
|
if (role == TimeRole || role == SectionRole) {
|
||||||
|
auto ts = isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
|
||||||
|
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == UserMarkerRole) {
|
||||||
|
QVariantList variantList;
|
||||||
|
for (User *user : m_currentRoom->usersAtEventId(evt.id())) {
|
||||||
|
if (user == m_currentRoom->localUser())
|
||||||
|
continue;
|
||||||
|
variantList.append(QVariant::fromValue(user));
|
||||||
|
}
|
||||||
|
return variantList;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ReplyRole) {
|
||||||
|
const QString &replyEventId = evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||||
|
if (replyEventId.isEmpty())
|
||||||
|
return {};
|
||||||
|
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
||||||
|
if (replyIt == m_currentRoom->timelineEdge())
|
||||||
|
return {};
|
||||||
|
const auto &replyEvt = **replyIt;
|
||||||
|
|
||||||
|
return QVariantMap {{"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) {
|
||||||
|
for (auto r = row + 1; r < rowCount(); ++r) {
|
||||||
|
auto i = index(r);
|
||||||
|
if (data(i, SpecialMarksRole) != EventStatus::Hidden) {
|
||||||
|
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, EventTypeRole) != data(idx, EventTypeRole) || data(idx, TimeRole).toDateTime().msecsTo(data(i, TimeRole).toDateTime()) > 600000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ShowSectionRole) {
|
||||||
|
for (auto r = row + 1; r < rowCount(); ++r) {
|
||||||
|
auto i = index(r);
|
||||||
|
if (data(i, SpecialMarksRole) != EventStatus::Hidden) {
|
||||||
|
return data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ReactionRole) {
|
||||||
|
const auto &annotations = m_currentRoom->relatedEvents(evt, EventRelation::Annotation());
|
||||||
|
if (annotations.isEmpty())
|
||||||
|
return {};
|
||||||
|
QMap<QString, QList<SpectralUser *>> reactions = {};
|
||||||
|
for (const auto &a : annotations) {
|
||||||
|
if (a->isRedacted()) // Just in case?
|
||||||
|
continue;
|
||||||
|
if (auto e = eventCast<const ReactionEvent>(a))
|
||||||
|
reactions[e->relation().key].append(static_cast<SpectralUser *>(m_currentRoom->user(e->senderId())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reactions.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList res = {};
|
||||||
|
auto i = reactions.constBegin();
|
||||||
|
while (i != reactions.constEnd()) {
|
||||||
|
QVariantList authors;
|
||||||
|
for (auto author : i.value()) {
|
||||||
|
authors.append(userAtEvent(author, m_currentRoom, evt));
|
||||||
|
}
|
||||||
|
bool hasLocalUser = i.value().contains(static_cast<SpectralUser *>(m_currentRoom->localUser()));
|
||||||
|
res.append(QVariantMap {{"reaction", i.key()}, {"count", i.value().count()}, {"authors", authors}, {"hasLocalUser", hasLocalUser}});
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int MessageEventModel::eventIDToIndex(const QString &eventID) const
|
||||||
|
{
|
||||||
|
const auto it = m_currentRoom->findInTimeline(eventID);
|
||||||
|
if (it == m_currentRoom->timelineEdge()) {
|
||||||
|
qWarning() << "Trying to find inexistent event:" << eventID;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,80 +6,82 @@
|
|||||||
#include "room.h"
|
#include "room.h"
|
||||||
#include "spectralroom.h"
|
#include "spectralroom.h"
|
||||||
|
|
||||||
class MessageEventModel : public QAbstractListModel {
|
class MessageEventModel : public QAbstractListModel
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(SpectralRoom* room READ room WRITE setRoom NOTIFY roomChanged)
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(SpectralRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
EventTypeRole = Qt::UserRole + 1,
|
EventTypeRole = Qt::UserRole + 1,
|
||||||
MessageRole,
|
MessageRole,
|
||||||
EventIdRole,
|
EventIdRole,
|
||||||
TimeRole,
|
TimeRole,
|
||||||
SectionRole,
|
SectionRole,
|
||||||
AuthorRole,
|
AuthorRole,
|
||||||
ContentRole,
|
ContentRole,
|
||||||
ContentTypeRole,
|
ContentTypeRole,
|
||||||
HighlightRole,
|
HighlightRole,
|
||||||
ReadMarkerRole,
|
ReadMarkerRole,
|
||||||
SpecialMarksRole,
|
SpecialMarksRole,
|
||||||
LongOperationRole,
|
LongOperationRole,
|
||||||
AnnotationRole,
|
AnnotationRole,
|
||||||
UserMarkerRole,
|
UserMarkerRole,
|
||||||
|
|
||||||
ReplyRole,
|
ReplyRole,
|
||||||
|
|
||||||
ShowAuthorRole,
|
ShowAuthorRole,
|
||||||
ShowSectionRole,
|
ShowSectionRole,
|
||||||
|
|
||||||
ReactionRole,
|
ReactionRole,
|
||||||
|
|
||||||
// For debugging
|
// For debugging
|
||||||
EventResolvedTypeRole,
|
EventResolvedTypeRole,
|
||||||
};
|
};
|
||||||
Q_ENUM(EventRoles)
|
Q_ENUM(EventRoles)
|
||||||
|
|
||||||
enum BubbleShapes {
|
enum BubbleShapes {
|
||||||
NoShape = 0,
|
NoShape = 0,
|
||||||
BeginShape,
|
BeginShape,
|
||||||
MiddleShape,
|
MiddleShape,
|
||||||
EndShape,
|
EndShape,
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit MessageEventModel(QObject* parent = nullptr);
|
explicit MessageEventModel(QObject *parent = nullptr);
|
||||||
~MessageEventModel() override;
|
~MessageEventModel() override;
|
||||||
|
|
||||||
SpectralRoom* room() const { return m_currentRoom; }
|
SpectralRoom *room() const
|
||||||
void setRoom(SpectralRoom* room);
|
{
|
||||||
|
return m_currentRoom;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
int refreshEvent(const QString& eventId);
|
int refreshEvent(const QString &eventId);
|
||||||
void refreshRow(int row);
|
void refreshRow(int row);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SpectralRoom* m_currentRoom = nullptr;
|
SpectralRoom *m_currentRoom = nullptr;
|
||||||
QString lastReadEventId;
|
QString lastReadEventId;
|
||||||
int rowBelowInserted = -1;
|
int rowBelowInserted = -1;
|
||||||
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);
|
||||||
void refreshEventRoles(int row, const QVector<int>& roles = {});
|
void refreshEventRoles(int row, const QVector<int> &roles = {});
|
||||||
int refreshEventRoles(const QString& eventId, const QVector<int>& roles = {});
|
int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {});
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void roomChanged();
|
void roomChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MESSAGEEVENTMODEL_H
|
#endif // MESSAGEEVENTMODEL_H
|
||||||
|
|||||||
@@ -12,45 +12,39 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct roomEventId {
|
struct roomEventId {
|
||||||
QString roomId;
|
QString roomId;
|
||||||
QString eventId;
|
QString eventId;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NotificationsManager : public QObject {
|
class NotificationsManager : public QObject
|
||||||
Q_OBJECT
|
{
|
||||||
public:
|
Q_OBJECT
|
||||||
NotificationsManager(QObject* parent = nullptr);
|
public:
|
||||||
|
NotificationsManager(QObject *parent = nullptr);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void notificationClicked(const QString roomId, const QString eventId);
|
void notificationClicked(const QString roomId, const QString eventId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#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)
|
||||||
QMap<uint, roomEventId> notificationIds;
|
QMap<uint, roomEventId> notificationIds;
|
||||||
|
|
||||||
// these slots are platform specific (D-Bus only)
|
// these slots are platform specific (D-Bus only)
|
||||||
// but Qt slot declarations can not be inside an ifdef!
|
// but Qt slot declarations can not be inside an ifdef!
|
||||||
public slots:
|
public slots:
|
||||||
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)
|
||||||
QDBusArgument& operator<<(QDBusArgument& arg, const QImage& image);
|
QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image);
|
||||||
const QDBusArgument& operator>>(const QDBusArgument& arg, QImage&);
|
const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -8,36 +8,28 @@
|
|||||||
#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");
|
||||||
|
|
||||||
if (capabilitiesReply.isValid()) {
|
if (capabilitiesReply.isValid()) {
|
||||||
const QStringList capabilities = capabilitiesReply.value();
|
const QStringList capabilities = capabilitiesReply.value();
|
||||||
serverSupportsHtml = capabilities.contains("body-markup");
|
serverSupportsHtml = capabilities.contains("body-markup");
|
||||||
} else {
|
} else {
|
||||||
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};
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This function is based on code from
|
* This function is based on code from
|
||||||
@@ -45,63 +37,59 @@ 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(), rect.height());
|
||||||
QRect crop((rect.width() - rect.height()) / 2, 0, rect.height(),
|
croppedImage = image.copy(crop);
|
||||||
rect.height());
|
} else {
|
||||||
croppedImage = image.copy(crop);
|
QRect crop(0, (rect.height() - rect.width()) / 2, rect.width(), rect.width());
|
||||||
|
croppedImage = image.copy(crop);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
QRect crop(0, (rect.height() - rect.width()) / 2, rect.width(),
|
croppedImage = image;
|
||||||
rect.width());
|
|
||||||
croppedImage = image.copy(crop);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
croppedImage = image;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString body = serverSupportsHtml ? text.toHtmlEscaped() : text;
|
const QString body = serverSupportsHtml ? text.toHtmlEscaped() : text;
|
||||||
|
|
||||||
QVariantMap hints;
|
QVariantMap hints;
|
||||||
hints["image-data"] = croppedImage;
|
hints["image-data"] = croppedImage;
|
||||||
hints["desktop-entry"] = "org.eu.encom.spectral";
|
hints["desktop-entry"] = "org.eu.encom.spectral";
|
||||||
QList<QVariant> argumentList;
|
QList<QVariant> argumentList;
|
||||||
argumentList << "Spectral"; // app_name
|
argumentList << "Spectral"; // app_name
|
||||||
argumentList << uint(0); // replace_id
|
argumentList << uint(0); // replace_id
|
||||||
argumentList << ""; // app_icon
|
argumentList << ""; // app_icon
|
||||||
argumentList << summary; // summary
|
argumentList << summary; // summary
|
||||||
argumentList << body; // body
|
argumentList << body; // body
|
||||||
argumentList << (QStringList("default") << "reply"); // actions
|
argumentList << (QStringList("default") << "reply"); // actions
|
||||||
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");
|
if (reply.type() == QDBusMessage::ErrorMessage) {
|
||||||
QDBusMessage reply =
|
qDebug() << "D-Bus Error:" << reply.errorMessage();
|
||||||
notifyApp.callWithArgumentList(QDBus::AutoDetect, "Notify", argumentList);
|
return 0;
|
||||||
if (reply.type() == QDBusMessage::ErrorMessage) {
|
} else {
|
||||||
qDebug() << "D-Bus Error:" << reply.errorMessage();
|
return reply.arguments().first().toUInt();
|
||||||
return 0;
|
}
|
||||||
} else {
|
|
||||||
return reply.arguments().first().toUInt();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationsManager::actionInvoked(uint id, QString action) {
|
void NotificationsManager::actionInvoked(uint id, QString action)
|
||||||
if (action == "default" && notificationIds.contains(id)) {
|
{
|
||||||
roomEventId idEntry = notificationIds[id];
|
if (action == "default" && notificationIds.contains(id)) {
|
||||||
emit notificationClicked(idEntry.roomId, idEntry.eventId);
|
roomEventId idEntry = notificationIds[id];
|
||||||
}
|
emit notificationClicked(idEntry.roomId, idEntry.eventId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationsManager::notificationClosed(uint id, uint reason) {
|
void NotificationsManager::notificationClosed(uint id, uint reason)
|
||||||
Q_UNUSED(reason);
|
{
|
||||||
notificationIds.remove(id);
|
Q_UNUSED(reason);
|
||||||
|
notificationIds.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,50 +101,52 @@ 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()) {
|
{
|
||||||
arg.beginStructure();
|
if (image.isNull()) {
|
||||||
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
|
arg.beginStructure();
|
||||||
arg.endStructure();
|
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
|
||||||
return arg;
|
arg.endStructure();
|
||||||
}
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
|
QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
|
||||||
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
|
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
|
||||||
|
|
||||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||||
// ABGR -> ARGB
|
// ABGR -> ARGB
|
||||||
QImage i = scaled.rgbSwapped();
|
QImage i = scaled.rgbSwapped();
|
||||||
#else
|
#else
|
||||||
// ABGR -> GBAR
|
// ABGR -> GBAR
|
||||||
QImage i(scaled.size(), scaled.format());
|
QImage i(scaled.size(), scaled.format());
|
||||||
for (int y = 0; y < i.height(); ++y) {
|
for (int y = 0; y < i.height(); ++y) {
|
||||||
QRgb *p = (QRgb *)scaled.scanLine(y);
|
QRgb *p = (QRgb *)scaled.scanLine(y);
|
||||||
QRgb *q = (QRgb *)i.scanLine(y);
|
QRgb *q = (QRgb *)i.scanLine(y);
|
||||||
QRgb *end = p + scaled.width();
|
QRgb *end = p + scaled.width();
|
||||||
while (p < end) {
|
while (p < end) {
|
||||||
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
|
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
|
||||||
p++;
|
p++;
|
||||||
q++;
|
q++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
arg.beginStructure();
|
arg.beginStructure();
|
||||||
arg << i.width();
|
arg << i.width();
|
||||||
arg << i.height();
|
arg << i.height();
|
||||||
arg << i.bytesPerLine();
|
arg << i.bytesPerLine();
|
||||||
arg << i.hasAlphaChannel();
|
arg << i.hasAlphaChannel();
|
||||||
int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
|
int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
|
||||||
arg << i.depth() / channels;
|
arg << i.depth() / channels;
|
||||||
arg << channels;
|
arg << channels;
|
||||||
arg << QByteArray(reinterpret_cast<const char *>(i.bits()), i.sizeInBytes());
|
arg << QByteArray(reinterpret_cast<const char *>(i.bits()), i.sizeInBytes());
|
||||||
arg.endStructure();
|
arg.endStructure();
|
||||||
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.
|
{
|
||||||
Q_ASSERT(0);
|
// This is needed to link but shouldn't be called.
|
||||||
return arg;
|
Q_ASSERT(0);
|
||||||
|
return arg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,79 +5,89 @@
|
|||||||
|
|
||||||
using namespace WinToastLib;
|
using namespace WinToastLib;
|
||||||
|
|
||||||
class CustomHandler : public IWinToastHandler {
|
class CustomHandler : public IWinToastHandler
|
||||||
public:
|
{
|
||||||
CustomHandler(uint id, NotificationsManager *parent)
|
public:
|
||||||
: notificationID(id), notificationsManager(parent) {}
|
CustomHandler(uint id, NotificationsManager *parent)
|
||||||
void toastActivated() {
|
: notificationID(id)
|
||||||
notificationsManager->actionInvoked(notificationID, "");
|
, notificationsManager(parent)
|
||||||
}
|
{
|
||||||
void toastActivated(int) {
|
}
|
||||||
notificationsManager->actionInvoked(notificationID, "");
|
void toastActivated()
|
||||||
}
|
{
|
||||||
void toastFailed() {
|
notificationsManager->actionInvoked(notificationID, "");
|
||||||
std::wcout << L"Error showing current toast" << std::endl;
|
}
|
||||||
}
|
void toastActivated(int)
|
||||||
void toastDismissed(WinToastDismissalReason) {
|
{
|
||||||
notificationsManager->notificationClosed(notificationID, 0);
|
notificationsManager->actionInvoked(notificationID, "");
|
||||||
}
|
}
|
||||||
|
void toastFailed()
|
||||||
|
{
|
||||||
|
std::wcout << L"Error showing current toast" << std::endl;
|
||||||
|
}
|
||||||
|
void toastDismissed(WinToastDismissalReason)
|
||||||
|
{
|
||||||
|
notificationsManager->notificationClosed(notificationID, 0);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint notificationID;
|
uint notificationID;
|
||||||
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(
|
{
|
||||||
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(event_id)
|
|
||||||
Q_UNUSED(icon)
|
|
||||||
|
|
||||||
if (!isInitialized) init();
|
|
||||||
|
|
||||||
auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
|
|
||||||
if (room_name != sender)
|
|
||||||
templ.setTextField(
|
|
||||||
QString("%1 - %2").arg(sender).arg(room_name).toStdWString(),
|
|
||||||
WinToastTemplate::FirstLine);
|
|
||||||
else
|
|
||||||
templ.setTextField(QString("%1").arg(sender).toStdWString(),
|
|
||||||
WinToastTemplate::FirstLine);
|
|
||||||
templ.setTextField(QString("%1").arg(text).toStdWString(),
|
|
||||||
WinToastTemplate::SecondLine);
|
|
||||||
|
|
||||||
count++;
|
|
||||||
CustomHandler *customHandler = new CustomHandler(count, this);
|
|
||||||
notificationIds[count] = roomEventId{room_id, event_id};
|
|
||||||
|
|
||||||
WinToast::instance()->showToast(templ, customHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationsManager::actionInvoked(uint id, QString action) {
|
void NotificationsManager::postNotification(const QString &room_id, const QString &event_id, const QString &room_name, const QString &sender, const QString &text, const QImage &icon)
|
||||||
if (notificationIds.contains(id)) {
|
{
|
||||||
roomEventId idEntry = notificationIds[id];
|
Q_UNUSED(room_id)
|
||||||
emit notificationClicked(idEntry.roomId, idEntry.eventId);
|
Q_UNUSED(event_id)
|
||||||
}
|
Q_UNUSED(icon)
|
||||||
|
|
||||||
|
if (!isInitialized)
|
||||||
|
init();
|
||||||
|
|
||||||
|
auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
|
||||||
|
if (room_name != sender)
|
||||||
|
templ.setTextField(QString("%1 - %2").arg(sender).arg(room_name).toStdWString(), WinToastTemplate::FirstLine);
|
||||||
|
else
|
||||||
|
templ.setTextField(QString("%1").arg(sender).toStdWString(), WinToastTemplate::FirstLine);
|
||||||
|
templ.setTextField(QString("%1").arg(text).toStdWString(), WinToastTemplate::SecondLine);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
CustomHandler *customHandler = new CustomHandler(count, this);
|
||||||
|
notificationIds[count] = roomEventId {room_id, event_id};
|
||||||
|
|
||||||
|
WinToast::instance()->showToast(templ, customHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationsManager::notificationClosed(uint id, uint reason) {
|
void NotificationsManager::actionInvoked(uint id, QString action)
|
||||||
notificationIds.remove(id);
|
{
|
||||||
|
if (notificationIds.contains(id)) {
|
||||||
|
roomEventId idEntry = notificationIds[id];
|
||||||
|
emit notificationClicked(idEntry.roomId, idEntry.eventId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotificationsManager::notificationClosed(uint id, uint reason)
|
||||||
|
{
|
||||||
|
notificationIds.remove(id);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,164 +1,156 @@
|
|||||||
#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;
|
||||||
using namespace ABI::Windows::UI::Notifications;
|
using namespace ABI::Windows::UI::Notifications;
|
||||||
using namespace Windows::Foundation;
|
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
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum WinToastDismissalReason {
|
||||||
|
UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled,
|
||||||
|
ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden,
|
||||||
|
TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut
|
||||||
|
};
|
||||||
|
virtual ~IWinToastHandler()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual void toastActivated() = 0;
|
||||||
|
virtual void toastActivated(int actionIndex) = 0;
|
||||||
|
virtual void toastDismissed(WinToastDismissalReason state) = 0;
|
||||||
|
virtual void toastFailed() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class IWinToastHandler {
|
class WinToastTemplate
|
||||||
public:
|
{
|
||||||
enum WinToastDismissalReason {
|
public:
|
||||||
UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled,
|
enum Duration { System, Short, Long };
|
||||||
ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden,
|
enum AudioOption { Default = 0, Silent = 1, Loop = 2 };
|
||||||
TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut
|
enum TextField { FirstLine = 0, SecondLine, ThirdLine };
|
||||||
};
|
enum WinToastTemplateType {
|
||||||
virtual ~IWinToastHandler() {}
|
ImageAndText01 = ToastTemplateType::ToastTemplateType_ToastImageAndText01,
|
||||||
virtual void toastActivated() = 0;
|
ImageAndText02 = ToastTemplateType::ToastTemplateType_ToastImageAndText02,
|
||||||
virtual void toastActivated(int actionIndex) = 0;
|
ImageAndText03 = ToastTemplateType::ToastTemplateType_ToastImageAndText03,
|
||||||
virtual void toastDismissed(WinToastDismissalReason state) = 0;
|
ImageAndText04 = ToastTemplateType::ToastTemplateType_ToastImageAndText04,
|
||||||
virtual void toastFailed() = 0;
|
Text01 = ToastTemplateType::ToastTemplateType_ToastText01,
|
||||||
|
Text02 = ToastTemplateType::ToastTemplateType_ToastText02,
|
||||||
|
Text03 = ToastTemplateType::ToastTemplateType_ToastText03,
|
||||||
|
Text04 = ToastTemplateType::ToastTemplateType_ToastText04,
|
||||||
|
WinToastTemplateTypeCount
|
||||||
};
|
};
|
||||||
|
|
||||||
class WinToastTemplate {
|
WinToastTemplate(_In_ WinToastTemplateType type = WinToastTemplateType::ImageAndText02);
|
||||||
public:
|
~WinToastTemplate();
|
||||||
enum Duration { System, Short, Long };
|
|
||||||
enum AudioOption { Default = 0, Silent = 1, Loop = 2 };
|
|
||||||
enum TextField { FirstLine = 0, SecondLine, ThirdLine };
|
|
||||||
enum WinToastTemplateType {
|
|
||||||
ImageAndText01 = ToastTemplateType::ToastTemplateType_ToastImageAndText01,
|
|
||||||
ImageAndText02 = ToastTemplateType::ToastTemplateType_ToastImageAndText02,
|
|
||||||
ImageAndText03 = ToastTemplateType::ToastTemplateType_ToastImageAndText03,
|
|
||||||
ImageAndText04 = ToastTemplateType::ToastTemplateType_ToastImageAndText04,
|
|
||||||
Text01 = ToastTemplateType::ToastTemplateType_ToastText01,
|
|
||||||
Text02 = ToastTemplateType::ToastTemplateType_ToastText02,
|
|
||||||
Text03 = ToastTemplateType::ToastTemplateType_ToastText03,
|
|
||||||
Text04 = ToastTemplateType::ToastTemplateType_ToastText04,
|
|
||||||
WinToastTemplateTypeCount
|
|
||||||
};
|
|
||||||
|
|
||||||
WinToastTemplate(_In_ WinToastTemplateType type = WinToastTemplateType::ImageAndText02);
|
void setTextField(_In_ const std::wstring &txt, _In_ TextField pos);
|
||||||
~WinToastTemplate();
|
void setImagePath(_In_ const std::wstring &imgPath);
|
||||||
|
void setAudioPath(_In_ const std::wstring &audioPath);
|
||||||
|
void setAttributionText(_In_ const std::wstring &attributionText);
|
||||||
|
void addAction(_In_ const std::wstring &label);
|
||||||
|
void setAudioOption(_In_ WinToastTemplate::AudioOption audioOption);
|
||||||
|
void setDuration(_In_ Duration duration);
|
||||||
|
void setExpiration(_In_ INT64 millisecondsFromNow);
|
||||||
|
std::size_t textFieldsCount() const;
|
||||||
|
std::size_t actionsCount() const;
|
||||||
|
bool hasImage() const;
|
||||||
|
const std::vector<std::wstring> &textFields() const;
|
||||||
|
const std::wstring &textField(_In_ TextField pos) const;
|
||||||
|
const std::wstring &actionLabel(_In_ int pos) const;
|
||||||
|
const std::wstring &imagePath() const;
|
||||||
|
const std::wstring &audioPath() const;
|
||||||
|
const std::wstring &attributionText() const;
|
||||||
|
INT64 expiration() const;
|
||||||
|
WinToastTemplateType type() const;
|
||||||
|
WinToastTemplate::AudioOption audioOption() const;
|
||||||
|
Duration duration() const;
|
||||||
|
|
||||||
void setTextField(_In_ const std::wstring& txt, _In_ TextField pos);
|
private:
|
||||||
void setImagePath(_In_ const std::wstring& imgPath);
|
std::vector<std::wstring> _textFields;
|
||||||
void setAudioPath(_In_ const std::wstring& audioPath);
|
std::vector<std::wstring> _actions;
|
||||||
void setAttributionText(_In_ const std::wstring & attributionText);
|
std::wstring _imagePath = L"";
|
||||||
void addAction(_In_ const std::wstring& label);
|
std::wstring _audioPath = L"";
|
||||||
void setAudioOption(_In_ WinToastTemplate::AudioOption audioOption);
|
std::wstring _attributionText = L"";
|
||||||
void setDuration(_In_ Duration duration);
|
INT64 _expiration = 0;
|
||||||
void setExpiration(_In_ INT64 millisecondsFromNow);
|
AudioOption _audioOption = WinToastTemplate::AudioOption::Default;
|
||||||
std::size_t textFieldsCount() const;
|
WinToastTemplateType _type = WinToastTemplateType::Text01;
|
||||||
std::size_t actionsCount() const;
|
Duration _duration = Duration::System;
|
||||||
bool hasImage() const;
|
};
|
||||||
const std::vector<std::wstring>& textFields() const;
|
|
||||||
const std::wstring& textField(_In_ TextField pos) const;
|
class WinToast
|
||||||
const std::wstring& actionLabel(_In_ int pos) const;
|
{
|
||||||
const std::wstring& imagePath() const;
|
public:
|
||||||
const std::wstring& audioPath() const;
|
enum WinToastError { NoError = 0, NotInitialized, SystemNotSupported, ShellLinkNotCreated, InvalidAppUserModelID, InvalidParameters, InvalidHandler, NotDisplayed, UnknownError };
|
||||||
const std::wstring& attributionText() const;
|
|
||||||
INT64 expiration() const;
|
enum ShortcutResult {
|
||||||
WinToastTemplateType type() const;
|
SHORTCUT_UNCHANGED = 0,
|
||||||
WinToastTemplate::AudioOption audioOption() const;
|
SHORTCUT_WAS_CHANGED = 1,
|
||||||
Duration duration() const;
|
SHORTCUT_WAS_CREATED = 2,
|
||||||
private:
|
|
||||||
std::vector<std::wstring> _textFields;
|
SHORTCUT_MISSING_PARAMETERS = -1,
|
||||||
std::vector<std::wstring> _actions;
|
SHORTCUT_INCOMPATIBLE_OS = -2,
|
||||||
std::wstring _imagePath = L"";
|
SHORTCUT_COM_INIT_FAILURE = -3,
|
||||||
std::wstring _audioPath = L"";
|
SHORTCUT_CREATE_FAILED = -4
|
||||||
std::wstring _attributionText = L"";
|
|
||||||
INT64 _expiration = 0;
|
|
||||||
AudioOption _audioOption = WinToastTemplate::AudioOption::Default;
|
|
||||||
WinToastTemplateType _type = WinToastTemplateType::Text01;
|
|
||||||
Duration _duration = Duration::System;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class WinToast {
|
WinToast(void);
|
||||||
public:
|
virtual ~WinToast();
|
||||||
enum WinToastError {
|
static WinToast *instance();
|
||||||
NoError = 0,
|
static bool isCompatible();
|
||||||
NotInitialized,
|
static bool isSupportingModernFeatures();
|
||||||
SystemNotSupported,
|
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());
|
||||||
ShellLinkNotCreated,
|
virtual bool initialize(_Out_ WinToastError *error = nullptr);
|
||||||
InvalidAppUserModelID,
|
virtual bool isInitialized() const;
|
||||||
InvalidParameters,
|
virtual bool hideToast(_In_ INT64 id);
|
||||||
InvalidHandler,
|
virtual INT64 showToast(_In_ const WinToastTemplate &toast, _In_ IWinToastHandler *handler, _Out_ WinToastError *error = nullptr);
|
||||||
NotDisplayed,
|
virtual void clear();
|
||||||
UnknownError
|
virtual enum ShortcutResult createShortcut();
|
||||||
};
|
|
||||||
|
|
||||||
enum ShortcutResult {
|
const std::wstring &appName() const;
|
||||||
SHORTCUT_UNCHANGED = 0,
|
const std::wstring &appUserModelId() const;
|
||||||
SHORTCUT_WAS_CHANGED = 1,
|
void setAppUserModelId(_In_ const std::wstring &appName);
|
||||||
SHORTCUT_WAS_CREATED = 2,
|
void setAppName(_In_ const std::wstring &appName);
|
||||||
|
|
||||||
SHORTCUT_MISSING_PARAMETERS = -1,
|
protected:
|
||||||
SHORTCUT_INCOMPATIBLE_OS = -2,
|
bool _isInitialized;
|
||||||
SHORTCUT_COM_INIT_FAILURE = -3,
|
bool _hasCoInitialized;
|
||||||
SHORTCUT_CREATE_FAILED = -4
|
std::wstring _appName;
|
||||||
};
|
std::wstring _aumi;
|
||||||
|
std::map<INT64, ComPtr<IToastNotification>> _buffer;
|
||||||
|
|
||||||
WinToast(void);
|
HRESULT validateShellLinkHelper(_Out_ bool &wasChanged);
|
||||||
virtual ~WinToast();
|
HRESULT createShellLinkHelper();
|
||||||
static WinToast* instance();
|
HRESULT setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &path);
|
||||||
static bool isCompatible();
|
HRESULT setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &path, _In_opt_ WinToastTemplate::AudioOption option = WinToastTemplate::AudioOption::Default);
|
||||||
static bool isSupportingModernFeatures();
|
HRESULT setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &text, _In_ int pos);
|
||||||
static std::wstring configureAUMI(_In_ const std::wstring& companyName,
|
HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &text);
|
||||||
_In_ const std::wstring& productName,
|
HRESULT addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &action, _In_ const std::wstring &arguments);
|
||||||
_In_ const std::wstring& subProduct = std::wstring(),
|
HRESULT addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring &duration);
|
||||||
_In_ const std::wstring& versionInformation = std::wstring()
|
ComPtr<IToastNotifier> notifier(_In_ bool *succeded) const;
|
||||||
);
|
void setError(_Out_ WinToastError *error, _In_ WinToastError value);
|
||||||
virtual bool initialize(_Out_ WinToastError* error = nullptr);
|
};
|
||||||
virtual bool isInitialized() const;
|
|
||||||
virtual bool hideToast(_In_ INT64 id);
|
|
||||||
virtual INT64 showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error = nullptr);
|
|
||||||
virtual void clear();
|
|
||||||
virtual enum ShortcutResult createShortcut();
|
|
||||||
|
|
||||||
const std::wstring& appName() const;
|
|
||||||
const std::wstring& appUserModelId() const;
|
|
||||||
void setAppUserModelId(_In_ const std::wstring& appName);
|
|
||||||
void setAppName(_In_ const std::wstring& appName);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool _isInitialized;
|
|
||||||
bool _hasCoInitialized;
|
|
||||||
std::wstring _appName;
|
|
||||||
std::wstring _aumi;
|
|
||||||
std::map<INT64, ComPtr<IToastNotification>> _buffer;
|
|
||||||
|
|
||||||
HRESULT validateShellLinkHelper(_Out_ bool& wasChanged);
|
|
||||||
HRESULT createShellLinkHelper();
|
|
||||||
HRESULT setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path);
|
|
||||||
HRESULT setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option = WinToastTemplate::AudioOption::Default);
|
|
||||||
HRESULT setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ int pos);
|
|
||||||
HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text);
|
|
||||||
HRESULT addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& action, _In_ const std::wstring& arguments);
|
|
||||||
HRESULT addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration);
|
|
||||||
ComPtr<IToastNotifier> notifier(_In_ bool* succeded) const;
|
|
||||||
void setError(_Out_ WinToastError* error, _In_ WinToastError value);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
#endif // WINTOASTLIB_H
|
#endif // WINTOASTLIB_H
|
||||||
|
|||||||
@@ -1,218 +1,226 @@
|
|||||||
#include "publicroomlistmodel.h"
|
#include "publicroomlistmodel.h"
|
||||||
|
|
||||||
PublicRoomListModel::PublicRoomListModel(QObject* parent)
|
PublicRoomListModel::PublicRoomListModel(QObject *parent)
|
||||||
: QAbstractListModel(parent) {}
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
void PublicRoomListModel::setConnection(Connection* conn) {
|
|
||||||
if (m_connection == conn)
|
|
||||||
return;
|
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
|
|
||||||
nextBatch = "";
|
|
||||||
attempted = false;
|
|
||||||
rooms.clear();
|
|
||||||
m_server.clear();
|
|
||||||
|
|
||||||
if (m_connection) {
|
|
||||||
m_connection->disconnect(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
endResetModel();
|
|
||||||
|
|
||||||
m_connection = conn;
|
|
||||||
|
|
||||||
if (job) {
|
|
||||||
job->abandon();
|
|
||||||
job = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_connection) {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
emit connectionChanged();
|
|
||||||
emit serverChanged();
|
|
||||||
emit hasMoreChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PublicRoomListModel::setServer(const QString& value) {
|
void PublicRoomListModel::setConnection(Connection *conn)
|
||||||
if (m_server == value)
|
{
|
||||||
return;
|
if (m_connection == conn)
|
||||||
|
return;
|
||||||
|
|
||||||
m_server = value;
|
beginResetModel();
|
||||||
|
|
||||||
beginResetModel();
|
nextBatch = "";
|
||||||
|
attempted = false;
|
||||||
|
rooms.clear();
|
||||||
|
m_server.clear();
|
||||||
|
|
||||||
nextBatch = "";
|
if (m_connection) {
|
||||||
attempted = false;
|
m_connection->disconnect(this);
|
||||||
rooms.clear();
|
|
||||||
|
|
||||||
endResetModel();
|
|
||||||
|
|
||||||
if (job) {
|
|
||||||
job->abandon();
|
|
||||||
job = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_connection) {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
emit serverChanged();
|
|
||||||
emit hasMoreChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PublicRoomListModel::setKeyword(const QString& value) {
|
|
||||||
if (m_keyword == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_keyword = value;
|
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
|
|
||||||
nextBatch = "";
|
|
||||||
attempted = false;
|
|
||||||
rooms.clear();
|
|
||||||
|
|
||||||
endResetModel();
|
|
||||||
|
|
||||||
if (job) {
|
|
||||||
job->abandon();
|
|
||||||
job = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_connection) {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
emit keywordChanged();
|
|
||||||
emit hasMoreChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PublicRoomListModel::next(int count) {
|
|
||||||
if (count < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (job) {
|
|
||||||
qDebug() << "PublicRoomListModel: Other jobs running, ignore";
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasMore())
|
|
||||||
return;
|
|
||||||
|
|
||||||
job = m_connection->callApi<QueryPublicRoomsJob>(
|
|
||||||
m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword});
|
|
||||||
|
|
||||||
connect(job, &BaseJob::finished, this, [=] {
|
|
||||||
attempted = true;
|
|
||||||
|
|
||||||
if (job->status() == BaseJob::Success) {
|
|
||||||
nextBatch = job->nextBatch();
|
|
||||||
|
|
||||||
this->beginInsertRows({}, rooms.count(),
|
|
||||||
rooms.count() + job->chunk().count() - 1);
|
|
||||||
rooms.append(job->chunk());
|
|
||||||
this->endInsertRows();
|
|
||||||
|
|
||||||
if (job->nextBatch().isEmpty()) {
|
|
||||||
emit hasMoreChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->job = nullptr;
|
endResetModel();
|
||||||
});
|
|
||||||
|
m_connection = conn;
|
||||||
|
|
||||||
|
if (job) {
|
||||||
|
job->abandon();
|
||||||
|
job = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_connection) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit connectionChanged();
|
||||||
|
emit serverChanged();
|
||||||
|
emit hasMoreChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant PublicRoomListModel::data(const QModelIndex& index, int role) const {
|
void PublicRoomListModel::setServer(const QString &value)
|
||||||
if (!index.isValid())
|
{
|
||||||
return QVariant();
|
if (m_server == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_server = value;
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
nextBatch = "";
|
||||||
|
attempted = false;
|
||||||
|
rooms.clear();
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
|
||||||
|
if (job) {
|
||||||
|
job->abandon();
|
||||||
|
job = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_connection) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit serverChanged();
|
||||||
|
emit hasMoreChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PublicRoomListModel::setKeyword(const QString &value)
|
||||||
|
{
|
||||||
|
if (m_keyword == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_keyword = value;
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
nextBatch = "";
|
||||||
|
attempted = false;
|
||||||
|
rooms.clear();
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
|
||||||
|
if (job) {
|
||||||
|
job->abandon();
|
||||||
|
job = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_connection) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit keywordChanged();
|
||||||
|
emit hasMoreChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PublicRoomListModel::next(int count)
|
||||||
|
{
|
||||||
|
if (count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (job) {
|
||||||
|
qDebug() << "PublicRoomListModel: Other jobs running, ignore";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMore())
|
||||||
|
return;
|
||||||
|
|
||||||
|
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter {m_keyword});
|
||||||
|
|
||||||
|
connect(job, &BaseJob::finished, this, [=] {
|
||||||
|
attempted = true;
|
||||||
|
|
||||||
|
if (job->status() == BaseJob::Success) {
|
||||||
|
nextBatch = job->nextBatch();
|
||||||
|
|
||||||
|
this->beginInsertRows({}, rooms.count(), rooms.count() + job->chunk().count() - 1);
|
||||||
|
rooms.append(job->chunk());
|
||||||
|
this->endInsertRows();
|
||||||
|
|
||||||
|
if (job->nextBatch().isEmpty()) {
|
||||||
|
emit hasMoreChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->job = nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
if (index.row() >= rooms.count()) {
|
||||||
|
qDebug() << "PublicRoomListModel, something's wrong: index.row() >= "
|
||||||
|
"rooms.count()";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto room = rooms.at(index.row());
|
||||||
|
if (role == NameRole) {
|
||||||
|
auto displayName = room.name;
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName = room.canonicalAlias;
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!room.aliases.isEmpty()) {
|
||||||
|
displayName = room.aliases.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return room.roomId;
|
||||||
|
}
|
||||||
|
if (role == AvatarRole) {
|
||||||
|
auto avatarUrl = room.avatarUrl;
|
||||||
|
|
||||||
|
if (avatarUrl.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatarUrl.remove(0, 6);
|
||||||
|
}
|
||||||
|
if (role == TopicRole) {
|
||||||
|
return room.topic;
|
||||||
|
}
|
||||||
|
if (role == RoomIDRole) {
|
||||||
|
return room.roomId;
|
||||||
|
}
|
||||||
|
if (role == MemberCountRole) {
|
||||||
|
return room.numJoinedMembers;
|
||||||
|
}
|
||||||
|
if (role == AllowGuestsRole) {
|
||||||
|
return room.guestCanJoin;
|
||||||
|
}
|
||||||
|
if (role == WorldReadableRole) {
|
||||||
|
return room.worldReadable;
|
||||||
|
}
|
||||||
|
if (role == IsJoinedRole) {
|
||||||
|
if (!m_connection)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return m_connection->room(room.roomId, JoinState::Join) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (index.row() >= rooms.count()) {
|
|
||||||
qDebug() << "PublicRoomListModel, something's wrong: index.row() >= "
|
|
||||||
"rooms.count()";
|
|
||||||
return {};
|
return {};
|
||||||
}
|
|
||||||
auto room = rooms.at(index.row());
|
|
||||||
if (role == NameRole) {
|
|
||||||
auto displayName = room.name;
|
|
||||||
if (!displayName.isEmpty()) {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
displayName = room.canonicalAlias;
|
|
||||||
if (!displayName.isEmpty()) {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!room.aliases.isEmpty()) {
|
|
||||||
displayName = room.aliases.front();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!displayName.isEmpty()) {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return room.roomId;
|
|
||||||
}
|
|
||||||
if (role == AvatarRole) {
|
|
||||||
auto avatarUrl = room.avatarUrl;
|
|
||||||
|
|
||||||
if (avatarUrl.isEmpty()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return avatarUrl.remove(0, 6);
|
|
||||||
}
|
|
||||||
if (role == TopicRole) {
|
|
||||||
return room.topic;
|
|
||||||
}
|
|
||||||
if (role == RoomIDRole) {
|
|
||||||
return room.roomId;
|
|
||||||
}
|
|
||||||
if (role == MemberCountRole) {
|
|
||||||
return room.numJoinedMembers;
|
|
||||||
}
|
|
||||||
if (role == AllowGuestsRole) {
|
|
||||||
return room.guestCanJoin;
|
|
||||||
}
|
|
||||||
if (role == WorldReadableRole) {
|
|
||||||
return room.worldReadable;
|
|
||||||
}
|
|
||||||
if (role == IsJoinedRole) {
|
|
||||||
if (!m_connection)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return m_connection->room(room.roomId, JoinState::Join) != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
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";
|
||||||
roles[AvatarRole] = "avatar";
|
roles[AvatarRole] = "avatar";
|
||||||
roles[TopicRole] = "topic";
|
roles[TopicRole] = "topic";
|
||||||
roles[RoomIDRole] = "roomID";
|
roles[RoomIDRole] = "roomID";
|
||||||
roles[MemberCountRole] = "memberCount";
|
roles[MemberCountRole] = "memberCount";
|
||||||
roles[AllowGuestsRole] = "allowGuests";
|
roles[AllowGuestsRole] = "allowGuests";
|
||||||
roles[WorldReadableRole] = "worldReadable";
|
roles[WorldReadableRole] = "worldReadable";
|
||||||
roles[IsJoinedRole] = "isJoined";
|
roles[IsJoinedRole] = "isJoined";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PublicRoomListModel::rowCount(const QModelIndex& parent) const {
|
int PublicRoomListModel::rowCount(const QModelIndex &parent) const
|
||||||
if (parent.isValid())
|
{
|
||||||
return 0;
|
if (parent.isValid())
|
||||||
|
return 0;
|
||||||
|
|
||||||
return rooms.count();
|
return rooms.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PublicRoomListModel::hasMore() const {
|
bool PublicRoomListModel::hasMore() const
|
||||||
return !(attempted && nextBatch.isEmpty());
|
{
|
||||||
|
return !(attempted && nextBatch.isEmpty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,64 +10,72 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
class PublicRoomListModel : public QAbstractListModel {
|
class PublicRoomListModel : public QAbstractListModel
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(Connection* connection READ connection WRITE setConnection NOTIFY
|
Q_OBJECT
|
||||||
connectionChanged)
|
Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY 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:
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
NameRole = Qt::DisplayRole + 1,
|
NameRole = Qt::DisplayRole + 1,
|
||||||
AvatarRole,
|
AvatarRole,
|
||||||
TopicRole,
|
TopicRole,
|
||||||
RoomIDRole,
|
RoomIDRole,
|
||||||
MemberCountRole,
|
MemberCountRole,
|
||||||
AllowGuestsRole,
|
AllowGuestsRole,
|
||||||
WorldReadableRole,
|
WorldReadableRole,
|
||||||
IsJoinedRole,
|
IsJoinedRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
PublicRoomListModel(QObject* parent = nullptr);
|
PublicRoomListModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
QVariant data(const QModelIndex& index, int role = NameRole) const override;
|
QVariant data(const QModelIndex &index, int role = NameRole) const override;
|
||||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Connection* connection() const { return m_connection; }
|
Connection *connection() const
|
||||||
void setConnection(Connection* value);
|
{
|
||||||
|
return m_connection;
|
||||||
|
}
|
||||||
|
void setConnection(Connection *value);
|
||||||
|
|
||||||
QString server() const { return m_server; }
|
QString server() const
|
||||||
void setServer(const QString& value);
|
{
|
||||||
|
return m_server;
|
||||||
|
}
|
||||||
|
void setServer(const QString &value);
|
||||||
|
|
||||||
QString keyword() const { return m_keyword; }
|
QString keyword() const
|
||||||
void setKeyword(const QString& value);
|
{
|
||||||
|
return m_keyword;
|
||||||
|
}
|
||||||
|
void setKeyword(const QString &value);
|
||||||
|
|
||||||
bool hasMore() const;
|
bool hasMore() const;
|
||||||
|
|
||||||
Q_INVOKABLE void next(int count = 50);
|
Q_INVOKABLE void next(int count = 50);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Connection* m_connection = nullptr;
|
Connection *m_connection = nullptr;
|
||||||
QString m_server;
|
QString m_server;
|
||||||
QString m_keyword;
|
QString m_keyword;
|
||||||
|
|
||||||
bool attempted = false;
|
bool attempted = false;
|
||||||
QString nextBatch;
|
QString nextBatch;
|
||||||
|
|
||||||
QVector<PublicRoomsChunk> rooms;
|
QVector<PublicRoomsChunk> rooms;
|
||||||
|
|
||||||
QueryPublicRoomsJob* job = nullptr;
|
QueryPublicRoomsJob *job = nullptr;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
void serverChanged();
|
void serverChanged();
|
||||||
void keywordChanged();
|
void keywordChanged();
|
||||||
void hasMoreChanged();
|
void hasMoreChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PUBLICROOMLISTMODEL_H
|
#endif // PUBLICROOMLISTMODEL_H
|
||||||
|
|||||||
@@ -11,251 +11,268 @@
|
|||||||
#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)
|
{
|
||||||
return;
|
if (connection == m_connection)
|
||||||
if (m_connection)
|
return;
|
||||||
m_connection->disconnect(this);
|
if (m_connection)
|
||||||
if (!connection) {
|
m_connection->disconnect(this);
|
||||||
qDebug() << "Removing current connection...";
|
if (!connection) {
|
||||||
m_connection = nullptr;
|
qDebug() << "Removing current connection...";
|
||||||
|
m_connection = nullptr;
|
||||||
|
beginResetModel();
|
||||||
|
m_rooms.clear();
|
||||||
|
endResetModel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connection = connection;
|
||||||
|
|
||||||
|
for (SpectralRoom *room : m_rooms)
|
||||||
|
room->disconnect(this);
|
||||||
|
|
||||||
|
connect(connection, &Connection::connected, this, &RoomListModel::doResetModel);
|
||||||
|
connect(connection, &Connection::invitedRoom, this, &RoomListModel::updateRoom);
|
||||||
|
connect(connection, &Connection::joinedRoom, this, &RoomListModel::updateRoom);
|
||||||
|
connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom);
|
||||||
|
connect(connection, &Connection::aboutToDeleteRoom, this, &RoomListModel::deleteRoom);
|
||||||
|
connect(connection, &Connection::directChatsListChanged, this, [=](Quotient::DirectChatsMap additions, Quotient::DirectChatsMap removals) {
|
||||||
|
for (QString roomID : additions.values() + removals.values()) {
|
||||||
|
auto room = connection->room(roomID);
|
||||||
|
if (room)
|
||||||
|
refresh(static_cast<SpectralRoom *>(room));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
doResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomListModel::doResetModel()
|
||||||
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_rooms.clear();
|
m_rooms.clear();
|
||||||
endResetModel();
|
for (auto r : m_connection->allRooms()) {
|
||||||
return;
|
doAddRoom(r);
|
||||||
}
|
|
||||||
|
|
||||||
m_connection = connection;
|
|
||||||
|
|
||||||
for (SpectralRoom* room : m_rooms)
|
|
||||||
room->disconnect(this);
|
|
||||||
|
|
||||||
connect(connection, &Connection::connected, this,
|
|
||||||
&RoomListModel::doResetModel);
|
|
||||||
connect(connection, &Connection::invitedRoom, this,
|
|
||||||
&RoomListModel::updateRoom);
|
|
||||||
connect(connection, &Connection::joinedRoom, this,
|
|
||||||
&RoomListModel::updateRoom);
|
|
||||||
connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom);
|
|
||||||
connect(connection, &Connection::aboutToDeleteRoom, this,
|
|
||||||
&RoomListModel::deleteRoom);
|
|
||||||
connect(connection, &Connection::directChatsListChanged, this,
|
|
||||||
[=](Quotient::DirectChatsMap additions,
|
|
||||||
Quotient::DirectChatsMap removals) {
|
|
||||||
for (QString roomID : additions.values() + removals.values()) {
|
|
||||||
auto room = connection->room(roomID);
|
|
||||||
if (room)
|
|
||||||
refresh(static_cast<SpectralRoom*>(room));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
doResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomListModel::doResetModel() {
|
|
||||||
beginResetModel();
|
|
||||||
m_rooms.clear();
|
|
||||||
for (auto r : m_connection->allRooms()) {
|
|
||||||
doAddRoom(r);
|
|
||||||
}
|
|
||||||
endResetModel();
|
|
||||||
refreshNotificationCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
SpectralRoom* RoomListModel::roomAt(int row) const {
|
|
||||||
return m_rooms.at(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomListModel::doAddRoom(Room* r) {
|
|
||||||
if (auto room = static_cast<SpectralRoom*>(r)) {
|
|
||||||
m_rooms.append(room);
|
|
||||||
connectRoomSignals(room);
|
|
||||||
emit roomAdded(room);
|
|
||||||
} else {
|
|
||||||
qCritical() << "Attempt to add nullptr to the room list";
|
|
||||||
Q_ASSERT(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomListModel::connectRoomSignals(SpectralRoom* room) {
|
|
||||||
connect(room, &Room::displaynameChanged, this, [=] { refresh(room); });
|
|
||||||
connect(room, &Room::unreadMessagesChanged, this, [=] { refresh(room); });
|
|
||||||
connect(room, &Room::notificationCountChanged, this, [=] { 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, [=] {
|
|
||||||
if (room->notificationCount() == 0)
|
|
||||||
return;
|
|
||||||
if (room->timelineSize() == 0)
|
|
||||||
return;
|
|
||||||
const RoomEvent* lastEvent = room->messageEvents().rbegin()->get();
|
|
||||||
if (lastEvent->isStateEvent())
|
|
||||||
return;
|
|
||||||
User* sender = room->user(lastEvent->senderId());
|
|
||||||
if (sender == room->localUser())
|
|
||||||
return;
|
|
||||||
emit newMessage(room->id(), lastEvent->id(), room->displayName(),
|
|
||||||
sender->displayname(), room->eventToString(*lastEvent),
|
|
||||||
room->avatar(128));
|
|
||||||
});
|
|
||||||
connect(room, &Room::highlightCountChanged, this, [=] {
|
|
||||||
if (room->highlightCount() == 0)
|
|
||||||
return;
|
|
||||||
if (room->timelineSize() == 0)
|
|
||||||
return;
|
|
||||||
const RoomEvent* lastEvent = room->messageEvents().rbegin()->get();
|
|
||||||
if (lastEvent->isStateEvent())
|
|
||||||
return;
|
|
||||||
User* sender = room->user(lastEvent->senderId());
|
|
||||||
if (sender == room->localUser())
|
|
||||||
return;
|
|
||||||
emit newHighlight(room->id(), lastEvent->id(), room->displayName(),
|
|
||||||
sender->displayname(), room->eventToString(*lastEvent),
|
|
||||||
room->avatar(128));
|
|
||||||
});
|
|
||||||
connect(room, &Room::notificationCountChanged, this,
|
|
||||||
&RoomListModel::refreshNotificationCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomListModel::refreshNotificationCount() {
|
|
||||||
int count = 0;
|
|
||||||
for (auto room : m_rooms) {
|
|
||||||
count += room->notificationCount();
|
|
||||||
}
|
|
||||||
m_notificationCount = count;
|
|
||||||
emit notificationCountChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomListModel::updateRoom(Room* room, Room* prev) {
|
|
||||||
// There are two cases when this method is called:
|
|
||||||
// 1. (prev == nullptr) adding a new room to the room list
|
|
||||||
// 2. (prev != nullptr) accepting/rejecting an invitation or inviting to
|
|
||||||
// the previously left room (in both cases prev has the previous state).
|
|
||||||
if (prev == room) {
|
|
||||||
qCritical() << "RoomListModel::updateRoom: room tried to replace itself";
|
|
||||||
refresh(static_cast<SpectralRoom*>(room));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (prev && room->id() != prev->id()) {
|
|
||||||
qCritical() << "RoomListModel::updateRoom: attempt to update room"
|
|
||||||
<< room->id() << "to" << prev->id();
|
|
||||||
// That doesn't look right but technically we still can do it.
|
|
||||||
}
|
|
||||||
// Ok, we're through with pre-checks, now for the real thing.
|
|
||||||
auto newRoom = static_cast<SpectralRoom*>(room);
|
|
||||||
const auto it = std::find_if(
|
|
||||||
m_rooms.begin(), m_rooms.end(),
|
|
||||||
[=](const SpectralRoom* r) { return r == prev || r == newRoom; });
|
|
||||||
if (it != m_rooms.end()) {
|
|
||||||
const int row = it - m_rooms.begin();
|
|
||||||
// There's no guarantee that prev != newRoom
|
|
||||||
if (*it == prev && *it != newRoom) {
|
|
||||||
prev->disconnect(this);
|
|
||||||
m_rooms.replace(row, newRoom);
|
|
||||||
connectRoomSignals(newRoom);
|
|
||||||
}
|
}
|
||||||
emit dataChanged(index(row), index(row));
|
endResetModel();
|
||||||
} else {
|
refreshNotificationCount();
|
||||||
beginInsertRows(QModelIndex(), m_rooms.count(), m_rooms.count());
|
|
||||||
doAddRoom(newRoom);
|
|
||||||
endInsertRows();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomListModel::deleteRoom(Room* room) {
|
SpectralRoom *RoomListModel::roomAt(int row) const
|
||||||
qDebug() << "Deleting room" << room->id();
|
{
|
||||||
const auto it = std::find(m_rooms.begin(), m_rooms.end(), room);
|
return m_rooms.at(row);
|
||||||
if (it == m_rooms.end())
|
|
||||||
return; // Already deleted, nothing to do
|
|
||||||
qDebug() << "Erasing room" << room->id();
|
|
||||||
const int row = it - m_rooms.begin();
|
|
||||||
beginRemoveRows(QModelIndex(), row, row);
|
|
||||||
m_rooms.erase(it);
|
|
||||||
endRemoveRows();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int RoomListModel::rowCount(const QModelIndex& parent) const {
|
void RoomListModel::doAddRoom(Room *r)
|
||||||
if (parent.isValid())
|
{
|
||||||
return 0;
|
if (auto room = static_cast<SpectralRoom *>(r)) {
|
||||||
return m_rooms.count();
|
m_rooms.append(room);
|
||||||
|
connectRoomSignals(room);
|
||||||
|
emit roomAdded(room);
|
||||||
|
} else {
|
||||||
|
qCritical() << "Attempt to add nullptr to the room list";
|
||||||
|
Q_ASSERT(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant RoomListModel::data(const QModelIndex& index, int role) const {
|
void RoomListModel::connectRoomSignals(SpectralRoom *room)
|
||||||
if (!index.isValid())
|
{
|
||||||
|
connect(room, &Room::displaynameChanged, this, [=] {
|
||||||
|
refresh(room);
|
||||||
|
});
|
||||||
|
connect(room, &Room::unreadMessagesChanged, this, [=] {
|
||||||
|
refresh(room);
|
||||||
|
});
|
||||||
|
connect(room, &Room::notificationCountChanged, this, [=] {
|
||||||
|
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, [=] {
|
||||||
|
if (room->notificationCount() == 0)
|
||||||
|
return;
|
||||||
|
if (room->timelineSize() == 0)
|
||||||
|
return;
|
||||||
|
const RoomEvent *lastEvent = room->messageEvents().rbegin()->get();
|
||||||
|
if (lastEvent->isStateEvent())
|
||||||
|
return;
|
||||||
|
User *sender = room->user(lastEvent->senderId());
|
||||||
|
if (sender == room->localUser())
|
||||||
|
return;
|
||||||
|
emit newMessage(room->id(), lastEvent->id(), room->displayName(), sender->displayname(), room->eventToString(*lastEvent), room->avatar(128));
|
||||||
|
});
|
||||||
|
connect(room, &Room::highlightCountChanged, this, [=] {
|
||||||
|
if (room->highlightCount() == 0)
|
||||||
|
return;
|
||||||
|
if (room->timelineSize() == 0)
|
||||||
|
return;
|
||||||
|
const RoomEvent *lastEvent = room->messageEvents().rbegin()->get();
|
||||||
|
if (lastEvent->isStateEvent())
|
||||||
|
return;
|
||||||
|
User *sender = room->user(lastEvent->senderId());
|
||||||
|
if (sender == room->localUser())
|
||||||
|
return;
|
||||||
|
emit newHighlight(room->id(), lastEvent->id(), room->displayName(), sender->displayname(), room->eventToString(*lastEvent), room->avatar(128));
|
||||||
|
});
|
||||||
|
connect(room, &Room::notificationCountChanged, this, &RoomListModel::refreshNotificationCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomListModel::refreshNotificationCount()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (auto room : m_rooms) {
|
||||||
|
count += room->notificationCount();
|
||||||
|
}
|
||||||
|
m_notificationCount = count;
|
||||||
|
emit notificationCountChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomListModel::updateRoom(Room *room, Room *prev)
|
||||||
|
{
|
||||||
|
// There are two cases when this method is called:
|
||||||
|
// 1. (prev == nullptr) adding a new room to the room list
|
||||||
|
// 2. (prev != nullptr) accepting/rejecting an invitation or inviting to
|
||||||
|
// the previously left room (in both cases prev has the previous state).
|
||||||
|
if (prev == room) {
|
||||||
|
qCritical() << "RoomListModel::updateRoom: room tried to replace itself";
|
||||||
|
refresh(static_cast<SpectralRoom *>(room));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (prev && room->id() != prev->id()) {
|
||||||
|
qCritical() << "RoomListModel::updateRoom: attempt to update room" << room->id() << "to" << prev->id();
|
||||||
|
// That doesn't look right but technically we still can do it.
|
||||||
|
}
|
||||||
|
// Ok, we're through with pre-checks, now for the real thing.
|
||||||
|
auto newRoom = static_cast<SpectralRoom *>(room);
|
||||||
|
const auto it = std::find_if(m_rooms.begin(), m_rooms.end(), [=](const SpectralRoom *r) {
|
||||||
|
return r == prev || r == newRoom;
|
||||||
|
});
|
||||||
|
if (it != m_rooms.end()) {
|
||||||
|
const int row = it - m_rooms.begin();
|
||||||
|
// There's no guarantee that prev != newRoom
|
||||||
|
if (*it == prev && *it != newRoom) {
|
||||||
|
prev->disconnect(this);
|
||||||
|
m_rooms.replace(row, newRoom);
|
||||||
|
connectRoomSignals(newRoom);
|
||||||
|
}
|
||||||
|
emit dataChanged(index(row), index(row));
|
||||||
|
} else {
|
||||||
|
beginInsertRows(QModelIndex(), m_rooms.count(), m_rooms.count());
|
||||||
|
doAddRoom(newRoom);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomListModel::deleteRoom(Room *room)
|
||||||
|
{
|
||||||
|
qDebug() << "Deleting room" << room->id();
|
||||||
|
const auto it = std::find(m_rooms.begin(), m_rooms.end(), room);
|
||||||
|
if (it == m_rooms.end())
|
||||||
|
return; // Already deleted, nothing to do
|
||||||
|
qDebug() << "Erasing room" << room->id();
|
||||||
|
const int row = it - m_rooms.begin();
|
||||||
|
beginRemoveRows(QModelIndex(), row, row);
|
||||||
|
m_rooms.erase(it);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
int RoomListModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid())
|
||||||
|
return 0;
|
||||||
|
return m_rooms.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
if (index.row() >= m_rooms.count()) {
|
||||||
|
qDebug() << "UserListModel: something wrong here...";
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
SpectralRoom *room = m_rooms.at(index.row());
|
||||||
|
if (role == NameRole)
|
||||||
|
return room->displayName();
|
||||||
|
if (role == AvatarRole)
|
||||||
|
return room->avatarMediaId();
|
||||||
|
if (role == TopicRole)
|
||||||
|
return room->topic();
|
||||||
|
if (role == CategoryRole) {
|
||||||
|
if (room->joinState() == JoinState::Invite)
|
||||||
|
return RoomType::Invited;
|
||||||
|
if (room->isFavourite())
|
||||||
|
return RoomType::Favorite;
|
||||||
|
if (room->isDirectChat())
|
||||||
|
return RoomType::Direct;
|
||||||
|
if (room->isLowPriority())
|
||||||
|
return RoomType::Deprioritized;
|
||||||
|
return RoomType::Normal;
|
||||||
|
}
|
||||||
|
if (role == UnreadCountRole)
|
||||||
|
return room->unreadCount();
|
||||||
|
if (role == NotificationCountRole)
|
||||||
|
return room->notificationCount();
|
||||||
|
if (role == HighlightCountRole)
|
||||||
|
return room->highlightCount();
|
||||||
|
if (role == LastEventRole)
|
||||||
|
return room->lastEvent();
|
||||||
|
if (role == LastActiveTimeRole)
|
||||||
|
return room->lastActiveTime();
|
||||||
|
if (role == JoinStateRole) {
|
||||||
|
if (!room->successorId().isEmpty())
|
||||||
|
return QStringLiteral("upgraded");
|
||||||
|
return toCString(room->joinState());
|
||||||
|
}
|
||||||
|
if (role == CurrentRoomRole)
|
||||||
|
return QVariant::fromValue(room);
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
if (index.row() >= m_rooms.count()) {
|
|
||||||
qDebug() << "UserListModel: something wrong here...";
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
SpectralRoom* room = m_rooms.at(index.row());
|
|
||||||
if (role == NameRole)
|
|
||||||
return room->displayName();
|
|
||||||
if (role == AvatarRole)
|
|
||||||
return room->avatarMediaId();
|
|
||||||
if (role == TopicRole)
|
|
||||||
return room->topic();
|
|
||||||
if (role == CategoryRole) {
|
|
||||||
if (room->joinState() == JoinState::Invite)
|
|
||||||
return RoomType::Invited;
|
|
||||||
if (room->isFavourite())
|
|
||||||
return RoomType::Favorite;
|
|
||||||
if (room->isDirectChat())
|
|
||||||
return RoomType::Direct;
|
|
||||||
if (room->isLowPriority())
|
|
||||||
return RoomType::Deprioritized;
|
|
||||||
return RoomType::Normal;
|
|
||||||
}
|
|
||||||
if (role == UnreadCountRole)
|
|
||||||
return room->unreadCount();
|
|
||||||
if (role == NotificationCountRole)
|
|
||||||
return room->notificationCount();
|
|
||||||
if (role == HighlightCountRole)
|
|
||||||
return room->highlightCount();
|
|
||||||
if (role == LastEventRole)
|
|
||||||
return room->lastEvent();
|
|
||||||
if (role == LastActiveTimeRole)
|
|
||||||
return room->lastActiveTime();
|
|
||||||
if (role == JoinStateRole) {
|
|
||||||
if (!room->successorId().isEmpty())
|
|
||||||
return QStringLiteral("upgraded");
|
|
||||||
return toCString(room->joinState());
|
|
||||||
}
|
|
||||||
if (role == CurrentRoomRole)
|
|
||||||
return QVariant::fromValue(room);
|
|
||||||
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);
|
{
|
||||||
if (it == m_rooms.end()) {
|
const auto it = std::find(m_rooms.begin(), m_rooms.end(), room);
|
||||||
qCritical() << "Room" << room->id() << "not found in the room list";
|
if (it == m_rooms.end()) {
|
||||||
return;
|
qCritical() << "Room" << room->id() << "not found in the room list";
|
||||||
}
|
return;
|
||||||
const auto idx = index(it - m_rooms.begin());
|
}
|
||||||
emit dataChanged(idx, idx, roles);
|
const auto idx = index(it - m_rooms.begin());
|
||||||
|
emit dataChanged(idx, idx, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> RoomListModel::roleNames() const {
|
QHash<int, QByteArray> RoomListModel::roleNames() const
|
||||||
QHash<int, QByteArray> roles;
|
{
|
||||||
roles[NameRole] = "name";
|
QHash<int, QByteArray> roles;
|
||||||
roles[AvatarRole] = "avatar";
|
roles[NameRole] = "name";
|
||||||
roles[TopicRole] = "topic";
|
roles[AvatarRole] = "avatar";
|
||||||
roles[CategoryRole] = "category";
|
roles[TopicRole] = "topic";
|
||||||
roles[UnreadCountRole] = "unreadCount";
|
roles[CategoryRole] = "category";
|
||||||
roles[NotificationCountRole] = "notificationCount";
|
roles[UnreadCountRole] = "unreadCount";
|
||||||
roles[HighlightCountRole] = "highlightCount";
|
roles[NotificationCountRole] = "notificationCount";
|
||||||
roles[LastEventRole] = "lastEvent";
|
roles[HighlightCountRole] = "highlightCount";
|
||||||
roles[LastActiveTimeRole] = "lastActiveTime";
|
roles[LastEventRole] = "lastEvent";
|
||||||
roles[JoinStateRole] = "joinState";
|
roles[LastActiveTimeRole] = "lastActiveTime";
|
||||||
roles[CurrentRoomRole] = "currentRoom";
|
roles[JoinStateRole] = "joinState";
|
||||||
return roles;
|
roles[CurrentRoomRole] = "currentRoom";
|
||||||
|
return roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,92 +10,87 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
class RoomType : public QObject {
|
class RoomType : public QObject
|
||||||
Q_OBJECT
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Types {
|
enum Types {
|
||||||
Invited = 1,
|
Invited = 1,
|
||||||
Favorite,
|
Favorite,
|
||||||
Direct,
|
Direct,
|
||||||
Normal,
|
Normal,
|
||||||
Deprioritized,
|
Deprioritized,
|
||||||
};
|
};
|
||||||
Q_ENUMS(Types)
|
Q_ENUMS(Types)
|
||||||
};
|
};
|
||||||
|
|
||||||
class RoomListModel : public QAbstractListModel {
|
class RoomListModel : public QAbstractListModel
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(Connection* connection READ connection WRITE setConnection)
|
Q_OBJECT
|
||||||
Q_PROPERTY(int notificationCount READ notificationCount NOTIFY
|
Q_PROPERTY(Connection *connection READ connection WRITE setConnection)
|
||||||
notificationCountChanged)
|
Q_PROPERTY(int notificationCount READ notificationCount NOTIFY notificationCountChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
NameRole = Qt::UserRole + 1,
|
NameRole = Qt::UserRole + 1,
|
||||||
AvatarRole,
|
AvatarRole,
|
||||||
TopicRole,
|
TopicRole,
|
||||||
CategoryRole,
|
CategoryRole,
|
||||||
UnreadCountRole,
|
UnreadCountRole,
|
||||||
NotificationCountRole,
|
NotificationCountRole,
|
||||||
HighlightCountRole,
|
HighlightCountRole,
|
||||||
LastEventRole,
|
LastEventRole,
|
||||||
LastActiveTimeRole,
|
LastActiveTimeRole,
|
||||||
JoinStateRole,
|
JoinStateRole,
|
||||||
CurrentRoomRole,
|
CurrentRoomRole,
|
||||||
};
|
};
|
||||||
Q_ENUM(EventRoles)
|
Q_ENUM(EventRoles)
|
||||||
|
|
||||||
RoomListModel(QObject* parent = nullptr);
|
RoomListModel(QObject *parent = nullptr);
|
||||||
virtual ~RoomListModel() override;
|
virtual ~RoomListModel() override;
|
||||||
|
|
||||||
Connection* connection() const { return m_connection; }
|
Connection *connection() const
|
||||||
void setConnection(Connection* connection);
|
{
|
||||||
void doResetModel();
|
return m_connection;
|
||||||
|
}
|
||||||
|
void setConnection(Connection *connection);
|
||||||
|
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);
|
||||||
void updateRoom(Room* room, Room* prev);
|
void updateRoom(Room *room, Room *prev);
|
||||||
void deleteRoom(Room* room);
|
void deleteRoom(Room *room);
|
||||||
void refresh(SpectralRoom* room, const QVector<int>& roles = {});
|
void refresh(SpectralRoom *room, const QVector<int> &roles = {});
|
||||||
void refreshNotificationCount();
|
void refreshNotificationCount();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Connection* m_connection = nullptr;
|
Connection *m_connection = nullptr;
|
||||||
QList<SpectralRoom*> m_rooms;
|
QList<SpectralRoom *> m_rooms;
|
||||||
|
|
||||||
int m_notificationCount = 0;
|
int m_notificationCount = 0;
|
||||||
|
|
||||||
void connectRoomSignals(SpectralRoom* room);
|
void connectRoomSignals(SpectralRoom *room);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
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
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,110 +18,105 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
class SpectralRoom : public Room {
|
class SpectralRoom : public Room
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
|
Q_OBJECT
|
||||||
Q_PROPERTY(QString cachedInput MEMBER m_cachedInput NOTIFY cachedInputChanged)
|
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
|
||||||
Q_PROPERTY(bool hasFileUploading READ hasFileUploading WRITE
|
Q_PROPERTY(QString cachedInput MEMBER m_cachedInput NOTIFY cachedInputChanged)
|
||||||
setHasFileUploading NOTIFY hasFileUploadingChanged)
|
Q_PROPERTY(bool hasFileUploading READ hasFileUploading WRITE setHasFileUploading NOTIFY hasFileUploadingChanged)
|
||||||
Q_PROPERTY(int fileUploadingProgress READ fileUploadingProgress NOTIFY
|
Q_PROPERTY(int fileUploadingProgress READ fileUploadingProgress NOTIFY fileUploadingProgressChanged)
|
||||||
fileUploadingProgressChanged)
|
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
|
||||||
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;
|
||||||
|
|
||||||
QString lastEvent() const;
|
QString lastEvent() const;
|
||||||
bool isEventHighlighted(const Quotient::RoomEvent* e) const;
|
bool isEventHighlighted(const Quotient::RoomEvent *e) const;
|
||||||
|
|
||||||
QDateTime lastActiveTime() const;
|
QDateTime lastActiveTime() const;
|
||||||
|
|
||||||
bool hasFileUploading() const { return m_hasFileUploading; }
|
bool hasFileUploading() const
|
||||||
void setHasFileUploading(bool value) {
|
{
|
||||||
if (value == m_hasFileUploading) {
|
return m_hasFileUploading;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
m_hasFileUploading = value;
|
void setHasFileUploading(bool value)
|
||||||
emit hasFileUploadingChanged();
|
{
|
||||||
}
|
if (value == m_hasFileUploading) {
|
||||||
|
return;
|
||||||
int fileUploadingProgress() const { return m_fileUploadingProgress; }
|
}
|
||||||
void setFileUploadingProgress(int value) {
|
m_hasFileUploading = value;
|
||||||
if (m_fileUploadingProgress == value) {
|
emit hasFileUploadingChanged();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
m_fileUploadingProgress = value;
|
|
||||||
emit fileUploadingProgressChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE int savedTopVisibleIndex() const;
|
int fileUploadingProgress() const
|
||||||
Q_INVOKABLE int savedBottomVisibleIndex() const;
|
{
|
||||||
Q_INVOKABLE void saveViewport(int topIndex, int bottomIndex);
|
return m_fileUploadingProgress;
|
||||||
|
}
|
||||||
|
void setFileUploadingProgress(int value)
|
||||||
|
{
|
||||||
|
if (m_fileUploadingProgress == value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_fileUploadingProgress = value;
|
||||||
|
emit fileUploadingProgressChanged();
|
||||||
|
}
|
||||||
|
|
||||||
Q_INVOKABLE QVariantList getUsers(const QString& keyword) const;
|
Q_INVOKABLE int savedTopVisibleIndex() const;
|
||||||
|
Q_INVOKABLE int savedBottomVisibleIndex() const;
|
||||||
|
Q_INVOKABLE void saveViewport(int topIndex, int bottomIndex);
|
||||||
|
|
||||||
Q_INVOKABLE QUrl urlToMxcUrl(QUrl mxcUrl);
|
Q_INVOKABLE QVariantList getUsers(const QString &keyword) const;
|
||||||
|
|
||||||
QString avatarMediaId() const;
|
Q_INVOKABLE QUrl urlToMxcUrl(QUrl mxcUrl);
|
||||||
|
|
||||||
QString eventToString(const RoomEvent& evt,
|
QString avatarMediaId() const;
|
||||||
Qt::TextFormat format = Qt::PlainText,
|
|
||||||
bool removeReply = true) const;
|
|
||||||
|
|
||||||
Q_INVOKABLE bool containsUser(QString userID) const;
|
QString eventToString(const RoomEvent &evt, Qt::TextFormat format = Qt::PlainText, bool removeReply = true) const;
|
||||||
|
|
||||||
Q_INVOKABLE bool canSendEvent(const QString& eventType) const;
|
Q_INVOKABLE bool containsUser(QString userID) const;
|
||||||
Q_INVOKABLE bool canSendState(const QString& eventType) const;
|
|
||||||
|
|
||||||
private:
|
Q_INVOKABLE bool canSendEvent(const QString &eventType) const;
|
||||||
QString m_cachedInput;
|
Q_INVOKABLE bool canSendState(const QString &eventType) const;
|
||||||
QSet<const Quotient::RoomEvent*> highlights;
|
|
||||||
|
|
||||||
bool m_hasFileUploading = false;
|
private:
|
||||||
int m_fileUploadingProgress = 0;
|
QString m_cachedInput;
|
||||||
|
QSet<const Quotient::RoomEvent *> highlights;
|
||||||
|
|
||||||
void checkForHighlights(const Quotient::TimelineItem& ti);
|
bool m_hasFileUploading = false;
|
||||||
|
int m_fileUploadingProgress = 0;
|
||||||
|
|
||||||
void onAddNewTimelineEvents(timeline_iter_t from) override;
|
void checkForHighlights(const Quotient::TimelineItem &ti);
|
||||||
void onAddHistoricalTimelineEvents(rev_iter_t from) override;
|
|
||||||
void onRedaction(const RoomEvent& prevEvent, const RoomEvent& after) override;
|
|
||||||
|
|
||||||
static QString markdownToHTML(const QString& plaintext);
|
void onAddNewTimelineEvents(timeline_iter_t from) override;
|
||||||
|
void onAddHistoricalTimelineEvents(rev_iter_t from) override;
|
||||||
|
void onRedaction(const RoomEvent &prevEvent, const RoomEvent &after) override;
|
||||||
|
|
||||||
private slots:
|
static QString markdownToHTML(const QString &plaintext);
|
||||||
void countChanged();
|
|
||||||
|
|
||||||
signals:
|
private slots:
|
||||||
void cachedInputChanged();
|
void countChanged();
|
||||||
void busyChanged();
|
|
||||||
void hasFileUploadingChanged();
|
|
||||||
void fileUploadingProgressChanged();
|
|
||||||
void backgroundChanged();
|
|
||||||
|
|
||||||
public slots:
|
signals:
|
||||||
void uploadFile(const QUrl& url, const QString& body = "");
|
void cachedInputChanged();
|
||||||
void acceptInvitation();
|
void busyChanged();
|
||||||
void forget();
|
void hasFileUploadingChanged();
|
||||||
void sendTypingNotification(bool isTyping);
|
void fileUploadingProgressChanged();
|
||||||
void postArbitaryMessage(const QString& text,
|
void backgroundChanged();
|
||||||
MessageEventType type = MessageEventType::Text,
|
|
||||||
const QString& replyEventId = "");
|
public slots:
|
||||||
void postPlainMessage(const QString& text,
|
void uploadFile(const QUrl &url, const QString &body = "");
|
||||||
MessageEventType type = MessageEventType::Text,
|
void acceptInvitation();
|
||||||
const QString& replyEventId = "");
|
void forget();
|
||||||
void postHtmlMessage(const QString& text,
|
void sendTypingNotification(bool isTyping);
|
||||||
const QString& html,
|
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 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);
|
||||||
void toggleReaction(const QString& eventId, const QString& reaction);
|
void toggleReaction(const QString &eventId, const QString &reaction);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SpectralRoom_H
|
#endif // SpectralRoom_H
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,17 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
class SpectralUser : public User {
|
class SpectralUser : public User
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(QColor color READ color CONSTANT)
|
Q_OBJECT
|
||||||
public:
|
Q_PROPERTY(QColor color READ color CONSTANT)
|
||||||
SpectralUser(QString userId, Connection* connection)
|
public:
|
||||||
: User(userId, connection) {}
|
SpectralUser(QString userId, Connection *connection)
|
||||||
|
: User(userId, connection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
QColor color();
|
QColor color();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SpectralUser_H
|
#endif // SpectralUser_H
|
||||||
|
|||||||
226
src/trayicon.cpp
226
src/trayicon.cpp
@@ -12,168 +12,168 @@
|
|||||||
#include <QtMacExtras>
|
#include <QtMacExtras>
|
||||||
#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,
|
painter->setRenderHint(QPainter::TextAntialiasing);
|
||||||
QIcon::State state) {
|
painter->setRenderHint(QPainter::SmoothPixmapTransform);
|
||||||
painter->setRenderHint(QPainter::TextAntialiasing);
|
painter->setRenderHint(QPainter::Antialiasing);
|
||||||
painter->setRenderHint(QPainter::SmoothPixmapTransform);
|
|
||||||
painter->setRenderHint(QPainter::Antialiasing);
|
|
||||||
|
|
||||||
icon_.paint(painter, rect, Qt::AlignCenter, mode, state);
|
icon_.paint(painter, rect, Qt::AlignCenter, mode, state);
|
||||||
|
|
||||||
if (isOnline && msgCount <= 0)
|
if (isOnline && msgCount <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QColor backgroundColor("red");
|
QColor backgroundColor("red");
|
||||||
QColor textColor("white");
|
QColor textColor("white");
|
||||||
|
|
||||||
QBrush brush;
|
QBrush brush;
|
||||||
brush.setStyle(Qt::SolidPattern);
|
brush.setStyle(Qt::SolidPattern);
|
||||||
brush.setColor(backgroundColor);
|
brush.setColor(backgroundColor);
|
||||||
|
|
||||||
painter->setBrush(brush);
|
painter->setBrush(brush);
|
||||||
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);
|
if (!isOnline) {
|
||||||
if (!isOnline) {
|
painter->drawText(bubble, Qt::AlignCenter, "x");
|
||||||
painter->drawText(bubble, Qt::AlignCenter, "x");
|
} else if (msgCount >= 100) {
|
||||||
} else if (msgCount >= 100) {
|
painter->drawText(bubble, Qt::AlignCenter, "99+");
|
||||||
painter->drawText(bubble, Qt::AlignCenter, "99+");
|
} else {
|
||||||
} else {
|
painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount));
|
||||||
painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount));
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
sizes.append(QSize(24, 24));
|
sizes.append(QSize(24, 24));
|
||||||
sizes.append(QSize(32, 32));
|
sizes.append(QSize(32, 32));
|
||||||
sizes.append(QSize(48, 48));
|
sizes.append(QSize(48, 48));
|
||||||
sizes.append(QSize(64, 64));
|
sizes.append(QSize(64, 64));
|
||||||
sizes.append(QSize(128, 128));
|
sizes.append(QSize(128, 128));
|
||||||
sizes.append(QSize(256, 256));
|
sizes.append(QSize(256, 256));
|
||||||
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);
|
{
|
||||||
{
|
QPainter painter(&result);
|
||||||
QPainter painter(&result);
|
paint(&painter, QRect(QPoint(0, 0), size), mode, state);
|
||||||
paint(&painter, QRect(QPoint(0, 0), size), mode, state);
|
}
|
||||||
}
|
return result;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TrayIcon::TrayIcon(QObject* parent) : QSystemTrayIcon(parent) {
|
TrayIcon::TrayIcon(QObject *parent)
|
||||||
QMenu* menu = new QMenu();
|
: QSystemTrayIcon(parent)
|
||||||
viewAction_ = new QAction(tr("Show"), parent);
|
{
|
||||||
quitAction_ = new QAction(tr("Quit"), parent);
|
QMenu *menu = new QMenu();
|
||||||
|
viewAction_ = new QAction(tr("Show"), parent);
|
||||||
|
quitAction_ = new QAction(tr("Quit"), parent);
|
||||||
|
|
||||||
connect(viewAction_, &QAction::triggered, this, &TrayIcon::showWindow);
|
connect(viewAction_, &QAction::triggered, this, &TrayIcon::showWindow);
|
||||||
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
|
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
|
||||||
|
|
||||||
menu->addAction(viewAction_);
|
menu->addAction(viewAction_);
|
||||||
menu->addAction(quitAction_);
|
menu->addAction(quitAction_);
|
||||||
|
|
||||||
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)
|
||||||
auto labelText = count == 0 ? "" : QString::number(count);
|
auto labelText = count == 0 ? "" : QString::number(count);
|
||||||
|
|
||||||
if (labelText == QtMac::badgeLabelText())
|
if (labelText == QtMac::badgeLabelText())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QtMac::setBadgeLabelText(labelText);
|
QtMac::setBadgeLabelText(labelText);
|
||||||
#elif defined(Q_OS_WIN)
|
#elif defined(Q_OS_WIN)
|
||||||
// FIXME: Find a way to use Windows apis for the badge counter (if any).
|
// FIXME: Find a way to use Windows apis for the badge counter (if any).
|
||||||
#else
|
#else
|
||||||
if (!icon_ || count == icon_->msgCount)
|
if (!icon_ || count == icon_->msgCount)
|
||||||
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));
|
||||||
|
|
||||||
icon_ = tmp;
|
icon_ = tmp;
|
||||||
#endif
|
#endif
|
||||||
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;
|
||||||
|
|
||||||
QtMac::setBadgeLabelText(labelText);
|
QtMac::setBadgeLabelText(labelText);
|
||||||
} else {
|
} else {
|
||||||
auto labelText = "x";
|
auto labelText = "x";
|
||||||
|
|
||||||
if (labelText == QtMac::badgeLabelText())
|
if (labelText == QtMac::badgeLabelText())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QtMac::setBadgeLabelText(labelText);
|
QtMac::setBadgeLabelText(labelText);
|
||||||
}
|
}
|
||||||
#elif defined(Q_OS_WIN)
|
#elif defined(Q_OS_WIN)
|
||||||
// FIXME: Find a way to use Windows apis for the badge counter (if any).
|
// FIXME: Find a way to use Windows apis for the badge counter (if any).
|
||||||
#else
|
#else
|
||||||
if (!icon_ || online == icon_->isOnline)
|
if (!icon_ || online == icon_->isOnline)
|
||||||
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));
|
||||||
|
|
||||||
icon_ = tmp;
|
icon_ = tmp;
|
||||||
#endif
|
#endif
|
||||||
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));
|
||||||
#else
|
#else
|
||||||
icon_ = new MsgCountComposedIcon(source);
|
icon_ = new MsgCountComposedIcon(source);
|
||||||
setIcon(QIcon(icon_));
|
setIcon(QIcon(icon_));
|
||||||
icon_->isOnline = m_isOnline;
|
icon_->isOnline = m_isOnline;
|
||||||
icon_->msgCount = m_notificationCount;
|
icon_->msgCount = m_notificationCount;
|
||||||
#endif
|
#endif
|
||||||
emit iconSourceChanged();
|
emit iconSourceChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,65 +10,68 @@
|
|||||||
#include <QRect>
|
#include <QRect>
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
|
|
||||||
class MsgCountComposedIcon : public QIconEngine {
|
class MsgCountComposedIcon : public QIconEngine
|
||||||
public:
|
{
|
||||||
MsgCountComposedIcon(const QString& filename);
|
public:
|
||||||
|
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,
|
virtual QIconEngine *clone() const;
|
||||||
QIcon::Mode mode,
|
virtual QList<QSize> availableSizes(QIcon::Mode mode, QIcon::State state) const;
|
||||||
QIcon::State state);
|
virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state);
|
||||||
virtual QIconEngine* clone() const;
|
|
||||||
virtual QList<QSize> availableSizes(QIcon::Mode mode,
|
|
||||||
QIcon::State state) const;
|
|
||||||
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?
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const int BubbleDiameter = 14;
|
const int BubbleDiameter = 14;
|
||||||
|
|
||||||
QIcon icon_;
|
QIcon icon_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TrayIcon : public QSystemTrayIcon {
|
class TrayIcon : public QSystemTrayIcon
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(QString iconSource READ iconSource WRITE setIconSource NOTIFY
|
Q_OBJECT
|
||||||
iconSourceChanged)
|
Q_PROPERTY(QString iconSource READ iconSource WRITE setIconSource NOTIFY iconSourceChanged)
|
||||||
Q_PROPERTY(int notificationCount READ notificationCount WRITE
|
Q_PROPERTY(int notificationCount READ notificationCount WRITE setNotificationCount NOTIFY notificationCountChanged)
|
||||||
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()
|
||||||
void setIconSource(const QString& source);
|
{
|
||||||
|
return m_iconSource;
|
||||||
|
}
|
||||||
|
void setIconSource(const QString &source);
|
||||||
|
|
||||||
int notificationCount() { return m_notificationCount; }
|
int notificationCount()
|
||||||
void setNotificationCount(int count);
|
{
|
||||||
|
return m_notificationCount;
|
||||||
|
}
|
||||||
|
void setNotificationCount(int count);
|
||||||
|
|
||||||
bool isOnline() { return m_isOnline; }
|
bool isOnline()
|
||||||
void setIsOnline(bool online);
|
{
|
||||||
|
return m_isOnline;
|
||||||
|
}
|
||||||
|
void setIsOnline(bool online);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void notificationCountChanged();
|
void notificationCountChanged();
|
||||||
void iconSourceChanged();
|
void iconSourceChanged();
|
||||||
void isOnlineChanged();
|
void isOnlineChanged();
|
||||||
|
|
||||||
void showWindow();
|
void showWindow();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_iconSource;
|
QString m_iconSource;
|
||||||
int m_notificationCount = 0;
|
int m_notificationCount = 0;
|
||||||
bool m_isOnline = true;
|
bool m_isOnline = true;
|
||||||
|
|
||||||
QAction* viewAction_;
|
QAction *viewAction_;
|
||||||
QAction* quitAction_;
|
QAction *quitAction_;
|
||||||
|
|
||||||
MsgCountComposedIcon* icon_ = nullptr;
|
MsgCountComposedIcon *icon_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TRAYICON_H
|
#endif // TRAYICON_H
|
||||||
|
|||||||
@@ -1,156 +1,163 @@
|
|||||||
#include "userdirectorylistmodel.h"
|
#include "userdirectorylistmodel.h"
|
||||||
|
|
||||||
UserDirectoryListModel::UserDirectoryListModel(QObject* parent)
|
UserDirectoryListModel::UserDirectoryListModel(QObject *parent)
|
||||||
: QAbstractListModel(parent) {}
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
void UserDirectoryListModel::setConnection(Connection* conn) {
|
|
||||||
if (m_connection == conn)
|
|
||||||
return;
|
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
|
|
||||||
m_limited = false;
|
|
||||||
attempted = false;
|
|
||||||
users.clear();
|
|
||||||
|
|
||||||
if (m_connection) {
|
|
||||||
m_connection->disconnect(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
endResetModel();
|
|
||||||
|
|
||||||
m_connection = conn;
|
|
||||||
|
|
||||||
if (job) {
|
|
||||||
job->abandon();
|
|
||||||
job = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit connectionChanged();
|
|
||||||
emit limitedChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserDirectoryListModel::setKeyword(const QString& value) {
|
void UserDirectoryListModel::setConnection(Connection *conn)
|
||||||
if (m_keyword == value)
|
{
|
||||||
return;
|
if (m_connection == conn)
|
||||||
|
return;
|
||||||
|
|
||||||
m_keyword = value;
|
beginResetModel();
|
||||||
|
|
||||||
m_limited = false;
|
m_limited = false;
|
||||||
attempted = false;
|
attempted = false;
|
||||||
|
users.clear();
|
||||||
|
|
||||||
if (job) {
|
if (m_connection) {
|
||||||
job->abandon();
|
m_connection->disconnect(this);
|
||||||
job = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit keywordChanged();
|
|
||||||
emit limitedChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UserDirectoryListModel::search(int count) {
|
|
||||||
if (count < 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (job) {
|
|
||||||
qDebug() << "UserDirectoryListModel: Other jobs running, ignore";
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attempted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
job = m_connection->callApi<SearchUserDirectoryJob>(m_keyword, count);
|
|
||||||
|
|
||||||
connect(job, &BaseJob::finished, this, [=] {
|
|
||||||
attempted = true;
|
|
||||||
|
|
||||||
if (job->status() == BaseJob::Success) {
|
|
||||||
auto users = job->results();
|
|
||||||
|
|
||||||
this->beginResetModel();
|
|
||||||
|
|
||||||
this->users = users;
|
|
||||||
this->m_limited = job->limited();
|
|
||||||
|
|
||||||
this->endResetModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->job = nullptr;
|
endResetModel();
|
||||||
|
|
||||||
|
m_connection = conn;
|
||||||
|
|
||||||
|
if (job) {
|
||||||
|
job->abandon();
|
||||||
|
job = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit connectionChanged();
|
||||||
emit limitedChanged();
|
emit limitedChanged();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant UserDirectoryListModel::data(const QModelIndex& index,
|
void UserDirectoryListModel::setKeyword(const QString &value)
|
||||||
int role) const {
|
{
|
||||||
if (!index.isValid())
|
if (m_keyword == value)
|
||||||
return QVariant();
|
return;
|
||||||
|
|
||||||
|
m_keyword = value;
|
||||||
|
|
||||||
|
m_limited = false;
|
||||||
|
attempted = false;
|
||||||
|
|
||||||
|
if (job) {
|
||||||
|
job->abandon();
|
||||||
|
job = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit keywordChanged();
|
||||||
|
emit limitedChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserDirectoryListModel::search(int count)
|
||||||
|
{
|
||||||
|
if (count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (job) {
|
||||||
|
qDebug() << "UserDirectoryListModel: Other jobs running, ignore";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
job = m_connection->callApi<SearchUserDirectoryJob>(m_keyword, count);
|
||||||
|
|
||||||
|
connect(job, &BaseJob::finished, this, [=] {
|
||||||
|
attempted = true;
|
||||||
|
|
||||||
|
if (job->status() == BaseJob::Success) {
|
||||||
|
auto users = job->results();
|
||||||
|
|
||||||
|
this->beginResetModel();
|
||||||
|
|
||||||
|
this->users = users;
|
||||||
|
this->m_limited = job->limited();
|
||||||
|
|
||||||
|
this->endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->job = nullptr;
|
||||||
|
|
||||||
|
emit limitedChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
if (index.row() >= users.count()) {
|
||||||
|
qDebug() << "UserDirectoryListModel, something's wrong: index.row() >= "
|
||||||
|
"users.count()";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto user = users.at(index.row());
|
||||||
|
if (role == NameRole) {
|
||||||
|
auto displayName = user.displayName;
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName = user.userId;
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown User";
|
||||||
|
}
|
||||||
|
if (role == AvatarRole) {
|
||||||
|
auto avatarUrl = user.avatarUrl;
|
||||||
|
|
||||||
|
if (avatarUrl.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatarUrl.remove(0, 6);
|
||||||
|
}
|
||||||
|
if (role == UserIDRole) {
|
||||||
|
return user.userId;
|
||||||
|
}
|
||||||
|
if (role == DirectChatsRole) {
|
||||||
|
if (!m_connection)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto userObj = m_connection->user(user.userId);
|
||||||
|
auto directChats = m_connection->directChats();
|
||||||
|
|
||||||
|
if (userObj && directChats.contains(userObj)) {
|
||||||
|
auto directChatsForUser = directChats.values(userObj);
|
||||||
|
if (!directChatsForUser.isEmpty()) {
|
||||||
|
return QVariant::fromValue(directChatsForUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (index.row() >= users.count()) {
|
|
||||||
qDebug() << "UserDirectoryListModel, something's wrong: index.row() >= "
|
|
||||||
"users.count()";
|
|
||||||
return {};
|
return {};
|
||||||
}
|
|
||||||
auto user = users.at(index.row());
|
|
||||||
if (role == NameRole) {
|
|
||||||
auto displayName = user.displayName;
|
|
||||||
if (!displayName.isEmpty()) {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
displayName = user.userId;
|
|
||||||
if (!displayName.isEmpty()) {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Unknown User";
|
|
||||||
}
|
|
||||||
if (role == AvatarRole) {
|
|
||||||
auto avatarUrl = user.avatarUrl;
|
|
||||||
|
|
||||||
if (avatarUrl.isEmpty()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return avatarUrl.remove(0, 6);
|
|
||||||
}
|
|
||||||
if (role == UserIDRole) {
|
|
||||||
return user.userId;
|
|
||||||
}
|
|
||||||
if (role == DirectChatsRole) {
|
|
||||||
if (!m_connection)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
auto userObj = m_connection->user(user.userId);
|
|
||||||
auto directChats = m_connection->directChats();
|
|
||||||
|
|
||||||
if (userObj && directChats.contains(userObj)) {
|
|
||||||
auto directChatsForUser = directChats.values(userObj);
|
|
||||||
if (!directChatsForUser.isEmpty()) {
|
|
||||||
return QVariant::fromValue(directChatsForUser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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";
|
||||||
roles[AvatarRole] = "avatar";
|
roles[AvatarRole] = "avatar";
|
||||||
roles[UserIDRole] = "userID";
|
roles[UserIDRole] = "userID";
|
||||||
roles[DirectChatsRole] = "directChats";
|
roles[DirectChatsRole] = "directChats";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
int UserDirectoryListModel::rowCount(const QModelIndex& parent) const {
|
int UserDirectoryListModel::rowCount(const QModelIndex &parent) const
|
||||||
if (parent.isValid())
|
{
|
||||||
return 0;
|
if (parent.isValid())
|
||||||
|
return 0;
|
||||||
|
|
||||||
return users.count();
|
return users.count();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,54 +9,62 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
class UserDirectoryListModel : public QAbstractListModel {
|
class UserDirectoryListModel : public QAbstractListModel
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(Connection* connection READ connection WRITE setConnection NOTIFY
|
Q_OBJECT
|
||||||
connectionChanged)
|
Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
|
||||||
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:
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
NameRole = Qt::DisplayRole + 1,
|
NameRole = Qt::DisplayRole + 1,
|
||||||
AvatarRole,
|
AvatarRole,
|
||||||
UserIDRole,
|
UserIDRole,
|
||||||
DirectChatsRole,
|
DirectChatsRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
UserDirectoryListModel(QObject* parent = nullptr);
|
UserDirectoryListModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
QVariant data(const QModelIndex& index, int role = NameRole) const override;
|
QVariant data(const QModelIndex &index, int role = NameRole) const override;
|
||||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Connection* connection() const { return m_connection; }
|
Connection *connection() const
|
||||||
void setConnection(Connection* value);
|
{
|
||||||
|
return m_connection;
|
||||||
|
}
|
||||||
|
void setConnection(Connection *value);
|
||||||
|
|
||||||
QString keyword() const { return m_keyword; }
|
QString keyword() const
|
||||||
void setKeyword(const QString& value);
|
{
|
||||||
|
return m_keyword;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Connection* m_connection = nullptr;
|
Connection *m_connection = nullptr;
|
||||||
QString m_keyword;
|
QString m_keyword;
|
||||||
bool m_limited = false;
|
bool m_limited = false;
|
||||||
|
|
||||||
bool attempted = false;
|
bool attempted = false;
|
||||||
|
|
||||||
QVector<SearchUserDirectoryJob::User> users;
|
QVector<SearchUserDirectoryJob::User> users;
|
||||||
|
|
||||||
SearchUserDirectoryJob* job = nullptr;
|
SearchUserDirectoryJob *job = nullptr;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
void keywordChanged();
|
void keywordChanged();
|
||||||
void limitedChanged();
|
void limitedChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // USERDIRECTORYLISTMODEL_H
|
#endif // USERDIRECTORYLISTMODEL_H
|
||||||
|
|||||||
@@ -12,170 +12,185 @@
|
|||||||
|
|
||||||
#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) {
|
{
|
||||||
if (m_currentRoom == room)
|
|
||||||
return;
|
|
||||||
|
|
||||||
using namespace Quotient;
|
|
||||||
beginResetModel();
|
|
||||||
if (m_currentRoom) {
|
|
||||||
m_currentRoom->disconnect(this);
|
|
||||||
// m_currentRoom->connection()->disconnect(this);
|
|
||||||
for (User* user : m_users)
|
|
||||||
user->disconnect(this);
|
|
||||||
m_users.clear();
|
|
||||||
}
|
|
||||||
m_currentRoom = room;
|
|
||||||
if (m_currentRoom) {
|
|
||||||
connect(m_currentRoom, &Room::userAdded, this, &UserListModel::userAdded);
|
|
||||||
connect(m_currentRoom, &Room::userRemoved, this,
|
|
||||||
&UserListModel::userRemoved);
|
|
||||||
connect(m_currentRoom, &Room::memberAboutToRename, this,
|
|
||||||
&UserListModel::userRemoved);
|
|
||||||
connect(m_currentRoom, &Room::memberRenamed, this,
|
|
||||||
&UserListModel::userAdded);
|
|
||||||
{
|
|
||||||
m_users = m_currentRoom->users();
|
|
||||||
std::sort(m_users.begin(), m_users.end(), room->memberSorter());
|
|
||||||
}
|
|
||||||
for (User* user : m_users) {
|
|
||||||
connect(user, &User::defaultAvatarChanged, this, [user, this](){avatarChanged(user);});
|
|
||||||
}
|
|
||||||
connect(m_currentRoom->connection(), &Connection::loggedOut, this,
|
|
||||||
[=] { setRoom(nullptr); });
|
|
||||||
qDebug() << m_users.count() << "user(s) in the room";
|
|
||||||
}
|
|
||||||
endResetModel();
|
|
||||||
emit roomChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Quotient::User* UserListModel::userAt(QModelIndex index) const {
|
void UserListModel::setRoom(Quotient::Room *room)
|
||||||
if (index.row() < 0 || index.row() >= m_users.size())
|
{
|
||||||
return nullptr;
|
if (m_currentRoom == room)
|
||||||
return m_users.at(index.row());
|
return;
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
beginResetModel();
|
||||||
|
if (m_currentRoom) {
|
||||||
|
m_currentRoom->disconnect(this);
|
||||||
|
// m_currentRoom->connection()->disconnect(this);
|
||||||
|
for (User *user : m_users)
|
||||||
|
user->disconnect(this);
|
||||||
|
m_users.clear();
|
||||||
|
}
|
||||||
|
m_currentRoom = room;
|
||||||
|
if (m_currentRoom) {
|
||||||
|
connect(m_currentRoom, &Room::userAdded, this, &UserListModel::userAdded);
|
||||||
|
connect(m_currentRoom, &Room::userRemoved, this, &UserListModel::userRemoved);
|
||||||
|
connect(m_currentRoom, &Room::memberAboutToRename, this, &UserListModel::userRemoved);
|
||||||
|
connect(m_currentRoom, &Room::memberRenamed, this, &UserListModel::userAdded);
|
||||||
|
{
|
||||||
|
m_users = m_currentRoom->users();
|
||||||
|
std::sort(m_users.begin(), m_users.end(), room->memberSorter());
|
||||||
|
}
|
||||||
|
for (User *user : m_users) {
|
||||||
|
connect(user, &User::defaultAvatarChanged, this, [user, this]() {
|
||||||
|
avatarChanged(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [=] {
|
||||||
|
setRoom(nullptr);
|
||||||
|
});
|
||||||
|
qDebug() << m_users.count() << "user(s) in the room";
|
||||||
|
}
|
||||||
|
endResetModel();
|
||||||
|
emit roomChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant UserListModel::data(const QModelIndex& index, int role) const {
|
Quotient::User *UserListModel::userAt(QModelIndex index) const
|
||||||
if (!index.isValid())
|
{
|
||||||
return QVariant();
|
if (index.row() < 0 || index.row() >= m_users.size())
|
||||||
|
return nullptr;
|
||||||
|
return m_users.at(index.row());
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant UserListModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
if (index.row() >= m_users.count()) {
|
||||||
|
qDebug() << "UserListModel, something's wrong: index.row() >= m_users.count()";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto user = m_users.at(index.row());
|
||||||
|
if (role == NameRole) {
|
||||||
|
return user->displayname(m_currentRoom);
|
||||||
|
}
|
||||||
|
if (role == UserIDRole) {
|
||||||
|
return user->id();
|
||||||
|
}
|
||||||
|
if (role == AvatarRole) {
|
||||||
|
return user->avatarMediaId(m_currentRoom);
|
||||||
|
}
|
||||||
|
if (role == ObjectRole) {
|
||||||
|
return QVariant::fromValue(user);
|
||||||
|
}
|
||||||
|
if (role == PermRole) {
|
||||||
|
auto pl = m_currentRoom->getCurrentState<RoomPowerLevelsEvent>();
|
||||||
|
auto userPl = pl->powerLevelForUser(user->id());
|
||||||
|
|
||||||
|
if (userPl == pl->content().usersDefault) { // Shortcut
|
||||||
|
return UserType::Member;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userPl < pl->powerLevelForState("m.room.message")) {
|
||||||
|
return UserType::Muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto userPls = pl->users();
|
||||||
|
|
||||||
|
int highestPl = pl->usersDefault();
|
||||||
|
QHash<QString, int>::const_iterator i = userPls.constBegin();
|
||||||
|
while (i != userPls.constEnd()) {
|
||||||
|
if (i.value() > highestPl) {
|
||||||
|
highestPl = i.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userPl == highestPl) {
|
||||||
|
return UserType::Owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userPl >= pl->powerLevelForState("m.room.power_levels")) {
|
||||||
|
return UserType::Admin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userPl >= pl->ban() || userPl >= pl->kick() || userPl >= pl->redact()) {
|
||||||
|
return UserType::Moderator;
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserType::Member;
|
||||||
|
}
|
||||||
|
|
||||||
if (index.row() >= m_users.count()) {
|
|
||||||
qDebug()
|
|
||||||
<< "UserListModel, something's wrong: index.row() >= m_users.count()";
|
|
||||||
return {};
|
return {};
|
||||||
}
|
|
||||||
auto user = m_users.at(index.row());
|
|
||||||
if (role == NameRole) {
|
|
||||||
return user->displayname(m_currentRoom);
|
|
||||||
}
|
|
||||||
if (role == UserIDRole) {
|
|
||||||
return user->id();
|
|
||||||
}
|
|
||||||
if (role == AvatarRole) {
|
|
||||||
return user->avatarMediaId(m_currentRoom);
|
|
||||||
}
|
|
||||||
if (role == ObjectRole) {
|
|
||||||
return QVariant::fromValue(user);
|
|
||||||
}
|
|
||||||
if (role == PermRole) {
|
|
||||||
auto pl = m_currentRoom->getCurrentState<RoomPowerLevelsEvent>();
|
|
||||||
auto userPl = pl->powerLevelForUser(user->id());
|
|
||||||
|
|
||||||
if (userPl == pl->content().usersDefault) { // Shortcut
|
|
||||||
return UserType::Member;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userPl < pl->powerLevelForState("m.room.message")) {
|
|
||||||
return UserType::Muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto userPls = pl->users();
|
|
||||||
|
|
||||||
int highestPl = pl->usersDefault();
|
|
||||||
QHash<QString, int>::const_iterator i = userPls.constBegin();
|
|
||||||
while (i != userPls.constEnd()) {
|
|
||||||
if (i.value() > highestPl) {
|
|
||||||
highestPl = i.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userPl == highestPl) {
|
|
||||||
return UserType::Owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userPl >= pl->powerLevelForState("m.room.power_levels")) {
|
|
||||||
return UserType::Admin;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userPl >= pl->ban() || userPl >= pl->kick() || userPl >= pl->redact()) {
|
|
||||||
return UserType::Moderator;
|
|
||||||
}
|
|
||||||
|
|
||||||
return UserType::Member;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int UserListModel::rowCount(const QModelIndex& parent) const {
|
int UserListModel::rowCount(const QModelIndex &parent) const
|
||||||
if (parent.isValid())
|
{
|
||||||
return 0;
|
if (parent.isValid())
|
||||||
|
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);
|
{
|
||||||
beginInsertRows(QModelIndex(), pos, pos);
|
auto pos = findUserPos(user);
|
||||||
m_users.insert(pos, user);
|
beginInsertRows(QModelIndex(), pos, pos);
|
||||||
endInsertRows();
|
m_users.insert(pos, user);
|
||||||
connect(user, &Quotient::User::defaultAvatarChanged, this, [user, this](){avatarChanged(user);});
|
endInsertRows();
|
||||||
|
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);
|
{
|
||||||
if (pos != m_users.size()) {
|
auto pos = findUserPos(user);
|
||||||
beginRemoveRows(QModelIndex(), pos, pos);
|
if (pos != m_users.size()) {
|
||||||
m_users.removeAt(pos);
|
beginRemoveRows(QModelIndex(), pos, pos);
|
||||||
endRemoveRows();
|
m_users.removeAt(pos);
|
||||||
user->disconnect(this);
|
endRemoveRows();
|
||||||
} else
|
user->disconnect(this);
|
||||||
qWarning() << "Trying to remove a room member not in the user list";
|
} else
|
||||||
|
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);
|
{
|
||||||
if (pos != m_users.size())
|
auto pos = findUserPos(user);
|
||||||
emit dataChanged(index(pos), index(pos), roles);
|
if (pos != m_users.size())
|
||||||
else
|
emit dataChanged(index(pos), index(pos), roles);
|
||||||
qWarning() << "Trying to access a room member not in the user list";
|
else
|
||||||
|
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";
|
||||||
roles[UserIDRole] = "userId";
|
roles[UserIDRole] = "userId";
|
||||||
roles[AvatarRole] = "avatar";
|
roles[AvatarRole] = "avatar";
|
||||||
roles[ObjectRole] = "user";
|
roles[ObjectRole] = "user";
|
||||||
roles[PermRole] = "perm";
|
roles[PermRole] = "perm";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,65 +6,70 @@
|
|||||||
#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:
|
||||||
enum Types {
|
enum Types {
|
||||||
Owner = 1,
|
Owner = 1,
|
||||||
Admin,
|
Admin,
|
||||||
Moderator,
|
Moderator,
|
||||||
Member,
|
Member,
|
||||||
Muted,
|
Muted,
|
||||||
};
|
};
|
||||||
Q_ENUMS(Types)
|
Q_ENUMS(Types)
|
||||||
};
|
};
|
||||||
|
|
||||||
class UserListModel : public QAbstractListModel {
|
class UserListModel : public QAbstractListModel
|
||||||
Q_OBJECT
|
{
|
||||||
Q_PROPERTY(
|
Q_OBJECT
|
||||||
Quotient::Room* room READ room WRITE setRoom NOTIFY roomChanged)
|
Q_PROPERTY(Quotient::Room *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
public:
|
public:
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
NameRole = Qt::UserRole + 1,
|
NameRole = Qt::UserRole + 1,
|
||||||
UserIDRole,
|
UserIDRole,
|
||||||
AvatarRole,
|
AvatarRole,
|
||||||
ObjectRole,
|
ObjectRole,
|
||||||
PermRole,
|
PermRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
UserListModel(QObject* parent = nullptr);
|
UserListModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
Quotient::Room* room() const { return m_currentRoom; }
|
Quotient::Room *room() const
|
||||||
void setRoom(Quotient::Room* room);
|
{
|
||||||
Quotient::User* userAt(QModelIndex index) const;
|
return m_currentRoom;
|
||||||
|
}
|
||||||
|
void setRoom(Quotient::Room *room);
|
||||||
|
Quotient::User *userAt(QModelIndex index) const;
|
||||||
|
|
||||||
QVariant data(const QModelIndex& index, int role = NameRole) const override;
|
QVariant data(const QModelIndex &index, int role = NameRole) const override;
|
||||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void roomChanged();
|
void roomChanged();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void userAdded(Quotient::User* user);
|
void userAdded(Quotient::User *user);
|
||||||
void userRemoved(Quotient::User* user);
|
void userRemoved(Quotient::User *user);
|
||||||
void refresh(Quotient::User* user, QVector<int> roles = {});
|
void refresh(Quotient::User *user, QVector<int> roles = {});
|
||||||
void avatarChanged(Quotient::User* user);
|
void avatarChanged(Quotient::User *user);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Quotient::Room* m_currentRoom;
|
Quotient::Room *m_currentRoom;
|
||||||
QList<Quotient::User*> m_users;
|
QList<Quotient::User *> m_users;
|
||||||
|
|
||||||
int findUserPos(Quotient::User* user) const;
|
int findUserPos(Quotient::User *user) const;
|
||||||
int findUserPos(const QString& username) const;
|
int findUserPos(const QString &username) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // USERLISTMODEL_H
|
#endif // USERLISTMODEL_H
|
||||||
|
|||||||
21
src/utils.h
21
src/utils.h
@@ -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{
|
} // namespace utils
|
||||||
"<a href=\"https://matrix.to/#/@.*?:.*?\">(.*?)</a>",
|
|
||||||
QRegularExpression::DotMatchesEverythingOption};
|
|
||||||
static const QRegularExpression strikethroughRegExp{
|
|
||||||
"<del>(.*?)</del>", QRegularExpression::DotMatchesEverythingOption};
|
|
||||||
} // namespace utils
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user