Modern C++.

This commit is contained in:
Black Hat
2019-08-04 22:36:32 +08:00
parent 5c75f009eb
commit ced82bd666
17 changed files with 335 additions and 491 deletions

View File

@@ -31,7 +31,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
"MinSizeRel" "RelWithDebInfo")
endif()
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 17)
# Setup command line parameters for the compiler and linker
foreach (FLAG "" all pedantic extra no-unused-parameter)

View File

@@ -276,42 +276,6 @@ Dialog {
}
}
}
MenuSeparator {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
Button {
Layout.fillWidth: true
flat: true
text: "Set background image"
onClicked: {
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
fileDialog.chosen.connect(function(path) {
if (!path) return
room.setBackgroundFromLocalFile(path)
})
fileDialog.open()
}
}
Button {
Layout.fillWidth: true
flat: true
text: "Clear background image"
onClicked: room.clearBackground()
}
}
}
Component {

View File

@@ -172,25 +172,10 @@ Item {
}
}
Image {
readonly property int sourceDim: (Math.ceil(Math.max(width, height) / 360) + 1) * 360
anchors.fill: parent
visible: currentRoom && currentRoom.backgroundMediaId
sourceSize.width: sourceDim
sourceSize.height: sourceDim
fillMode: Image.PreserveAspectCrop
source: currentRoom && currentRoom.backgroundMediaId ? "image://mxc/" + currentRoom.backgroundMediaId : ""
}
Rectangle {
anchors.fill: parent
visible: currentRoom && !currentRoom.backgroundMediaId
visible: currentRoom
color: MSettings.darkTheme ? "#242424" : "#EBEFF2"
}
@@ -258,12 +243,12 @@ Item {
if (currentRoom) {
movingTimer.restart()
// var lastScrollPosition = sortedMessageEventModel.mapFromSource(currentRoom.savedTopVisibleIndex())
// if (lastScrollPosition === 0) {
// messageListView.positionViewAtBeginning()
// } else {
// messageListView.currentIndex = lastScrollPosition
// }
// var lastScrollPosition = sortedMessageEventModel.mapFromSource(currentRoom.savedTopVisibleIndex())
// if (lastScrollPosition === 0) {
// messageListView.positionViewAtBeginning()
// } else {
// messageListView.currentIndex = lastScrollPosition
// }
if (messageListView.contentY < messageListView.originY + 10 || currentRoom.timelineSize < 20)
currentRoom.getPreviousContent(50)
@@ -544,7 +529,7 @@ Item {
anchors.left: parent.left
anchors.bottom: parent.bottom
visible: currentRoom && currentRoom.hasUsersTyping
visible: currentRoom && currentRoom.usersTyping.length > 0
padding: 4
contentItem: RowLayout {
@@ -554,7 +539,7 @@ Item {
spacing: -8
Repeater {
model: currentRoom && currentRoom.hasUsersTyping ? currentRoom.usersTyping : null
model: currentRoom && currentRoom.usersTyping.length > 0 ? currentRoom.usersTyping : null
delegate: Rectangle {
Layout.preferredWidth: 28

View File

@@ -20,7 +20,7 @@ class AccountListModel : public QAbstractListModel {
QHash<int, QByteArray> roleNames() const override;
Controller* controller() { return m_controller; }
Controller* controller() const { return m_controller; }
void setController(Controller* value);
private:

View File

@@ -44,11 +44,11 @@ Controller::Controller(QObject* parent) : QObject(parent) {
connect(&m_ncm, &QNetworkConfigurationManager::onlineStateChanged, this,
&Controller::isOnlineChanged);
QTimer::singleShot(0, this, SLOT(invokeLogin()));
QTimer::singleShot(0, this, [=] { invokeLogin(); });
}
Controller::~Controller() {
for (Connection* c : m_connections) {
for (auto c : m_connections) {
c->stopSync();
c->saveState();
}
@@ -66,74 +66,79 @@ void Controller::loginWithCredentials(QString serverAddr,
QString user,
QString pass,
QString deviceName) {
if (!user.isEmpty() && !pass.isEmpty()) {
if (deviceName.isEmpty()) {
deviceName = "Spectral " + QSysInfo::machineHostName() + " " +
QSysInfo::productType() + " " + QSysInfo::productVersion() +
" " + QSysInfo::currentCpuArchitecture();
}
QUrl serverUrl(serverAddr);
Connection* 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);
});
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()) {
QUrl serverUrl(serverAddr);
Connection* 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);
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) {
@@ -149,7 +154,8 @@ void Controller::logout(Connection* conn) {
job.setAutoDelete(true);
job.setKey(conn->userId());
QEventLoop loop;
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop,
&QEventLoop::quit);
job.start();
loop.exec();
@@ -169,21 +175,24 @@ void Controller::logout(Connection* conn) {
void Controller::addConnection(Connection* c) {
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
m_connections.push_back(c);
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)
if (!status) {
return;
}
c->stopSync();
c->sync(30000);
@@ -231,8 +240,11 @@ void Controller::invokeLogin() {
c->connectWithToken(account.userId(), accessToken, account.deviceId());
}
}
if (!m_connections.isEmpty())
if (!m_connections.isEmpty()) {
setConnection(m_connections[0]);
}
emit initiated();
}
@@ -333,7 +345,7 @@ bool Controller::saveAccessTokenToKeyChain(const AccountSettings& account,
}
void Controller::joinRoom(Connection* c, const QString& alias) {
JoinRoomJob* joinRoomJob = c->joinRoom(alias);
auto joinRoomJob = c->joinRoom(alias);
joinRoomJob->connect(joinRoomJob, &JoinRoomJob::failure, [=] {
emit errorOccured("Join Room Failed", joinRoomJob->errorString());
});
@@ -342,7 +354,7 @@ void Controller::joinRoom(Connection* c, const QString& alias) {
void Controller::createRoom(Connection* c,
const QString& name,
const QString& topic) {
CreateRoomJob* createRoomJob =
auto createRoomJob =
c->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
createRoomJob->connect(createRoomJob, &CreateRoomJob::failure, [=] {
emit errorOccured("Create Room Failed", createRoomJob->errorString());
@@ -350,7 +362,7 @@ void Controller::createRoom(Connection* c,
}
void Controller::createDirectChat(Connection* c, const QString& userID) {
CreateRoomJob* createRoomJob = c->createDirectChat(userID);
auto createRoomJob = c->createDirectChat(userID);
createRoomJob->connect(createRoomJob, &CreateRoomJob::failure, [=] {
emit errorOccured("Create Direct Chat Failed",
createRoomJob->errorString());
@@ -358,24 +370,16 @@ void Controller::createDirectChat(Connection* c, const QString& userID) {
}
void Controller::playAudio(QUrl localFile) {
QMediaPlayer* player = new QMediaPlayer;
auto player = new QMediaPlayer;
player->setMedia(localFile);
player->play();
connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); });
}
int Controller::dpi() {
return SettingsGroup("Interface").value("dpi", 100).toInt();
}
void Controller::setDpi(int dpi) {
SettingsGroup("Interface").setValue("dpi", dpi);
}
void Controller::changeAvatar(Connection* conn, QUrl localFile) {
auto job = conn->uploadFile(localFile.toLocalFile());
if (isJobRunning(job)) {
connect(job, &BaseJob::success, this, [this, conn, job] {
connect(job, &BaseJob::success, this, [conn, job] {
conn->callApi<SetAvatarUrlJob>(conn->userId(), job->contentUri());
});
}

View File

@@ -34,10 +34,7 @@ class Controller : public QObject {
Q_INVOKABLE void loginWithCredentials(QString, QString, QString, QString);
Q_INVOKABLE void loginWithAccessToken(QString, QString, QString, QString);
QVector<Connection*> connections() { return m_connections; }
Q_INVOKABLE int dpi();
Q_INVOKABLE void setDpi(int dpi);
QVector<Connection*> connections() const { return m_connections; }
// All the non-Q_INVOKABLE functions.
void addConnection(Connection* c);
@@ -46,19 +43,20 @@ class Controller : public QObject {
// All the Q_PROPERTYs.
int accountCount() { return m_connections.count(); }
bool quitOnLastWindowClosed() {
bool quitOnLastWindowClosed() const {
return QApplication::quitOnLastWindowClosed();
}
void setQuitOnLastWindowClosed(bool value) {
if (quitOnLastWindowClosed() != value) {
QApplication::setQuitOnLastWindowClosed(value);
emit quitOnLastWindowClosedChanged();
}
}
bool isOnline() { return m_ncm.isOnline(); }
bool isOnline() const { return m_ncm.isOnline(); }
bool busy() { return m_busy; }
bool busy() const { return m_busy; }
void setBusy(bool busy) {
if (m_busy == busy) {
return;
@@ -67,9 +65,10 @@ class Controller : public QObject {
emit busyChanged();
}
Connection* connection() {
Connection* connection() const {
if (m_connection.isNull())
return nullptr;
return m_connection;
}

View File

@@ -28,49 +28,49 @@ QVariantList EmojiModel::filterModel(const QString& filter) {
QVariantList result;
for (QVariant e : people) {
Emoji emoji = qvariant_cast<Emoji>(e);
auto emoji = qvariant_cast<Emoji>(e);
if (emoji.shortname.startsWith(filter)) {
result.append(e);
}
}
for (QVariant e : nature) {
Emoji emoji = qvariant_cast<Emoji>(e);
auto emoji = qvariant_cast<Emoji>(e);
if (emoji.shortname.startsWith(filter)) {
result.append(e);
}
}
for (QVariant e : food) {
Emoji emoji = qvariant_cast<Emoji>(e);
auto emoji = qvariant_cast<Emoji>(e);
if (emoji.shortname.startsWith(filter)) {
result.append(e);
}
}
for (QVariant e : activity) {
Emoji emoji = qvariant_cast<Emoji>(e);
auto emoji = qvariant_cast<Emoji>(e);
if (emoji.shortname.startsWith(filter)) {
result.append(e);
}
}
for (QVariant e : travel) {
Emoji emoji = qvariant_cast<Emoji>(e);
auto emoji = qvariant_cast<Emoji>(e);
if (emoji.shortname.startsWith(filter)) {
result.append(e);
}
}
for (QVariant e : objects) {
Emoji emoji = qvariant_cast<Emoji>(e);
auto emoji = qvariant_cast<Emoji>(e);
if (emoji.shortname.startsWith(filter)) {
result.append(e);
}
}
for (QVariant e : symbols) {
Emoji emoji = qvariant_cast<Emoji>(e);
auto emoji = qvariant_cast<Emoji>(e);
if (emoji.shortname.startsWith(filter)) {
result.append(e);
}
}
for (QVariant e : flags) {
Emoji emoji = qvariant_cast<Emoji>(e);
auto emoji = qvariant_cast<Emoji>(e);
if (emoji.shortname.startsWith(filter)) {
result.append(e);
}

View File

@@ -12,11 +12,11 @@ ImageClipboard::ImageClipboard(QObject* parent)
&ImageClipboard::imageChanged);
}
bool ImageClipboard::hasImage() {
bool ImageClipboard::hasImage() const {
return !image().isNull();
}
QImage ImageClipboard::image() {
QImage ImageClipboard::image() const {
return m_clipboard->image();
}
@@ -31,8 +31,9 @@ bool ImageClipboard::saveImage(const QUrl& localPath) {
QString path = QFileInfo(localPath.toLocalFile()).absolutePath();
QDir dir;
if (!dir.exists(path))
if (!dir.exists(path)) {
dir.mkpath(path);
}
i.save(localPath.toLocalFile());

View File

@@ -13,8 +13,8 @@ class ImageClipboard : public QObject {
public:
explicit ImageClipboard(QObject* parent = nullptr);
bool hasImage();
QImage image();
bool hasImage() const;
QImage image() const;
Q_INVOKABLE bool saveImage(const QUrl& localPath);

View File

@@ -307,6 +307,8 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
return "audio";
case MessageEventType::Video:
return "video";
default:
break;
}
if (e->hasFileContent())
return "file";
@@ -540,7 +542,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
return {};
}
int MessageEventModel::eventIDToIndex(const QString& eventID) {
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;

View File

@@ -48,9 +48,9 @@ class MessageEventModel : public QAbstractListModel {
};
explicit MessageEventModel(QObject* parent = nullptr);
~MessageEventModel();
~MessageEventModel() override;
SpectralRoom* room() { return m_currentRoom; }
SpectralRoom* room() const { return m_currentRoom; }
void setRoom(SpectralRoom* room);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -58,7 +58,7 @@ class MessageEventModel : public QAbstractListModel {
int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE int eventIDToIndex(const QString& eventID);
Q_INVOKABLE int eventIDToIndex(const QString& eventID) const;
private slots:
int refreshEvent(const QString& eventId);

View File

@@ -59,18 +59,19 @@ void RoomListModel::setConnection(Connection* connection) {
void RoomListModel::doResetModel() {
beginResetModel();
m_rooms.clear();
for (auto r : m_connection->roomMap())
for (auto r : m_connection->roomMap()) {
doAddRoom(r);
}
endResetModel();
refreshNotificationCount();
}
SpectralRoom* RoomListModel::roomAt(int row) {
SpectralRoom* RoomListModel::roomAt(int row) const {
return m_rooms.at(row);
}
void RoomListModel::doAddRoom(Room* r) {
if (auto* room = static_cast<SpectralRoom*>(r)) {
if (auto room = static_cast<SpectralRoom*>(r)) {
m_rooms.append(room);
connectRoomSignals(room);
emit roomAdded(room);
@@ -134,7 +135,7 @@ void RoomListModel::updateRoom(Room* room, Room* prev) {
// 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);
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; });

View File

@@ -45,23 +45,23 @@ class RoomListModel : public QAbstractListModel {
CurrentRoomRole,
};
RoomListModel(QObject* parent = 0);
virtual ~RoomListModel();
RoomListModel(QObject* parent = nullptr);
virtual ~RoomListModel() override;
Connection* connection() { return m_connection; }
Connection* connection() const { return m_connection; }
void setConnection(Connection* connection);
void doResetModel();
Q_INVOKABLE SpectralRoom* roomAt(int row);
Q_INVOKABLE SpectralRoom* roomAt(int row) const;
QVariant data(const QModelIndex& index,
int role = Qt::DisplayRole) const override;
Q_INVOKABLE int rowCount(
const QModelIndex& parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const;
QHash<int, QByteArray> roleNames() const override;
int notificationCount() { return m_notificationCount; }
int notificationCount() const { return m_notificationCount; }
private slots:
void doAddRoom(Room* room);

View File

@@ -15,6 +15,8 @@
#include "events/typingevent.h"
#include "jobs/downloadfilejob.h"
#include <functional>
#include <QFileDialog>
#include <QFileInfo>
#include <QImageReader>
@@ -34,24 +36,10 @@ SpectralRoom::SpectralRoom(Connection* connection,
&SpectralRoom::countChanged);
connect(this, &SpectralRoom::highlightCountChanged, this,
&SpectralRoom::countChanged);
connect(this, &Room::addedMessages, this, [=] { setBusy(false); });
connect(this, &Room::fileTransferCompleted, this, [=] {
setFileUploadingProgress(0);
setHasFileUploading(false);
});
connect(this, &Room::accountDataChanged, this, [=](QString type) {
if (type == backgroundEventType)
emit backgroundChanged();
});
}
inline QString getMIME(const QUrl& fileUrl) {
return QMimeDatabase().mimeTypeForFile(fileUrl.toLocalFile()).name();
}
inline QSize getImageSize(const QUrl& imageUrl) {
QImageReader reader(imageUrl.toLocalFile());
return reader.size();
}
void SpectralRoom::uploadFile(const QUrl& url, const QString& body) {
@@ -61,13 +49,13 @@ void SpectralRoom::uploadFile(const QUrl& url, const QString& body) {
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, url, false);
setHasFileUploading(true);
connect(this, &Room::fileTransferCompleted,
[=](QString id, QUrl localFile, QUrl mxcUrl) {
[=](QString id, QUrl /*localFile*/, QUrl /*mxcUrl*/) {
if (id == txnId) {
setFileUploadingProgress(0);
setHasFileUploading(false);
}
});
connect(this, &Room::fileTransferFailed, [=](QString id, QString error) {
connect(this, &Room::fileTransferFailed, [=](QString id, QString /*error*/) {
if (id == txnId) {
setFileUploadingProgress(0);
setHasFileUploading(false);
@@ -91,24 +79,14 @@ void SpectralRoom::forget() {
connection()->forgetRoom(id());
}
bool SpectralRoom::hasUsersTyping() {
QList<User*> users = usersTyping();
if (users.isEmpty())
return false;
int count = users.length();
if (users.contains(localUser()))
count--;
return count != 0;
}
QVariantList SpectralRoom::getUsersTyping() {
QList<User*> users = usersTyping();
users.removeOne(localUser());
QVariantList out;
QVariantList SpectralRoom::getUsersTyping() const {
auto users = usersTyping();
users.removeAll(localUser());
QVariantList userVariants;
for (User* user : users) {
out.append(QVariant::fromValue(user));
userVariants.append(QVariant::fromValue(user));
}
return out;
return userVariants;
}
void SpectralRoom::sendTypingNotification(bool isTyping) {
@@ -116,7 +94,7 @@ void SpectralRoom::sendTypingNotification(bool isTyping) {
id(), isTyping, 10000);
}
QString SpectralRoom::lastEvent() {
QString SpectralRoom::lastEvent() const {
for (auto i = messageEvents().rbegin(); i < messageEvents().rend(); i++) {
const RoomEvent* evt = i->get();
@@ -185,7 +163,7 @@ void SpectralRoom::countChanged() {
}
}
QDateTime SpectralRoom::lastActiveTime() {
QDateTime SpectralRoom::lastActiveTime() const {
if (timelineSize() == 0)
return QDateTime();
return messageEvents().rbegin()->get()->timestamp();
@@ -194,13 +172,13 @@ QDateTime SpectralRoom::lastActiveTime() {
int SpectralRoom::savedTopVisibleIndex() const {
return firstDisplayedMarker() == timelineEdge()
? 0
: firstDisplayedMarker() - messageEvents().rbegin();
: int(firstDisplayedMarker() - messageEvents().rbegin());
}
int SpectralRoom::savedBottomVisibleIndex() const {
return lastDisplayedMarker() == timelineEdge()
? 0
: lastDisplayedMarker() - messageEvents().rbegin();
: int(lastDisplayedMarker() - messageEvents().rbegin());
}
void SpectralRoom::saveViewport(int topIndex, int bottomIndex) {
@@ -217,17 +195,13 @@ void SpectralRoom::saveViewport(int topIndex, int bottomIndex) {
setLastDisplayedEvent(maxTimelineIndex() - bottomIndex);
}
void SpectralRoom::getPreviousContent(int limit) {
setBusy(true);
Room::getPreviousContent(limit);
}
QVariantList SpectralRoom::getUsers(const QString& prefix) {
auto userList = users();
QVariantList SpectralRoom::getUsers(const QString& keyword) const {
const auto userList = users();
QVariantList matchedList;
for (auto u : userList)
if (u->displayname(this).toLower().startsWith(prefix.toLower()))
for (const auto u : userList)
if (u->displayname(this).contains(keyword, Qt::CaseInsensitive)) {
matchedList.append(QVariant::fromValue(u));
}
return matchedList;
}
@@ -236,54 +210,167 @@ QUrl SpectralRoom::urlToMxcUrl(QUrl mxcUrl) {
return DownloadFileJob::makeRequestUrl(connection()->homeserver(), mxcUrl);
}
QUrl SpectralRoom::backgroundUrl() {
return hasAccountData(backgroundEventType)
? QUrl(accountData(backgroundEventType)
.get()
->contentJson()["url"]
.toString())
: QUrl();
QString SpectralRoom::avatarMediaId() const {
if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
return avatar;
}
// Use the first (excluding self) user's avatar for direct chats
const auto dcUsers = directChatUsers();
for (const auto u : dcUsers) {
if (u != localUser()) {
return u->avatarMediaId();
}
}
return {};
}
void SpectralRoom::setBackgroundUrl(QUrl url) {
if (url.isEmpty() || url == backgroundUrl())
return;
QString SpectralRoom::eventToString(const RoomEvent& evt,
Qt::TextFormat format) const {
const bool prettyPrint = (format == Qt::RichText);
connection()->callApi<SetAccountDataPerRoomJob>(
localUser()->id(), id(), backgroundEventType,
QJsonObject{{"url", url.toString()}});
}
using namespace QMatrixClient;
return visit(
evt,
[prettyPrint](const RoomMessageEvent& e) {
using namespace MessageEventContent;
void SpectralRoom::setBackgroundFromLocalFile(QUrl url) {
if (url.isEmpty())
return;
auto txnId = connection()->generateTxnId();
Room::uploadFile(txnId, url);
connect(this, &Room::fileTransferCompleted,
[=](QString id, QUrl localFile, QUrl mxcUrl) {
if (id == txnId) {
setBackgroundUrl(mxcUrl);
if (prettyPrint && e.hasTextContent() &&
e.mimeType().name() != "text/plain")
return static_cast<const TextContent*>(e.content())->body;
if (e.hasFileContent()) {
auto fileCaption =
e.content()->fileInfo()->originalName.toHtmlEscaped();
if (fileCaption.isEmpty()) {
fileCaption = prettyPrint
? QMatrixClient::prettyPrint(e.plainBody())
: e.plainBody();
}
return !fileCaption.isEmpty() ? fileCaption : tr("a file");
}
return prettyPrint ? QMatrixClient::prettyPrint(e.plainBody())
: e.plainBody();
},
[this](const RoomMemberEvent& e) {
// FIXME: Rewind to the name that was at the time of this event
auto subjectName = this->user(e.userId())->displayname();
// The below code assumes senderName output in AuthorRole
switch (e.membership()) {
case MembershipType::Invite:
if (e.repeatsState())
return tr("reinvited %1 to the room").arg(subjectName);
FALLTHROUGH;
case MembershipType::Join: {
if (e.repeatsState())
return tr("joined the room (repeated)");
if (!e.prevContent() ||
e.membership() != e.prevContent()->membership) {
return e.membership() == MembershipType::Invite
? tr("invited %1 to the room").arg(subjectName)
: tr("joined the room");
}
QString text{};
if (e.isRename()) {
if (e.displayName().isEmpty())
text = tr("cleared their display name");
else
text = tr("changed their display name to %1")
.arg(e.displayName().toHtmlEscaped());
}
if (e.isAvatarUpdate()) {
if (!text.isEmpty())
text += " and ";
if (e.avatarUrl().isEmpty())
text += tr("cleared their avatar");
else if (e.prevContent()->avatarUrl.isEmpty())
text += tr("set an avatar");
else
text += tr("updated their avatar");
}
return text;
}
case MembershipType::Leave:
if (e.prevContent() &&
e.prevContent()->membership == MembershipType::Invite) {
return (e.senderId() != e.userId())
? tr("withdrew %1's invitation").arg(subjectName)
: tr("rejected the invitation");
}
});
}
void SpectralRoom::clearBackground() {
connection()->callApi<SetAccountDataPerRoomJob>(
localUser()->id(), id(), backgroundEventType, QJsonObject{});
}
QString SpectralRoom::backgroundMediaId() {
if (!hasAccountData(backgroundEventType))
return {};
auto url = backgroundUrl();
return url.authority() + url.path();
if (e.prevContent() &&
e.prevContent()->membership == MembershipType::Ban) {
return (e.senderId() != e.userId())
? tr("unbanned %1").arg(subjectName)
: tr("self-unbanned");
}
return (e.senderId() != e.userId())
? tr("has put %1 out of the room: %2")
.arg(subjectName, e.contentJson()["reason"_ls]
.toString()
.toHtmlEscaped())
: tr("left the room");
case MembershipType::Ban:
return (e.senderId() != e.userId())
? tr("banned %1 from the room: %2")
.arg(subjectName, e.contentJson()["reason"_ls]
.toString()
.toHtmlEscaped())
: tr("self-banned from the room");
case MembershipType::Knock:
return tr("knocked");
default:;
}
return tr("made something unknown");
},
[](const RoomAliasesEvent& e) {
return tr("has set room aliases on server %1 to: %2")
.arg(e.stateKey(), QLocale().createSeparatedList(e.aliases()));
},
[](const RoomCanonicalAliasEvent& e) {
return (e.alias().isEmpty())
? tr("cleared the room main alias")
: tr("set the room main alias to: %1").arg(e.alias());
},
[](const RoomNameEvent& e) {
return (e.name().isEmpty()) ? tr("cleared the room name")
: tr("set the room name to: %1")
.arg(e.name().toHtmlEscaped());
},
[prettyPrint](const RoomTopicEvent& e) {
return (e.topic().isEmpty())
? tr("cleared the topic")
: tr("set the topic to: %1")
.arg(prettyPrint
? QMatrixClient::prettyPrint(e.topic())
: e.topic());
},
[](const RoomAvatarEvent&) { return tr("changed the room avatar"); },
[](const EncryptionEvent&) {
return tr("activated End-to-End Encryption");
},
[](const RoomCreateEvent& e) {
return (e.isUpgrade() ? tr("upgraded the room to version %1")
: tr("created the room, version %1"))
.arg(e.version().isEmpty() ? "1" : e.version().toHtmlEscaped());
},
[](const StateEventBase& e) {
// A small hack for state events from TWIM bot
return e.stateKey() == "twim"
? tr("updated the database", "TWIM bot updated the database")
: e.stateKey().isEmpty()
? tr("updated %1 state", "%1 - Matrix event type")
.arg(e.matrixType())
: tr("updated %1 state for %2",
"%1 - Matrix event type, %2 - state key")
.arg(e.matrixType(),
e.stateKey().toHtmlEscaped());
},
tr("Unknown event"));
}
void SpectralRoom::changeAvatar(QUrl localFile) {
auto job = connection()->uploadFile(localFile.toLocalFile());
const auto job = connection()->uploadFile(localFile.toLocalFile());
if (isJobRunning(job)) {
connect(job, &BaseJob::success, this, [this, job] {
connection()->callApi<SetRoomStateJob>(
@@ -297,7 +384,7 @@ void SpectralRoom::addLocalAlias(const QString& alias) {
if (aliases.contains(alias))
return;
aliases.append(alias);
aliases += alias;
setLocalAliases(aliases);
}
@@ -314,12 +401,12 @@ void SpectralRoom::removeLocalAlias(const QString& alias) {
QString SpectralRoom::markdownToHTML(const QString& markdown) {
const auto str = markdown.toUtf8();
const char* tmp_buf =
char* tmp_buf =
cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_DEFAULT);
std::string html(tmp_buf);
const std::string html(tmp_buf);
free((char*)tmp_buf);
free(tmp_buf);
auto result = QString::fromStdString(html).trimmed();
@@ -332,8 +419,8 @@ QString SpectralRoom::markdownToHTML(const QString& markdown) {
void SpectralRoom::postArbitaryMessage(const QString& text,
MessageEventType type,
const QString& replyEventId) {
auto parsedHTML = markdownToHTML(text);
bool isRichText = Qt::mightBeRichText(parsedHTML);
const auto parsedHTML = markdownToHTML(text);
const bool isRichText = Qt::mightBeRichText(parsedHTML);
if (isRichText) { // Markdown
postHtmlMessage(text, parsedHTML, type, replyEventId);
@@ -463,7 +550,7 @@ void SpectralRoom::toggleReaction(const QString& eventId,
}
if (!redactEventIds.isEmpty()) {
for (auto redactEventId : redactEventIds) {
for (const auto& redactEventId : redactEventIds) {
redactEvent(redactEventId);
}
} else {

View File

@@ -20,20 +20,12 @@ using namespace QMatrixClient;
class SpectralRoom : public Room {
Q_OBJECT
Q_PROPERTY(bool hasUsersTyping READ hasUsersTyping NOTIFY typingChanged)
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
Q_PROPERTY(QString cachedInput READ cachedInput WRITE setCachedInput NOTIFY
cachedInputChanged)
Q_PROPERTY(bool hasFileUploading READ hasFileUploading NOTIFY
hasFileUploadingChanged)
Q_PROPERTY(QString cachedInput MEMBER m_cachedInput NOTIFY cachedInputChanged)
Q_PROPERTY(bool hasFileUploading READ hasFileUploading WRITE
setHasFileUploading NOTIFY hasFileUploadingChanged)
Q_PROPERTY(int fileUploadingProgress READ fileUploadingProgress NOTIFY
fileUploadingProgressChanged)
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
Q_PROPERTY(QUrl backgroundUrl READ backgroundUrl WRITE setBackgroundUrl NOTIFY
backgroundChanged)
Q_PROPERTY(
QString backgroundMediaId READ backgroundMediaId NOTIFY backgroundChanged)
// Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged)
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged
STORED false)
@@ -42,244 +34,51 @@ class SpectralRoom : public Room {
QString roomId,
JoinState joinState = {});
const QString& cachedInput() const { return m_cachedInput; }
void setCachedInput(const QString& input) {
if (input != m_cachedInput) {
m_cachedInput = input;
emit cachedInputChanged();
}
}
QVariantList getUsersTyping() const;
bool busy() { return m_busy; }
void setBusy(bool value) {
if (m_busy != value) {
m_busy = value;
emit busyChanged();
}
}
bool hasUsersTyping();
QVariantList getUsersTyping();
QString lastEvent();
QString lastEvent() const;
bool isEventHighlighted(const QMatrixClient::RoomEvent* e) const;
QDateTime lastActiveTime();
QDateTime lastActiveTime() const;
bool hasFileUploading() { return m_hasFileUploading; }
bool hasFileUploading() const { return m_hasFileUploading; }
void setHasFileUploading(bool value) {
if (m_hasFileUploading != value) {
m_hasFileUploading = value;
emit hasFileUploadingChanged();
if (value == m_hasFileUploading) {
return;
}
m_hasFileUploading = value;
emit hasFileUploadingChanged();
}
int fileUploadingProgress() { return m_fileUploadingProgress; }
int fileUploadingProgress() const { return m_fileUploadingProgress; }
void setFileUploadingProgress(int value) {
if (m_fileUploadingProgress != value) {
m_fileUploadingProgress = value;
emit fileUploadingProgressChanged();
if (m_fileUploadingProgress == value) {
return;
}
m_fileUploadingProgress = value;
emit fileUploadingProgressChanged();
}
Q_INVOKABLE int savedTopVisibleIndex() const;
Q_INVOKABLE int savedBottomVisibleIndex() const;
Q_INVOKABLE void saveViewport(int topIndex, int bottomIndex);
Q_INVOKABLE void getPreviousContent(int limit = 10);
Q_INVOKABLE QVariantList getUsers(const QString& prefix);
Q_INVOKABLE QVariantList getUsers(const QString& keyword) const;
Q_INVOKABLE QUrl urlToMxcUrl(QUrl mxcUrl);
QUrl avatarUrl() const {
if (!Room::avatarUrl().isEmpty())
return Room::avatarUrl();
QString avatarMediaId() const;
// Use the first (excluding self) user's avatar for direct chats
const auto dcUsers = directChatUsers();
for (auto* u : dcUsers)
if (u != localUser())
return u->avatarUrl();
return {};
}
QString avatarMediaId() const {
if (!Room::avatarMediaId().isEmpty())
return Room::avatarMediaId();
// Use the first (excluding self) user's avatar for direct chats
const auto dcUsers = directChatUsers();
for (auto* u : dcUsers)
if (u != localUser())
return u->avatarMediaId();
return {};
}
QUrl backgroundUrl();
Q_INVOKABLE void setBackgroundUrl(QUrl url);
Q_INVOKABLE void clearBackground();
Q_INVOKABLE void setBackgroundFromLocalFile(QUrl url);
QString backgroundMediaId();
template <typename BaseEventT>
QString eventToString(const BaseEventT& evt,
Qt::TextFormat format = Qt::PlainText) {
bool prettyPrint = (format == Qt::RichText);
using namespace QMatrixClient;
return visit(
evt,
[this, prettyPrint](const RoomMessageEvent& e) {
using namespace MessageEventContent;
if (prettyPrint && e.hasTextContent() &&
e.mimeType().name() != "text/plain")
return static_cast<const TextContent*>(e.content())->body;
if (e.hasFileContent()) {
auto fileCaption =
e.content()->fileInfo()->originalName.toHtmlEscaped();
if (fileCaption.isEmpty()) {
if (prettyPrint)
fileCaption = this->prettyPrint(e.plainBody());
else
fileCaption = e.plainBody();
}
return !fileCaption.isEmpty() ? fileCaption : tr("a file");
}
return prettyPrint ? this->prettyPrint(e.plainBody()) : e.plainBody();
},
[this](const RoomMemberEvent& e) {
// FIXME: Rewind to the name that was at the time of this event
auto subjectName = this->user(e.userId())->displayname();
// The below code assumes senderName output in AuthorRole
switch (e.membership()) {
case MembershipType::Invite:
if (e.repeatsState())
return tr("reinvited %1 to the room").arg(subjectName);
FALLTHROUGH;
case MembershipType::Join: {
if (e.repeatsState())
return tr("joined the room (repeated)");
if (!e.prevContent() ||
e.membership() != e.prevContent()->membership) {
return e.membership() == MembershipType::Invite
? tr("invited %1 to the room").arg(subjectName)
: tr("joined the room");
}
QString text{};
if (e.isRename()) {
if (e.displayName().isEmpty())
text = tr("cleared their display name");
else
text = tr("changed their display name to %1")
.arg(e.displayName().toHtmlEscaped());
}
if (e.isAvatarUpdate()) {
if (!text.isEmpty())
text += " and ";
if (e.avatarUrl().isEmpty())
text += tr("cleared their avatar");
else if (e.prevContent()->avatarUrl.isEmpty())
text += tr("set an avatar");
else
text += tr("updated their avatar");
}
return text;
}
case MembershipType::Leave:
if (e.prevContent() &&
e.prevContent()->membership == MembershipType::Invite) {
return (e.senderId() != e.userId())
? tr("withdrew %1's invitation").arg(subjectName)
: tr("rejected the invitation");
}
if (e.prevContent() &&
e.prevContent()->membership == MembershipType::Ban) {
return (e.senderId() != e.userId())
? tr("unbanned %1").arg(subjectName)
: tr("self-unbanned");
}
return (e.senderId() != e.userId())
? tr("has put %1 out of the room: %2")
.arg(subjectName, e.contentJson()["reason"_ls]
.toString()
.toHtmlEscaped())
: tr("left the room");
case MembershipType::Ban:
return (e.senderId() != e.userId())
? tr("banned %1 from the room: %2")
.arg(subjectName, e.contentJson()["reason"_ls]
.toString()
.toHtmlEscaped())
: tr("self-banned from the room");
case MembershipType::Knock:
return tr("knocked");
default:;
}
return tr("made something unknown");
},
[](const RoomAliasesEvent& e) {
return tr("has set room aliases on server %1 to: %2")
.arg(e.stateKey(), QLocale().createSeparatedList(e.aliases()));
},
[](const RoomCanonicalAliasEvent& e) {
return (e.alias().isEmpty())
? tr("cleared the room main alias")
: tr("set the room main alias to: %1").arg(e.alias());
},
[](const RoomNameEvent& e) {
return (e.name().isEmpty()) ? tr("cleared the room name")
: tr("set the room name to: %1")
.arg(e.name().toHtmlEscaped());
},
[this, prettyPrint](const RoomTopicEvent& e) {
return (e.topic().isEmpty())
? tr("cleared the topic")
: tr("set the topic to: %1")
.arg(prettyPrint ? this->prettyPrint(e.topic())
: e.topic());
},
[](const RoomAvatarEvent&) { return tr("changed the room avatar"); },
[](const EncryptionEvent&) {
return tr("activated End-to-End Encryption");
},
[](const RoomCreateEvent& e) {
return (e.isUpgrade() ? tr("upgraded the room to version %1")
: tr("created the room, version %1"))
.arg(e.version().isEmpty() ? "1" : e.version().toHtmlEscaped());
},
[](const StateEventBase& e) {
// A small hack for state events from TWIM bot
return e.stateKey() == "twim"
? tr("updated the database",
"TWIM bot updated the database")
: e.stateKey().isEmpty()
? tr("updated %1 state", "%1 - Matrix event type")
.arg(e.matrixType())
: tr("updated %1 state for %2",
"%1 - Matrix event type, %2 - state key")
.arg(e.matrixType(),
e.stateKey().toHtmlEscaped());
},
tr("Unknown event"));
}
QString eventToString(const RoomEvent& evt,
Qt::TextFormat format = Qt::PlainText) const;
private:
const QString backgroundEventType = "org.eu.encom.spectral.background";
QString m_cachedInput;
QSet<const QMatrixClient::RoomEvent*> highlights;
bool m_hasFileUploading = false;
int m_fileUploadingProgress = 0;
bool m_busy = false;
void checkForHighlights(const QMatrixClient::TimelineItem& ti);
void onAddNewTimelineEvents(timeline_iter_t from) override;

View File

@@ -50,7 +50,7 @@ void UserListModel::setRoom(QMatrixClient::Room* room) {
emit roomChanged();
}
QMatrixClient::User* UserListModel::userAt(QModelIndex index) {
QMatrixClient::User* UserListModel::userAt(QModelIndex index) const {
if (index.row() < 0 || index.row() >= m_users.size())
return nullptr;
return m_users.at(index.row());
@@ -133,9 +133,11 @@ int UserListModel::findUserPos(const QString& username) const {
QHash<int, QByteArray> UserListModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[UserIDRole] = "userId";
roles[AvatarRole] = "avatar";
roles[ObjectRole] = "user";
return roles;
}

View File

@@ -28,9 +28,9 @@ class UserListModel : public QAbstractListModel {
UserListModel(QObject* parent = nullptr);
QMatrixClient::Room* room() { return m_currentRoom; }
QMatrixClient::Room* room() const { return m_currentRoom; }
void setRoom(QMatrixClient::Room* room);
User* userAt(QModelIndex index);
User* userAt(QModelIndex index) const;
QVariant data(const QModelIndex& index, int role = NameRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;