summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ImageItem.cc175
-rw-r--r--src/MatrixClient.cc36
-rw-r--r--src/TimelineItem.cc37
-rw-r--r--src/TimelineView.cc46
-rw-r--r--src/TimelineViewManager.cc2
5 files changed, 293 insertions, 3 deletions
diff --git a/src/ImageItem.cc b/src/ImageItem.cc
new file mode 100644

index 00000000..8298926d --- /dev/null +++ b/src/ImageItem.cc
@@ -0,0 +1,175 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QBrush> +#include <QDebug> +#include <QDesktopServices> +#include <QImage> +#include <QPainter> +#include <QPixmap> + +#include "ImageItem.h" + +ImageItem::ImageItem(QSharedPointer<MatrixClient> client, const Event &event, const QString &body, const QUrl &url, QWidget *parent) + : QWidget(parent) + , url_{url} + , text_{body} + , event_{event} + , client_{client} +{ + setMaximumSize(max_width_, max_height_); + setMouseTracking(true); + setCursor(Qt::PointingHandCursor); + setStyleSheet("background-color: blue"); + + 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(), media_params); + + client_.data()->downloadImage(event.eventId(), url_); + + connect(client_.data(), + SIGNAL(imageDownloaded(const QString &, const QPixmap &)), + this, + SLOT(imageDownloaded(const QString &, const QPixmap &))); +} + +void ImageItem::imageDownloaded(const QString &event_id, const QPixmap &img) +{ + if (event_id != event_.eventId()) + return; + + setImage(img); +} + +void ImageItem::openUrl() +{ + if (url_.toString().isEmpty()) + return; + + if (!QDesktopServices::openUrl(url_)) + qWarning() << "Could not open url" << url_.toString(); +} + +void ImageItem::scaleImage() +{ + if (image_.isNull()) + return; + + 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); + + if (min_aspect_ratio > 1) { + width_ = image_.width(); + height_ = image_.height(); + } else { + width_ = image_.width() * min_aspect_ratio; + height_ = image_.height() * min_aspect_ratio; + } + + setMinimumSize(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_); + + return QSize(width_, height_); +} + +void ImageItem::setImage(const QPixmap &image) +{ + 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 + qDebug() << "Opening image overlay. Not implemented yet."; +} + +void ImageItem::resizeEvent(QResizeEvent *event) +{ + Q_UNUSED(event); + + scaleImage(); +} + +void ImageItem::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QFont font("Open Sans"); + font.setPixelSize(12); + + QFontMetrics metrics(font); + int fontHeight = metrics.height(); + + if (image_.isNull()) { + int height = fontHeight + 10; + + setMinimumSize(max_width_, fontHeight + 10); + + QString elidedText = metrics.elidedText(text_, Qt::ElideRight, max_width_ - 10); + + painter.setFont(font); + painter.setPen(QPen(QColor(66, 133, 244))); + painter.drawText(QPoint(0, height / 2 + 2), elidedText); + + return; + } + + painter.fillRect(QRect(0, 0, width_, height_), scaled_image_); + + // 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); + + painter.setFont(font); + painter.setPen(QPen(QColor("white"))); + painter.drawText(QPoint(5, height_ - fontHeight / 2), elidedText); +} diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index 381f5023..6b4a81bb 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc
@@ -309,6 +309,30 @@ void MatrixClient::onGetOwnAvatarResponse(QNetworkReply *reply) emit ownAvatarRetrieved(pixmap); } +void MatrixClient::onImageResponse(QNetworkReply *reply) +{ + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } + + auto img = reply->readAll(); + + if (img.size() == 0) + return; + + QPixmap pixmap; + pixmap.loadFromData(img); + + auto event_id = reply->property("event_id").toString(); + + emit imageDownloaded(event_id, pixmap); +} + void MatrixClient::onResponse(QNetworkReply *reply) { switch (reply->property("endpoint").toInt()) { @@ -327,6 +351,9 @@ void MatrixClient::onResponse(QNetworkReply *reply) case Endpoint::GetOwnProfile: onGetOwnProfileResponse(reply); break; + case Endpoint::Image: + onImageResponse(reply); + break; case Endpoint::InitialSync: onInitialSyncResponse(reply); break; @@ -528,6 +555,15 @@ void MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url reply->setProperty("endpoint", Endpoint::RoomAvatar); } +void MatrixClient::downloadImage(const QString &event_id, const QUrl &url) +{ + QNetworkRequest image_request(url); + + QNetworkReply *reply = get(image_request); + reply->setProperty("event_id", event_id); + reply->setProperty("endpoint", Endpoint::Image); +} + void MatrixClient::fetchOwnAvatar(const QUrl &avatar_url) { QList<QString> url_parts = avatar_url.toString().split("mxc://"); diff --git a/src/TimelineItem.cc b/src/TimelineItem.cc
index 607522a3..4d33db70 100644 --- a/src/TimelineItem.cc +++ b/src/TimelineItem.cc
@@ -18,6 +18,7 @@ #include <QDateTime> #include <QDebug> +#include "ImageItem.h" #include "TimelineItem.h" TimelineItem::TimelineItem(const QString &userid, const QString &color, const QString &body, QWidget *parent) @@ -36,6 +37,42 @@ TimelineItem::TimelineItem(const QString &body, QWidget *parent) setupLayout(); } +TimelineItem::TimelineItem(ImageItem *image, const Event &event, const QString &color, QWidget *parent) + : QWidget(parent) +{ + auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); + generateTimestamp(timestamp); + generateBody(event.sender(), color, ""); + + top_layout_ = new QHBoxLayout(); + top_layout_->setMargin(0); + top_layout_->addWidget(time_label_); + + auto right_layout = new QVBoxLayout(); + right_layout->addWidget(content_label_); + right_layout->addWidget(image); + + top_layout_->addLayout(right_layout); + top_layout_->addStretch(1); + + setLayout(top_layout_); +} + +TimelineItem::TimelineItem(ImageItem *image, const Event &event, QWidget *parent) + : QWidget(parent) +{ + auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); + generateTimestamp(timestamp); + + top_layout_ = new QHBoxLayout(); + top_layout_->setMargin(0); + top_layout_->addWidget(time_label_); + top_layout_->addWidget(image, 1); + top_layout_->addStretch(1); + + setLayout(top_layout_); +} + TimelineItem::TimelineItem(const Event &event, bool with_sender, const QString &color, QWidget *parent) : QWidget(parent) { diff --git a/src/TimelineView.cc b/src/TimelineView.cc
index 00a42557..95c7a351 100644 --- a/src/TimelineView.cc +++ b/src/TimelineView.cc
@@ -21,19 +21,22 @@ #include <QtWidgets/QLabel> #include <QtWidgets/QSpacerItem> +#include "ImageItem.h" #include "TimelineItem.h" #include "TimelineView.h" #include "TimelineViewManager.h" -TimelineView::TimelineView(const QList<Event> &events, QWidget *parent) +TimelineView::TimelineView(const QList<Event> &events, QSharedPointer<MatrixClient> client, QWidget *parent) : QWidget(parent) + , client_{client} { init(); addEvents(events); } -TimelineView::TimelineView(QWidget *parent) +TimelineView::TimelineView(QSharedPointer<MatrixClient> client, QWidget *parent) : QWidget(parent) + , client_{client} { init(); } @@ -74,6 +77,28 @@ int TimelineView::addEvents(const QList<Event> &events) last_sender_ = event.sender(); message_count += 1; + } else if (msg_type == "m.image") { + // TODO: Move this into serialization. + if (!event.content().contains("url")) { + qWarning() << "Missing url from m.image event" << event.content(); + continue; + } + + if (!event.content().contains("body")) { + qWarning() << "Missing body from m.image event" << event.content(); + continue; + } + + QUrl url(event.content().value("url").toString()); + QString body(event.content().value("body").toString()); + + auto with_sender = last_sender_ != event.sender(); + auto color = TimelineViewManager::getUserColor(event.sender()); + + addImageItem(body, url, event, color, with_sender); + + last_sender_ = event.sender(); + message_count += 1; } } } @@ -111,6 +136,23 @@ void TimelineView::init() SLOT(sliderRangeChanged(int, int))); } +void TimelineView::addImageItem(const QString &body, + const QUrl &url, + const Event &event, + const QString &color, + bool with_sender) +{ + auto image = new ImageItem(client_, event, body, url); + + if (with_sender) { + auto item = new TimelineItem(image, event, color, scroll_widget_); + scroll_layout_->addWidget(item); + } else { + auto item = new TimelineItem(image, event, scroll_widget_); + scroll_layout_->addWidget(item); + } +} + void TimelineView::addHistoryItem(const Event &event, const QString &color, bool with_sender) { TimelineItem *item = new TimelineItem(event, with_sender, color, scroll_widget_); diff --git a/src/TimelineViewManager.cc b/src/TimelineViewManager.cc
index 3783b250..ddb142d3 100644 --- a/src/TimelineViewManager.cc +++ b/src/TimelineViewManager.cc
@@ -81,7 +81,7 @@ void TimelineViewManager::initialize(const Rooms &rooms) auto events = it.value().timeline().events(); // Create a history view with the room events. - TimelineView *view = new TimelineView(events); + TimelineView *view = new TimelineView(events, client_); views_.insert(it.key(), view); // Add the view in the widget stack.