1
0
forked from blue/squawk

message preview refactor, several bugs about label size, animations are now playing in previews

This commit is contained in:
Blue 2021-05-16 01:07:49 +03:00
parent 4307262f6e
commit 0d584c5aba
Signed by untrusted user: blue
GPG Key ID: 9B203B252A63EE38
20 changed files with 498 additions and 164 deletions

View File

@ -127,12 +127,19 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
FileInfo::Preview p = FileInfo::Preview::none; FileInfo::Preview p = FileInfo::Preview::none;
QSize size; QSize size;
if (big == "image") { if (big == "image") {
if (parts.back() == "gif") { QMovie mov(path);
//TODO need to consider GIF as a movie if (mov.isValid()) {
p = FileInfo::Preview::animation;
} else {
p = FileInfo::Preview::picture;
} }
p = FileInfo::Preview::picture;
QImage img(path); QImage img(path);
size = img.size(); size = img.size();
// } else if (big == "video") {
// p = FileInfo::Preview::movie;
// QMovie mov(path);
// size = mov.scaledSize();
// qDebug() << mov.isValid();
} else { } else {
size = defaultIconFileInfoHeight; size = defaultIconFileInfoHeight;
} }

View File

@ -33,6 +33,7 @@
#include <QMimeDatabase> #include <QMimeDatabase>
#include <QFileInfo> #include <QFileInfo>
#include <QImage> #include <QImage>
#include <QMovie>
#include <QSize> #include <QSize>
#include <QUrl> #include <QUrl>
#include <QLibrary> #include <QLibrary>
@ -51,7 +52,7 @@ namespace Shared {
enum class Preview { enum class Preview {
none, none,
picture, picture,
movie animation
}; };
QString name; QString name;

View File

@ -13,8 +13,6 @@ target_sources(squawk PRIVATE
group.h group.h
item.cpp item.cpp
item.h item.h
messagefeed.cpp
messagefeed.h
participant.cpp participant.cpp
participant.h participant.h
presence.cpp presence.cpp
@ -25,4 +23,4 @@ target_sources(squawk PRIVATE
room.h room.h
roster.cpp roster.cpp
roster.h roster.h
) )

View File

@ -20,7 +20,8 @@
#define ELEMENT_H #define ELEMENT_H
#include "item.h" #include "item.h"
#include "messagefeed.h"
#include "ui/widgets/messageline/messagefeed.h"
namespace Models { namespace Models {

View File

@ -5,18 +5,10 @@ target_sources(squawk PRIVATE
comboboxdelegate.h comboboxdelegate.h
exponentialblur.cpp exponentialblur.cpp
exponentialblur.h exponentialblur.h
feedview.cpp
feedview.h
flowlayout.cpp flowlayout.cpp
flowlayout.h flowlayout.h
image.cpp image.cpp
image.h image.h
message.cpp
message.h
messagedelegate.cpp
messagedelegate.h
messageline.cpp
messageline.h
progress.cpp progress.cpp
progress.h progress.h
resizer.cpp resizer.cpp

View File

@ -21,3 +21,4 @@ target_sources(squawk PRIVATE
) )
add_subdirectory(vcard) add_subdirectory(vcard)
add_subdirectory(messageline)

View File

@ -31,16 +31,19 @@
#include "shared/message.h" #include "shared/message.h"
#include "shared/order.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/icons.h"
#include "shared/utils.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 namespace Ui
{ {
class Conversation; class Conversation;

View File

@ -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
)

View File

@ -24,7 +24,7 @@
#include <QDebug> #include <QDebug>
#include "messagedelegate.h" #include "messagedelegate.h"
#include "ui/models/messagefeed.h" #include "messagefeed.h"
constexpr int maxMessageHeight = 10000; constexpr int maxMessageHeight = 10000;
constexpr int approximateSingleMessageHeight = 20; constexpr int approximateSingleMessageHeight = 20;

View File

@ -24,8 +24,8 @@
#include <deque> #include <deque>
#include <set> #include <set>
#include <ui/models/messagefeed.h> #include <ui/widgets/messageline/messagefeed.h>
#include "progress.h" #include <ui/utils/progress.h>
/** /**
* @todo write docs * @todo write docs

View File

@ -22,13 +22,12 @@
#include <QMouseEvent> #include <QMouseEvent>
#include "messagedelegate.h" #include "messagedelegate.h"
#include "ui/models/messagefeed.h" #include "messagefeed.h"
constexpr int avatarHeight = 50; constexpr int avatarHeight = 50;
constexpr int margin = 6; constexpr int margin = 6;
constexpr int textMargin = 2; constexpr int textMargin = 2;
constexpr int statusIconSize = 16; constexpr int statusIconSize = 16;
constexpr int maxAttachmentHeight = 500;
MessageDelegate::MessageDelegate(QObject* parent): MessageDelegate::MessageDelegate(QObject* parent):
QStyledItemDelegate(parent), QStyledItemDelegate(parent),
@ -44,6 +43,7 @@ MessageDelegate::MessageDelegate(QObject* parent):
bars(new std::map<QString, QProgressBar*>()), bars(new std::map<QString, QProgressBar*>()),
statusIcons(new std::map<QString, QLabel*>()), statusIcons(new std::map<QString, QLabel*>()),
bodies(new std::map<QString, QLabel*>()), bodies(new std::map<QString, QLabel*>()),
previews(new std::map<QString, Preview*>()),
idsToKeep(new std::set<QString>()), idsToKeep(new std::set<QString>()),
clearingWidgets(false) clearingWidgets(false)
{ {
@ -72,10 +72,15 @@ MessageDelegate::~MessageDelegate()
delete pair.second; delete pair.second;
} }
for (const std::pair<const QString, Preview*>& pair: *previews){
delete pair.second;
}
delete idsToKeep; delete idsToKeep;
delete buttons; delete buttons;
delete bars; delete bars;
delete bodies; delete bodies;
delete previews;
} }
void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const 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; break;
case Models::errorDownload: { case Models::errorDownload: {
paintButton(getButton(data), painter, data.sentByMe, opt); paintButton(getButton(data), painter, data.sentByMe, opt);
painter->setFont(dateFont); paintComment(data, painter, opt);
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);
} }
break; break;
case Models::errorUpload:{ case Models::errorUpload:{
clearHelperWidget(data); clearHelperWidget(data);
paintPreview(data, painter, opt); paintPreview(data, painter, opt);
painter->setFont(dateFont); paintComment(data, painter, opt);
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);
} }
break; break;
} }
@ -181,6 +176,8 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
body->setParent(vp); body->setParent(vp);
body->setMaximumWidth(bodySize.width()); body->setMaximumWidth(bodySize.width());
body->setMinimumWidth(bodySize.width()); body->setMinimumWidth(bodySize.width());
body->setMinimumHeight(bodySize.height());
body->setMaximumHeight(bodySize.height());
body->setAlignment(opt.displayAlignment); body->setAlignment(opt.displayAlignment);
messageLeft = opt.rect.x(); messageLeft = opt.rect.x();
if (data.sentByMe) { if (data.sentByMe) {
@ -232,7 +229,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
case Models::none: case Models::none:
break; break;
case Models::uploading: case Models::uploading:
messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
case Models::downloading: case Models::downloading:
messageSize.rheight() += barHeight + textMargin; messageSize.rheight() += barHeight + textMargin;
break; break;
@ -241,14 +238,14 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
break; break;
case Models::ready: case Models::ready:
case Models::local: case Models::local:
messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
break; break;
case Models::errorDownload: case Models::errorDownload:
messageSize.rheight() += buttonHeight + textMargin; messageSize.rheight() += buttonHeight + textMargin;
messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin; messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin;
break; break;
case Models::errorUpload: 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; messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin;
break; break;
} }
@ -319,6 +316,17 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent
option.rect.adjust(0, buttonHeight + textMargin, 0, 0); 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 void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
{ {
QPoint start = option.rect.topLeft(); 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 void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
{ {
Shared::Global::FileInfo info = Shared::Global::getFileInfo(data.attach.localPath); Preview* preview = 0;
QSize size = constrainAttachSize(info.size, option.rect.size()); std::map<QString, Preview*>::iterator itr = previews->find(data.id);
QPoint start; QSize size = option.rect.size();
if (data.sentByMe) { if (itr != previews->end()) {
start = {option.rect.width() - size.width(), option.rect.top()}; preview = itr->second;
start.rx() += margin; preview->actualize(data.attach.localPath, size, option.rect.topLeft());
} else { } else {
start = option.rect.topLeft(); QWidget* vp = static_cast<QWidget*>(painter->device());
} preview = new Preview(data.attach.localPath, size, option.rect.topLeft(), data.sentByMe, vp);
QRect rect(start, size); previews->insert(std::make_pair(data.id, preview));
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();
}
} }
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 QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
@ -432,6 +411,13 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
std::map<QString, QLabel*>::const_iterator itr = statusIcons->find(data.id); std::map<QString, QLabel*>::const_iterator itr = statusIcons->find(data.id);
QLabel* result = 0; 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<uint8_t>(data.state)])); QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
QString tt = Shared::Global::getName(data.state); QString tt = Shared::Global::getName(data.state);
if (data.state == Shared::Message::State::error) { if (data.state == Shared::Message::State::error) {
@ -439,25 +425,11 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
tt += ": " + data.error; tt += ": " + data.error;
} }
} }
if (result->toolTip() != tt) { //If i just assign pixmap every time unconditionally
if (itr != statusIcons->end()) { result->setPixmap(q.pixmap(statusIconSize)); //it invokes an infinite cycle of repaint
result = itr->second; result->setToolTip(tt); //may be it's better to subclass and store last condition in int?
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);
} }
result->setToolTip(tt);
//result->setText(std::to_string((int)data.state).c_str());
return result; return result;
} }
@ -488,50 +460,28 @@ void MessageDelegate::beginClearWidgets()
clearingWidgets = true; clearingWidgets = true;
} }
template <typename T>
void removeElements(std::map<QString, T*>* elements, std::set<QString>* idsToKeep) {
std::set<QString> toRemove;
for (const std::pair<const QString, T*>& 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() void MessageDelegate::endClearWidgets()
{ {
if (clearingWidgets) { if (clearingWidgets) {
std::set<QString> toRemoveButtons; removeElements(buttons, idsToKeep);
std::set<QString> toRemoveBars; removeElements(bars, idsToKeep);
std::set<QString> toRemoveIcons; removeElements(statusIcons, idsToKeep);
std::set<QString> toRemoveBodies; removeElements(bodies, idsToKeep);
for (const std::pair<const QString, FeedButton*>& pair: *buttons) { removeElements(previews, idsToKeep);
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
delete pair.second;
toRemoveButtons.insert(pair.first);
}
}
for (const std::pair<const QString, QProgressBar*>& pair: *bars) {
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
delete pair.second;
toRemoveBars.insert(pair.first);
}
}
for (const std::pair<const QString, QLabel*>& pair: *statusIcons) {
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
delete pair.second;
toRemoveIcons.insert(pair.first);
}
}
for (const std::pair<const QString, QLabel*>& 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);
}
idsToKeep->clear(); idsToKeep->clear();
clearingWidgets = false; 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 // void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
// { // {
// //

View File

@ -34,6 +34,8 @@
#include "shared/global.h" #include "shared/global.h"
#include "shared/utils.h" #include "shared/utils.h"
#include "preview.h"
namespace Models { namespace Models {
struct FeedItem; struct FeedItem;
}; };
@ -62,13 +64,12 @@ protected:
void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
void paintBar(QProgressBar* bar, 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 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; QPushButton* getButton(const Models::FeedItem& data) const;
QProgressBar* getBar(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const;
QLabel* getStatusIcon(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const;
QLabel* getBody(const Models::FeedItem& data) const; QLabel* getBody(const Models::FeedItem& data) const;
void clearHelperWidget(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: protected slots:
void onButtonPushed() const; void onButtonPushed() const;
@ -93,6 +94,7 @@ private:
std::map<QString, QProgressBar*>* bars; std::map<QString, QProgressBar*>* bars;
std::map<QString, QLabel*>* statusIcons; std::map<QString, QLabel*>* statusIcons;
std::map<QString, QLabel*>* bodies; std::map<QString, QLabel*>* bodies;
std::map<QString, Preview*>* previews;
std::set<QString>* idsToKeep; std::set<QString>* idsToKeep;
bool clearingWidgets; bool clearingWidgets;

View File

@ -17,8 +17,9 @@
*/ */
#include "messagefeed.h" #include "messagefeed.h"
#include "element.h"
#include "room.h" #include <ui/models/element.h>
#include <ui/models/room.h>
#include <QDebug> #include <QDebug>

View File

@ -0,0 +1,304 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* 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 "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;
}

View File

@ -0,0 +1,79 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* 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 PREVIEW_H
#define PREVIEW_H
#include <QWidget>
#include <QString>
#include <QPoint>
#include <QSize>
#include <QLabel>
#include <QIcon>
#include <QPixmap>
#include <QMovie>
#include <QFont>
#include <QFontMetrics>
#include <shared/global.h>
/**
* @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