Warning, file /pim/akregator/src/articleviewerwidget.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     This file is part of Akregator.
0003 
0004     SPDX-FileCopyrightText: 2004 Teemu Rytilahti <tpr@d5k.net>
0005     SPDX-FileCopyrightText: 2005 Frank Osterfeld <osterfeld@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0008 */
0009 
0010 #include "articleviewerwidget.h"
0011 
0012 #include "actionmanager.h"
0013 #include "akregator_debug.h"
0014 #include "akregatorconfig.h"
0015 #include "articleformatter.h"
0016 #include "articlejobs.h"
0017 #include "feed.h"
0018 #include "openurlrequest.h"
0019 #include "treenode.h"
0020 
0021 #include <KActionCollection>
0022 
0023 #include <QElapsedTimer>
0024 
0025 #include "articleviewer-ng/webengine/articlehtmlwebenginewriter.h"
0026 #include "articleviewer-ng/webengine/articleviewerwebengine.h"
0027 #include "articleviewer-ng/webengine/articleviewerwebenginewidgetng.h"
0028 #include "defaultnormalviewformatter.h"
0029 #include <QGridLayout>
0030 #include <QKeyEvent>
0031 
0032 #include "defaultcombinedviewformatter.h"
0033 
0034 using namespace Akregator;
0035 using namespace Akregator::Filters;
0036 
0037 ArticleViewerWidget::ArticleViewerWidget(const QString &grantleeDirectory, KActionCollection *ac, QWidget *parent)
0038     : QWidget(parent)
0039     , m_node(nullptr)
0040     , m_articleViewerWidgetNg(new Akregator::ArticleViewerWebEngineWidgetNg(ac, this))
0041     , m_grantleeDirectory(grantleeDirectory)
0042 {
0043     auto layout = new QGridLayout(this);
0044     layout->setContentsMargins({});
0045     layout->addWidget(m_articleViewerWidgetNg);
0046     m_articleHtmlWriter = new Akregator::ArticleHtmlWebEngineWriter(m_articleViewerWidgetNg->articleViewerNg(), this);
0047     connect(m_articleViewerWidgetNg->articleViewerNg(), &ArticleViewerWebEngine::signalOpenUrlRequest, this, &ArticleViewerWidget::signalOpenUrlRequest);
0048     connect(m_articleViewerWidgetNg->articleViewerNg(), &ArticleViewerWebEngine::showStatusBarMessage, this, &ArticleViewerWidget::showStatusBarMessage);
0049 }
0050 
0051 ArticleViewerWidget::~ArticleViewerWidget() = default;
0052 
0053 QSharedPointer<ArticleFormatter> ArticleViewerWidget::normalViewFormatter()
0054 {
0055     if (!m_normalViewFormatter.data()) {
0056         m_normalViewFormatter =
0057             QSharedPointer<ArticleFormatter>(new DefaultNormalViewFormatter(m_grantleeDirectory, m_articleViewerWidgetNg->articleViewerNg()));
0058     }
0059     return m_normalViewFormatter;
0060 }
0061 
0062 QSharedPointer<ArticleFormatter> ArticleViewerWidget::combinedViewFormatter()
0063 {
0064     if (!m_combinedViewFormatter.data()) {
0065         m_combinedViewFormatter =
0066             QSharedPointer<ArticleFormatter>(new DefaultCombinedViewFormatter(m_grantleeDirectory, m_articleViewerWidgetNg->articleViewerNg()));
0067     }
0068     return m_combinedViewFormatter;
0069 }
0070 
0071 void ArticleViewerWidget::slotZoomChangeInFrame(qreal value)
0072 {
0073     m_articleViewerWidgetNg->articleViewerNg()->setZoomFactor(value);
0074 }
0075 
0076 void ArticleViewerWidget::slotCopy()
0077 {
0078     m_articleViewerWidgetNg->articleViewerNg()->slotCopy();
0079 }
0080 
0081 void ArticleViewerWidget::slotSelectionChanged()
0082 {
0083     ActionManager::getInstance()->action(QStringLiteral("viewer_copy"))->setEnabled(!m_articleViewerWidgetNg->articleViewerNg()->selectedText().isEmpty());
0084 }
0085 
0086 void ArticleViewerWidget::slotPrint()
0087 {
0088     m_articleViewerWidgetNg->slotPrint();
0089 }
0090 
0091 void ArticleViewerWidget::slotPrintPreview()
0092 {
0093     m_articleViewerWidgetNg->slotPrintPreview();
0094 }
0095 
0096 void ArticleViewerWidget::connectToNode(TreeNode *node)
0097 {
0098     if (node) {
0099         if (m_viewMode == CombinedView) {
0100             connect(node, &TreeNode::signalChanged, this, &ArticleViewerWidget::slotUpdateCombinedView);
0101             connect(node, &TreeNode::signalArticlesAdded, this, &ArticleViewerWidget::slotArticlesAdded);
0102             connect(node, &TreeNode::signalArticlesRemoved, this, &ArticleViewerWidget::slotArticlesRemoved);
0103             connect(node, &TreeNode::signalArticlesUpdated, this, &ArticleViewerWidget::slotArticlesUpdated);
0104         } else if (m_viewMode == SummaryView) {
0105             connect(node, &TreeNode::signalChanged, this, &ArticleViewerWidget::slotShowSummary);
0106         }
0107 
0108         connect(node, &TreeNode::signalDestroyed, this, &ArticleViewerWidget::slotClear);
0109     }
0110 }
0111 
0112 void ArticleViewerWidget::disconnectFromNode(TreeNode *node)
0113 {
0114     if (node) {
0115         node->disconnect(this);
0116     }
0117 }
0118 
0119 void ArticleViewerWidget::renderContent(const QString &text)
0120 {
0121     m_currentText = text;
0122     reload();
0123 }
0124 
0125 void ArticleViewerWidget::beginWriting()
0126 {
0127     m_articleHtmlWriter->begin();
0128 }
0129 
0130 void ArticleViewerWidget::endWriting()
0131 {
0132     m_articleHtmlWriter->end();
0133 }
0134 
0135 void ArticleViewerWidget::slotShowSummary(TreeNode *node)
0136 {
0137     m_viewMode = SummaryView;
0138 
0139     if (!node) {
0140         slotClear();
0141         return;
0142     }
0143 
0144     if (node != m_node) {
0145         disconnectFromNode(m_node);
0146         connectToNode(node);
0147         m_node = node;
0148     }
0149 
0150     const QString summary = normalViewFormatter()->formatSummary(node);
0151     m_link.clear();
0152     renderContent(summary);
0153 
0154     setArticleActionsEnabled(false);
0155 }
0156 
0157 void ArticleViewerWidget::showArticle(const Akregator::Article &article)
0158 {
0159     if (article.isNull() || article.isDeleted()) {
0160         slotClear();
0161         return;
0162     }
0163 
0164     const QUrl xmlUrl = QUrl(article.feed()->xmlUrl());
0165     qCDebug(AKREGATOR_LOG) << "showing Article - xmlUrl:" << xmlUrl;
0166     m_articleHtmlWriter->setBaseUrl(xmlUrl);
0167     m_viewMode = NormalView;
0168     disconnectFromNode(m_node);
0169     m_article = article;
0170     m_node = nullptr;
0171     m_link = article.link();
0172     if (article.feed()->loadLinkedWebsite()) {
0173         openUrl(article.link());
0174     } else {
0175         renderContent(normalViewFormatter()->formatArticles(QList<Akregator::Article>() << article, ArticleFormatter::ShowIcon));
0176     }
0177 
0178     setArticleActionsEnabled(true);
0179 }
0180 
0181 bool ArticleViewerWidget::openUrl(const QUrl &url)
0182 {
0183     if (!m_article.isNull() && m_article.feed()->loadLinkedWebsite()) {
0184         m_articleViewerWidgetNg->articleViewerNg()->load(url);
0185     } else {
0186         reload();
0187     }
0188     return true;
0189 }
0190 
0191 void ArticleViewerWidget::setFilters(const std::vector<QSharedPointer<const AbstractMatcher>> &filters)
0192 {
0193     if (filters == m_filters) {
0194         return;
0195     }
0196 
0197     m_filters = filters;
0198 
0199     slotUpdateCombinedView();
0200 }
0201 
0202 void ArticleViewerWidget::slotUpdateCombinedView()
0203 {
0204     if (m_viewMode != CombinedView) {
0205         return;
0206     }
0207 
0208     if (!m_node) {
0209         return slotClear();
0210     }
0211 
0212     m_articleViewerWidgetNg->saveCurrentPosition();
0213     QString text;
0214 
0215     int num = 0;
0216     QElapsedTimer spent;
0217     spent.start();
0218 
0219     const auto filterEnd = m_filters.cend();
0220 
0221     QList<Article> articles;
0222     for (const Article &i : std::as_const(m_articles)) {
0223         if (i.isDeleted()) {
0224             continue;
0225         }
0226 
0227         auto func = [i](const QSharedPointer<const Filters::AbstractMatcher> &matcher) -> bool {
0228             return !matcher->matches(i);
0229         };
0230         if (std::find_if(m_filters.cbegin(), filterEnd, func) != filterEnd) {
0231             continue;
0232         }
0233         articles << i;
0234         ++num;
0235     }
0236     text = combinedViewFormatter()->formatArticles(articles, ArticleFormatter::NoIcon);
0237 
0238     qCDebug(AKREGATOR_LOG) << "Combined view rendering: (" << num << " articles):"
0239                            << "generating HTML:" << spent.elapsed() << "ms";
0240     renderContent(text);
0241     qCDebug(AKREGATOR_LOG) << "HTML rendering:" << spent.elapsed() << "ms";
0242 }
0243 
0244 void ArticleViewerWidget::slotArticlesUpdated(TreeNode * /*node*/, const QList<Article> & /*list*/)
0245 {
0246     if (m_viewMode == CombinedView) {
0247         slotUpdateCombinedView();
0248     }
0249 }
0250 
0251 void ArticleViewerWidget::slotArticlesAdded(TreeNode * /*node*/, const QList<Article> &list)
0252 {
0253     if (m_viewMode == CombinedView) {
0254         // TODO sort list, then merge
0255         m_articles << list;
0256         std::sort(m_articles.begin(), m_articles.end());
0257         slotUpdateCombinedView();
0258     }
0259 }
0260 
0261 void ArticleViewerWidget::slotArticlesRemoved(TreeNode * /*node*/, const QList<Article> &list)
0262 {
0263     Q_UNUSED(list)
0264 
0265     if (m_viewMode == CombinedView) {
0266         slotUpdateCombinedView();
0267     }
0268 }
0269 
0270 void ArticleViewerWidget::slotClear()
0271 {
0272     disconnectFromNode(m_node);
0273     m_node = nullptr;
0274     m_article = Article();
0275     m_articles.clear();
0276 
0277     renderContent(QString());
0278 }
0279 
0280 void ArticleViewerWidget::showNode(TreeNode *node)
0281 {
0282     m_viewMode = CombinedView;
0283 
0284     if (node != m_node) {
0285         disconnectFromNode(m_node);
0286     }
0287 
0288     connectToNode(node);
0289 
0290     m_articles.clear();
0291     m_article = Article();
0292     m_node = node;
0293 
0294     delete m_listJob;
0295 
0296     m_listJob = node->createListJob();
0297     connect(m_listJob.data(), &ArticleListJob::finished, this, &ArticleViewerWidget::slotArticlesListed);
0298     m_listJob->start();
0299 
0300     slotUpdateCombinedView();
0301 }
0302 
0303 qreal ArticleViewerWidget::zoomFactor() const
0304 {
0305     return m_articleViewerWidgetNg->articleViewerNg()->zoomFactor();
0306 }
0307 
0308 void ArticleViewerWidget::slotArticlesListed(KJob *job)
0309 {
0310     Q_ASSERT(job);
0311     Q_ASSERT(job == m_listJob);
0312 
0313     TreeNode *node = m_listJob->node();
0314 
0315     if (job->error() || !node) {
0316         if (!node) {
0317             qCWarning(AKREGATOR_LOG) << "Node to be listed is already deleted";
0318         } else {
0319             qCWarning(AKREGATOR_LOG) << job->errorText();
0320         }
0321         slotUpdateCombinedView();
0322         return;
0323     }
0324 
0325     m_articles = m_listJob->articles();
0326     std::sort(m_articles.begin(), m_articles.end());
0327 
0328     if (!m_articles.isEmpty()) {
0329         m_link = m_articles.first().link();
0330     } else {
0331         m_link = QUrl();
0332     }
0333 
0334     slotUpdateCombinedView();
0335 }
0336 
0337 void ArticleViewerWidget::keyPressEvent(QKeyEvent *e)
0338 {
0339     e->ignore();
0340 }
0341 
0342 void ArticleViewerWidget::updateAfterConfigChanged()
0343 {
0344     switch (m_viewMode) {
0345     case NormalView:
0346         if (!m_article.isNull()) {
0347             renderContent(normalViewFormatter()->formatArticles(QList<Akregator::Article>() << m_article, ArticleFormatter::ShowIcon));
0348         }
0349         break;
0350     case CombinedView:
0351         slotUpdateCombinedView();
0352         break;
0353     case SummaryView:
0354         slotShowSummary(m_node);
0355         break;
0356     }
0357     m_articleViewerWidgetNg->articleViewerNg()->updateSecurity();
0358 }
0359 
0360 void ArticleViewerWidget::reload()
0361 {
0362     beginWriting();
0363     m_articleHtmlWriter->queue(m_currentText);
0364     endWriting();
0365 }
0366 
0367 QSize ArticleViewerWidget::sizeHint() const
0368 {
0369     // Increase height a bit so that we can (roughly) read 25 lines of text
0370     QSize sh = QWidget::sizeHint();
0371     sh.setHeight(qMax(sh.height(), 25 * fontMetrics().height()));
0372     return sh;
0373 }
0374 
0375 void ArticleViewerWidget::displayAboutPage()
0376 {
0377     m_articleViewerWidgetNg->articleViewerNg()->showAboutPage();
0378 }
0379 
0380 void ArticleViewerWidget::setArticleActionsEnabled(bool enabled)
0381 {
0382     ActionManager::getInstance()->setArticleActionsEnabled(enabled);
0383 }
0384 
0385 Akregator::ArticleViewerWebEngineWidgetNg *ArticleViewerWidget::articleViewerWidgetNg() const
0386 {
0387     return m_articleViewerWidgetNg;
0388 }
0389 
0390 #include "moc_articleviewerwidget.cpp"