From f34289399e904982be540623ace0fd2f0baae16d Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 3 May 2021 14:23:41 +0300 Subject: [PATCH] text inside of message is selectable again, links are clickable, some refactor, some bugfixes --- core/rosteritem.cpp | 3 +- ui/CMakeLists.txt | 15 +-- ui/utils/CMakeLists.txt | 32 +++++ ui/utils/dropshadoweffect.cpp | 145 ----------------------- ui/utils/dropshadoweffect.h | 95 --------------- ui/utils/{eb.cpp => exponentialblur.cpp} | 2 +- ui/utils/{eb.h => exponentialblur.h} | 6 +- ui/utils/feedview.cpp | 2 +- ui/utils/messagedelegate.cpp | 97 ++++++++++----- ui/utils/messagedelegate.h | 3 + ui/utils/shadowoverlay.h | 2 +- ui/widgets/CMakeLists.txt | 2 +- ui/widgets/conversation.cpp | 1 - ui/widgets/conversation.h | 2 +- 14 files changed, 115 insertions(+), 292 deletions(-) create mode 100644 ui/utils/CMakeLists.txt delete mode 100644 ui/utils/dropshadoweffect.cpp delete mode 100644 ui/utils/dropshadoweffect.h rename ui/utils/{eb.cpp => exponentialblur.cpp} (99%) rename ui/utils/{eb.h => exponentialblur.h} (92%) diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp index 5014ddd..9b121fb 100644 --- a/core/rosteritem.cpp +++ b/core/rosteritem.cpp @@ -377,6 +377,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs } if (added == 0 && wasEmpty) { archiveState = empty; + nextRequest(); break; } if (requestedCount != -1) { @@ -397,7 +398,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs } catch (const Archive::Empty& e) { } - if (!found || requestedCount > responseCache.size()) { + if (!found || requestedCount > int(responseCache.size())) { if (archiveState == complete) { nextRequest(); } else { diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 4b53439..00570c9 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -14,6 +14,7 @@ if(Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) endif() +add_subdirectory(utils) add_subdirectory(widgets) set(squawkUI_SRC @@ -31,19 +32,6 @@ set(squawkUI_SRC models/reference.cpp models/messagefeed.cpp models/element.cpp - utils/messageline.cpp - utils/message.cpp - utils/resizer.cpp - utils/image.cpp - utils/flowlayout.cpp - utils/badge.cpp - utils/progress.cpp - utils/comboboxdelegate.cpp - utils/dropshadoweffect.cpp - utils/feedview.cpp - utils/messagedelegate.cpp - utils/eb.cpp - utils/shadowoverlay.cpp ) # Tell CMake to create the helloworld executable @@ -51,5 +39,6 @@ add_library(squawkUI ${squawkUI_SRC}) # Use the Widgets module from Qt 5. target_link_libraries(squawkUI squawkWidgets) +target_link_libraries(squawkUI squawkUIUtils) target_link_libraries(squawkUI Qt5::Widgets) target_link_libraries(squawkUI Qt5::DBus) diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt new file mode 100644 index 0000000..e656bde --- /dev/null +++ b/ui/utils/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.3) +project(squawkUIUtils) + +# Instruct CMake to run moc automatically when needed. +set(CMAKE_AUTOMOC ON) +# Instruct CMake to create code from Qt designer ui files +set(CMAKE_AUTOUIC ON) + +# Find the QtWidgets library +find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets Core) + +set(squawkUIUtils_SRC +# messageline.cpp +# message.cpp + resizer.cpp +# image.cpp + flowlayout.cpp + badge.cpp + progress.cpp + comboboxdelegate.cpp + feedview.cpp + messagedelegate.cpp + exponentialblur.cpp + shadowoverlay.cpp +) + +# Tell CMake to create the helloworld executable +add_library(squawkUIUtils ${squawkUIUtils_SRC}) + +# Use the Widgets module from Qt 5. +target_link_libraries(squawkUIUtils squawkWidgets) +target_link_libraries(squawkUIUtils Qt5::Widgets) diff --git a/ui/utils/dropshadoweffect.cpp b/ui/utils/dropshadoweffect.cpp deleted file mode 100644 index 1090fcd..0000000 --- a/ui/utils/dropshadoweffect.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 "dropshadoweffect.h" - -PixmapFilter::PixmapFilter(QObject* parent):QObject(parent) {} -PixmapFilter::~PixmapFilter(){} -QRectF PixmapFilter::boundingRectFor(const QRectF &rect) const {return rect;} - -PixmapDropShadowFilter::PixmapDropShadowFilter(QObject *parent): - PixmapFilter(parent), - mColor(63, 63, 63, 180), - mRadius(1), - mThickness(2), - top(true), - right(true), - bottom(true), - left(true){} - -PixmapDropShadowFilter::~PixmapDropShadowFilter() {} -qreal PixmapDropShadowFilter::blurRadius() const {return mRadius;} -void PixmapDropShadowFilter::setBlurRadius(qreal radius) {mRadius = radius;} -QColor PixmapDropShadowFilter::color() const {return mColor;} -void PixmapDropShadowFilter::setColor(const QColor &color) {mColor = color;} -qreal PixmapDropShadowFilter::thickness() const {return mThickness;} -void PixmapDropShadowFilter::setThickness(qreal thickness) {mThickness = thickness;} -void PixmapDropShadowFilter::setFrame(bool ptop, bool pright, bool pbottom, bool pleft) -{ - top = ptop; - right = pright; - bottom = pbottom; - left = pleft; -} - -void DropShadowEffect::setThickness(qreal thickness) -{ - if (filter.thickness() == thickness) - return; - - filter.setThickness(thickness); - update(); -} - - -void PixmapDropShadowFilter::draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src) const -{ - if (px.isNull()) - return; - - QImage tmp({px.width(), px.height() + int(mThickness)}, QImage::Format_ARGB32_Premultiplied); - tmp.setDevicePixelRatio(px.devicePixelRatioF()); - tmp.fill(0); - QPainter tmpPainter(&tmp); - tmpPainter.setCompositionMode(QPainter::CompositionMode_Source); - if (top) { - QRectF shadow(0, 0, px.width(), mThickness); - tmpPainter.fillRect(shadow, mColor); - } - if (right) { - QRectF shadow(px.width() - mThickness, 0, mThickness, px.height()); - tmpPainter.fillRect(shadow, mColor); - } - if (bottom) { - QRectF shadow(0, px.height() - mThickness, px.width(), mThickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space - tmpPainter.fillRect(shadow, mColor); - } - if (left) { - QRectF shadow(0, 0, mThickness, px.height()); - tmpPainter.fillRect(shadow, mColor); - } - - Utils::exponentialblur(tmp, mRadius, false, 0); - tmpPainter.end(); - - // Draw the actual pixmap... - p->drawPixmap(pos, px, src); - - // draw the blurred drop shadow... - p->drawImage(pos, tmp); -} - -qreal DropShadowEffect::blurRadius() const {return filter.blurRadius();} -void DropShadowEffect::setBlurRadius(qreal blurRadius) -{ - if (qFuzzyCompare(filter.blurRadius(), blurRadius)) - return; - - filter.setBlurRadius(blurRadius); - updateBoundingRect(); - emit blurRadiusChanged(blurRadius); -} - -void DropShadowEffect::setFrame(bool top, bool right, bool bottom, bool left) -{ - filter.setFrame(top, right, bottom, left); - update(); -} - - -QColor DropShadowEffect::color() const {return filter.color();} -void DropShadowEffect::setColor(const QColor &color) -{ - if (filter.color() == color) - return; - - filter.setColor(color); - update(); - emit colorChanged(color); -} - -void DropShadowEffect::draw(QPainter* painter) -{ - if (filter.blurRadius() <= 0 && filter.thickness() == 0) { - drawSource(painter); - return; - } - - PixmapPadMode mode = PadToEffectiveBoundingRect; - - // Draw pixmap in device coordinates to avoid pixmap scaling. - QPoint offset; - const QPixmap pixmap = sourcePixmap(Qt::DeviceCoordinates, &offset, mode); - if (pixmap.isNull()) - return; - - QTransform restoreTransform = painter->worldTransform(); - painter->setWorldTransform(QTransform()); - filter.draw(painter, offset, pixmap); - painter->setWorldTransform(restoreTransform); -} diff --git a/ui/utils/dropshadoweffect.h b/ui/utils/dropshadoweffect.h deleted file mode 100644 index f374ce3..0000000 --- a/ui/utils/dropshadoweffect.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 DROPSHADOWEFFECT_H -#define DROPSHADOWEFFECT_H - -#include -#include -#include -#include - -#include "eb.h" - -class PixmapFilter : public QObject -{ - Q_OBJECT -public: - PixmapFilter(QObject *parent = nullptr); - virtual ~PixmapFilter() = 0; - - virtual QRectF boundingRectFor(const QRectF &rect) const; - virtual void draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &srcRect = QRectF()) const = 0; -}; - -class PixmapDropShadowFilter : public PixmapFilter -{ - Q_OBJECT - -public: - PixmapDropShadowFilter(QObject *parent = nullptr); - ~PixmapDropShadowFilter(); - - void draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src = QRectF()) const override; - - qreal blurRadius() const; - void setBlurRadius(qreal radius); - - QColor color() const; - void setColor(const QColor &color); - - qreal thickness() const; - void setThickness(qreal thickness); - void setFrame(bool top, bool right, bool bottom, bool left); - -protected: - QColor mColor; - qreal mRadius; - qreal mThickness; - bool top; - bool right; - bool bottom; - bool left; -}; - -class DropShadowEffect : public QGraphicsEffect -{ - Q_OBJECT -public: - qreal blurRadius() const; - QColor color() const; - void setFrame(bool top, bool right, bool bottom, bool left); - void setThickness(qreal thickness); - -signals: - void blurRadiusChanged(qreal blurRadius); - void colorChanged(const QColor &color); - -public slots: - void setBlurRadius(qreal blurRadius); - void setColor(const QColor &color); - -protected: - void draw(QPainter * painter) override; - -protected: - PixmapDropShadowFilter filter; - -}; - -#endif // DROPSHADOWEFFECT_H diff --git a/ui/utils/eb.cpp b/ui/utils/exponentialblur.cpp similarity index 99% rename from ui/utils/eb.cpp rename to ui/utils/exponentialblur.cpp index f44e53b..cb222dc 100644 --- a/ui/utils/eb.cpp +++ b/ui/utils/exponentialblur.cpp @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -#include "eb.h" +#include "exponentialblur.h" static const int tileSize = 32; template diff --git a/ui/utils/eb.h b/ui/utils/exponentialblur.h similarity index 92% rename from ui/utils/eb.h rename to ui/utils/exponentialblur.h index 665a9ee..0a5df8a 100644 --- a/ui/utils/eb.h +++ b/ui/utils/exponentialblur.h @@ -16,8 +16,8 @@ * along with this program. If not, see . */ -#ifndef EB_H -#define EB_H +#ifndef EXPONENTIALBLUR_H +#define EXPONENTIALBLUR_H #include #include @@ -31,4 +31,4 @@ namespace Utils { void exponentialblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0); }; -#endif // EB_H +#endif // EXPONENTIALBLUR_H diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp index 58bcc45..226b9a7 100644 --- a/ui/utils/feedview.cpp +++ b/ui/utils/feedview.cpp @@ -25,7 +25,6 @@ #include "messagedelegate.h" #include "ui/models/messagefeed.h" -#include "eb.h" constexpr int maxMessageHeight = 10000; constexpr int approximateSingleMessageHeight = 20; @@ -339,6 +338,7 @@ void FeedView::positionProgress() progressPosition -= hint.offset + hint.height; } progressPosition += vo; + progressPosition = qMin(progressPosition, 0); progress.move((width() - progressSize) / 2, progressPosition); } diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp index c98710c..0ea64d8 100644 --- a/ui/utils/messagedelegate.cpp +++ b/ui/utils/messagedelegate.cpp @@ -31,20 +31,21 @@ constexpr int statusIconSize = 16; constexpr int maxAttachmentHeight = 500; MessageDelegate::MessageDelegate(QObject* parent): -QStyledItemDelegate(parent), -bodyFont(), -nickFont(), -dateFont(), -bodyMetrics(bodyFont), -nickMetrics(nickFont), -dateMetrics(dateFont), -buttonHeight(0), -barHeight(0), -buttons(new std::map()), -bars(new std::map()), -statusIcons(new std::map()), -idsToKeep(new std::set()), -clearingWidgets(false) + QStyledItemDelegate(parent), + bodyFont(), + nickFont(), + dateFont(), + bodyMetrics(bodyFont), + nickMetrics(nickFont), + dateMetrics(dateFont), + buttonHeight(0), + barHeight(0), + buttons(new std::map()), + bars(new std::map()), + statusIcons(new std::map()), + bodies(new std::map()), + idsToKeep(new std::set()), + clearingWidgets(false) { QPushButton btn; buttonHeight = btn.sizeHint().height(); @@ -67,9 +68,14 @@ MessageDelegate::~MessageDelegate() delete pair.second; } + for (const std::pair& pair: *bodies){ + delete pair.second; + } + delete idsToKeep; delete buttons; delete bars; + delete bodies; } void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const @@ -105,8 +111,10 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio opt.rect = messageRect; QSize messageSize(0, 0); + QSize bodySize(0, 0); if (data.text.size() > 0) { messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size(); + bodySize = messageSize; } messageSize.rheight() += nickMetrics.lineSpacing(); messageSize.rheight() += dateMetrics.height(); @@ -146,12 +154,21 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } painter->restore(); - int messageLeft = 10000; //TODO + int messageLeft = INT16_MAX; + QWidget* vp = static_cast(painter->device()); if (data.text.size() > 0) { - painter->setFont(bodyFont); - painter->drawText(opt.rect, opt.displayAlignment | Qt::TextWordWrap, data.text, &rect); - opt.rect.adjust(0, rect.height() + textMargin, 0, 0); - messageLeft = rect.x(); + QLabel* body = getBody(data); + body->setParent(vp); + body->setMaximumWidth(bodySize.width()); + body->setMinimumWidth(bodySize.width()); + body->setAlignment(opt.displayAlignment); + messageLeft = opt.rect.x(); + if (data.sentByMe) { + messageLeft = opt.rect.topRight().x() - bodySize.width(); + } + body->move(messageLeft, opt.rect.y()); + body->show(); + opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0); } painter->setFont(dateFont); QColor q = painter->pen().color(); @@ -164,7 +181,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } QLabel* statusIcon = getStatusIcon(data); - QWidget* vp = static_cast(painter->device()); statusIcon->setParent(vp); statusIcon->move(messageLeft, opt.rect.y()); statusIcon->show(); @@ -280,15 +296,7 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const { QPoint start = option.rect.topLeft(); - - //QWidget* vp = static_cast(painter->device()); - -// if (bar->parent() != vp) { -// bar->setParent(vp); -// } -// bar->move(start); - bar->resize(option.rect.width(), barHeight); - // bar->show(); + bar->resize(option.rect.width(), barHeight); painter->translate(start); bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren); @@ -410,6 +418,27 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const return result; } +QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const +{ + std::map::const_iterator itr = bodies->find(data.id); + QLabel* result = 0; + + if (itr != bodies->end()) { + result = itr->second; + } else { + result = new QLabel(); + result->setFont(bodyFont); + result->setWordWrap(true); + result->setOpenExternalLinks(true); + result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); + bodies->insert(std::make_pair(data.id, result)); + } + + result->setText(Shared::processMessageBody(data.text)); + + return result; +} + void MessageDelegate::beginClearWidgets() { idsToKeep->clear(); @@ -422,6 +451,7 @@ void MessageDelegate::endClearWidgets() 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; @@ -440,6 +470,12 @@ void MessageDelegate::endClearWidgets() 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); @@ -450,6 +486,9 @@ void MessageDelegate::endClearWidgets() for (const QString& key : toRemoveIcons) { statusIcons->erase(key); } + for (const QString& key : toRemoveBodies) { + bodies->erase(key); + } idsToKeep->clear(); clearingWidgets = false; diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h index 97822eb..6a257b7 100644 --- a/ui/utils/messagedelegate.h +++ b/ui/utils/messagedelegate.h @@ -32,6 +32,7 @@ #include "shared/icons.h" #include "shared/global.h" +#include "shared/utils.h" namespace Models { struct FeedItem; @@ -64,6 +65,7 @@ protected: 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; @@ -91,6 +93,7 @@ private: std::map* buttons; std::map* bars; std::map* statusIcons; + std::map* bodies; std::set* idsToKeep; bool clearingWidgets; diff --git a/ui/utils/shadowoverlay.h b/ui/utils/shadowoverlay.h index 36aa5d5..524115a 100644 --- a/ui/utils/shadowoverlay.h +++ b/ui/utils/shadowoverlay.h @@ -26,7 +26,7 @@ #include #include -#include +#include /** * @todo write docs diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index 830fee6..abf238c 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -25,7 +25,7 @@ add_library(squawkWidgets ${squawkWidgets_SRC}) # Use the Widgets module from Qt 5. target_link_libraries(squawkWidgets vCardUI) +target_link_libraries(squawkWidgets squawkUIUtils) target_link_libraries(squawkWidgets Qt5::Widgets) -target_link_libraries(squawkWidgets squawkUI) qt5_use_modules(squawkWidgets Core Widgets) diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 39f6837..e678caf 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -18,7 +18,6 @@ #include "conversation.h" #include "ui_conversation.h" -#include "ui/utils/dropshadoweffect.h" #include #include diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 5f98d8a..eaec954 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -24,12 +24,12 @@ #include #include #include +#include #include "shared/message.h" #include "order.h" #include "ui/models/account.h" #include "ui/models/roster.h" -#include "ui/utils/messageline.h" #include "ui/utils/flowlayout.h" #include "ui/utils/badge.h" #include "ui/utils/feedview.h"