Modern C++.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; });
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user