diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index d393a65d..6bfbf400 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -153,6 +153,18 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
view_manager_,
SLOT(sendEmoteMessage(const QString &)));
+ connect(text_input_, &TextInputWidget::uploadImage, this, [=](QString filename) {
+ client_->uploadImage(current_room_, filename);
+ });
+
+ connect(client_.data(),
+ &MatrixClient::imageUploaded,
+ this,
+ [=](QString roomid, QString filename, QString url) {
+ text_input_->hideUploadSpinner();
+ view_manager_->sendImageMessage(roomid, filename, url);
+ });
+
connect(client_.data(),
SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)),
this,
diff --git a/src/ImageItem.cc b/src/ImageItem.cc
index 77523465..e84a2a9f 100644
--- a/src/ImageItem.cc
+++ b/src/ImageItem.cc
@@ -18,6 +18,7 @@
#include <QBrush>
#include <QDebug>
#include <QDesktopServices>
+#include <QFileInfo>
#include <QImage>
#include <QPainter>
#include <QPixmap>
@@ -26,169 +27,198 @@
#include "ImageOverlayDialog.h"
namespace events = matrix::events;
-namespace msgs = matrix::events::messages;
+namespace msgs = matrix::events::messages;
ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
- const events::MessageEvent<msgs::Image> &event,
- QWidget *parent)
+ const events::MessageEvent<msgs::Image> &event,
+ QWidget *parent)
: QWidget(parent)
, event_{ event }
, client_{ client }
{
- setMouseTracking(true);
- setCursor(Qt::PointingHandCursor);
- setAttribute(Qt::WA_Hover, true);
+ setMouseTracking(true);
+ setCursor(Qt::PointingHandCursor);
+ setAttribute(Qt::WA_Hover, true);
- url_ = event.msgContent().url();
- text_ = event.content().body();
+ url_ = event.msgContent().url();
+ text_ = event.content().body();
- QList<QString> url_parts = url_.toString().split("mxc://");
+ QList<QString> url_parts = url_.toString().split("mxc://");
- if (url_parts.size() != 2) {
- qDebug() << "Invalid format for image" << url_.toString();
- return;
- }
+ if (url_parts.size() != 2) {
+ qDebug() << "Invalid format for image" << url_.toString();
+ return;
+ }
- QString media_params = url_parts[1];
- url_ = QString("%1/_matrix/media/r0/download/%2").arg(client_.data()->getHomeServer().toString(), media_params);
+ QString media_params = url_parts[1];
+ url_ = QString("%1/_matrix/media/r0/download/%2")
+ .arg(client_.data()->getHomeServer().toString(), media_params);
- client_.data()->downloadImage(event.eventId(), url_);
+ client_.data()->downloadImage(event.eventId(), url_);
- connect(client_.data(),
- SIGNAL(imageDownloaded(const QString &, const QPixmap &)),
- this,
- SLOT(imageDownloaded(const QString &, const QPixmap &)));
+ connect(client_.data(),
+ SIGNAL(imageDownloaded(const QString &, const QPixmap &)),
+ this,
+ SLOT(imageDownloaded(const QString &, const QPixmap &)));
+}
+
+ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
+ const QString &url,
+ const QString &filename,
+ QWidget *parent)
+ : QWidget(parent)
+ , url_{ url }
+ , text_{ QFileInfo(filename).fileName() }
+ , client_{ client }
+{
+ setMouseTracking(true);
+ setCursor(Qt::PointingHandCursor);
+ setAttribute(Qt::WA_Hover, true);
+
+ QList<QString> url_parts = url_.toString().split("mxc://");
+
+ if (url_parts.size() != 2) {
+ qDebug() << "Invalid format for image" << url_.toString();
+ return;
+ }
+
+ QString media_params = url_parts[1];
+ url_ = QString("%1/_matrix/media/r0/download/%2")
+ .arg(client_.data()->getHomeServer().toString(), media_params);
+
+ setImage(QPixmap(filename));
}
void
ImageItem::imageDownloaded(const QString &event_id, const QPixmap &img)
{
- if (event_id != event_.eventId())
- return;
+ if (event_id != event_.eventId())
+ return;
- setImage(img);
+ setImage(img);
}
void
ImageItem::openUrl()
{
- if (url_.toString().isEmpty())
- return;
+ if (url_.toString().isEmpty())
+ return;
- if (!QDesktopServices::openUrl(url_))
- qWarning() << "Could not open url" << url_.toString();
+ if (!QDesktopServices::openUrl(url_))
+ qWarning() << "Could not open url" << url_.toString();
}
void
ImageItem::scaleImage()
{
- if (image_.isNull())
- return;
+ if (image_.isNull())
+ return;
- auto width_ratio = (double)max_width_ / (double)image_.width();
- auto height_ratio = (double)max_height_ / (double)image_.height();
+ auto width_ratio = (double)max_width_ / (double)image_.width();
+ auto height_ratio = (double)max_height_ / (double)image_.height();
- auto min_aspect_ratio = std::min(width_ratio, height_ratio);
+ auto min_aspect_ratio = std::min(width_ratio, height_ratio);
- if (min_aspect_ratio > 1) {
- width_ = image_.width();
- height_ = image_.height();
- } else {
- width_ = image_.width() * min_aspect_ratio;
- height_ = image_.height() * min_aspect_ratio;
- }
+ if (min_aspect_ratio > 1) {
+ width_ = image_.width();
+ height_ = image_.height();
+ } else {
+ width_ = image_.width() * min_aspect_ratio;
+ height_ = image_.height() * min_aspect_ratio;
+ }
- setFixedSize(width_, height_);
- scaled_image_ = image_.scaled(width_, height_, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ setFixedSize(width_, height_);
+ scaled_image_ =
+ image_.scaled(width_, height_, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
QSize
ImageItem::sizeHint() const
{
- if (image_.isNull())
- return QSize(max_width_, bottom_height_);
+ if (image_.isNull())
+ return QSize(max_width_, bottom_height_);
- return QSize(width_, height_);
+ return QSize(width_, height_);
}
void
ImageItem::setImage(const QPixmap &image)
{
- image_ = image;
- scaleImage();
- update();
+ image_ = image;
+ scaleImage();
+ update();
}
void
ImageItem::mousePressEvent(QMouseEvent *event)
{
- if (event->button() != Qt::LeftButton)
- return;
-
- if (image_.isNull()) {
- openUrl();
- return;
- }
-
- auto point = event->pos();
-
- // Click on the text box.
- if (QRect(0, height_ - bottom_height_, width_, bottom_height_).contains(point)) {
- openUrl();
- } else {
- auto image_dialog = new ImageOverlayDialog(image_, this);
- image_dialog->show();
- }
+ if (event->button() != Qt::LeftButton)
+ return;
+
+ if (image_.isNull()) {
+ openUrl();
+ return;
+ }
+
+ auto point = event->pos();
+
+ // Click on the text box.
+ if (QRect(0, height_ - bottom_height_, width_, bottom_height_).contains(point)) {
+ openUrl();
+ } else {
+ auto image_dialog = new ImageOverlayDialog(image_, this);
+ image_dialog->show();
+ }
}
void
ImageItem::resizeEvent(QResizeEvent *event)
{
- Q_UNUSED(event);
+ Q_UNUSED(event);
- scaleImage();
+ scaleImage();
}
void
ImageItem::paintEvent(QPaintEvent *event)
{
- Q_UNUSED(event);
+ Q_UNUSED(event);
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
- QFont font("Open Sans");
- font.setPixelSize(12);
+ QFont font("Open Sans");
+ font.setPixelSize(12);
- QFontMetrics metrics(font);
- int fontHeight = metrics.height();
+ QFontMetrics metrics(font);
+ int fontHeight = metrics.height();
- if (image_.isNull()) {
- int height = fontHeight + 10;
+ if (image_.isNull()) {
+ int height = fontHeight + 10;
- QString elidedText = metrics.elidedText(text_, Qt::ElideRight, max_width_ - 10);
+ QString elidedText = metrics.elidedText(text_, Qt::ElideRight, max_width_ - 10);
- setFixedSize(metrics.width(elidedText), fontHeight + 10);
+ setFixedSize(metrics.width(elidedText), fontHeight + 10);
- painter.setFont(font);
- painter.setPen(QPen(QColor(66, 133, 244)));
- painter.drawText(QPoint(0, height / 2 + 2), elidedText);
+ painter.setFont(font);
+ painter.setPen(QPen(QColor(66, 133, 244)));
+ painter.drawText(QPoint(0, height / 2 + 2), elidedText);
- return;
- }
+ return;
+ }
- painter.fillRect(QRect(0, 0, width_, height_), scaled_image_);
+ painter.fillRect(QRect(0, 0, width_, height_), scaled_image_);
- if (underMouse()) {
- // Bottom text section
- painter.fillRect(QRect(0, height_ - bottom_height_, width_, bottom_height_),
- QBrush(QColor(33, 33, 33, 128)));
+ if (underMouse()) {
+ // Bottom text section
+ painter.fillRect(QRect(0, height_ - bottom_height_, width_, bottom_height_),
+ QBrush(QColor(33, 33, 33, 128)));
- QString elidedText = metrics.elidedText(text_, Qt::ElideRight, width_ - 10);
+ QString elidedText = metrics.elidedText(text_, Qt::ElideRight, width_ - 10);
- font.setWeight(500);
- painter.setFont(font);
- painter.setPen(QPen(QColor("white")));
- painter.drawText(QPoint(5, height_ - fontHeight / 2), elidedText);
- }
+ font.setWeight(500);
+ painter.setFont(font);
+ painter.setPen(QPen(QColor("white")));
+ painter.drawText(QPoint(5, height_ - fontHeight / 2), elidedText);
+ }
}
diff --git a/src/LoginPage.cc b/src/LoginPage.cc
index c4048f98..87485878 100644
--- a/src/LoginPage.cc
+++ b/src/LoginPage.cc
@@ -26,274 +26,275 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent)
, inferredServerAddress_()
, client_{ client }
{
- setStyleSheet("background-color: #f9f9f9");
-
- top_layout_ = new QVBoxLayout();
-
- top_bar_layout_ = new QHBoxLayout();
- top_bar_layout_->setSpacing(0);
- top_bar_layout_->setMargin(0);
-
- back_button_ = new FlatButton(this);
- back_button_->setMinimumSize(QSize(30, 30));
- back_button_->setForegroundColor("#333333");
-
- top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
- top_bar_layout_->addStretch(1);
-
- QIcon icon;
- icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off);
-
- back_button_->setIcon(icon);
- back_button_->setIconSize(QSize(24, 24));
-
- QIcon advanced_settings_icon;
- advanced_settings_icon.addFile(":/icons/icons/cog.png", QSize(), QIcon::Normal, QIcon::Off);
-
- logo_ = new QLabel(this);
- logo_->setPixmap(QPixmap(":/logos/nheko-128.png"));
-
- logo_layout_ = new QHBoxLayout();
- logo_layout_->setContentsMargins(0, 0, 0, 20);
- logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
-
- form_wrapper_ = new QHBoxLayout();
- form_widget_ = new QWidget();
- form_widget_->setMinimumSize(QSize(350, 200));
-
- form_layout_ = new QVBoxLayout();
- form_layout_->setSpacing(20);
- form_layout_->setContentsMargins(0, 0, 0, 30);
- form_widget_->setLayout(form_layout_);
-
- form_wrapper_->addStretch(1);
- form_wrapper_->addWidget(form_widget_);
- form_wrapper_->addStretch(1);
-
- matrixid_input_ = new TextField(this);
- matrixid_input_->setTextColor("#333333");
- matrixid_input_->setLabel(tr("Matrix ID"));
- matrixid_input_->setInkColor("#555459");
- matrixid_input_->setBackgroundColor("#f9f9f9");
- matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
-
- spinner_ = new CircularProgress(this);
- spinner_->setColor("#acc7dc");
- spinner_->setSize(32);
- spinner_->setMaximumWidth(spinner_->width());
- spinner_->hide();
-
- errorIcon_ = new QLabel(this);
- errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png"));
- errorIcon_->hide();
-
- matrixidLayout_ = new QHBoxLayout();
- matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter);
-
- password_input_ = new TextField(this);
- password_input_->setTextColor("#333333");
- password_input_->setLabel(tr("Password"));
- password_input_->setInkColor("#555459");
- password_input_->setBackgroundColor("#f9f9f9");
- password_input_->setEchoMode(QLineEdit::Password);
-
- serverInput_ = new TextField(this);
- serverInput_->setTextColor("#333333");
- serverInput_->setLabel("Homeserver address");
- serverInput_->setInkColor("#555459");
- serverInput_->setBackgroundColor("#f9f9f9");
- serverInput_->setPlaceholderText("matrix.org");
- serverInput_->hide();
-
- serverLayout_ = new QHBoxLayout();
- serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter);
-
- form_layout_->addLayout(matrixidLayout_);
- form_layout_->addWidget(password_input_, Qt::AlignHCenter, 0);
- form_layout_->addLayout(serverLayout_);
-
- button_layout_ = new QHBoxLayout();
- button_layout_->setSpacing(0);
- button_layout_->setContentsMargins(0, 0, 0, 30);
-
- login_button_ = new RaisedButton(tr("LOGIN"), this);
- login_button_->setBackgroundColor(QColor("#333333"));
- login_button_->setForegroundColor(QColor("white"));
- login_button_->setMinimumSize(350, 65);
- login_button_->setFontSize(20);
- login_button_->setCornerRadius(3);
-
- button_layout_->addStretch(1);
- button_layout_->addWidget(login_button_);
- button_layout_->addStretch(1);
-
- QFont font;
- font.setPixelSize(conf::fontSize);
-
- error_label_ = new QLabel(this);
- error_label_->setFont(font);
- error_label_->setStyleSheet("color: #E22826");
-
- top_layout_->addLayout(top_bar_layout_);
- top_layout_->addStretch(1);
- top_layout_->addLayout(logo_layout_);
- top_layout_->addLayout(form_wrapper_);
- top_layout_->addStretch(1);
- top_layout_->addLayout(button_layout_);
- top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
- top_layout_->addStretch(1);
-
- setLayout(top_layout_);
-
- connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
- connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked()));
- connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
- connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
- connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
- connect(client_.data(), SIGNAL(loginError(QString)), this, SLOT(loginError(QString)));
- connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
- connect(client_.data(), SIGNAL(versionError(QString)), this, SLOT(versionError(QString)));
- connect(client_.data(), SIGNAL(versionSuccess()), this, SLOT(versionSuccess()));
- connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
+ setStyleSheet("background-color: #f9f9f9");
+
+ top_layout_ = new QVBoxLayout();
+
+ top_bar_layout_ = new QHBoxLayout();
+ top_bar_layout_->setSpacing(0);
+ top_bar_layout_->setMargin(0);
+
+ back_button_ = new FlatButton(this);
+ back_button_->setMinimumSize(QSize(30, 30));
+ back_button_->setForegroundColor("#333333");
+
+ top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
+ top_bar_layout_->addStretch(1);
+
+ QIcon icon;
+ icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off);
+
+ back_button_->setIcon(icon);
+ back_button_->setIconSize(QSize(24, 24));
+
+ QIcon advanced_settings_icon;
+ advanced_settings_icon.addFile(":/icons/icons/cog.png", QSize(), QIcon::Normal, QIcon::Off);
+
+ logo_ = new QLabel(this);
+ logo_->setPixmap(QPixmap(":/logos/nheko-128.png"));
+
+ logo_layout_ = new QHBoxLayout();
+ logo_layout_->setContentsMargins(0, 0, 0, 20);
+ logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
+
+ form_wrapper_ = new QHBoxLayout();
+ form_widget_ = new QWidget();
+ form_widget_->setMinimumSize(QSize(350, 200));
+
+ form_layout_ = new QVBoxLayout();
+ form_layout_->setSpacing(20);
+ form_layout_->setContentsMargins(0, 0, 0, 30);
+ form_widget_->setLayout(form_layout_);
+
+ form_wrapper_->addStretch(1);
+ form_wrapper_->addWidget(form_widget_);
+ form_wrapper_->addStretch(1);
+
+ matrixid_input_ = new TextField(this);
+ matrixid_input_->setTextColor("#333333");
+ matrixid_input_->setLabel(tr("Matrix ID"));
+ matrixid_input_->setInkColor("#555459");
+ matrixid_input_->setBackgroundColor("#f9f9f9");
+ matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
+
+ spinner_ = new LoadingIndicator(this);
+ spinner_->setColor("#acc7dc");
+ spinner_->setFixedHeight(40);
+ spinner_->setFixedWidth(40);
+ spinner_->hide();
+
+ errorIcon_ = new QLabel(this);
+ errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png"));
+ errorIcon_->hide();
+
+ matrixidLayout_ = new QHBoxLayout();
+ matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter);
+
+ password_input_ = new TextField(this);
+ password_input_->setTextColor("#333333");
+ password_input_->setLabel(tr("Password"));
+ password_input_->setInkColor("#555459");
+ password_input_->setBackgroundColor("#f9f9f9");
+ password_input_->setEchoMode(QLineEdit::Password);
+
+ serverInput_ = new TextField(this);
+ serverInput_->setTextColor("#333333");
+ serverInput_->setLabel("Homeserver address");
+ serverInput_->setInkColor("#555459");
+ serverInput_->setBackgroundColor("#f9f9f9");
+ serverInput_->setPlaceholderText("matrix.org");
+ serverInput_->hide();
+
+ serverLayout_ = new QHBoxLayout();
+ serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter);
+
+ form_layout_->addLayout(matrixidLayout_);
+ form_layout_->addWidget(password_input_, Qt::AlignHCenter, 0);
+ form_layout_->addLayout(serverLayout_);
+
+ button_layout_ = new QHBoxLayout();
+ button_layout_->setSpacing(0);
+ button_layout_->setContentsMargins(0, 0, 0, 30);
+
+ login_button_ = new RaisedButton(tr("LOGIN"), this);
+ login_button_->setBackgroundColor(QColor("#333333"));
+ login_button_->setForegroundColor(QColor("white"));
+ login_button_->setMinimumSize(350, 65);
+ login_button_->setFontSize(20);
+ login_button_->setCornerRadius(3);
+
+ button_layout_->addStretch(1);
+ button_layout_->addWidget(login_button_);
+ button_layout_->addStretch(1);
+
+ QFont font;
+ font.setPixelSize(conf::fontSize);
+
+ error_label_ = new QLabel(this);
+ error_label_->setFont(font);
+ error_label_->setStyleSheet("color: #E22826");
+
+ top_layout_->addLayout(top_bar_layout_);
+ top_layout_->addStretch(1);
+ top_layout_->addLayout(logo_layout_);
+ top_layout_->addLayout(form_wrapper_);
+ top_layout_->addStretch(1);
+ top_layout_->addLayout(button_layout_);
+ top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
+ top_layout_->addStretch(1);
+
+ setLayout(top_layout_);
+
+ connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
+ connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked()));
+ connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
+ connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
+ connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
+ connect(client_.data(), SIGNAL(loginError(QString)), this, SLOT(loginError(QString)));
+ connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
+ connect(client_.data(), SIGNAL(versionError(QString)), this, SLOT(versionError(QString)));
+ connect(client_.data(), SIGNAL(versionSuccess()), this, SLOT(versionSuccess()));
+ connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
}
void
LoginPage::loginError(QString error)
{
- error_label_->setText(error);
+ error_label_->setText(error);
}
bool
LoginPage::isMatrixIdValid()
{
- int pos = 0;
- auto matrix_id = matrixid_input_->text();
+ int pos = 0;
+ auto matrix_id = matrixid_input_->text();
- return InputValidator::Id.validate(matrix_id, pos) == QValidator::Acceptable;
+ return InputValidator::Id.validate(matrix_id, pos) == QValidator::Acceptable;
}
void
LoginPage::onMatrixIdEntered()
{
- error_label_->setText("");
-
- if (!isMatrixIdValid()) {
- loginError("You have entered an invalid Matrix ID e.g @joe:matrix.org");
- return;
- } else if (password_input_->text().isEmpty()) {
- loginError(tr("Empty password"));
- }
-
- QString homeServer = matrixid_input_->text().split(":").at(1);
- if (homeServer != inferredServerAddress_) {
- serverInput_->hide();
- serverLayout_->removeWidget(errorIcon_);
- errorIcon_->hide();
- if (serverInput_->isVisible()) {
- matrixidLayout_->removeWidget(spinner_);
- serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
- spinner_->show();
- } else {
- serverLayout_->removeWidget(spinner_);
- matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
- spinner_->show();
- }
-
- inferredServerAddress_ = homeServer;
- serverInput_->setText(homeServer);
- client_->setServer(homeServer);
- client_->versions();
- }
+ error_label_->setText("");
+
+ if (!isMatrixIdValid()) {
+ loginError("You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ return;
+ } else if (password_input_->text().isEmpty()) {
+ loginError(tr("Empty password"));
+ }
+
+ QString homeServer = matrixid_input_->text().split(":").at(1);
+ if (homeServer != inferredServerAddress_) {
+ serverInput_->hide();
+ serverLayout_->removeWidget(errorIcon_);
+ errorIcon_->hide();
+ if (serverInput_->isVisible()) {
+ matrixidLayout_->removeWidget(spinner_);
+ serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
+ spinner_->start();
+ } else {
+ serverLayout_->removeWidget(spinner_);
+ matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
+ spinner_->start();
+ }
+
+ inferredServerAddress_ = homeServer;
+ serverInput_->setText(homeServer);
+ client_->setServer(homeServer);
+ client_->versions();
+ }
}
void
LoginPage::onServerAddressEntered()
{
- error_label_->setText("");
- client_->setServer(serverInput_->text());
- client_->versions();
-
- serverLayout_->removeWidget(errorIcon_);
- errorIcon_->hide();
- serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
- spinner_->show();
+ error_label_->setText("");
+ client_->setServer(serverInput_->text());
+ client_->versions();
+
+ serverLayout_->removeWidget(errorIcon_);
+ errorIcon_->hide();
+ serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
+ spinner_->start();
}
void
LoginPage::versionError(QString error)
{
- // Matrix homeservers are often kept on a subdomain called 'matrix'
- // so let's try that next, unless the address was set explicitly or the domain part of the username already
- // points to this subdomain
- QUrl currentServer = client_->getHomeServer();
- QString mxidAddress = matrixid_input_->text().split(":").at(1);
- if (currentServer.host() == inferredServerAddress_ && !currentServer.host().startsWith("matrix")) {
- error_label_->setText("");
- currentServer.setHost(QString("matrix.") + currentServer.host());
- serverInput_->setText(currentServer.host());
- client_->setServer(currentServer.host());
- client_->versions();
- return;
- }
-
- error_label_->setText(error);
- serverInput_->show();
-
- spinner_->hide();
- serverLayout_->removeWidget(spinner_);
- serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
- errorIcon_->show();
- matrixidLayout_->removeWidget(spinner_);
+ // Matrix homeservers are often kept on a subdomain called 'matrix'
+ // so let's try that next, unless the address was set explicitly or the domain part of the
+ // username already points to this subdomain
+ QUrl currentServer = client_->getHomeServer();
+ QString mxidAddress = matrixid_input_->text().split(":").at(1);
+ if (currentServer.host() == inferredServerAddress_ &&
+ !currentServer.host().startsWith("matrix")) {
+ error_label_->setText("");
+ currentServer.setHost(QString("matrix.") + currentServer.host());
+ serverInput_->setText(currentServer.host());
+ client_->setServer(currentServer.host());
+ client_->versions();
+ return;
+ }
+
+ error_label_->setText(error);
+ serverInput_->show();
+
+ spinner_->stop();
+ serverLayout_->removeWidget(spinner_);
+ serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
+ errorIcon_->show();
+ matrixidLayout_->removeWidget(spinner_);
}
void
LoginPage::versionSuccess()
{
- serverLayout_->removeWidget(spinner_);
- matrixidLayout_->removeWidget(spinner_);
- spinner_->hide();
+ serverLayout_->removeWidget(spinner_);
+ matrixidLayout_->removeWidget(spinner_);
+ spinner_->stop();
- if (serverInput_->isVisible())
- serverInput_->hide();
+ if (serverInput_->isVisible())
+ serverInput_->hide();
}
void
LoginPage::onLoginButtonClicked()
{
- error_label_->setText("");
-
- if (!isMatrixIdValid()) {
- loginError("You have entered an invalid Matrix ID e.g @joe:matrix.org");
- } else if (password_input_->text().isEmpty()) {
- loginError("Empty password");
- } else {
- QString user = matrixid_input_->text().split(":").at(0).split("@").at(1);
- QString password = password_input_->text();
- client_->setServer(serverInput_->text());
- client_->login(user, password);
- }
+ error_label_->setText("");
+
+ if (!isMatrixIdValid()) {
+ loginError("You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ } else if (password_input_->text().isEmpty()) {
+ loginError("Empty password");
+ } else {
+ QString user = matrixid_input_->text().split(":").at(0).split("@").at(1);
+ QString password = password_input_->text();
+ client_->setServer(serverInput_->text());
+ client_->login(user, password);
+ }
}
void
LoginPage::reset()
{
- matrixid_input_->clear();
- password_input_->clear();
- serverInput_->clear();
+ matrixid_input_->clear();
+ password_input_->clear();
+ serverInput_->clear();
- spinner_->hide();
- errorIcon_->hide();
- serverLayout_->removeWidget(spinner_);
- serverLayout_->removeWidget(errorIcon_);
- matrixidLayout_->removeWidget(spinner_);
+ spinner_->stop();
+ errorIcon_->hide();
+ serverLayout_->removeWidget(spinner_);
+ serverLayout_->removeWidget(errorIcon_);
+ matrixidLayout_->removeWidget(spinner_);
- inferredServerAddress_.clear();
+ inferredServerAddress_.clear();
}
void
LoginPage::onBackButtonClicked()
{
- emit backButtonClicked();
+ emit backButtonClicked();
}
LoginPage::~LoginPage()
diff --git a/src/MainWindow.cc b/src/MainWindow.cc
index 1567e8ba..c76657d7 100644
--- a/src/MainWindow.cc
+++ b/src/MainWindow.cc
@@ -15,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "MainWindow.h"
#include "Config.h"
+#include "MainWindow.h"
#include <QLayout>
#include <QNetworkReply>
@@ -30,217 +30,225 @@ MainWindow::MainWindow(QWidget *parent)
, progress_modal_{ nullptr }
, spinner_{ nullptr }
{
- QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
- setSizePolicy(sizePolicy);
- setWindowTitle("nheko");
- setObjectName("MainWindow");
- setStyleSheet("QWidget#MainWindow {background-color: #f9f9f9}");
+ QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setSizePolicy(sizePolicy);
+ setWindowTitle("nheko");
+ setObjectName("MainWindow");
+ setStyleSheet("QWidget#MainWindow {background-color: #f9f9f9}");
- restoreWindowSize();
- setMinimumSize(QSize(conf::window::minWidth, conf::window::minHeight));
+ restoreWindowSize();
+ setMinimumSize(QSize(conf::window::minWidth, conf::window::minHeight));
- QFont font("Open Sans");
- font.setPixelSize(conf::fontSize);
- font.setStyleStrategy(QFont::PreferAntialias);
- setFont(font);
+ QFont font("Open Sans");
+ font.setPixelSize(conf::fontSize);
+ font.setStyleStrategy(QFont::PreferAntialias);
+ setFont(font);
- client_ = QSharedPointer<MatrixClient>(new MatrixClient("matrix.org"));
- trayIcon_ = new TrayIcon(":/logos/nheko-32.png", this);
+ client_ = QSharedPointer<MatrixClient>(new MatrixClient("matrix.org"));
+ trayIcon_ = new TrayIcon(":/logos/nheko-32.png", this);
- welcome_page_ = new WelcomePage(this);
- login_page_ = new LoginPage(client_, this);
- register_page_ = new RegisterPage(client_, this);
- chat_page_ = new ChatPage(client_, this);
+ welcome_page_ = new WelcomePage(this);
+ login_page_ = new LoginPage(client_, this);
+ register_page_ = new RegisterPage(client_, this);
+ chat_page_ = new ChatPage(client_, this);
- // Initialize sliding widget manager.
- sliding_stack_ = new SlidingStackWidget(this);
- sliding_stack_->addWidget(welcome_page_);
- sliding_stack_->addWidget(login_page_);
- sliding_stack_->addWidget(register_page_);
- sliding_stack_->addWidget(chat_page_);
+ // Initialize sliding widget manager.
+ sliding_stack_ = new SlidingStackWidget(this);
+ sliding_stack_->addWidget(welcome_page_);
+ sliding_stack_->addWidget(login_page_);
+ sliding_stack_->addWidget(register_page_);
+ sliding_stack_->addWidget(chat_page_);
- setCentralWidget(sliding_stack_);
+ setCentralWidget(sliding_stack_);
- connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
- connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
+ connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
+ connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
- connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
- connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
+ connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
+ connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
- connect(chat_page_, SIGNAL(close()), this, SLOT(showWelcomePage()));
- connect(chat_page_, SIGNAL(changeWindowTitle(QString)), this, SLOT(setWindowTitle(QString)));
- connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
+ connect(chat_page_, SIGNAL(close()), this, SLOT(showWelcomePage()));
+ connect(
+ chat_page_, SIGNAL(changeWindowTitle(QString)), this, SLOT(setWindowTitle(QString)));
+ connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
- connect(trayIcon_,
- SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
- this,
- SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
+ connect(trayIcon_,
+ SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
+ this,
+ SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
- connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
+ connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
- connect(client_.data(),
- SIGNAL(loginSuccess(QString, QString, QString)),
- this,
- SLOT(showChatPage(QString, QString, QString)));
+ connect(client_.data(),
+ SIGNAL(loginSuccess(QString, QString, QString)),
+ this,
+ SLOT(showChatPage(QString, QString, QString)));
- QSettings settings;
+ QSettings settings;
- if (hasActiveUser()) {
- QString token = settings.value("auth/access_token").toString();
- QString home_server = settings.value("auth/home_server").toString();
- QString user_id = settings.value("auth/user_id").toString();
+ if (hasActiveUser()) {
+ QString token = settings.value("auth/access_token").toString();
+ QString home_server = settings.value("auth/home_server").toString();
+ QString user_id = settings.value("auth/user_id").toString();
- showChatPage(user_id, home_server, token);
- }
+ showChatPage(user_id, home_server, token);
+ }
}
void
MainWindow::restoreWindowSize()
{
- QSettings settings;
- int savedWidth = settings.value("window/width").toInt();
- int savedheight = settings.value("window/height").toInt();
-
- if (savedWidth == 0 || savedheight == 0)
- resize(conf::window::width, conf::window::height);
- else
- resize(savedWidth, savedheight);
+ QSettings settings;
+ int savedWidth = settings.value("window/width").toInt();
+ int savedheight = settings.value("window/height").toInt();
+
+ if (savedWidth == 0 || savedheight == 0)
+ resize(conf::window::width, conf::window::height);
+ else
+ resize(savedWidth, savedheight);
}
void
MainWindow::saveCurrentWindowSize()
{
- QSettings settings;
- QSize current = size();
+ QSettings settings;
+ QSize current = size();
- settings.setValue("window/width", current.width());
- settings.setValue("window/height", current.height());
+ settings.setValue("window/width", current.width());
+ settings.setValue("window/height", current.height());
}
void
MainWindow::removeOverlayProgressBar()
{
- QTimer *timer = new QTimer(this);
- timer->setSingleShot(true);
+ QTimer *timer = new QTimer(this);
+ timer->setSingleShot(true);
- connect(timer, &QTimer::timeout, [=]() {
- timer->deleteLater();
+ connect(timer, &QTimer::timeout, [=]() {
+ timer->deleteLater();
- if (progress_modal_ != nullptr) {
- progress_modal_->deleteLater();
- progress_modal_->fadeOut();
- }
+ if (progress_modal_ != nullptr) {
+ progress_modal_->deleteLater();
+ progress_modal_->fadeOut();
+ }
- if (spinner_ != nullptr)
- spinner_->deleteLater();
+ if (spinner_ != nullptr)
+ spinner_->deleteLater();
- progress_modal_ = nullptr;
- spinner_ = nullptr;
- });
+ spinner_->stop();
- timer->start(500);
+ progress_modal_ = nullptr;
+ spinner_ = nullptr;
+ });
+
+ timer->start(500);
}
void
MainWindow::showChatPage(QString userid, QString homeserver, QString token)
{
- QSettings settings;
- settings.setValue("auth/access_token", token);
- settings.setValue("auth/home_server", homeserver);
- settings.setValue("auth/user_id", userid);
-
- int index = sliding_stack_->getWidgetIndex(chat_page_);
- int modalOpacityDuration = 300;
-
- // If we go directly from the welcome page don't show an animation.
- if (sliding_stack_->currentIndex() == 0) {
- sliding_stack_->setCurrentIndex(index);
- modalOpacityDuration = 0;
- } else {
- sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT);
- }
-
- if (spinner_ == nullptr) {
- spinner_ = new CircularProgress(this);
- spinner_->setColor("#acc7dc");
- spinner_->setSize(100);
- }
-
- if (progress_modal_ == nullptr) {
- progress_modal_ = new OverlayModal(this, spinner_);
- progress_modal_->fadeIn();
- progress_modal_->setDuration(modalOpacityDuration);
- }
-
- login_page_->reset();
- chat_page_->bootstrap(userid, homeserver, token);
-
- instance_ = this;
+ QSettings settings;
+ settings.setValue("auth/access_token", token);
+ settings.setValue("auth/home_server", homeserver);
+ settings.setValue("auth/user_id", userid);
+
+ int index = sliding_stack_->getWidgetIndex(chat_page_);
+ int modalOpacityDuration = 300;
+
+ // If we go directly from the welcome page don't show an animation.
+ if (sliding_stack_->currentIndex() == 0) {
+ sliding_stack_->setCurrentIndex(index);
+ modalOpacityDuration = 0;
+ } else {
+ sliding_stack_->slideInIndex(index,
+ SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT);
+ }
+
+ if (spinner_ == nullptr) {
+ spinner_ = new LoadingIndicator(this);
+ spinner_->setColor("#acc7dc");
+ spinner_->setFixedHeight(120);
+ spinner_->setFixedWidth(120);
+ spinner_->start();
+ }
+
+ if (progress_modal_ == nullptr) {
+ progress_modal_ = new OverlayModal(this, spinner_);
+ progress_modal_->fadeIn();
+ progress_modal_->setDuration(modalOpacityDuration);
+ }
+
+ login_page_->reset();
+ chat_page_->bootstrap(userid, homeserver, token);
+
+ instance_ = this;
}
void
MainWindow::showWelcomePage()
{
- int index = sliding_stack_->getWidgetIndex(welcome_page_);
-
- if (sliding_stack_->currentIndex() == sliding_stack_->getWidgetIndex(login_page_))
- sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT);
- else
- sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT);
+ int index = sliding_stack_->getWidgetIndex(welcome_page_);
+
+ if (sliding_stack_->currentIndex() == sliding_stack_->getWidgetIndex(login_page_))
+ sliding_stack_->slideInIndex(index,
+ SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT);
+ else
+ sliding_stack_->slideInIndex(index,
+ SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT);
}
void
MainWindow::showLoginPage()
{
- int index = sliding_stack_->getWidgetIndex(login_page_);
- sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT);
+ int index = sliding_stack_->getWidgetIndex(login_page_);
+ sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT);
}
void
MainWindow::showRegisterPage()
{
- int index = sliding_stack_->getWidgetIndex(register_page_);
- sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT);
+ int index = sliding_stack_->getWidgetIndex(register_page_);
+ sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::RIGHT_TO_LEFT);
}
void
MainWindow::closeEvent(QCloseEvent *event)
{
- if (isVisible()) {
- event->ignore();
- hide();
- }
+ if (isVisible()) {
+ event->ignore();
+ hide();
+ }
}
void
MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
{
- switch (reason) {
- case QSystemTrayIcon::Trigger:
- if (!isVisible()) {
- show();
- } else {
- hide();
- }
- break;
- default:
- break;
- }
+ switch (reason) {
+ case QSystemTrayIcon::Trigger:
+ if (!isVisible()) {
+ show();
+ } else {
+ hide();
+ }
+ break;
+ default:
+ break;
+ }
}
bool
MainWindow::hasActiveUser()
{
- QSettings settings;
+ QSettings settings;
- return settings.contains("auth/access_token") && settings.contains("auth/home_server") &&
- settings.contains("auth/user_id");
+ return settings.contains("auth/access_token") && settings.contains("auth/home_server") &&
+ settings.contains("auth/user_id");
}
MainWindow *
MainWindow::instance()
{
- return instance_;
+ return instance_;
}
MainWindow::~MainWindow()
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index e053642d..2e4f7c47 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -16,6 +16,8 @@
*/
#include <QDebug>
+#include <QFile>
+#include <QImageReader>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
@@ -34,11 +36,10 @@
MatrixClient::MatrixClient(QString server, QObject *parent)
: QNetworkAccessManager(parent)
+ , clientApiUrl_{ "/_matrix/client/r0" }
+ , mediaApiUrl_{ "/_matrix/media/r0" }
+ , server_{ "https://" + server }
{
- server_ = "https://" + server;
- api_url_ = "/_matrix/client/r0";
- token_ = "";
-
QSettings settings;
txn_id_ = settings.value("client/transaction_id", 1).toInt();
@@ -237,6 +238,42 @@ MatrixClient::onInitialSyncResponse(QNetworkReply *reply)
}
void
+MatrixClient::onImageUploadResponse(QNetworkReply *reply)
+{
+ reply->deleteLater();
+
+ int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+ if (status == 0 || status >= 400) {
+ emit syncFailed(reply->errorString());
+ return;
+ }
+
+ auto data = reply->readAll();
+
+ if (data.isEmpty())
+ return;
+
+ auto json = QJsonDocument::fromJson(data);
+
+ if (!json.isObject()) {
+ qDebug() << "Media upload: Response is not a json object.";
+ return;
+ }
+
+ QJsonObject object = json.object();
+ if (!object.contains("content_uri")) {
+ qDebug() << "Media upload: Missing content_uri key";
+ qDebug() << object;
+ return;
+ }
+
+ emit imageUploaded(reply->property("room_id").toString(),
+ reply->property("filename").toString(),
+ object.value("content_uri").toString());
+}
+
+void
MatrixClient::onSyncResponse(QNetworkReply *reply)
{
reply->deleteLater();
@@ -450,6 +487,9 @@ MatrixClient::onResponse(QNetworkReply *reply)
case Endpoint::InitialSync:
onInitialSyncResponse(reply);
break;
+ case Endpoint::ImageUpload:
+ onImageUploadResponse(reply);
+ break;
case Endpoint::Sync:
onSyncResponse(reply);
break;
@@ -477,7 +517,7 @@ void
MatrixClient::login(const QString &username, const QString &password) noexcept
{
QUrl endpoint(server_);
- endpoint.setPath(api_url_ + "/login");
+ endpoint.setPath(clientApiUrl_ + "/login");
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
@@ -495,7 +535,7 @@ MatrixClient::logout() noexcept
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
- endpoint.setPath(api_url_ + "/logout");
+ endpoint.setPath(clientApiUrl_ + "/logout");
endpoint.setQuery(query);
QNetworkRequest request(endpoint);
@@ -515,7 +555,7 @@ MatrixClient::registerUser(const QString &user, const QString &pass, const QStri
query.addQueryItem("kind", "user");
QUrl endpoint(server_);
- endpoint.setPath(api_url_ + "/register");
+ endpoint.setPath(clientApiUrl_ + "/register");
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
@@ -549,7 +589,7 @@ MatrixClient::sync() noexcept
query.addQueryItem("since", next_batch_);
QUrl endpoint(server_);
- endpoint.setPath(api_url_ + "/sync");
+ endpoint.setPath(clientApiUrl_ + "/sync");
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
@@ -561,32 +601,35 @@ MatrixClient::sync() noexcept
void
MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty,
const QString &roomid,
- const QString &msg) noexcept
+ const QString &msg,
+ const QString &url) noexcept
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
- endpoint.setPath(api_url_ +
+ endpoint.setPath(clientApiUrl_ +
QString("/rooms/%1/send/m.room.message/%2").arg(roomid).arg(txn_id_));
endpoint.setQuery(query);
QString msgType("");
+ QJsonObject body;
switch (ty) {
case matrix::events::MessageEventType::Text:
- msgType = "m.text";
+ body = { { "msgtype", "m.text" }, { "body", msg } };
break;
case matrix::events::MessageEventType::Emote:
- msgType = "m.emote";
+ body = { { "msgtype", "m.emote" }, { "body", msg } };
break;
- default:
- msgType = "m.text";
+ case matrix::events::MessageEventType::Image:
+ body = { { "msgtype", "m.image" }, { "body", msg }, { "url", url } };
break;
+ default:
+ qDebug() << "SendRoomMessage: Unknown message type for" << msg;
+ return;
}
- QJsonObject body{ { "msgtype", msgType }, { "body", msg } };
-
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
@@ -617,7 +660,7 @@ MatrixClient::initialSync() noexcept
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
- endpoint.setPath(api_url_ + "/sync");
+ endpoint.setPath(clientApiUrl_ + "/sync");
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
@@ -650,7 +693,7 @@ MatrixClient::getOwnProfile() noexcept
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
- endpoint.setPath(api_url_ + "/profile/" + userid);
+ endpoint.setPath(clientApiUrl_ + "/profile/" + userid);
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
@@ -762,7 +805,7 @@ MatrixClient::messages(const QString &room_id, const QString &from_token, int li
query.addQueryItem("limit", QString::number(limit));
QUrl endpoint(server_);
- endpoint.setPath(api_url_ + QString("/rooms/%1/messages").arg(room_id));
+ endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/messages").arg(room_id));
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
@@ -771,3 +814,31 @@ MatrixClient::messages(const QString &room_id, const QString &from_token, int li
reply->setProperty("endpoint", static_cast<int>(Endpoint::Messages));
reply->setProperty("room_id", room_id);
}
+
+void
+MatrixClient::uploadImage(const QString &roomid, const QString &filename)
+{
+ QUrlQuery query;
+ query.addQueryItem("access_token", token_);
+
+ QUrl endpoint(server_);
+ endpoint.setPath(mediaApiUrl_ + "/upload");
+ endpoint.setQuery(query);
+
+ QFile file(filename);
+ if (!file.open(QIODevice::ReadWrite)) {
+ qDebug() << "Error while reading" << filename;
+ return;
+ }
+
+ auto imgFormat = QString(QImageReader::imageFormat(filename));
+
+ QNetworkRequest request(QString(endpoint.toEncoded()));
+ request.setHeader(QNetworkRequest::ContentLengthHeader, file.size());
+ request.setHeader(QNetworkRequest::ContentTypeHeader, QString("image/%1").arg(imgFormat));
+
+ QNetworkReply *reply = post(request, file.readAll());
+ reply->setProperty("endpoint", static_cast<int>(Endpoint::ImageUpload));
+ reply->setProperty("room_id", roomid);
+ reply->setProperty("filename", filename);
+}
diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc
index bd74186e..ce208feb 100644
--- a/src/TextInputWidget.cc
+++ b/src/TextInputWidget.cc
@@ -17,6 +17,8 @@
#include <QDebug>
#include <QFile>
+#include <QFileDialog>
+#include <QImageReader>
#include <QPainter>
#include <QStyleOption>
@@ -47,17 +49,23 @@ TextInputWidget::TextInputWidget(QWidget *parent)
setCursor(Qt::ArrowCursor);
setStyleSheet("background-color: #f8fbfe; height: 45px;");
- top_layout_ = new QHBoxLayout();
- top_layout_->setSpacing(0);
- top_layout_->setMargin(0);
-
- send_file_button_ = new FlatButton(this);
+ topLayout_ = new QHBoxLayout();
+ topLayout_->setSpacing(2);
+ topLayout_->setMargin(4);
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));
+
+ sendFileBtn_ = new FlatButton(this);
+ sendFileBtn_->setForegroundColor(QColor("#acc7dc"));
+ sendFileBtn_->setIcon(send_file_icon);
+ sendFileBtn_->setIconSize(QSize(24, 24));
+
+ spinner_ = new LoadingIndicator(this);
+ spinner_->setColor("#acc7dc");
+ spinner_->setFixedHeight(40);
+ spinner_->setFixedWidth(40);
+ spinner_->hide();
QFont font;
font.setPixelSize(conf::fontSize);
@@ -68,33 +76,34 @@ TextInputWidget::TextInputWidget(QWidget *parent)
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"));
+ sendMessageBtn_ = new FlatButton(this);
+ sendMessageBtn_->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));
+ sendMessageBtn_->setIcon(send_message_icon);
+ sendMessageBtn_->setIconSize(QSize(24, 24));
- emoji_button_ = new EmojiPickButton(this);
- emoji_button_->setForegroundColor(QColor("#acc7dc"));
+ emojiBtn_ = new EmojiPickButton(this);
+ emojiBtn_->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));
+ emojiBtn_->setIcon(emoji_icon);
+ emojiBtn_->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_);
+ topLayout_->addWidget(sendFileBtn_);
+ topLayout_->addWidget(input_);
+ topLayout_->addWidget(emojiBtn_);
+ topLayout_->addWidget(sendMessageBtn_);
- setLayout(top_layout_);
+ setLayout(topLayout_);
- connect(send_message_button_, SIGNAL(clicked()), this, SLOT(onSendButtonClicked()));
- connect(input_, SIGNAL(enterPressed()), send_message_button_, SIGNAL(clicked()));
- connect(emoji_button_,
+ connect(sendMessageBtn_, SIGNAL(clicked()), this, SLOT(onSendButtonClicked()));
+ connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection()));
+ connect(input_, SIGNAL(enterPressed()), sendMessageBtn_, SIGNAL(clicked()));
+ connect(emojiBtn_,
SIGNAL(emojiSelected(const QString &)),
this,
SLOT(addSelectedEmoji(const QString &)));
@@ -155,6 +164,55 @@ TextInputWidget::parseEmoteCommand(const QString &cmd)
return QString("");
}
+void
+TextInputWidget::openFileSelection()
+{
+ QStringList supportedFiles;
+ supportedFiles << "jpeg"
+ << "gif"
+ << "png"
+ << "bmp"
+ << "tiff"
+ << "webp";
+
+ auto fileName = QFileDialog::getOpenFileName(
+ this,
+ tr("Select an image"),
+ "",
+ tr("Image Files (*.bmp *.gif *.jpg *.jpeg *.png *.tiff *.webp)"));
+
+ if (fileName.isEmpty())
+ return;
+
+ auto imageFormat = QString(QImageReader::imageFormat(fileName));
+ if (!supportedFiles.contains(imageFormat)) {
+ qDebug() << "Unsupported image format for" << fileName;
+ return;
+ }
+
+ emit uploadImage(fileName);
+ showUploadSpinner();
+}
+
+void
+TextInputWidget::showUploadSpinner()
+{
+ topLayout_->removeWidget(sendFileBtn_);
+ sendFileBtn_->hide();
+
+ topLayout_->insertWidget(0, spinner_);
+ spinner_->start();
+}
+
+void
+TextInputWidget::hideUploadSpinner()
+{
+ topLayout_->removeWidget(spinner_);
+ topLayout_->insertWidget(0, sendFileBtn_);
+ sendFileBtn_->show();
+ spinner_->stop();
+}
+
TextInputWidget::~TextInputWidget()
{
}
diff --git a/src/TimelineItem.cc b/src/TimelineItem.cc
index 9d24a96c..92351d63 100644
--- a/src/TimelineItem.cc
+++ b/src/TimelineItem.cc
@@ -107,6 +107,39 @@ TimelineItem::TimelineItem(events::MessageEventType ty,
mainLayout_->addWidget(body_);
}
+TimelineItem::TimelineItem(ImageItem *image,
+ const QString &userid,
+ bool withSender,
+ QWidget *parent)
+ : QWidget{ parent }
+{
+ init();
+
+ auto displayName = TimelineViewManager::displayName(userid);
+ auto timestamp = QDateTime::currentDateTime();
+
+ descriptionMsg_ = { "You", userid, " sent an image", descriptiveTime(timestamp) };
+
+ generateTimestamp(timestamp);
+
+ auto imageLayout = new QHBoxLayout();
+ imageLayout->setMargin(0);
+ imageLayout->addWidget(image);
+ imageLayout->addStretch(1);
+
+ if (withSender) {
+ generateBody(displayName, "");
+ setupAvatarLayout(displayName);
+ mainLayout_->addLayout(headerLayout_);
+
+ AvatarProvider::resolve(userid, this);
+ } else {
+ setupSimpleLayout();
+ }
+
+ mainLayout_->addLayout(imageLayout);
+}
+
/*
* Used to display images. The avatar and the username are displayed.
*/
diff --git a/src/TimelineView.cc b/src/TimelineView.cc
index 518676ac..600ccb94 100644
--- a/src/TimelineView.cc
+++ b/src/TimelineView.cc
@@ -223,8 +223,9 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire
eventIds_[text.eventId()] = true;
- if (isPendingMessage(text, local_user_)) {
- removePendingMessage(text);
+ if (isPendingMessage(
+ text.eventId(), text.content().body(), text.sender(), local_user_)) {
+ removePendingMessage(text.eventId(), text.content().body());
return nullptr;
}
@@ -245,7 +246,6 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire
if (isDuplicate(notice.eventId()))
return nullptr;
- ;
eventIds_[notice.eventId()] = true;
@@ -269,6 +269,12 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire
eventIds_[img.eventId()] = true;
+ if (isPendingMessage(
+ img.eventId(), img.msgContent().url(), img.sender(), local_user_)) {
+ removePendingMessage(img.eventId(), img.msgContent().url());
+ return nullptr;
+ }
+
auto with_sender = isSenderRendered(img.sender(), direction);
updateLastSender(img.sender(), direction);
@@ -289,8 +295,11 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire
eventIds_[emote.eventId()] = true;
- if (isPendingMessage(emote, local_user_)) {
- removePendingMessage(emote);
+ if (isPendingMessage(emote.eventId(),
+ emote.content().body(),
+ emote.sender(),
+ local_user_)) {
+ removePendingMessage(emote.eventId(), emote.content().body());
return nullptr;
}
@@ -472,6 +481,24 @@ TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString
}
void
+TimelineView::addUserMessage(const QString &url, const QString &filename, int txn_id)
+{
+ QSettings settings;
+ auto user_id = settings.value("auth/user_id").toString();
+ auto with_sender = lastSender_ != user_id;
+
+ auto image = new ImageItem(client_, url, filename, this);
+
+ TimelineItem *view_item = new TimelineItem(image, user_id, with_sender, scroll_widget_);
+ scroll_layout_->addWidget(view_item);
+
+ lastSender_ = user_id;
+
+ PendingMessage message(txn_id, url, "", view_item);
+ pending_msgs_.push_back(message);
+}
+
+void
TimelineView::notifyForLastEvent()
{
auto lastItem = scroll_layout_->itemAt(scroll_layout_->count() - 1);
@@ -482,3 +509,33 @@ TimelineView::notifyForLastEvent()
else
qWarning() << "Cast to TimelineView failed" << room_id_;
}
+
+bool
+TimelineView::isPendingMessage(const QString &eventid,
+ const QString &body,
+ const QString &sender,
+ const QString &local_userid)
+{
+ if (sender != local_userid)
+ return false;
+
+ for (const auto &msg : pending_msgs_) {
+ if (msg.event_id == eventid || msg.body == body)
+ return true;
+ }
+
+ return false;
+}
+
+void
+TimelineView::removePendingMessage(const QString &eventid, const QString &body)
+{
+ for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); it++) {
+ int index = std::distance(pending_msgs_.begin(), it);
+
+ if (it->event_id == eventid || it->body == body) {
+ pending_msgs_.removeAt(index);
+ break;
+ }
+ }
+}
diff --git a/src/TimelineViewManager.cc b/src/TimelineViewManager.cc
index 0bb56bf9..2adbba34 100644
--- a/src/TimelineViewManager.cc
+++ b/src/TimelineViewManager.cc
@@ -19,6 +19,7 @@
#include <QApplication>
#include <QDebug>
+#include <QFileInfo>
#include <QSettings>
#include <QStackedWidget>
#include <QWidget>
@@ -73,6 +74,23 @@ TimelineViewManager::sendEmoteMessage(const QString &msg)
}
void
+TimelineViewManager::sendImageMessage(const QString &roomid,
+ const QString &filename,
+ const QString &url)
+{
+ if (!views_.contains(roomid)) {
+ qDebug() << "Cannot send m.image message to a non-managed view";
+ return;
+ }
+
+ auto view = views_[roomid];
+
+ view->addUserMessage(url, filename, client_->transactionId());
+ client_->sendRoomMessage(
+ matrix::events::MessageEventType::Image, roomid, QFileInfo(filename).fileName(), url);
+}
+
+void
TimelineViewManager::clearAll()
{
for (auto view : views_)
diff --git a/src/ui/CircularProgress.cc b/src/ui/CircularProgress.cc
deleted file mode 100644
index 425ece13..00000000
--- a/src/ui/CircularProgress.cc
+++ /dev/null
@@ -1,201 +0,0 @@
-#include <QPainter>
-#include <QParallelAnimationGroup>
-#include <QPen>
-#include <QPropertyAnimation>
-
-#include "CircularProgress.h"
-#include "Theme.h"
-
-CircularProgress::CircularProgress(QWidget *parent)
- : QProgressBar{ parent }
- , progress_type_{ ui::ProgressType::IndeterminateProgress }
- , width_{ 6.25 }
- , size_{ 64 }
- , duration_{ 3050 }
-{
- delegate_ = new CircularProgressDelegate(this);
-
- setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
-
- auto group = new QParallelAnimationGroup(this);
- group->setLoopCount(-1);
-
- auto length_animation = new QPropertyAnimation(this);
- length_animation->setPropertyName("dashLength");
- length_animation->setTargetObject(delegate_);
- length_animation->setEasingCurve(QEasingCurve::InOutQuad);
- length_animation->setStartValue(0.1);
- length_animation->setKeyValueAt(0.15, 3);
- length_animation->setKeyValueAt(0.6, 20);
- length_animation->setKeyValueAt(0.7, 20);
- length_animation->setEndValue(20);
- length_animation->setDuration(duration_);
-
- auto offset_animation = new QPropertyAnimation(this);
- offset_animation->setPropertyName("dashOffset");
- offset_animation->setTargetObject(delegate_);
- offset_animation->setEasingCurve(QEasingCurve::InOutSine);
- offset_animation->setStartValue(0);
- offset_animation->setKeyValueAt(0.15, 0);
- offset_animation->setKeyValueAt(0.6, -7);
- offset_animation->setKeyValueAt(0.7, -7);
- offset_animation->setEndValue(-25);
- offset_animation->setDuration(duration_);
-
- auto angle_animation = new QPropertyAnimation(this);
- angle_animation->setPropertyName("angle");
- angle_animation->setTargetObject(delegate_);
- angle_animation->setStartValue(0);
- angle_animation->setEndValue(360);
- angle_animation->setDuration(duration_);
-
- group->addAnimation(length_animation);
- group->addAnimation(offset_animation);
- group->addAnimation(angle_animation);
-
- group->start();
-}
-
-void
-CircularProgress::setProgressType(ui::ProgressType type)
-{
- progress_type_ = type;
- update();
-}
-
-void
-CircularProgress::setLineWidth(qreal width)
-{
- width_ = width;
- update();
- updateGeometry();
-}
-
-void
-CircularProgress::setSize(int size)
-{
- size_ = size;
- update();
- updateGeometry();
-}
-
-ui::ProgressType
-CircularProgress::progressType() const
-{
- return progress_type_;
-}
-
-qreal
-CircularProgress::lineWidth() const
-{
- return width_;
-}
-
-int
-CircularProgress::size() const
-{
- return size_;
-}
-
-void
-CircularProgress::setColor(const QColor &color)
-{
- color_ = color;
-}
-
-QColor
-CircularProgress::color() const
-{
- if (!color_.isValid()) {
- return QColor("red");
- }
-
- return color_;
-}
-
-QSize
-CircularProgress::sizeHint() const
-{
- const qreal s = size_ + width_ + 8;
- return QSize(s, s);
-}
-
-void
-CircularProgress::paintEvent(QPaintEvent *event)
-{
- Q_UNUSED(event);
-
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing);
-
- /*
- * If the progress bar is disabled draw an X instead
- */
- if (!isEnabled()) {
- QPen pen;
- pen.setCapStyle(Qt::RoundCap);
- pen.setWidthF(lineWidth());
- pen.setColor("gray");
-
- auto center = rect().center();
-
- painter.setPen(pen);
- painter.drawLine(center - QPointF(20, 20), center + QPointF(20, 20));
- painter.drawLine(center + QPointF(20, -20), center - QPointF(20, -20));
-
- return;
- }
-
- if (progress_type_ == ui::ProgressType::IndeterminateProgress) {
- painter.translate(width() / 2, height() / 2);
- painter.rotate(delegate_->angle());
- }
-
- QPen pen;
- pen.setCapStyle(Qt::RoundCap);
- pen.setWidthF(width_);
- pen.setColor(color());
-
- if (ui::ProgressType::IndeterminateProgress == progress_type_) {
- QVector<qreal> pattern;
- pattern << delegate_->dashLength() * size_ / 50 << 30 * size_ / 50;
-
- pen.setDashOffset(delegate_->dashOffset() * size_ / 50);
- pen.setDashPattern(pattern);
-
- painter.setPen(pen);
-
- painter.drawEllipse(QPoint(0, 0), size_ / 2, size_ / 2);
- } else {
- painter.setPen(pen);
-
- const qreal x = (width() - size_) / 2;
- const qreal y = (height() - size_) / 2;
-
- const qreal a = 360 * (value() - minimum()) / (maximum() - minimum());
-
- QPainterPath path;
- path.arcMoveTo(x, y, size_, size_, 0);
- path.arcTo(x, y, size_, size_, 0, a);
-
- painter.drawPath(path);
- }
-}
-
-CircularProgress::~CircularProgress()
-{
-}
-
-CircularProgressDelegate::CircularProgressDelegate(CircularProgress *parent)
- : QObject(parent)
- , progress_(parent)
- , dash_offset_(0)
- , dash_length_(89)
- , angle_(0)
-{
- Q_ASSERT(parent);
-}
-
-CircularProgressDelegate::~CircularProgressDelegate()
-{
-}
diff --git a/src/ui/LoadingIndicator.cc b/src/ui/LoadingIndicator.cc
new file mode 100644
index 00000000..0fafaf23
--- /dev/null
+++ b/src/ui/LoadingIndicator.cc
@@ -0,0 +1,86 @@
+#include "LoadingIndicator.h"
+
+#include <QDebug>
+#include <QPoint>
+#include <QtGlobal>
+
+LoadingIndicator::LoadingIndicator(QWidget *parent)
+ : QWidget(parent)
+ , interval_(70)
+ , angle_(0)
+ , color_(Qt::black)
+{
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ setFocusPolicy(Qt::NoFocus);
+
+ timer_ = new QTimer();
+ connect(timer_, SIGNAL(timeout()), this, SLOT(onTimeout()));
+}
+
+LoadingIndicator::~LoadingIndicator()
+{
+ stop();
+
+ delete timer_;
+}
+
+void
+LoadingIndicator::paintEvent(QPaintEvent *e)
+{
+ Q_UNUSED(e)
+
+ if (!timer_->isActive())
+ return;
+
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ int width = qMin(this->width(), this->height());
+
+ int outerRadius = (width - 4) * 0.5f;
+ int innerRadius = outerRadius * 0.78f;
+
+ int capsuleRadius = (outerRadius - innerRadius) / 2;
+
+ for (int i = 0; i < 8; i++) {
+ QColor color = color_;
+
+ color.setAlphaF(1.0f - (i / 8.0f));
+
+ painter.setPen(Qt::NoPen);
+ painter.setBrush(color);
+
+ qreal radius = capsuleRadius * (1.0f - (i / 16.0f));
+
+ painter.save();
+
+ painter.translate(rect().center());
+ painter.rotate(angle_ - i * 45.0f);
+
+ QPointF center = QPointF(-capsuleRadius, -innerRadius);
+ painter.drawEllipse(center, radius * 2, radius * 2);
+
+ painter.restore();
+ }
+}
+
+void
+LoadingIndicator::start()
+{
+ timer_->start(interval_);
+ show();
+}
+
+void
+LoadingIndicator::stop()
+{
+ timer_->stop();
+ hide();
+}
+
+void
+LoadingIndicator::onTimeout()
+{
+ angle_ = (angle_ + 45) % 360;
+ update();
+}
|