Skip to content
Snippets Groups Projects
Commit c9d03b79 authored by Konstantinos Sideris's avatar Konstantinos Sideris
Browse files

Add initial support for inline images

parent 4b4035ee
No related branches found
No related tags found
No related merge requests found
......@@ -76,6 +76,7 @@ set(SRC_FILES
src/EmojiPanel.cc
src/EmojiPickButton.cc
src/EmojiProvider.cc
src/ImageItem.cc
src/TimelineItem.cc
src/TimelineView.cc
src/TimelineViewManager.cc
......@@ -127,6 +128,7 @@ qt5_wrap_cpp(MOC_HEADERS
include/EmojiItemDelegate.h
include/EmojiPanel.h
include/EmojiPickButton.h
include/ImageItem.h
include/TimelineItem.h
include/TimelineView.h
include/TimelineViewManager.h
......
/*
* 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/>.
*/
#ifndef TIMELINE_IMAGE_ITEM_H
#define TIMELINE_IMAGE_ITEM_H
#include <QEvent>
#include <QMouseEvent>
#include <QSharedPointer>
#include <QWidget>
#include "MatrixClient.h"
class ImageItem : public QWidget
{
Q_OBJECT
public:
ImageItem(QSharedPointer<MatrixClient> client,
const Event &event,
const QString &body,
const QUrl &url,
QWidget *parent = nullptr);
void setImage(const QPixmap &image);
QSize sizeHint() const override;
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private slots:
void imageDownloaded(const QString &event_id, const QPixmap &img);
private:
void scaleImage();
void openUrl();
int max_width_ = 500;
int max_height_ = 300;
int width_;
int height_;
QPixmap scaled_image_;
QPixmap image_;
QUrl url_;
QString text_;
int bottom_height_ = 30;
Event event_;
QSharedPointer<MatrixClient> client_;
};
#endif // TIMELINE_IMAGE_ITEM_H
......@@ -42,6 +42,7 @@ public:
void versions() noexcept;
void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
void fetchOwnAvatar(const QUrl &avatar_url);
void downloadImage(const QString &event_id, const QUrl &url);
inline QString getHomeServer();
inline int transactionId();
......@@ -68,6 +69,7 @@ signals:
void roomAvatarRetrieved(const QString &roomid, const QPixmap &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);
......@@ -84,6 +86,7 @@ private:
GetOwnProfile,
GetOwnAvatar,
GetProfile,
Image,
InitialSync,
Login,
Logout,
......@@ -105,6 +108,7 @@ private:
void onInitialSyncResponse(QNetworkReply *reply);
void onSyncResponse(QNetworkReply *reply);
void onRoomAvatarResponse(QNetworkReply *reply);
void onImageResponse(QNetworkReply *reply);
// Client API prefix.
QString api_url_;
......
......@@ -23,6 +23,7 @@
#include <QWidget>
#include "Sync.h"
#include "ImageItem.h"
class TimelineItem : public QWidget
{
......@@ -35,6 +36,10 @@ public:
TimelineItem(const QString &userid, const QString &color, const QString &body, QWidget *parent = 0);
TimelineItem(const QString &body, QWidget *parent = 0);
// For inline images.
TimelineItem(ImageItem *image, const Event &event, const QString &color, QWidget *parent);
TimelineItem(ImageItem *image, const Event &event, QWidget *parent);
~TimelineItem();
private:
......
......@@ -49,11 +49,13 @@ class TimelineView : public QWidget
Q_OBJECT
public:
explicit TimelineView(QWidget *parent = 0);
explicit TimelineView(const QList<Event> &events, QWidget *parent = 0);
TimelineView(QSharedPointer<MatrixClient> client, QWidget *parent = 0);
TimelineView(const QList<Event> &events, QSharedPointer<MatrixClient> client, QWidget *parent = 0);
~TimelineView();
// FIXME: Reduce the parameters
void addHistoryItem(const Event &event, const QString &color, bool with_sender);
void addImageItem(const QString &body, const QUrl &url, const Event &event, const QString &color, bool with_sender);
int addEvents(const QList<Event> &events);
void addUserTextMessage(const QString &msg, int txn_id);
void updatePendingMessage(int txn_id, QString event_id);
......@@ -76,6 +78,7 @@ private:
QString last_sender_;
QList<PendingMessage> pending_msgs_;
QSharedPointer<MatrixClient> client_;
};
#endif // HISTORY_VIEW_H
/*
* 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);
}
......@@ -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://");
......
......@@ -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)
{
......
......@@ -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();
}
......@@ -73,6 +76,28 @@ int TimelineView::addEvents(const QList<Event> &events)
addHistoryItem(event, color, with_sender);
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_);
......
......@@ -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.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment