From 0d584c5aba04889854838f7e06e70f9ea1e1cd40 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 16 May 2021 01:07:49 +0300 Subject: [PATCH] message preview refactor, several bugs about label size, animations are now playing in previews --- shared/global.cpp | 13 +- shared/global.h | 3 +- ui/models/CMakeLists.txt | 4 +- ui/models/element.h | 3 +- ui/utils/CMakeLists.txt | 8 - ui/widgets/CMakeLists.txt | 1 + ui/widgets/conversation.h | 17 +- ui/widgets/messageline/CMakeLists.txt | 14 + .../messageline}/feedview.cpp | 2 +- ui/{utils => widgets/messageline}/feedview.h | 4 +- ui/{utils => widgets/messageline}/message.cpp | 0 ui/{utils => widgets/messageline}/message.h | 0 .../messageline}/messagedelegate.cpp | 199 ++++-------- .../messageline}/messagedelegate.h | 6 +- .../messageline}/messagefeed.cpp | 5 +- .../messageline}/messagefeed.h | 0 .../messageline}/messageline.cpp | 0 .../messageline}/messageline.h | 0 ui/widgets/messageline/preview.cpp | 304 ++++++++++++++++++ ui/widgets/messageline/preview.h | 79 +++++ 20 files changed, 498 insertions(+), 164 deletions(-) create mode 100644 ui/widgets/messageline/CMakeLists.txt rename ui/{utils => widgets/messageline}/feedview.cpp (99%) rename ui/{utils => widgets/messageline}/feedview.h (97%) rename ui/{utils => widgets/messageline}/message.cpp (100%) rename ui/{utils => widgets/messageline}/message.h (100%) rename ui/{utils => widgets/messageline}/messagedelegate.cpp (73%) rename ui/{utils => widgets/messageline}/messagedelegate.h (94%) rename ui/{models => widgets/messageline}/messagefeed.cpp (99%) rename ui/{models => widgets/messageline}/messagefeed.h (100%) rename ui/{utils => widgets/messageline}/messageline.cpp (100%) rename ui/{utils => widgets/messageline}/messageline.h (100%) create mode 100644 ui/widgets/messageline/preview.cpp create mode 100644 ui/widgets/messageline/preview.h diff --git a/shared/global.cpp b/shared/global.cpp index 25a1c87..0330a00 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -127,12 +127,19 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path) FileInfo::Preview p = FileInfo::Preview::none; QSize size; if (big == "image") { - if (parts.back() == "gif") { - //TODO need to consider GIF as a movie + QMovie mov(path); + if (mov.isValid()) { + p = FileInfo::Preview::animation; + } else { + p = FileInfo::Preview::picture; } - p = FileInfo::Preview::picture; QImage img(path); size = img.size(); +// } else if (big == "video") { +// p = FileInfo::Preview::movie; +// QMovie mov(path); +// size = mov.scaledSize(); +// qDebug() << mov.isValid(); } else { size = defaultIconFileInfoHeight; } diff --git a/shared/global.h b/shared/global.h index b6bbe37..03cf84d 100644 --- a/shared/global.h +++ b/shared/global.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,7 @@ namespace Shared { enum class Preview { none, picture, - movie + animation }; QString name; diff --git a/ui/models/CMakeLists.txt b/ui/models/CMakeLists.txt index 98ef1c3..629db32 100644 --- a/ui/models/CMakeLists.txt +++ b/ui/models/CMakeLists.txt @@ -13,8 +13,6 @@ target_sources(squawk PRIVATE group.h item.cpp item.h - messagefeed.cpp - messagefeed.h participant.cpp participant.h presence.cpp @@ -25,4 +23,4 @@ target_sources(squawk PRIVATE room.h roster.cpp roster.h - ) \ No newline at end of file + ) diff --git a/ui/models/element.h b/ui/models/element.h index af44791..94d67cb 100644 --- a/ui/models/element.h +++ b/ui/models/element.h @@ -20,7 +20,8 @@ #define ELEMENT_H #include "item.h" -#include "messagefeed.h" + +#include "ui/widgets/messageline/messagefeed.h" namespace Models { diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt index 5ad5cb7..b46d30d 100644 --- a/ui/utils/CMakeLists.txt +++ b/ui/utils/CMakeLists.txt @@ -5,18 +5,10 @@ target_sources(squawk PRIVATE comboboxdelegate.h exponentialblur.cpp exponentialblur.h - feedview.cpp - feedview.h flowlayout.cpp flowlayout.h image.cpp image.h - message.cpp - message.h - messagedelegate.cpp - messagedelegate.h - messageline.cpp - messageline.h progress.cpp progress.h resizer.cpp diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index 0cacf6f..c7e47e0 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -21,3 +21,4 @@ target_sources(squawk PRIVATE ) add_subdirectory(vcard) +add_subdirectory(messageline) diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 0b0dcb2..3f048fb 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -31,16 +31,19 @@ #include "shared/message.h" #include "shared/order.h" -#include "ui/models/account.h" -#include "ui/models/roster.h" -#include "ui/utils/flowlayout.h" -#include "ui/utils/badge.h" -#include "ui/utils/feedview.h" -#include "ui/utils/messagedelegate.h" -#include "ui/utils/shadowoverlay.h" #include "shared/icons.h" #include "shared/utils.h" +#include "ui/models/account.h" +#include "ui/models/roster.h" + +#include "ui/utils/flowlayout.h" +#include "ui/utils/badge.h" +#include "ui/utils/shadowoverlay.h" + +#include "ui/widgets/messageline/feedview.h" +#include "ui/widgets/messageline/messagedelegate.h" + namespace Ui { class Conversation; diff --git a/ui/widgets/messageline/CMakeLists.txt b/ui/widgets/messageline/CMakeLists.txt new file mode 100644 index 0000000..7cace9d --- /dev/null +++ b/ui/widgets/messageline/CMakeLists.txt @@ -0,0 +1,14 @@ +target_sources(squawk PRIVATE + messagedelegate.cpp + messagedelegate.h + #messageline.cpp + #messageline.h + preview.cpp + preview.h + messagefeed.cpp + messagefeed.h + feedview.cpp + feedview.h + #message.cpp + #message.h + ) diff --git a/ui/utils/feedview.cpp b/ui/widgets/messageline/feedview.cpp similarity index 99% rename from ui/utils/feedview.cpp rename to ui/widgets/messageline/feedview.cpp index 5f515aa..6d8c180 100644 --- a/ui/utils/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -24,7 +24,7 @@ #include #include "messagedelegate.h" -#include "ui/models/messagefeed.h" +#include "messagefeed.h" constexpr int maxMessageHeight = 10000; constexpr int approximateSingleMessageHeight = 20; diff --git a/ui/utils/feedview.h b/ui/widgets/messageline/feedview.h similarity index 97% rename from ui/utils/feedview.h rename to ui/widgets/messageline/feedview.h index f5509fd..b20276c 100644 --- a/ui/utils/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -24,8 +24,8 @@ #include #include -#include -#include "progress.h" +#include +#include /** * @todo write docs diff --git a/ui/utils/message.cpp b/ui/widgets/messageline/message.cpp similarity index 100% rename from ui/utils/message.cpp rename to ui/widgets/messageline/message.cpp diff --git a/ui/utils/message.h b/ui/widgets/messageline/message.h similarity index 100% rename from ui/utils/message.h rename to ui/widgets/messageline/message.h diff --git a/ui/utils/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp similarity index 73% rename from ui/utils/messagedelegate.cpp rename to ui/widgets/messageline/messagedelegate.cpp index 0381ae3..8405964 100644 --- a/ui/utils/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -22,13 +22,12 @@ #include #include "messagedelegate.h" -#include "ui/models/messagefeed.h" +#include "messagefeed.h" constexpr int avatarHeight = 50; constexpr int margin = 6; constexpr int textMargin = 2; constexpr int statusIconSize = 16; -constexpr int maxAttachmentHeight = 500; MessageDelegate::MessageDelegate(QObject* parent): QStyledItemDelegate(parent), @@ -44,6 +43,7 @@ MessageDelegate::MessageDelegate(QObject* parent): bars(new std::map()), statusIcons(new std::map()), bodies(new std::map()), + previews(new std::map()), idsToKeep(new std::set()), clearingWidgets(false) { @@ -72,10 +72,15 @@ MessageDelegate::~MessageDelegate() delete pair.second; } + for (const std::pair& pair: *previews){ + delete pair.second; + } + delete idsToKeep; delete buttons; delete bars; delete bodies; + delete previews; } void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const @@ -151,24 +156,14 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio break; case Models::errorDownload: { paintButton(getButton(data), painter, data.sentByMe, opt); - painter->setFont(dateFont); - QColor q = painter->pen().color(); - q.setAlpha(180); - painter->setPen(q); - painter->drawText(opt.rect, opt.displayAlignment, data.attach.error, &rect); - opt.rect.adjust(0, rect.height() + textMargin, 0, 0); + paintComment(data, painter, opt); } break; case Models::errorUpload:{ clearHelperWidget(data); paintPreview(data, painter, opt); - painter->setFont(dateFont); - QColor q = painter->pen().color(); - q.setAlpha(180); - painter->setPen(q); - painter->drawText(opt.rect, opt.displayAlignment, data.attach.error, &rect); - opt.rect.adjust(0, rect.height() + textMargin, 0, 0); + paintComment(data, painter, opt); } break; } @@ -181,6 +176,8 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio body->setParent(vp); body->setMaximumWidth(bodySize.width()); body->setMinimumWidth(bodySize.width()); + body->setMinimumHeight(bodySize.height()); + body->setMaximumHeight(bodySize.height()); body->setAlignment(opt.displayAlignment); messageLeft = opt.rect.x(); if (data.sentByMe) { @@ -232,7 +229,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel case Models::none: break; case Models::uploading: - messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; case Models::downloading: messageSize.rheight() += barHeight + textMargin; break; @@ -241,14 +238,14 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel break; case Models::ready: case Models::local: - messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; break; case Models::errorDownload: messageSize.rheight() += buttonHeight + textMargin; messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin; break; case Models::errorUpload: - messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin; break; } @@ -319,6 +316,17 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent option.rect.adjust(0, buttonHeight + textMargin, 0, 0); } +void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const +{ + painter->setFont(dateFont); + QColor q = painter->pen().color(); + q.setAlpha(180); + painter->setPen(q); + QRect rect; + painter->drawText(option.rect, option.displayAlignment, data.attach.error, &rect); + option.rect.adjust(0, rect.height() + textMargin, 0, 0); +} + void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const { QPoint start = option.rect.topLeft(); @@ -332,49 +340,20 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const { - Shared::Global::FileInfo info = Shared::Global::getFileInfo(data.attach.localPath); - QSize size = constrainAttachSize(info.size, option.rect.size()); - - QPoint start; - if (data.sentByMe) { - start = {option.rect.width() - size.width(), option.rect.top()}; - start.rx() += margin; + Preview* preview = 0; + std::map::iterator itr = previews->find(data.id); + + QSize size = option.rect.size(); + if (itr != previews->end()) { + preview = itr->second; + preview->actualize(data.attach.localPath, size, option.rect.topLeft()); } else { - start = option.rect.topLeft(); - } - QRect rect(start, size); - switch (info.preview) { - case Shared::Global::FileInfo::Preview::picture: { - QImage img(data.attach.localPath); - if (img.isNull()) { - emit invalidPath(data.id); - } else { - painter->drawImage(rect, img); - } - } - break; - default: { - QIcon icon = QIcon::fromTheme(info.mime.iconName()); - - painter->save(); - - painter->setFont(bodyFont); - int labelWidth = option.rect.width() - size.width() - margin; - QString elidedName = bodyMetrics.elidedText(info.name, Qt::ElideMiddle, labelWidth); - QSize nameSize = bodyMetrics.boundingRect(QRect(start, QSize(labelWidth, 0)), 0, elidedName).size(); - if (data.sentByMe) { - start.rx() -= nameSize.width() + margin; - } - painter->drawPixmap({start, size}, icon.pixmap(info.size)); - start.rx() += size.width() + margin; - start.ry() += nameSize.height() + (size.height() - nameSize.height()) / 2; - painter->drawText(start, elidedName); - - painter->restore(); - } + QWidget* vp = static_cast(painter->device()); + preview = new Preview(data.attach.localPath, size, option.rect.topLeft(), data.sentByMe, vp); + previews->insert(std::make_pair(data.id, preview)); } - option.rect.adjust(0, size.height() + textMargin, 0, 0); + option.rect.adjust(0, preview->size().height() + textMargin, 0, 0); } QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const @@ -432,6 +411,13 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const std::map::const_iterator itr = statusIcons->find(data.id); QLabel* result = 0; + if (itr != statusIcons->end()) { + result = itr->second; + } else { + result = new QLabel(); + statusIcons->insert(std::make_pair(data.id, result)); + } + QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast(data.state)])); QString tt = Shared::Global::getName(data.state); if (data.state == Shared::Message::State::error) { @@ -439,25 +425,11 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const tt += ": " + data.error; } } - - if (itr != statusIcons->end()) { - result = itr->second; - if (result->toolTip() != tt) { //If i just assign pixmap every time unconditionally - result->setPixmap(q.pixmap(statusIconSize)); //it involves into an infinite cycle of repaint - result->setToolTip(tt); //may be it's better to subclass and store last condition in int? - } - } else { - result = new QLabel(); - statusIcons->insert(std::make_pair(data.id, result)); - result->setPixmap(q.pixmap(statusIconSize)); - result->setToolTip(tt); + if (result->toolTip() != tt) { //If i just assign pixmap every time unconditionally + result->setPixmap(q.pixmap(statusIconSize)); //it invokes an infinite cycle of repaint + result->setToolTip(tt); //may be it's better to subclass and store last condition in int? } - - - result->setToolTip(tt); - //result->setText(std::to_string((int)data.state).c_str()); - return result; } @@ -488,50 +460,28 @@ void MessageDelegate::beginClearWidgets() clearingWidgets = true; } +template +void removeElements(std::map* elements, std::set* idsToKeep) { + std::set toRemove; + for (const std::pair& pair: *elements) { + if (idsToKeep->find(pair.first) == idsToKeep->end()) { + delete pair.second; + toRemove.insert(pair.first); + } + } + for (const QString& key : toRemove) { + elements->erase(key); + } +} + void MessageDelegate::endClearWidgets() { if (clearingWidgets) { - std::set toRemoveButtons; - std::set toRemoveBars; - std::set toRemoveIcons; - std::set toRemoveBodies; - for (const std::pair& pair: *buttons) { - if (idsToKeep->find(pair.first) == idsToKeep->end()) { - delete pair.second; - toRemoveButtons.insert(pair.first); - } - } - for (const std::pair& pair: *bars) { - if (idsToKeep->find(pair.first) == idsToKeep->end()) { - delete pair.second; - toRemoveBars.insert(pair.first); - } - } - for (const std::pair& pair: *statusIcons) { - if (idsToKeep->find(pair.first) == idsToKeep->end()) { - delete pair.second; - toRemoveIcons.insert(pair.first); - } - } - for (const std::pair& pair: *bodies) { - if (idsToKeep->find(pair.first) == idsToKeep->end()) { - delete pair.second; - toRemoveBodies.insert(pair.first); - } - } - - for (const QString& key : toRemoveButtons) { - buttons->erase(key); - } - for (const QString& key : toRemoveBars) { - bars->erase(key); - } - for (const QString& key : toRemoveIcons) { - statusIcons->erase(key); - } - for (const QString& key : toRemoveBodies) { - bodies->erase(key); - } + removeElements(buttons, idsToKeep); + removeElements(bars, idsToKeep); + removeElements(statusIcons, idsToKeep); + removeElements(bodies, idsToKeep); + removeElements(previews, idsToKeep); idsToKeep->clear(); clearingWidgets = false; @@ -559,25 +509,6 @@ void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const } } -QSize MessageDelegate::calculateAttachSize(const QString& path, const QRect& bounds) const -{ - Shared::Global::FileInfo info = Shared::Global::getFileInfo(path); - - return constrainAttachSize(info.size, bounds.size()); -} - -QSize MessageDelegate::constrainAttachSize(QSize src, QSize bounds) const -{ - bounds.setHeight(maxAttachmentHeight); - - if (src.width() > bounds.width() || src.height() > bounds.height()) { - src.scale(bounds, Qt::KeepAspectRatio); - } - - return src; -} - - // void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const // { // diff --git a/ui/utils/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h similarity index 94% rename from ui/utils/messagedelegate.h rename to ui/widgets/messageline/messagedelegate.h index 3af80e1..5c2989d 100644 --- a/ui/utils/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -34,6 +34,8 @@ #include "shared/global.h" #include "shared/utils.h" +#include "preview.h" + namespace Models { struct FeedItem; }; @@ -62,13 +64,12 @@ protected: void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; + void paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; QPushButton* getButton(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const; QLabel* getBody(const Models::FeedItem& data) const; void clearHelperWidget(const Models::FeedItem& data) const; - QSize calculateAttachSize(const QString& path, const QRect& bounds) const; - QSize constrainAttachSize(QSize src, QSize bounds) const; protected slots: void onButtonPushed() const; @@ -93,6 +94,7 @@ private: std::map* bars; std::map* statusIcons; std::map* bodies; + std::map* previews; std::set* idsToKeep; bool clearingWidgets; diff --git a/ui/models/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp similarity index 99% rename from ui/models/messagefeed.cpp rename to ui/widgets/messageline/messagefeed.cpp index c64d7ab..4f22113 100644 --- a/ui/models/messagefeed.cpp +++ b/ui/widgets/messageline/messagefeed.cpp @@ -17,8 +17,9 @@ */ #include "messagefeed.h" -#include "element.h" -#include "room.h" + +#include +#include #include diff --git a/ui/models/messagefeed.h b/ui/widgets/messageline/messagefeed.h similarity index 100% rename from ui/models/messagefeed.h rename to ui/widgets/messageline/messagefeed.h diff --git a/ui/utils/messageline.cpp b/ui/widgets/messageline/messageline.cpp similarity index 100% rename from ui/utils/messageline.cpp rename to ui/widgets/messageline/messageline.cpp diff --git a/ui/utils/messageline.h b/ui/widgets/messageline/messageline.h similarity index 100% rename from ui/utils/messageline.h rename to ui/widgets/messageline/messageline.h diff --git a/ui/widgets/messageline/preview.cpp b/ui/widgets/messageline/preview.cpp new file mode 100644 index 0000000..8c56cbc --- /dev/null +++ b/ui/widgets/messageline/preview.cpp @@ -0,0 +1,304 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 . + */ + +#include "preview.h" + + +constexpr int margin = 6; +constexpr int maxAttachmentHeight = 500; + +bool Preview::fontInitialized = false; +QFont Preview::font; +QFontMetrics Preview::metrics(Preview::font); + +Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, bool pRight, QWidget* pParent): + info(Shared::Global::getFileInfo(pPath)), + path(pPath), + maxSize(pMaxSize), + actualSize(constrainAttachSize(info.size, maxSize)), + cachedLabelSize(0, 0), + position(pos), + widget(0), + label(0), + parent(pParent), + movie(0), + fileReachable(true), + actualPreview(false), + right(pRight) +{ + if (!fontInitialized) { + font.setBold(true); + font.setPixelSize(14); + metrics = QFontMetrics(font); + fontInitialized = true; + } + + initializeElements(); + if (fileReachable) { + positionElements(); + } +} + +Preview::~Preview() +{ + clean(); +} + +void Preview::clean() +{ + if (fileReachable) { + if (info.preview == Shared::Global::FileInfo::Preview::animation) { + delete movie; + } + delete widget; + if (!actualPreview) { + delete label; + } else { + actualPreview = false; + } + } else { + fileReachable = true; + } +} + +void Preview::actualize(const QString& newPath, const QSize& newSize, const QPoint& newPoint) +{ + bool positionChanged = false; + bool sizeChanged = false; + bool maxSizeChanged = false; + + if (maxSize != newSize) { + maxSize = newSize; + maxSizeChanged = true; + QSize ns = constrainAttachSize(info.size, maxSize); + if (actualSize != ns) { + sizeChanged = true; + actualSize = ns; + } + } + if (position != newPoint) { + position = newPoint; + positionChanged = true; + } + + if (!setPath(newPath) && fileReachable) { + if (sizeChanged) { + applyNewSize(); + if (maxSizeChanged && !actualPreview) { + applyNewMaxSize(); + } + } else if (maxSizeChanged) { + applyNewMaxSize(); + } + if (positionChanged || !actualPreview) { + positionElements(); + } + } +} + +void Preview::setSize(const QSize& newSize) +{ + bool sizeChanged = false; + bool maxSizeChanged = false; + + if (maxSize != newSize) { + maxSize = newSize; + maxSizeChanged = true; + QSize ns = constrainAttachSize(info.size, maxSize); + if (actualSize != ns) { + sizeChanged = true; + actualSize = ns; + } + } + + if (fileReachable) { + if (sizeChanged) { + applyNewSize(); + } + if (maxSizeChanged || !actualPreview) { + applyNewMaxSize(); + } + } +} + +void Preview::applyNewSize() +{ + switch (info.preview) { + case Shared::Global::FileInfo::Preview::picture: { + QPixmap img(path); + if (img.isNull()) { + fileReachable = false; + } else { + img = img.scaled(actualSize, Qt::KeepAspectRatio); + widget->resize(actualSize); + widget->setPixmap(img); + } + } + break; + case Shared::Global::FileInfo::Preview::animation:{ + movie->setScaledSize(actualSize); + widget->resize(actualSize); + } + break; + default: { + QIcon icon = QIcon::fromTheme(info.mime.iconName()); + widget->setPixmap(icon.pixmap(actualSize)); + widget->resize(actualSize); + } + } +} + +void Preview::applyNewMaxSize() +{ + switch (info.preview) { + case Shared::Global::FileInfo::Preview::picture: + case Shared::Global::FileInfo::Preview::animation: + break; + default: { + int labelWidth = maxSize.width() - actualSize.width() - margin; + QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, labelWidth); + cachedLabelSize = metrics.size(0, elidedName); + label->setText(elidedName); + label->resize(cachedLabelSize); + } + } +} + + +QSize Preview::size() const +{ + if (actualPreview) { + return actualSize; + } else { + return QSize(actualSize.width() + margin + cachedLabelSize.width(), actualSize.height()); + } +} + +bool Preview::isFileReachable() const +{ + return fileReachable; +} + +void Preview::setPosition(const QPoint& newPoint) +{ + if (position != newPoint) { + position = newPoint; + if (fileReachable) { + positionElements(); + } + } +} + +bool Preview::setPath(const QString& newPath) +{ + if (path != newPath) { + path = newPath; + info = Shared::Global::getFileInfo(path); + actualSize = constrainAttachSize(info.size, maxSize); + clean(); + initializeElements(); + if (fileReachable) { + positionElements(); + } + return true; + } else { + return false; + } +} + +void Preview::initializeElements() +{ + switch (info.preview) { + case Shared::Global::FileInfo::Preview::picture: { + QPixmap img(path); + if (img.isNull()) { + fileReachable = false; + } else { + actualPreview = true; + img = img.scaled(actualSize, Qt::KeepAspectRatio); + widget = new QLabel(parent); + widget->setPixmap(img); + widget->show(); + } + } + break; + case Shared::Global::FileInfo::Preview::animation:{ + movie = new QMovie(path); + if (!movie->isValid()) { + fileReachable = false; + delete movie; + } else { + actualPreview = true; + movie->setScaledSize(actualSize); + widget = new QLabel(parent); + widget->setMovie(movie); + movie->start(); + widget->show(); + } + } + break; + default: { + QIcon icon = QIcon::fromTheme(info.mime.iconName()); + widget = new QLabel(parent); + widget->setPixmap(icon.pixmap(actualSize)); + widget->show(); + + label = new QLabel(parent); + label->setFont(font); + int labelWidth = maxSize.width() - actualSize.width() - margin; + QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, labelWidth); + cachedLabelSize = metrics.size(0, elidedName); + label->setText(elidedName); + label->show(); + } + } +} + +void Preview::positionElements() +{ + int start = position.x(); + if (right) { + start += maxSize.width() - size().width(); + } + widget->move(start, position.y()); + if (!actualPreview) { + int x = start + actualSize.width() + margin; + int y = position.y() + (actualSize.height() - cachedLabelSize.height()) / 2; + label->move(x, y); + } +} + +QSize Preview::calculateAttachSize(const QString& path, const QRect& bounds) +{ + Shared::Global::FileInfo info = Shared::Global::getFileInfo(path); + + return constrainAttachSize(info.size, bounds.size()); +} + +QSize Preview::constrainAttachSize(QSize src, QSize bounds) +{ + if (bounds.height() > maxAttachmentHeight) { + bounds.setHeight(maxAttachmentHeight); + } + + if (src.width() > bounds.width() || src.height() > bounds.height()) { + src.scale(bounds, Qt::KeepAspectRatio); + } + + return src; +} diff --git a/ui/widgets/messageline/preview.h b/ui/widgets/messageline/preview.h new file mode 100644 index 0000000..3d560d3 --- /dev/null +++ b/ui/widgets/messageline/preview.h @@ -0,0 +1,79 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 . + */ + +#ifndef PREVIEW_H +#define PREVIEW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * @todo write docs + */ +class Preview { +public: + Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, bool pRight, QWidget* parent); + ~Preview(); + + void actualize(const QString& newPath, const QSize& newSize, const QPoint& newPoint); + void setPosition(const QPoint& newPoint); + void setSize(const QSize& newSize); + bool setPath(const QString& newPath); + bool isFileReachable() const; + QSize size() const; + + static QSize constrainAttachSize(QSize src, QSize bounds); + static QSize calculateAttachSize(const QString& path, const QRect& bounds); + static bool fontInitialized; + static QFont font; + static QFontMetrics metrics; + +private: + void initializeElements(); + void positionElements(); + void clean(); + void applyNewSize(); + void applyNewMaxSize(); + +private: + Shared::Global::FileInfo info; + QString path; + QSize maxSize; + QSize actualSize; + QSize cachedLabelSize; + QPoint position; + QLabel* widget; + QLabel* label; + QWidget* parent; + QMovie* movie; + bool fileReachable; + bool actualPreview; + bool right; +}; + +#endif // PREVIEW_H