diff --git a/resources/icons.qrc b/resources/icons.qrc index 30fae3bd5..db31a46ea 100644 --- a/resources/icons.qrc +++ b/resources/icons.qrc @@ -71,6 +71,7 @@ ./graphics/Breeze/mimetypes/64/text-html.svg ./graphics/Breeze/places/96/user-trash.svg ./graphics/Breeze/actions/22/view-fullscreen.svg + ./graphics/Breeze/actions/22/viewimage.svg ./graphics/Breeze/actions/32/view-list-details.svg ./graphics/Breeze/actions/32/view-refresh.svg ./graphics/Breeze/actions/22/view-restore.svg @@ -147,6 +148,7 @@ ./graphics/Breeze Dark/mimetypes/64/text-html.svg ./graphics/Breeze Dark/places/96/user-trash.svg ./graphics/Breeze Dark/actions/22/view-fullscreen.svg + ./graphics/Breeze Dark/actions/22/viewimage.svg ./graphics/Breeze Dark/actions/32/view-list-details.svg ./graphics/Breeze Dark/actions/32/view-refresh.svg ./graphics/Breeze Dark/actions/22/view-restore.svg @@ -303,6 +305,7 @@ ./graphics/Numix/22/actions/up.svg ./graphics/Numix/22/places/user-trash.svg ./graphics/Numix/22/actions/view-fullscreen.svg + ./graphics/Numix/22/actions/viewimage.svg ./graphics/Numix/22/actions/view-list-details.svg ./graphics/Numix/22/actions/view-refresh.svg ./graphics/Numix/22/actions/view-restore.svg diff --git a/src/librssguard/gui/messagepreviewer.cpp b/src/librssguard/gui/messagepreviewer.cpp index 45af3eb4b..d922c602d 100644 --- a/src/librssguard/gui/messagepreviewer.cpp +++ b/src/librssguard/gui/messagepreviewer.cpp @@ -68,6 +68,18 @@ MessagePreviewer::MessagePreviewer(QWidget* parent) clear(); } +MessagePreviewer::~MessagePreviewer() { + if (m_viewerLayout->count() > 1) { + // Make sure that previewer does not delete any custom article + // viewers as those are responsibility to free by their accounts. + auto* wdg = m_viewerLayout->widget(1); + + wdg->setParent(nullptr); + + m_viewerLayout->removeWidget(wdg); + } +} + void MessagePreviewer::reloadFontSettings() { m_msgBrowser->reloadFontSettings(); } diff --git a/src/librssguard/gui/messagepreviewer.h b/src/librssguard/gui/messagepreviewer.h index 391698ba1..edbaeb9e6 100644 --- a/src/librssguard/gui/messagepreviewer.h +++ b/src/librssguard/gui/messagepreviewer.h @@ -34,6 +34,7 @@ class MessagePreviewer : public QWidget { public: explicit MessagePreviewer(QWidget* parent = nullptr); + virtual ~MessagePreviewer(); void reloadFontSettings(); diff --git a/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.cpp b/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.cpp index da6c20995..e63dc5ef0 100644 --- a/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.cpp +++ b/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.cpp @@ -23,10 +23,11 @@ #include #include -LiteHtmlViewer::LiteHtmlViewer(QWidget* parent) : QLiteHtmlWidget(parent), m_downloader(new Downloader(this)) { +LiteHtmlViewer::LiteHtmlViewer(QWidget* parent) : QLiteHtmlWidget(parent), m_downloader(new Downloader(this)), + m_reloadingWithImages(false) { setResourceHandler([this](const QUrl& url) { emit loadProgress(-1); - return handleResource(url); + return m_reloadingWithImages ? handleResource(url) : QByteArray{}; }); setFrameShape(QFrame::Shape::NoFrame); @@ -131,68 +132,9 @@ void LiteHtmlViewer::clear() { } void LiteHtmlViewer::loadMessages(const QList& messages, RootItem* root) { - Skin skin = qApp->skins()->currentSkin(); - QString messages_layout; - QString single_message_layout = skin.m_layoutMarkup; + auto html_messages = qApp->skins()->generateHtmlOfArticles(messages, root); - for (const Message& message : messages) { - QString enclosures; - QString enclosure_images; - - for (const Enclosure& enclosure : message.m_enclosures) { - QString enc_url = QUrl::fromPercentEncoding(enclosure.m_url.toUtf8()); - - enclosures += skin.m_enclosureMarkup.arg(enc_url, - QSL("🧷"), - enclosure.m_mimeType); - - if (enclosure.m_mimeType.startsWith(QSL("image/")) && - qApp->settings()->value(GROUP(Messages), SETTING(Messages::DisplayEnclosuresInMessage)).toBool()) { - // Add thumbnail image. - enclosure_images += skin.m_enclosureImageMarkup.arg( - enclosure.m_url, - enclosure.m_mimeType, - qApp->settings()->value(GROUP(Messages), SETTING(Messages::MessageHeadImageHeight)).toString()); - } - } - - QString msg_date = qApp->settings()->value(GROUP(Messages), SETTING(Messages::UseCustomDate)).toBool() - ? message.m_created.toLocalTime().toString(qApp->settings()->value(GROUP(Messages), - SETTING(Messages::CustomDateFormat)).toString()) - : qApp->localization()->loadedLocale().toString(message.m_created.toLocalTime(), - QLocale::FormatType::ShortFormat); - - messages_layout.append(single_message_layout - .arg(message.m_title, - tr("Written by ") + (message.m_author.isEmpty() ? - tr("unknown author") : - message.m_author), - message.m_url, - message.m_contents, - msg_date, - enclosures, - enclosure_images, - QString::number(message.m_id))); - } - - QString msg_contents = skin.m_layoutMarkupWrapper.arg(messages.size() == 1 - ? messages.at(0).m_title - : tr("Newspaper view"), - messages_layout); - auto* feed = root->getParentServiceRoot()->getItemFromSubTree([messages](const RootItem* it) { - return it->kind() == RootItem::Kind::Feed && it->customId() == messages.at(0).m_feedId; - })->toFeed(); - QString base_url; - - if (feed != nullptr) { - QUrl url(NetworkFactory::sanitizeUrl(feed->source())); - - if (url.isValid()) { - base_url = url.scheme() + QSL("://") + url.host(); - } - } - - setHtml(msg_contents, QUrl::fromUserInput(base_url)); + setHtml(html_messages.first, html_messages.second); emit loadFinished(true); } @@ -261,26 +203,46 @@ void LiteHtmlViewer::onLinkClicked(const QUrl& link) { } } +void LiteHtmlViewer::reloadPageWithImages() { + m_reloadingWithImages = true; + + auto scroll = verticalScrollBar()->value(); + + setHtml(html(), url()); + + if (scroll > 0) { + verticalScrollBar()->setValue(scroll); + } + + m_reloadingWithImages = false; +} + void LiteHtmlViewer::showContextMenu(const QPoint& pos, const QUrl& url) { if (m_contextMenu.isNull()) { m_contextMenu.reset(new QMenu("Context menu for web browser", this)); - m_actionCopyUrl.reset(m_contextMenu->addAction(qApp->icons()->fromTheme(QSL("edit-copy")), - tr("Copy URL"), - [url]() { - QGuiApplication::clipboard()->setText(url.toString(), QClipboard::Mode::Clipboard); - })); + m_actionCopyUrl.reset(new QAction(qApp->icons()->fromTheme(QSL("edit-copy")), + tr("Copy URL"), + this)); - m_actionCopyText.reset(m_contextMenu->addAction(qApp->icons()->fromTheme(QSL("edit-copy")), - tr("Copy selection"), - [this]() { + connect(m_actionCopyUrl.data(), &QAction::triggered, this, [url]() { + QGuiApplication::clipboard()->setText(url.toString(), QClipboard::Mode::Clipboard); + }); + + m_actionCopyText.reset(new QAction(qApp->icons()->fromTheme(QSL("edit-copy")), + tr("Copy selection"), + this)); + + connect(m_actionCopyText.data(), &QAction::triggered, this, [this]() { QGuiApplication::clipboard()->setText(QLiteHtmlWidget::selectedText(), QClipboard::Mode::Clipboard); - })); + }); // Add option to open link in external viewe - m_actionOpenLinkExternally.reset(m_contextMenu->addAction(qApp->icons()->fromTheme(QSL("document-open")), - tr("Open link in external browser"), - [url]() { + m_actionOpenLinkExternally.reset(new QAction(qApp->icons()->fromTheme(QSL("document-open")), + tr("Open link in external browser"), + this)); + + connect(m_actionOpenLinkExternally.data(), &QAction::triggered, this, [url]() { qApp->web()->openUrlInExternalBrowser(url.toString()); if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::BringAppToFrontAfterMessageOpenedExternally)).toBool()) { @@ -288,19 +250,24 @@ void LiteHtmlViewer::showContextMenu(const QPoint& pos, const QUrl& url) { qApp->mainForm()->display(); }); } - })); - } - else { - m_contextMenu->clear(); + }); + + m_actionReloadWithImages.reset(new QAction(qApp->icons()->fromTheme(QSL("viewimage"), QSL("view-refresh")), + tr("Reload with images"), + this)); + + connect(m_actionReloadWithImages.data(), &QAction::triggered, this, &LiteHtmlViewer::reloadPageWithImages); } m_actionCopyUrl->setEnabled(url.isValid()); m_actionCopyText->setEnabled(!QLiteHtmlWidget::selectedText().isEmpty()); m_actionOpenLinkExternally->setEnabled(url.isValid()); + m_contextMenu->clear(); m_contextMenu->addActions({ m_actionCopyUrl.data(), m_actionCopyText.data(), - m_actionOpenLinkExternally.data() }); + m_actionOpenLinkExternally.data(), + m_actionReloadWithImages.data() }); if (url.isValid()) { QFileIconProvider icon_provider; diff --git a/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.h b/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.h index faa8211e4..d1c76ea4d 100644 --- a/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.h +++ b/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.h @@ -40,6 +40,7 @@ class LiteHtmlViewer : public QLiteHtmlWidget, public WebViewer { private slots: void selectedTextChanged(bool available); void onLinkClicked(const QUrl& link); + void reloadPageWithImages(); void showContextMenu(const QPoint& pos, const QUrl& url); signals: @@ -63,6 +64,8 @@ class LiteHtmlViewer : public QLiteHtmlWidget, public WebViewer { QScopedPointer m_actionCopyUrl; QScopedPointer m_actionCopyText; QScopedPointer m_actionOpenLinkExternally; + QScopedPointer m_actionReloadWithImages; + bool m_reloadingWithImages; }; #endif // LITEHTMLVIEWER_H diff --git a/src/librssguard/gui/webviewers/webengine/webengineviewer.cpp b/src/librssguard/gui/webviewers/webengine/webengineviewer.cpp index 8534259a5..a9a73f68c 100644 --- a/src/librssguard/gui/webviewers/webengine/webengineviewer.cpp +++ b/src/librssguard/gui/webviewers/webengine/webengineviewer.cpp @@ -53,75 +53,16 @@ WebEnginePage* WebEngineViewer::page() const { } void WebEngineViewer::loadMessages(const QList& messages, RootItem* root) { - Skin skin = qApp->skins()->currentSkin(); - QString messages_layout; - QString single_message_layout = skin.m_layoutMarkup; - - for (const Message& message : messages) { - QString enclosures; - QString enclosure_images; - - for (const Enclosure& enclosure : message.m_enclosures) { - QString enc_url = QUrl::fromPercentEncoding(enclosure.m_url.toUtf8()); - - enclosures += skin.m_enclosureMarkup.arg(enc_url, - QSL("🧷"), - enclosure.m_mimeType); - - if (enclosure.m_mimeType.startsWith(QSL("image/")) && - qApp->settings()->value(GROUP(Messages), SETTING(Messages::DisplayEnclosuresInMessage)).toBool()) { - // Add thumbnail image. - enclosure_images += skin.m_enclosureImageMarkup.arg( - enclosure.m_url, - enclosure.m_mimeType, - qApp->settings()->value(GROUP(Messages), SETTING(Messages::MessageHeadImageHeight)).toString()); - } - } - - QString msg_date = qApp->settings()->value(GROUP(Messages), SETTING(Messages::UseCustomDate)).toBool() - ? message.m_created.toLocalTime().toString(qApp->settings()->value(GROUP(Messages), - SETTING(Messages::CustomDateFormat)).toString()) - : qApp->localization()->loadedLocale().toString(message.m_created.toLocalTime(), - QLocale::FormatType::ShortFormat); - - messages_layout.append(single_message_layout - .arg(message.m_title, - tr("Written by ") + (message.m_author.isEmpty() ? - tr("unknown author") : - message.m_author), - message.m_url, - message.m_contents, - msg_date, - enclosures, - enclosure_images, - QString::number(message.m_id))); - } - - m_messageContents = skin.m_layoutMarkupWrapper.arg(messages.size() == 1 - ? messages.at(0).m_title - : tr("Newspaper view"), - messages_layout); + auto html_messages = qApp->skins()->generateHtmlOfArticles(messages, root); m_root = root; - - auto* feed = root->getParentServiceRoot()->getItemFromSubTree([messages](const RootItem* it) { - return it->kind() == RootItem::Kind::Feed && it->customId() == messages.at(0).m_feedId; - })->toFeed(); - - m_messageBaseUrl = QString(); - - if (feed != nullptr) { - QUrl url(NetworkFactory::sanitizeUrl(feed->source())); - - if (url.isValid()) { - m_messageBaseUrl = url.scheme() + QSL("://") + url.host(); - } - } + m_messageContents = html_messages.first; + m_messageBaseUrl = html_messages.second; bool previously_enabled = isEnabled(); setEnabled(false); - setHtml(m_messageContents, m_messageBaseUrl /*, QUrl::fromUserInput(INTERNAL_URL_MESSAGE)*/); + setHtml(m_messageContents, m_messageBaseUrl); setEnabled(previously_enabled); page()->runJavaScript(QSL("window.scrollTo(0, 0);")); diff --git a/src/librssguard/gui/webviewers/webengine/webengineviewer.h b/src/librssguard/gui/webviewers/webengine/webengineviewer.h index 0325804bb..28397fc77 100644 --- a/src/librssguard/gui/webviewers/webengine/webengineviewer.h +++ b/src/librssguard/gui/webviewers/webengine/webengineviewer.h @@ -58,7 +58,7 @@ class WebEngineViewer : public QWebEngineView, public WebViewer { private: WebBrowser* m_browser; RootItem* m_root; - QString m_messageBaseUrl; + QUrl m_messageBaseUrl; QString m_messageContents; }; diff --git a/src/librssguard/miscellaneous/skinfactory.cpp b/src/librssguard/miscellaneous/skinfactory.cpp index 1f4d0e5ff..6565981f9 100644 --- a/src/librssguard/miscellaneous/skinfactory.cpp +++ b/src/librssguard/miscellaneous/skinfactory.cpp @@ -4,6 +4,8 @@ #include "exceptions/ioexception.h" #include "miscellaneous/application.h" +#include "network-web/networkfactory.h" +#include "services/abstract/rootitem.h" #include #include @@ -129,6 +131,71 @@ QString SkinFactory::adBlockedPage(const QString& url, const QString& filter) { return currentSkin().m_layoutMarkupWrapper.arg(tr("This page was blocked by AdBlock"), adblocked); } +QPair SkinFactory::generateHtmlOfArticles(const QList& messages, RootItem* root) const { + Skin skin = currentSkin(); + QString messages_layout; + QString single_message_layout = skin.m_layoutMarkup; + + for (const Message& message : messages) { + QString enclosures; + QString enclosure_images; + + for (const Enclosure& enclosure : message.m_enclosures) { + QString enc_url = QUrl::fromPercentEncoding(enclosure.m_url.toUtf8()); + + enclosures += skin.m_enclosureMarkup.arg(enc_url, + QSL("🧷"), + enclosure.m_mimeType); + + if (enclosure.m_mimeType.startsWith(QSL("image/")) && + qApp->settings()->value(GROUP(Messages), SETTING(Messages::DisplayEnclosuresInMessage)).toBool()) { + // Add thumbnail image. + enclosure_images += skin.m_enclosureImageMarkup.arg( + enclosure.m_url, + enclosure.m_mimeType, + qApp->settings()->value(GROUP(Messages), SETTING(Messages::MessageHeadImageHeight)).toString()); + } + } + + QString msg_date = qApp->settings()->value(GROUP(Messages), SETTING(Messages::UseCustomDate)).toBool() + ? message.m_created.toLocalTime().toString(qApp->settings()->value(GROUP(Messages), + SETTING(Messages::CustomDateFormat)).toString()) + : qApp->localization()->loadedLocale().toString(message.m_created.toLocalTime(), + QLocale::FormatType::ShortFormat); + + messages_layout.append(single_message_layout + .arg(message.m_title, + tr("Written by ") + (message.m_author.isEmpty() ? + tr("unknown author") : + message.m_author), + message.m_url, + message.m_contents, + msg_date, + enclosures, + enclosure_images, + QString::number(message.m_id))); + } + + QString msg_contents = skin.m_layoutMarkupWrapper.arg(messages.size() == 1 + ? messages.at(0).m_title + : tr("Newspaper view"), + messages_layout); + auto* feed = root->getParentServiceRoot()->getItemFromSubTree([messages](const RootItem* it) { + return it->kind() == RootItem::Kind::Feed && it->customId() == messages.at(0).m_feedId; + })->toFeed(); + QString base_url; + + if (feed != nullptr) { + QUrl url(NetworkFactory::sanitizeUrl(feed->source())); + + if (url.isValid()) { + base_url = url.scheme() + QSL("://") + url.host(); + } + } + + return { msg_contents, base_url }; +} + Skin SkinFactory::skinInfo(const QString& skin_name, bool* ok) const { Skin skin; const QStringList skins_root_folders = { diff --git a/src/librssguard/miscellaneous/skinfactory.h b/src/librssguard/miscellaneous/skinfactory.h index d4dadba94..4f8bbc5f4 100644 --- a/src/librssguard/miscellaneous/skinfactory.h +++ b/src/librssguard/miscellaneous/skinfactory.h @@ -5,6 +5,8 @@ #include +#include "core/message.h" + #include #include #include @@ -12,6 +14,8 @@ #include #include +class RootItem; + class SkinEnums : public QObject { Q_OBJECT @@ -82,6 +86,8 @@ class RSSGUARD_DLLSPEC SkinFactory : public QObject { QString adBlockedPage(const QString& url, const QString& filter); + QPair generateHtmlOfArticles(const QList& messages, RootItem* root) const; + // Gets skin about a particular skin. Skin skinInfo(const QString& skin_name, bool* ok = nullptr) const; diff --git a/src/librssguard/services/abstract/cacheforserviceroot.cpp b/src/librssguard/services/abstract/cacheforserviceroot.cpp index 10454301c..24d605f46 100644 --- a/src/librssguard/services/abstract/cacheforserviceroot.cpp +++ b/src/librssguard/services/abstract/cacheforserviceroot.cpp @@ -12,6 +12,8 @@ CacheForServiceRoot::CacheForServiceRoot() : m_uniqueId(NO_PARENT_CATEGORY), m_cacheSaveMutex(new QMutex()) {} +CacheForServiceRoot::~CacheForServiceRoot() {} + void CacheForServiceRoot::addLabelsAssignmentsToCache(const QStringList& ids_of_messages, const QString& lbl_custom_id, bool assign) { diff --git a/src/librssguard/services/abstract/cacheforserviceroot.h b/src/librssguard/services/abstract/cacheforserviceroot.h index 763589b8f..0c89238af 100644 --- a/src/librssguard/services/abstract/cacheforserviceroot.h +++ b/src/librssguard/services/abstract/cacheforserviceroot.h @@ -20,6 +20,7 @@ struct CacheSnapshot { class CacheForServiceRoot { public: explicit CacheForServiceRoot(); + virtual ~CacheForServiceRoot(); virtual void saveAllCachedData(bool ignore_errors) = 0; diff --git a/src/librssguard/services/abstract/serviceroot.cpp b/src/librssguard/services/abstract/serviceroot.cpp index 6aca669c5..acf678398 100644 --- a/src/librssguard/services/abstract/serviceroot.cpp +++ b/src/librssguard/services/abstract/serviceroot.cpp @@ -440,7 +440,7 @@ void ServiceRoot::syncIn() { QIcon original_icon = icon(); setIcon(qApp->icons()->fromTheme(QSL("view-refresh"))); - itemChanged(QList() << this); + itemChanged({ this }); RootItem* new_tree = obtainNewTreeForSyncIn(); if (new_tree != nullptr) { diff --git a/src/librssguard/services/gmail/gmailserviceroot.cpp b/src/librssguard/services/gmail/gmailserviceroot.cpp index 6e226deef..ac30d08b2 100644 --- a/src/librssguard/services/gmail/gmailserviceroot.cpp +++ b/src/librssguard/services/gmail/gmailserviceroot.cpp @@ -25,6 +25,12 @@ GmailServiceRoot::GmailServiceRoot(RootItem* parent) setIcon(GmailEntryPoint().icon()); } +GmailServiceRoot::~GmailServiceRoot() { + if (!m_emailPreview.isNull()) { + m_emailPreview->deleteLater(); + } +} + void GmailServiceRoot::updateTitle() { setTitle(TextFactory::extractUsernameFromEmail(m_network->username()) + QSL(" (Gmail)")); } @@ -100,7 +106,7 @@ bool GmailServiceRoot::wantsBaggedIdsOfExistingMessages() const { CustomMessagePreviewer* GmailServiceRoot::customMessagePreviewer() { if (m_emailPreview.isNull()) { - m_emailPreview.reset(new EmailPreviewer()); + m_emailPreview = new EmailPreviewer(); } return m_emailPreview.data(); diff --git a/src/librssguard/services/gmail/gmailserviceroot.h b/src/librssguard/services/gmail/gmailserviceroot.h index 63aa8e8ad..7d48d1dde 100644 --- a/src/librssguard/services/gmail/gmailserviceroot.h +++ b/src/librssguard/services/gmail/gmailserviceroot.h @@ -15,6 +15,7 @@ class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot { public: explicit GmailServiceRoot(RootItem* parent = nullptr); + virtual ~GmailServiceRoot(); void setNetwork(GmailNetworkFactory* network); GmailNetworkFactory* network() const; @@ -49,7 +50,7 @@ class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot { void updateTitle(); private: - QScopedPointer m_emailPreview; + QPointer m_emailPreview; GmailNetworkFactory* m_network; QAction* m_actionReply; Message m_replyToMessage;