diff --git a/include/MatrixClient.h b/include/MatrixClient.h index c20d02cc..58b24f9b 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -19,6 +19,7 @@ #include +#include "MessageEvent.h" #include "Profile.h" #include "RoomMessages.h" #include "Sync.h" @@ -29,145 +30,152 @@ */ class MatrixClient : public QNetworkAccessManager { - Q_OBJECT + Q_OBJECT public: - MatrixClient(QString server, QObject *parent = 0); + MatrixClient(QString server, QObject *parent = 0); - // Client API. - void initialSync() noexcept; - void sync() noexcept; - void sendTextMessage(const QString &roomid, const QString &msg) noexcept; - void login(const QString &username, const QString &password) noexcept; - void registerUser(const QString &username, const QString &password, const QString &server) noexcept; - void versions() noexcept; - void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url); - void fetchUserAvatar(const QString &userId, const QUrl &avatarUrl); - void fetchOwnAvatar(const QUrl &avatar_url); - void downloadImage(const QString &event_id, const QUrl &url); - void messages(const QString &room_id, const QString &from_token, int limit = 20) noexcept; + // Client API. + void initialSync() noexcept; + void sync() noexcept; + void sendRoomMessage(matrix::events::MessageEventType ty, + const QString &roomid, + const QString &msg) noexcept; + void login(const QString &username, const QString &password) noexcept; + void registerUser(const QString &username, + const QString &password, + const QString &server) noexcept; + void versions() noexcept; + void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url); + void fetchUserAvatar(const QString &userId, const QUrl &avatarUrl); + void fetchOwnAvatar(const QUrl &avatar_url); + void downloadImage(const QString &event_id, const QUrl &url); + void messages(const QString &room_id, const QString &from_token, int limit = 20) noexcept; - inline QUrl getHomeServer(); - inline int transactionId(); - inline void incrementTransactionId(); + inline QUrl getHomeServer(); + inline int transactionId(); + inline void incrementTransactionId(); - void reset() noexcept; + void reset() noexcept; public slots: - void getOwnProfile() noexcept; - void logout() noexcept; + void getOwnProfile() noexcept; + void logout() noexcept; - inline void setServer(const QString &server); - inline void setAccessToken(const QString &token); - inline void setNextBatchToken(const QString &next_batch); + inline void setServer(const QString &server); + inline void setAccessToken(const QString &token); + inline void setNextBatchToken(const QString &next_batch); signals: - void loginError(const QString &error); - void registerError(const QString &error); - void versionError(const QString &error); + void loginError(const QString &error); + void registerError(const QString &error); + void versionError(const QString &error); - void loggedOut(); + void loggedOut(); - void loginSuccess(const QString &userid, const QString &homeserver, const QString &token); - void registerSuccess(const QString &userid, const QString &homeserver, const QString &token); - void versionSuccess(); + void loginSuccess(const QString &userid, const QString &homeserver, const QString &token); + void registerSuccess(const QString &userid, + const QString &homeserver, + const QString &token); + void versionSuccess(); - void roomAvatarRetrieved(const QString &roomid, const QPixmap &img); - void userAvatarRetrieved(const QString &userId, const QImage &img); - void ownAvatarRetrieved(const QPixmap &img); - void imageDownloaded(const QString &event_id, const QPixmap &img); + void roomAvatarRetrieved(const QString &roomid, const QPixmap &img); + void userAvatarRetrieved(const QString &userId, const QImage &img); + void ownAvatarRetrieved(const QPixmap &img); + void imageDownloaded(const QString &event_id, const QPixmap &img); - // Returned profile data for the user's account. - void getOwnProfileResponse(const QUrl &avatar_url, const QString &display_name); - void initialSyncCompleted(const SyncResponse &response); - void syncCompleted(const SyncResponse &response); - void syncFailed(const QString &msg); - void messageSent(const QString &event_id, const QString &roomid, const int txn_id); - void messagesRetrieved(const QString &room_id, const RoomMessages &msgs); + // Returned profile data for the user's account. + void getOwnProfileResponse(const QUrl &avatar_url, const QString &display_name); + void initialSyncCompleted(const SyncResponse &response); + void syncCompleted(const SyncResponse &response); + void syncFailed(const QString &msg); + void messageSent(const QString &event_id, const QString &roomid, const int txn_id); + void emoteSent(const QString &event_id, const QString &roomid, const int txn_id); + void messagesRetrieved(const QString &room_id, const RoomMessages &msgs); private slots: - void onResponse(QNetworkReply *reply); + void onResponse(QNetworkReply *reply); private: - enum class Endpoint { - GetOwnAvatar, - GetOwnProfile, - GetProfile, - Image, - InitialSync, - Login, - Logout, - Messages, - Register, - RoomAvatar, - UserAvatar, - SendTextMessage, - Sync, - Versions, - }; + enum class Endpoint { + GetOwnAvatar, + GetOwnProfile, + GetProfile, + Image, + InitialSync, + Login, + Logout, + Messages, + Register, + RoomAvatar, + SendRoomMessage, + Sync, + UserAvatar, + Versions, + }; - // Response handlers. - void onLoginResponse(QNetworkReply *reply); - void onLogoutResponse(QNetworkReply *reply); - void onRegisterResponse(QNetworkReply *reply); - void onVersionsResponse(QNetworkReply *reply); - void onGetOwnProfileResponse(QNetworkReply *reply); - void onGetOwnAvatarResponse(QNetworkReply *reply); - void onSendTextMessageResponse(QNetworkReply *reply); - void onInitialSyncResponse(QNetworkReply *reply); - void onSyncResponse(QNetworkReply *reply); - void onRoomAvatarResponse(QNetworkReply *reply); - void onUserAvatarResponse(QNetworkReply *reply); - void onImageResponse(QNetworkReply *reply); - void onMessagesResponse(QNetworkReply *reply); + // Response handlers. + void onGetOwnAvatarResponse(QNetworkReply *reply); + void onGetOwnProfileResponse(QNetworkReply *reply); + void onImageResponse(QNetworkReply *reply); + void onInitialSyncResponse(QNetworkReply *reply); + void onLoginResponse(QNetworkReply *reply); + void onLogoutResponse(QNetworkReply *reply); + void onMessagesResponse(QNetworkReply *reply); + void onRegisterResponse(QNetworkReply *reply); + void onRoomAvatarResponse(QNetworkReply *reply); + void onSendRoomMessage(QNetworkReply *reply); + void onSyncResponse(QNetworkReply *reply); + void onUserAvatarResponse(QNetworkReply *reply); + void onVersionsResponse(QNetworkReply *reply); - // Client API prefix. - QString api_url_; + // Client API prefix. + QString api_url_; - // The Matrix server used for communication. - QUrl server_; + // The Matrix server used for communication. + QUrl server_; - // The access token used for authentication. - QString token_; + // The access token used for authentication. + QString token_; - // Increasing transaction ID. - int txn_id_; + // Increasing transaction ID. + int txn_id_; - // Token to be used for the next sync. - QString next_batch_; + // Token to be used for the next sync. + QString next_batch_; }; inline QUrl MatrixClient::getHomeServer() { - return server_; + return server_; } inline int MatrixClient::transactionId() { - return txn_id_; + return txn_id_; } inline void MatrixClient::setServer(const QString &server) { - server_ = QUrl(QString("https://%1").arg(server)); + server_ = QUrl(QString("https://%1").arg(server)); } inline void MatrixClient::setAccessToken(const QString &token) { - token_ = token; + token_ = token; } inline void MatrixClient::setNextBatchToken(const QString &next_batch) { - next_batch_ = next_batch; + next_batch_ = next_batch; } inline void MatrixClient::incrementTransactionId() { - txn_id_ += 1; + txn_id_ += 1; } diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h index 690a899b..73c2a603 100644 --- a/include/TextInputWidget.h +++ b/include/TextInputWidget.h @@ -25,49 +25,51 @@ #include "EmojiPickButton.h" #include "FlatButton.h" +static const QString EMOTE_COMMAND("/me "); + class FilteredTextEdit : public QTextEdit { - Q_OBJECT + Q_OBJECT public: - explicit FilteredTextEdit(QWidget *parent = nullptr); - void keyPressEvent(QKeyEvent *event); + explicit FilteredTextEdit(QWidget *parent = nullptr); + void keyPressEvent(QKeyEvent *event); signals: - void enterPressed(); + void enterPressed(); }; -class TextInputWidget : public QWidget +class TextInputWidget : public QFrame { - Q_OBJECT + Q_OBJECT public: - TextInputWidget(QWidget *parent = 0); - ~TextInputWidget(); + TextInputWidget(QWidget *parent = 0); + ~TextInputWidget(); public slots: - void onSendButtonClicked(); - inline void focusLineEdit(); + void onSendButtonClicked(); + inline void focusLineEdit(); private slots: - void addSelectedEmoji(const QString &emoji); + void addSelectedEmoji(const QString &emoji); signals: - void sendTextMessage(QString msg); - -protected: - void paintEvent(QPaintEvent *event) override; + void sendTextMessage(QString msg); + void sendEmoteMessage(QString msg); private: - QHBoxLayout *top_layout_; - FilteredTextEdit *input_; + QString parseEmoteCommand(const QString &cmd); - FlatButton *send_file_button_; - FlatButton *send_message_button_; - EmojiPickButton *emoji_button_; + QHBoxLayout *top_layout_; + FilteredTextEdit *input_; + + FlatButton *send_file_button_; + FlatButton *send_message_button_; + EmojiPickButton *emoji_button_; }; inline void TextInputWidget::focusLineEdit() { - input_->setFocus(); + input_->setFocus(); } diff --git a/include/TimelineItem.h b/include/TimelineItem.h index 59c79d50..edc15dab 100644 --- a/include/TimelineItem.h +++ b/include/TimelineItem.h @@ -50,8 +50,11 @@ public: QWidget *parent = 0); // For local messages. - TimelineItem(const QString &userid, QString body, QWidget *parent = 0); - TimelineItem(QString body, QWidget *parent = 0); + TimelineItem(events::MessageEventType ty, + const QString &userid, + QString body, + bool withSender, + QWidget *parent = 0); TimelineItem(ImageItem *img, const events::MessageEvent &e, diff --git a/include/TimelineView.h b/include/TimelineView.h index 7583e4c2..3ecf8ba7 100644 --- a/include/TimelineView.h +++ b/include/TimelineView.h @@ -27,8 +27,9 @@ #include "Sync.h" #include "TimelineItem.h" -#include "Image.h" #include "Emote.h" +#include "Image.h" +#include "MessageEvent.h" #include "Notice.h" #include "RoomInfoListItem.h" #include "Text.h" @@ -83,7 +84,7 @@ public: // Add new events at the end of the timeline. int addEvents(const Timeline &timeline); - void addUserTextMessage(const QString &msg, int txn_id); + void addUserMessage(matrix::events::MessageEventType ty, const QString &msg, int txn_id); void updatePendingMessage(int txn_id, QString event_id); void scrollDown(); @@ -100,14 +101,19 @@ signals: private: void init(); - void removePendingMessage(const events::MessageEvent &e); void addTimelineItem(TimelineItem *item, TimelineDirection direction); void updateLastSender(const QString &user_id, TimelineDirection direction); void notifyForLastEvent(); // Used to determine whether or not we should prefix a message with the sender's name. bool isSenderRendered(const QString &user_id, TimelineDirection direction); - bool isPendingMessage(const events::MessageEvent &e, const QString &userid); + + template + bool isPendingMessage(const events::MessageEvent &e, const QString &userid); + + template + void removePendingMessage(const events::MessageEvent &e); + inline bool isDuplicate(const QString &event_id); // Return nullptr if the event couldn't be parsed. @@ -153,3 +159,32 @@ TimelineView::isDuplicate(const QString &event_id) { return eventIds_.contains(event_id); } + +template +bool +TimelineView::isPendingMessage(const events::MessageEvent &e, const QString &local_userid) +{ + if (e.sender() != local_userid) + return false; + + for (const auto &msg : pending_msgs_) { + if (msg.event_id == e.eventId() || msg.body == e.content().body()) + return true; + } + + return false; +} + +template +void +TimelineView::removePendingMessage(const events::MessageEvent &e) +{ + for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); it++) { + int index = std::distance(pending_msgs_.begin(), it); + + if (it->event_id == e.eventId() || it->body == e.content().body()) { + pending_msgs_.removeAt(index); + break; + } + } +} diff --git a/include/TimelineViewManager.h b/include/TimelineViewManager.h index 85e186dc..d3ca198e 100644 --- a/include/TimelineViewManager.h +++ b/include/TimelineViewManager.h @@ -23,6 +23,7 @@ #include #include "MatrixClient.h" +#include "MessageEvent.h" #include "RoomInfoListItem.h" #include "Sync.h" #include "TimelineView.h" @@ -54,6 +55,7 @@ signals: public slots: void setHistoryView(const QString &room_id); void sendTextMessage(const QString &msg); + void sendEmoteMessage(const QString &msg); private slots: void messageSent(const QString &eventid, const QString &roomid, int txnid); diff --git a/src/ChatPage.cc b/src/ChatPage.cc index 9bbf58b7..d393a65d 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -148,6 +148,11 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) view_manager_, SLOT(sendTextMessage(const QString &))); + connect(text_input_, + SIGNAL(sendEmoteMessage(const QString &)), + view_manager_, + SLOT(sendEmoteMessage(const QString &))); + connect(client_.data(), SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)), this, diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index 430bacf9..e42b4184 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -35,709 +35,733 @@ MatrixClient::MatrixClient(QString server, QObject *parent) : QNetworkAccessManager(parent) { - server_ = "https://" + server; - api_url_ = "/_matrix/client/r0"; - token_ = ""; + server_ = "https://" + server; + api_url_ = "/_matrix/client/r0"; + token_ = ""; - QSettings settings; - txn_id_ = settings.value("client/transaction_id", 1).toInt(); + QSettings settings; + txn_id_ = settings.value("client/transaction_id", 1).toInt(); - connect(this, SIGNAL(finished(QNetworkReply *)), this, SLOT(onResponse(QNetworkReply *))); + connect(this, SIGNAL(finished(QNetworkReply *)), this, SLOT(onResponse(QNetworkReply *))); } void MatrixClient::reset() noexcept { - next_batch_ = ""; - server_ = ""; - token_ = ""; + next_batch_ = ""; + server_ = ""; + token_ = ""; - txn_id_ = 0; + txn_id_ = 0; } void MatrixClient::onVersionsResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status_code == 404) { - emit versionError("Versions endpoint was not found on the server. Possibly not a Matrix server"); - return; - } + if (status_code == 404) { + emit versionError( + "Versions endpoint was not found on the server. Possibly not a Matrix server"); + return; + } - if (status_code >= 400) { - qWarning() << "API version error: " << reply->errorString(); - emit versionError("An unknown error occured. Please try again."); - return; - } + if (status_code >= 400) { + qWarning() << "API version error: " << reply->errorString(); + emit versionError("An unknown error occured. Please try again."); + return; + } - auto data = reply->readAll(); - auto json = QJsonDocument::fromJson(data); + auto data = reply->readAll(); + auto json = QJsonDocument::fromJson(data); - VersionsResponse response; + VersionsResponse response; - try { - response.deserialize(json); - if (!response.isVersionSupported(0, 2, 0)) - emit versionError("Server does not support required API version."); - else - emit versionSuccess(); - } catch (DeserializationException &e) { - qWarning() << "Malformed JSON response" << e.what(); - emit versionError("Malformed response. Possibly not a Matrix server"); - } + try { + response.deserialize(json); + if (!response.isVersionSupported(0, 2, 0)) + emit versionError("Server does not support required API version."); + else + emit versionSuccess(); + } catch (DeserializationException &e) { + qWarning() << "Malformed JSON response" << e.what(); + emit versionError("Malformed response. Possibly not a Matrix server"); + } } void MatrixClient::onLoginResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status_code == 403) { - emit loginError(tr("Wrong username or password")); - return; - } + if (status_code == 403) { + emit loginError(tr("Wrong username or password")); + return; + } - if (status_code == 404) { - emit loginError(tr("Login endpoint was not found on the server")); - return; - } + if (status_code == 404) { + emit loginError(tr("Login endpoint was not found on the server")); + return; + } - if (status_code >= 400) { - qWarning() << "Login error: " << reply->errorString(); - emit loginError(tr("An unknown error occured. Please try again.")); - return; - } + if (status_code >= 400) { + qWarning() << "Login error: " << reply->errorString(); + emit loginError(tr("An unknown error occured. Please try again.")); + return; + } - auto data = reply->readAll(); - auto json = QJsonDocument::fromJson(data); + auto data = reply->readAll(); + auto json = QJsonDocument::fromJson(data); - LoginResponse response; + LoginResponse response; - try { - response.deserialize(json); - emit loginSuccess(response.getUserId(), server_.host(), response.getAccessToken()); - } catch (DeserializationException &e) { - qWarning() << "Malformed JSON response" << e.what(); - emit loginError(tr("Malformed response. Possibly not a Matrix server")); - } + try { + response.deserialize(json); + emit loginSuccess(response.getUserId(), server_.host(), response.getAccessToken()); + } catch (DeserializationException &e) { + qWarning() << "Malformed JSON response" << e.what(); + emit loginError(tr("Malformed response. Possibly not a Matrix server")); + } } void MatrixClient::onLogoutResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status != 200) { - qWarning() << "Logout error: " << reply->errorString(); - return; - } + if (status != 200) { + qWarning() << "Logout error: " << reply->errorString(); + return; + } - emit loggedOut(); + emit loggedOut(); } void MatrixClient::onRegisterResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - auto data = reply->readAll(); - auto json = QJsonDocument::fromJson(data); + auto data = reply->readAll(); + auto json = QJsonDocument::fromJson(data); - if (status == 0 || status >= 400) { - if (json.isObject() && json.object().contains("error")) - emit registerError(json.object().value("error").toString()); - else - emit registerError(reply->errorString()); + if (status == 0 || status >= 400) { + if (json.isObject() && json.object().contains("error")) + emit registerError(json.object().value("error").toString()); + else + emit registerError(reply->errorString()); - return; - } + return; + } - RegisterResponse response; + RegisterResponse response; - try { - response.deserialize(json); - emit registerSuccess(response.getUserId(), response.getHomeServer(), response.getAccessToken()); - } catch (DeserializationException &e) { - qWarning() << "Register" << e.what(); - emit registerError("Received malformed response."); - } + try { + response.deserialize(json); + emit registerSuccess( + response.getUserId(), response.getHomeServer(), response.getAccessToken()); + } catch (DeserializationException &e) { + qWarning() << "Register" << e.what(); + emit registerError("Received malformed response."); + } } void MatrixClient::onGetOwnProfileResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status >= 400) { - qWarning() << reply->errorString(); - return; - } + if (status >= 400) { + qWarning() << reply->errorString(); + return; + } - auto data = reply->readAll(); - auto json = QJsonDocument::fromJson(data); + auto data = reply->readAll(); + auto json = QJsonDocument::fromJson(data); - ProfileResponse response; + ProfileResponse response; - try { - response.deserialize(json); - emit getOwnProfileResponse(response.getAvatarUrl(), response.getDisplayName()); - } catch (DeserializationException &e) { - qWarning() << "Profile:" << e.what(); - } + try { + response.deserialize(json); + emit getOwnProfileResponse(response.getAvatarUrl(), response.getDisplayName()); + } catch (DeserializationException &e) { + qWarning() << "Profile:" << e.what(); + } } void MatrixClient::onInitialSyncResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status == 0 || status >= 400) { - qWarning() << reply->errorString(); - return; - } + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } - auto data = reply->readAll(); + auto data = reply->readAll(); - if (data.isEmpty()) - return; + if (data.isEmpty()) + return; - auto json = QJsonDocument::fromJson(data); + auto json = QJsonDocument::fromJson(data); - SyncResponse response; + SyncResponse response; - try { - response.deserialize(json); - } catch (DeserializationException &e) { - qWarning() << "Sync malformed response" << e.what(); - return; - } + try { + response.deserialize(json); + } catch (DeserializationException &e) { + qWarning() << "Sync malformed response" << e.what(); + return; + } - emit initialSyncCompleted(response); + emit initialSyncCompleted(response); } void MatrixClient::onSyncResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status == 0 || status >= 400) { - emit syncFailed(reply->errorString()); - return; - } + if (status == 0 || status >= 400) { + emit syncFailed(reply->errorString()); + return; + } - auto data = reply->readAll(); + auto data = reply->readAll(); - if (data.isEmpty()) - return; + if (data.isEmpty()) + return; - auto json = QJsonDocument::fromJson(data); + auto json = QJsonDocument::fromJson(data); - SyncResponse response; + SyncResponse response; - try { - response.deserialize(json); - emit syncCompleted(response); - } catch (DeserializationException &e) { - qWarning() << "Sync malformed response" << e.what(); - } + try { + response.deserialize(json); + emit syncCompleted(response); + } catch (DeserializationException &e) { + qWarning() << "Sync malformed response" << e.what(); + } } void -MatrixClient::onSendTextMessageResponse(QNetworkReply *reply) +MatrixClient::onSendRoomMessage(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status == 0 || status >= 400) { - qWarning() << reply->errorString(); - return; - } + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } - auto data = reply->readAll(); + auto data = reply->readAll(); - if (data.isEmpty()) - return; + if (data.isEmpty()) + return; - auto json = QJsonDocument::fromJson(data); + auto json = QJsonDocument::fromJson(data); - if (!json.isObject()) { - qDebug() << "Send message response is not a JSON object"; - return; - } + if (!json.isObject()) { + qDebug() << "Send message response is not a JSON object"; + return; + } - auto object = json.object(); + auto object = json.object(); - if (!object.contains("event_id")) { - qDebug() << "SendTextMessage: missing event_id from response"; - return; - } + if (!object.contains("event_id")) { + qDebug() << "SendTextMessage: missing event_id from response"; + return; + } - emit messageSent(object.value("event_id").toString(), - reply->property("roomid").toString(), - reply->property("txn_id").toInt()); + emit messageSent(object.value("event_id").toString(), + reply->property("roomid").toString(), + reply->property("txn_id").toInt()); } void MatrixClient::onRoomAvatarResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status == 0 || status >= 400) { - qWarning() << reply->errorString(); - return; - } + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } - auto img = reply->readAll(); + auto img = reply->readAll(); - if (img.size() == 0) - return; + if (img.size() == 0) + return; - auto roomid = reply->property("roomid").toString(); + auto roomid = reply->property("roomid").toString(); - QPixmap pixmap; - pixmap.loadFromData(img); + QPixmap pixmap; + pixmap.loadFromData(img); - emit roomAvatarRetrieved(roomid, pixmap); + emit roomAvatarRetrieved(roomid, pixmap); } void MatrixClient::onUserAvatarResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status == 0 || status >= 400) { - qWarning() << reply->errorString(); - return; - } + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } - auto data = reply->readAll(); + auto data = reply->readAll(); - if (data.size() == 0) - return; + if (data.size() == 0) + return; - auto roomid = reply->property("userid").toString(); + auto roomid = reply->property("userid").toString(); - QImage img; - img.loadFromData(data); + QImage img; + img.loadFromData(data); - emit userAvatarRetrieved(roomid, img); + emit userAvatarRetrieved(roomid, img); } void MatrixClient::onGetOwnAvatarResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status == 0 || status >= 400) { - qWarning() << reply->errorString(); - return; - } + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } - auto img = reply->readAll(); + auto img = reply->readAll(); - if (img.size() == 0) - return; + if (img.size() == 0) + return; - QPixmap pixmap; - pixmap.loadFromData(img); + QPixmap pixmap; + pixmap.loadFromData(img); - emit ownAvatarRetrieved(pixmap); + emit ownAvatarRetrieved(pixmap); } void MatrixClient::onImageResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status == 0 || status >= 400) { - qWarning() << reply->errorString(); - return; - } + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } - auto img = reply->readAll(); + auto img = reply->readAll(); - if (img.size() == 0) - return; + if (img.size() == 0) + return; - QPixmap pixmap; - pixmap.loadFromData(img); + QPixmap pixmap; + pixmap.loadFromData(img); - auto event_id = reply->property("event_id").toString(); + auto event_id = reply->property("event_id").toString(); - emit imageDownloaded(event_id, pixmap); + emit imageDownloaded(event_id, pixmap); } void MatrixClient::onMessagesResponse(QNetworkReply *reply) { - reply->deleteLater(); + reply->deleteLater(); - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status == 0 || status >= 400) { - qWarning() << reply->errorString(); - return; - } + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } - auto data = reply->readAll(); - auto room_id = reply->property("room_id").toString(); + auto data = reply->readAll(); + auto room_id = reply->property("room_id").toString(); - RoomMessages msgs; + RoomMessages msgs; - try { - msgs.deserialize(QJsonDocument::fromJson(data)); - } catch (const DeserializationException &e) { - qWarning() << "Room messages from" << room_id << e.what(); - return; - } + try { + msgs.deserialize(QJsonDocument::fromJson(data)); + } catch (const DeserializationException &e) { + qWarning() << "Room messages from" << room_id << e.what(); + return; + } - emit messagesRetrieved(room_id, msgs); + emit messagesRetrieved(room_id, msgs); } void MatrixClient::onResponse(QNetworkReply *reply) { - switch (static_cast(reply->property("endpoint").toInt())) { - case Endpoint::Versions: - onVersionsResponse(reply); - break; - case Endpoint::Login: - onLoginResponse(reply); - break; - case Endpoint::Logout: - onLogoutResponse(reply); - break; - case Endpoint::Register: - onRegisterResponse(reply); - break; - case Endpoint::GetOwnProfile: - onGetOwnProfileResponse(reply); - break; - case Endpoint::Image: - onImageResponse(reply); - break; - case Endpoint::InitialSync: - onInitialSyncResponse(reply); - break; - case Endpoint::Sync: - onSyncResponse(reply); - break; - case Endpoint::SendTextMessage: - onSendTextMessageResponse(reply); - break; - case Endpoint::RoomAvatar: - onRoomAvatarResponse(reply); - break; - case Endpoint::UserAvatar: - onUserAvatarResponse(reply); - break; - case Endpoint::GetOwnAvatar: - onGetOwnAvatarResponse(reply); - break; - case Endpoint::Messages: - onMessagesResponse(reply); - break; - default: - break; - } + switch (static_cast(reply->property("endpoint").toInt())) { + case Endpoint::Versions: + onVersionsResponse(reply); + break; + case Endpoint::Login: + onLoginResponse(reply); + break; + case Endpoint::Logout: + onLogoutResponse(reply); + break; + case Endpoint::Register: + onRegisterResponse(reply); + break; + case Endpoint::GetOwnProfile: + onGetOwnProfileResponse(reply); + break; + case Endpoint::Image: + onImageResponse(reply); + break; + case Endpoint::InitialSync: + onInitialSyncResponse(reply); + break; + case Endpoint::Sync: + onSyncResponse(reply); + break; + case Endpoint::SendRoomMessage: + onSendRoomMessage(reply); + break; + case Endpoint::RoomAvatar: + onRoomAvatarResponse(reply); + break; + case Endpoint::UserAvatar: + onUserAvatarResponse(reply); + break; + case Endpoint::GetOwnAvatar: + onGetOwnAvatarResponse(reply); + break; + case Endpoint::Messages: + onMessagesResponse(reply); + break; + default: + break; + } } void MatrixClient::login(const QString &username, const QString &password) noexcept { - QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/login"); + QUrl endpoint(server_); + endpoint.setPath(api_url_ + "/login"); - QNetworkRequest request(endpoint); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QNetworkRequest request(endpoint); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - LoginRequest body(username, password); + LoginRequest body(username, password); - QNetworkReply *reply = post(request, body.serialize()); - reply->setProperty("endpoint", static_cast(Endpoint::Login)); + QNetworkReply *reply = post(request, body.serialize()); + reply->setProperty("endpoint", static_cast(Endpoint::Login)); } void MatrixClient::logout() noexcept { - QUrlQuery query; - query.addQueryItem("access_token", token_); + QUrlQuery query; + query.addQueryItem("access_token", token_); - QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/logout"); - endpoint.setQuery(query); + QUrl endpoint(server_); + endpoint.setPath(api_url_ + "/logout"); + endpoint.setQuery(query); - QNetworkRequest request(endpoint); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QNetworkRequest request(endpoint); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QJsonObject body{}; - QNetworkReply *reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact)); - reply->setProperty("endpoint", static_cast(Endpoint::Logout)); + QJsonObject body{}; + QNetworkReply *reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact)); + reply->setProperty("endpoint", static_cast(Endpoint::Logout)); } void MatrixClient::registerUser(const QString &user, const QString &pass, const QString &server) noexcept { - setServer(server); + setServer(server); - QUrlQuery query; - query.addQueryItem("kind", "user"); + QUrlQuery query; + query.addQueryItem("kind", "user"); - QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/register"); - endpoint.setQuery(query); + QUrl endpoint(server_); + endpoint.setPath(api_url_ + "/register"); + endpoint.setQuery(query); - QNetworkRequest request(QString(endpoint.toEncoded())); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QNetworkRequest request(QString(endpoint.toEncoded())); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - RegisterRequest body(user, pass); + RegisterRequest body(user, pass); - QNetworkReply *reply = post(request, body.serialize()); - reply->setProperty("endpoint", static_cast(Endpoint::Register)); + QNetworkReply *reply = post(request, body.serialize()); + reply->setProperty("endpoint", static_cast(Endpoint::Register)); } void MatrixClient::sync() noexcept { - QJsonObject filter{ { "room", QJsonObject{ { "ephemeral", QJsonObject{ { "limit", 0 } } } } }, - { "presence", QJsonObject{ { "limit", 0 } } } }; + QJsonObject filter{ { "room", + QJsonObject{ { "ephemeral", QJsonObject{ { "limit", 0 } } } } }, + { "presence", QJsonObject{ { "limit", 0 } } } }; - QUrlQuery query; - query.addQueryItem("set_presence", "online"); - query.addQueryItem("filter", QJsonDocument(filter).toJson(QJsonDocument::Compact)); - query.addQueryItem("timeout", "30000"); - query.addQueryItem("access_token", token_); + QUrlQuery query; + query.addQueryItem("set_presence", "online"); + query.addQueryItem("filter", QJsonDocument(filter).toJson(QJsonDocument::Compact)); + query.addQueryItem("timeout", "30000"); + query.addQueryItem("access_token", token_); - if (next_batch_.isEmpty()) { - qDebug() << "Sync requires a valid next_batch token. Initial sync should be performed."; - return; - } + if (next_batch_.isEmpty()) { + qDebug() + << "Sync requires a valid next_batch token. Initial sync should be performed."; + return; + } - query.addQueryItem("since", next_batch_); + query.addQueryItem("since", next_batch_); - QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/sync"); - endpoint.setQuery(query); + QUrl endpoint(server_); + endpoint.setPath(api_url_ + "/sync"); + endpoint.setQuery(query); - QNetworkRequest request(QString(endpoint.toEncoded())); + QNetworkRequest request(QString(endpoint.toEncoded())); - QNetworkReply *reply = get(request); - reply->setProperty("endpoint", static_cast(Endpoint::Sync)); + QNetworkReply *reply = get(request); + reply->setProperty("endpoint", static_cast(Endpoint::Sync)); } void -MatrixClient::sendTextMessage(const QString &roomid, const QString &msg) noexcept +MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty, + const QString &roomid, + const QString &msg) noexcept { - QUrlQuery query; - query.addQueryItem("access_token", token_); + QUrlQuery query; + query.addQueryItem("access_token", token_); - QUrl endpoint(server_); - endpoint.setPath(api_url_ + QString("/rooms/%1/send/m.room.message/%2").arg(roomid).arg(txn_id_)); - endpoint.setQuery(query); + QUrl endpoint(server_); + endpoint.setPath(api_url_ + + QString("/rooms/%1/send/m.room.message/%2").arg(roomid).arg(txn_id_)); + endpoint.setQuery(query); - QJsonObject body{ { "msgtype", "m.text" }, { "body", msg } }; + QString msgType(""); - QNetworkRequest request(QString(endpoint.toEncoded())); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + switch (ty) { + case matrix::events::MessageEventType::Text: + msgType = "m.text"; + break; + case matrix::events::MessageEventType::Emote: + msgType = "m.emote"; + break; + default: + msgType = "m.text"; + break; + } - QNetworkReply *reply = put(request, QJsonDocument(body).toJson(QJsonDocument::Compact)); + QJsonObject body{ { "msgtype", msgType }, { "body", msg } }; - reply->setProperty("endpoint", static_cast(Endpoint::SendTextMessage)); - reply->setProperty("txn_id", txn_id_); - reply->setProperty("roomid", roomid); + QNetworkRequest request(QString(endpoint.toEncoded())); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - incrementTransactionId(); + QNetworkReply *reply = put(request, QJsonDocument(body).toJson(QJsonDocument::Compact)); + reply->setProperty("endpoint", static_cast(Endpoint::SendRoomMessage)); + reply->setProperty("txn_id", txn_id_); + reply->setProperty("roomid", roomid); + + incrementTransactionId(); } void MatrixClient::initialSync() noexcept { - QJsonArray excluded_presence = { - QString("m.presence"), - }; + QJsonArray excluded_presence = { + QString("m.presence"), + }; - QJsonObject filter{ { "room", - QJsonObject{ { "timeline", QJsonObject{ { "limit", 20 } } }, - { "ephemeral", QJsonObject{ { "limit", 0 } } } } }, - { "presence", QJsonObject{ { "not_types", excluded_presence } } } }; + QJsonObject filter{ { "room", + QJsonObject{ { "timeline", QJsonObject{ { "limit", 20 } } }, + { "ephemeral", QJsonObject{ { "limit", 0 } } } } }, + { "presence", QJsonObject{ { "not_types", excluded_presence } } } }; - QUrlQuery query; - query.addQueryItem("full_state", "true"); - query.addQueryItem("set_presence", "online"); - query.addQueryItem("filter", QJsonDocument(filter).toJson(QJsonDocument::Compact)); - query.addQueryItem("access_token", token_); + QUrlQuery query; + query.addQueryItem("full_state", "true"); + query.addQueryItem("set_presence", "online"); + query.addQueryItem("filter", QJsonDocument(filter).toJson(QJsonDocument::Compact)); + query.addQueryItem("access_token", token_); - QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/sync"); - endpoint.setQuery(query); + QUrl endpoint(server_); + endpoint.setPath(api_url_ + "/sync"); + endpoint.setQuery(query); - QNetworkRequest request(QString(endpoint.toEncoded())); + QNetworkRequest request(QString(endpoint.toEncoded())); - QNetworkReply *reply = get(request); - reply->setProperty("endpoint", static_cast(Endpoint::InitialSync)); + QNetworkReply *reply = get(request); + reply->setProperty("endpoint", static_cast(Endpoint::InitialSync)); } void MatrixClient::versions() noexcept { - QUrl endpoint(server_); - endpoint.setPath("/_matrix/client/versions"); + QUrl endpoint(server_); + endpoint.setPath("/_matrix/client/versions"); - QNetworkRequest request(endpoint); + QNetworkRequest request(endpoint); - QNetworkReply *reply = get(request); - reply->setProperty("endpoint", static_cast(Endpoint::Versions)); + QNetworkReply *reply = get(request); + reply->setProperty("endpoint", static_cast(Endpoint::Versions)); } void MatrixClient::getOwnProfile() noexcept { - // FIXME: Remove settings from the matrix client. The class should store the user's matrix ID. - QSettings settings; - auto userid = settings.value("auth/user_id", "").toString(); + // FIXME: Remove settings from the matrix client. The class should store the user's matrix + // ID. + QSettings settings; + auto userid = settings.value("auth/user_id", "").toString(); - QUrlQuery query; - query.addQueryItem("access_token", token_); + QUrlQuery query; + query.addQueryItem("access_token", token_); - QUrl endpoint(server_); - endpoint.setPath(api_url_ + "/profile/" + userid); - endpoint.setQuery(query); + QUrl endpoint(server_); + endpoint.setPath(api_url_ + "/profile/" + userid); + endpoint.setQuery(query); - QNetworkRequest request(QString(endpoint.toEncoded())); + QNetworkRequest request(QString(endpoint.toEncoded())); - QNetworkReply *reply = get(request); - reply->setProperty("endpoint", static_cast(Endpoint::GetOwnProfile)); + QNetworkReply *reply = get(request); + reply->setProperty("endpoint", static_cast(Endpoint::GetOwnProfile)); } void MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url) { - QList url_parts = avatar_url.toString().split("mxc://"); + QList url_parts = avatar_url.toString().split("mxc://"); - if (url_parts.size() != 2) { - qDebug() << "Invalid format for room avatar " << avatar_url.toString(); - return; - } + if (url_parts.size() != 2) { + qDebug() << "Invalid format for room avatar " << avatar_url.toString(); + return; + } - QUrlQuery query; - query.addQueryItem("width", "512"); - query.addQueryItem("height", "512"); - query.addQueryItem("method", "crop"); + QUrlQuery query; + query.addQueryItem("width", "512"); + query.addQueryItem("height", "512"); + query.addQueryItem("method", "crop"); - QString media_url = QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]); + QString media_url = + QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]); - QUrl endpoint(media_url); - endpoint.setQuery(query); + QUrl endpoint(media_url); + endpoint.setQuery(query); - QNetworkRequest avatar_request(endpoint); + QNetworkRequest avatar_request(endpoint); - QNetworkReply *reply = get(avatar_request); - reply->setProperty("roomid", roomid); - reply->setProperty("endpoint", static_cast(Endpoint::RoomAvatar)); + QNetworkReply *reply = get(avatar_request); + reply->setProperty("roomid", roomid); + reply->setProperty("endpoint", static_cast(Endpoint::RoomAvatar)); } void MatrixClient::fetchUserAvatar(const QString &userId, const QUrl &avatarUrl) { - QList url_parts = avatarUrl.toString().split("mxc://"); + QList url_parts = avatarUrl.toString().split("mxc://"); - if (url_parts.size() != 2) { - qDebug() << "Invalid format for user avatar " << avatarUrl.toString(); - return; - } + if (url_parts.size() != 2) { + qDebug() << "Invalid format for user avatar " << avatarUrl.toString(); + return; + } - QUrlQuery query; - query.addQueryItem("width", "128"); - query.addQueryItem("height", "128"); - query.addQueryItem("method", "crop"); + QUrlQuery query; + query.addQueryItem("width", "128"); + query.addQueryItem("height", "128"); + query.addQueryItem("method", "crop"); - QString media_url = QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]); + QString media_url = + QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]); - QUrl endpoint(media_url); - endpoint.setQuery(query); + QUrl endpoint(media_url); + endpoint.setQuery(query); - QNetworkRequest avatar_request(endpoint); + QNetworkRequest avatar_request(endpoint); - QNetworkReply *reply = get(avatar_request); - reply->setProperty("userid", userId); - reply->setProperty("endpoint", static_cast(Endpoint::UserAvatar)); + QNetworkReply *reply = get(avatar_request); + reply->setProperty("userid", userId); + reply->setProperty("endpoint", static_cast(Endpoint::UserAvatar)); } void MatrixClient::downloadImage(const QString &event_id, const QUrl &url) { - QNetworkRequest image_request(url); + QNetworkRequest image_request(url); - QNetworkReply *reply = get(image_request); - reply->setProperty("event_id", event_id); - reply->setProperty("endpoint", static_cast(Endpoint::Image)); + QNetworkReply *reply = get(image_request); + reply->setProperty("event_id", event_id); + reply->setProperty("endpoint", static_cast(Endpoint::Image)); } void MatrixClient::fetchOwnAvatar(const QUrl &avatar_url) { - QList url_parts = avatar_url.toString().split("mxc://"); + QList url_parts = avatar_url.toString().split("mxc://"); - if (url_parts.size() != 2) { - qDebug() << "Invalid format for media " << avatar_url.toString(); - return; - } + if (url_parts.size() != 2) { + qDebug() << "Invalid format for media " << avatar_url.toString(); + return; + } - QUrlQuery query; - query.addQueryItem("width", "512"); - query.addQueryItem("height", "512"); - query.addQueryItem("method", "crop"); + QUrlQuery query; + query.addQueryItem("width", "512"); + query.addQueryItem("height", "512"); + query.addQueryItem("method", "crop"); - QString media_url = QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]); + QString media_url = + QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]); - QUrl endpoint(media_url); - endpoint.setQuery(query); + QUrl endpoint(media_url); + endpoint.setQuery(query); - QNetworkRequest avatar_request(endpoint); + QNetworkRequest avatar_request(endpoint); - QNetworkReply *reply = get(avatar_request); - reply->setProperty("endpoint", static_cast(Endpoint::GetOwnAvatar)); + QNetworkReply *reply = get(avatar_request); + reply->setProperty("endpoint", static_cast(Endpoint::GetOwnAvatar)); } void MatrixClient::messages(const QString &room_id, const QString &from_token, int limit) noexcept { - QUrlQuery query; - query.addQueryItem("access_token", token_); - query.addQueryItem("from", from_token); - query.addQueryItem("dir", "b"); - query.addQueryItem("limit", QString::number(limit)); + QUrlQuery query; + query.addQueryItem("access_token", token_); + query.addQueryItem("from", from_token); + query.addQueryItem("dir", "b"); + query.addQueryItem("limit", QString::number(limit)); - QUrl endpoint(server_); - endpoint.setPath(api_url_ + QString("/rooms/%1/messages").arg(room_id)); - endpoint.setQuery(query); + QUrl endpoint(server_); + endpoint.setPath(api_url_ + QString("/rooms/%1/messages").arg(room_id)); + endpoint.setQuery(query); - QNetworkRequest request(QString(endpoint.toEncoded())); + QNetworkRequest request(QString(endpoint.toEncoded())); - QNetworkReply *reply = get(request); - reply->setProperty("endpoint", static_cast(Endpoint::Messages)); - reply->setProperty("room_id", room_id); + QNetworkReply *reply = get(request); + reply->setProperty("endpoint", static_cast(Endpoint::Messages)); + reply->setProperty("room_id", room_id); } diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index 328c99e2..bd74186e 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -26,123 +26,133 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent) : QTextEdit(parent) { - setAcceptRichText(false); + setAcceptRichText(false); } void FilteredTextEdit::keyPressEvent(QKeyEvent *event) { - if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) - emit enterPressed(); - else - QTextEdit::keyPressEvent(event); + if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) + emit enterPressed(); + else + QTextEdit::keyPressEvent(event); } TextInputWidget::TextInputWidget(QWidget *parent) - : QWidget(parent) + : QFrame(parent) { - setFont(QFont("Emoji One")); + setFont(QFont("Emoji One")); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - setCursor(Qt::ArrowCursor); - setStyleSheet("background-color: #f8fbfe; height: 45px;"); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + setCursor(Qt::ArrowCursor); + setStyleSheet("background-color: #f8fbfe; height: 45px;"); - top_layout_ = new QHBoxLayout(); - top_layout_->setSpacing(0); - top_layout_->setMargin(0); + top_layout_ = new QHBoxLayout(); + top_layout_->setSpacing(0); + top_layout_->setMargin(0); - send_file_button_ = new FlatButton(this); + send_file_button_ = new FlatButton(this); - QIcon send_file_icon; - send_file_icon.addFile(":/icons/icons/clip-dark.png", QSize(), QIcon::Normal, QIcon::Off); - send_file_button_->setForegroundColor(QColor("#acc7dc")); - send_file_button_->setIcon(send_file_icon); - send_file_button_->setIconSize(QSize(24, 24)); + QIcon send_file_icon; + send_file_icon.addFile(":/icons/icons/clip-dark.png", QSize(), QIcon::Normal, QIcon::Off); + send_file_button_->setForegroundColor(QColor("#acc7dc")); + send_file_button_->setIcon(send_file_icon); + send_file_button_->setIconSize(QSize(24, 24)); - QFont font; - font.setPixelSize(conf::fontSize); + QFont font; + font.setPixelSize(conf::fontSize); - input_ = new FilteredTextEdit(this); - input_->setFixedHeight(45); - input_->setFont(font); - input_->setPlaceholderText(tr("Write a message...")); - input_->setStyleSheet("color: #333333; border-radius: 0; padding-top: 10px;"); + input_ = new FilteredTextEdit(this); + input_->setFixedHeight(45); + input_->setFont(font); + input_->setPlaceholderText(tr("Write a message...")); + input_->setStyleSheet("color: #333333; border-radius: 0; padding-top: 10px;"); - send_message_button_ = new FlatButton(this); - send_message_button_->setForegroundColor(QColor("#acc7dc")); + send_message_button_ = new FlatButton(this); + send_message_button_->setForegroundColor(QColor("#acc7dc")); - QIcon send_message_icon; - send_message_icon.addFile(":/icons/icons/share-dark.png", QSize(), QIcon::Normal, QIcon::Off); - send_message_button_->setIcon(send_message_icon); - send_message_button_->setIconSize(QSize(24, 24)); + QIcon send_message_icon; + send_message_icon.addFile( + ":/icons/icons/share-dark.png", QSize(), QIcon::Normal, QIcon::Off); + send_message_button_->setIcon(send_message_icon); + send_message_button_->setIconSize(QSize(24, 24)); - emoji_button_ = new EmojiPickButton(this); - emoji_button_->setForegroundColor(QColor("#acc7dc")); + emoji_button_ = new EmojiPickButton(this); + emoji_button_->setForegroundColor(QColor("#acc7dc")); - QIcon emoji_icon; - emoji_icon.addFile(":/icons/icons/smile.png", QSize(), QIcon::Normal, QIcon::Off); - emoji_button_->setIcon(emoji_icon); - emoji_button_->setIconSize(QSize(24, 24)); + QIcon emoji_icon; + emoji_icon.addFile(":/icons/icons/smile.png", QSize(), QIcon::Normal, QIcon::Off); + emoji_button_->setIcon(emoji_icon); + emoji_button_->setIconSize(QSize(24, 24)); - top_layout_->addWidget(send_file_button_); - top_layout_->addWidget(input_); - top_layout_->addWidget(emoji_button_); - top_layout_->addWidget(send_message_button_); + top_layout_->addWidget(send_file_button_); + top_layout_->addWidget(input_); + top_layout_->addWidget(emoji_button_); + top_layout_->addWidget(send_message_button_); - setLayout(top_layout_); + setLayout(top_layout_); - connect(send_message_button_, SIGNAL(clicked()), this, SLOT(onSendButtonClicked())); - connect(input_, SIGNAL(enterPressed()), send_message_button_, SIGNAL(clicked())); - connect(emoji_button_, SIGNAL(emojiSelected(const QString &)), this, SLOT(addSelectedEmoji(const QString &))); + connect(send_message_button_, SIGNAL(clicked()), this, SLOT(onSendButtonClicked())); + connect(input_, SIGNAL(enterPressed()), send_message_button_, SIGNAL(clicked())); + connect(emoji_button_, + SIGNAL(emojiSelected(const QString &)), + this, + SLOT(addSelectedEmoji(const QString &))); } void TextInputWidget::addSelectedEmoji(const QString &emoji) { - QTextCursor cursor = input_->textCursor(); + QTextCursor cursor = input_->textCursor(); - QFont emoji_font("Emoji One"); - emoji_font.setPixelSize(conf::emojiSize); + QFont emoji_font("Emoji One"); + emoji_font.setPixelSize(conf::emojiSize); - QFont text_font("Open Sans"); - text_font.setPixelSize(conf::fontSize); + QFont text_font("Open Sans"); + text_font.setPixelSize(conf::fontSize); - QTextCharFormat charfmt; - charfmt.setFont(emoji_font); - input_->setCurrentCharFormat(charfmt); + QTextCharFormat charfmt; + charfmt.setFont(emoji_font); + input_->setCurrentCharFormat(charfmt); - input_->insertPlainText(emoji); - cursor.movePosition(QTextCursor::End); + input_->insertPlainText(emoji); + cursor.movePosition(QTextCursor::End); - charfmt.setFont(text_font); - input_->setCurrentCharFormat(charfmt); + charfmt.setFont(text_font); + input_->setCurrentCharFormat(charfmt); - input_->show(); + input_->show(); } void TextInputWidget::onSendButtonClicked() { - auto msg_text = input_->document()->toPlainText().trimmed(); + auto msgText = input_->document()->toPlainText().trimmed(); - if (msg_text.isEmpty()) - return; + if (msgText.isEmpty()) + return; - emit sendTextMessage(msg_text); + if (msgText.startsWith(EMOTE_COMMAND)) { + auto text = parseEmoteCommand(msgText); - input_->clear(); + if (!text.isEmpty()) + emit sendEmoteMessage(text); + } else { + emit sendTextMessage(msgText); + } + + input_->clear(); } -void -TextInputWidget::paintEvent(QPaintEvent *event) +QString +TextInputWidget::parseEmoteCommand(const QString &cmd) { - Q_UNUSED(event); + auto text = cmd.right(cmd.size() - EMOTE_COMMAND.size()).trimmed(); - QStyleOption option; - option.initFrom(this); + if (!text.isEmpty()) + return text; - QPainter painter(this); - style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this); + return QString(""); } TextInputWidget::~TextInputWidget() diff --git a/src/TimelineItem.cc b/src/TimelineItem.cc index 62ebc515..9d24a96c 100644 --- a/src/TimelineItem.cc +++ b/src/TimelineItem.cc @@ -67,46 +67,42 @@ TimelineItem::init() } /* - * For messages created locally. The avatar and the username are displayed. + * For messages created locally. */ -TimelineItem::TimelineItem(const QString &userid, QString body, QWidget *parent) +TimelineItem::TimelineItem(events::MessageEventType ty, + const QString &userid, + QString body, + bool withSender, + QWidget *parent) : QWidget(parent) { init(); - descriptionMsg_ = { "You: ", userid, body, descriptiveTime(QDateTime::currentDateTime()) }; - body.replace(URL_REGEX, URL_HTML); auto displayName = TimelineViewManager::displayName(userid); + auto timestamp = QDateTime::currentDateTime(); - generateTimestamp(QDateTime::currentDateTime()); - generateBody(displayName, body); - - setupAvatarLayout(displayName); - - mainLayout_->addLayout(headerLayout_); - mainLayout_->addWidget(body_); - - AvatarProvider::resolve(userid, this); -} - -/* - * For messages created locally. Only the text is displayed. - */ -TimelineItem::TimelineItem(QString body, QWidget *parent) - : QWidget(parent) -{ - QSettings settings; - auto userid = settings.value("auth/user_id").toString(); - - init(); - descriptionMsg_ = { "You: ", userid, body, descriptiveTime(QDateTime::currentDateTime()) }; + if (ty == events::MessageEventType::Emote) { + body = QString("* %1 %2").arg(displayName).arg(body); + descriptionMsg_ = { "", userid, body, descriptiveTime(timestamp) }; + } else { + descriptionMsg_ = { + "You: ", userid, body, descriptiveTime(QDateTime::currentDateTime()) + }; + } body.replace(URL_REGEX, URL_HTML); + generateTimestamp(timestamp); - generateTimestamp(QDateTime::currentDateTime()); - generateBody(body); + if (withSender) { + generateBody(displayName, body); + setupAvatarLayout(displayName); + mainLayout_->addLayout(headerLayout_); - setupSimpleLayout(); + AvatarProvider::resolve(userid, this); + } else { + generateBody(body); + setupSimpleLayout(); + } mainLayout_->addWidget(body_); } diff --git a/src/TimelineView.cc b/src/TimelineView.cc index 4dd63604..518676ac 100644 --- a/src/TimelineView.cc +++ b/src/TimelineView.cc @@ -289,7 +289,10 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire eventIds_[emote.eventId()] = true; - // TODO Check if it's a message waiting for validation + if (isPendingMessage(emote, local_user_)) { + removePendingMessage(emote); + return nullptr; + } auto with_sender = isSenderRendered(emote.sender(), direction); @@ -452,55 +455,19 @@ TimelineView::updatePendingMessage(int txn_id, QString event_id) } } -bool -TimelineView::isPendingMessage(const events::MessageEvent &e, - const QString &local_userid) -{ - if (e.sender() != local_userid) - return false; - - for (const auto &msg : pending_msgs_) { - if (msg.event_id == e.eventId() || msg.body == e.content().body()) - return true; - } - - return false; -} - void -TimelineView::removePendingMessage(const events::MessageEvent &e) -{ - for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); it++) { - int index = std::distance(pending_msgs_.begin(), it); - - if (it->event_id == e.eventId() || it->body == e.content().body()) { - pending_msgs_.removeAt(index); - break; - } - } -} - -void -TimelineView::addUserTextMessage(const QString &body, int txn_id) +TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString &body, int txn_id) { QSettings settings; - auto user_id = settings.value("auth/user_id").toString(); - + auto user_id = settings.value("auth/user_id").toString(); auto with_sender = lastSender_ != user_id; - TimelineItem *view_item; - - if (with_sender) - view_item = new TimelineItem(user_id, body, scroll_widget_); - else - view_item = new TimelineItem(body, scroll_widget_); - + TimelineItem *view_item = new TimelineItem(ty, user_id, body, with_sender, scroll_widget_); scroll_layout_->addWidget(view_item); lastSender_ = user_id; PendingMessage message(txn_id, body, "", view_item); - pending_msgs_.push_back(message); } diff --git a/src/TimelineViewManager.cc b/src/TimelineViewManager.cc index 3cb61889..0bb56bf9 100644 --- a/src/TimelineViewManager.cc +++ b/src/TimelineViewManager.cc @@ -32,10 +32,8 @@ TimelineViewManager::TimelineViewManager(QSharedPointer client, QW { setStyleSheet("QWidget { background: #f8fbfe; color: #e8e8e8; border: none;}"); - connect(client_.data(), - SIGNAL(messageSent(const QString &, const QString &, int)), - this, - SLOT(messageSent(const QString &, const QString &, int))); + connect( + client_.data(), &MatrixClient::messageSent, this, &TimelineViewManager::messageSent); } TimelineViewManager::~TimelineViewManager() @@ -59,8 +57,19 @@ TimelineViewManager::sendTextMessage(const QString &msg) auto room_id = active_room_; auto view = views_[room_id]; - view->addUserTextMessage(msg, client_->transactionId()); - client_->sendTextMessage(room_id, msg); + view->addUserMessage(matrix::events::MessageEventType::Text, msg, client_->transactionId()); + client_->sendRoomMessage(matrix::events::MessageEventType::Text, room_id, msg); +} + +void +TimelineViewManager::sendEmoteMessage(const QString &msg) +{ + auto room_id = active_room_; + auto view = views_[room_id]; + + view->addUserMessage( + matrix::events::MessageEventType::Emote, msg, client_->transactionId()); + client_->sendRoomMessage(matrix::events::MessageEventType::Emote, room_id, msg); } void