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.
|