File indexing completed on 2024-05-05 09:27:15

0001 /*
0002     This file is part of Akregator.
0003 
0004     SPDX-FileCopyrightText: 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
0005     SPDX-FileCopyrightText: 2004 Sashmit Bhaduri <smt@vfemail.net>
0006     SPDX-FileCopyrightText: 2005 Frank Osterfeld <osterfeld@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0009 */
0010 
0011 #include "mainwidget.h"
0012 #include "abstractselectioncontroller.h"
0013 #include "actionmanagerimpl.h"
0014 #include "akregator_part.h"
0015 #include "akregatorconfig.h"
0016 #include "articlejobs.h"
0017 #include "articlelistview.h"
0018 #include "articleviewerwidget.h"
0019 #include "createfeedcommand.h"
0020 #include "createfoldercommand.h"
0021 #include "deletesubscriptioncommand.h"
0022 #include "editsubscriptioncommand.h"
0023 #include "expireitemscommand.h"
0024 #include "importfeedlistcommand.h"
0025 #include "utils.h"
0026 #include <PimCommon/BroadcastStatus>
0027 
0028 #include "feedlist.h"
0029 #include "fetchqueue.h"
0030 #include "folder.h"
0031 #include "framemanager.h"
0032 #include "job/downloadarticlejob.h"
0033 #include "kernel.h"
0034 #include "mainframe.h"
0035 #include "openurlrequest.h"
0036 #include "progressmanager.h"
0037 #include "selectioncontroller.h"
0038 #include "subscriptionlistmodel.h"
0039 #include "subscriptionlistview.h"
0040 #include "tabwidget.h"
0041 #include "treenode.h"
0042 #include "types.h"
0043 #include "widgets/searchbar.h"
0044 #include <WebEngineViewer/ZoomActionMenu>
0045 
0046 #include <KAboutData>
0047 #include <KLocalizedString>
0048 #include <KMessageBox>
0049 #include <KShell>
0050 
0051 #include <KToggleAction>
0052 
0053 #include <QApplication>
0054 #include <QClipboard>
0055 #include <QDesktopServices>
0056 #include <QDomDocument>
0057 #include <QSplitter>
0058 #include <QTimer>
0059 #include <QUrlQuery>
0060 #include <QVBoxLayout>
0061 
0062 #include <PimCommon/NeedUpdateVersionUtils>
0063 #include <PimCommon/NeedUpdateVersionWidget>
0064 #include <PimCommon/NetworkManager>
0065 #include <algorithm>
0066 #include <chrono>
0067 #include <memory>
0068 #include <webengine/webengineframe.h>
0069 
0070 using namespace std::chrono_literals;
0071 
0072 using namespace Akregator;
0073 
0074 MainWidget::~MainWidget()
0075 {
0076     // if m_shuttingDown is false, slotOnShutdown was not called. That
0077     // means that not the whole app is shutdown, only the part. So it
0078     // should be no risk to do the cleanups now
0079     if (!m_shuttingDown) {
0080         slotOnShutdown();
0081     }
0082 }
0083 
0084 MainWidget::MainWidget(Part *part, QWidget *parent, ActionManagerImpl *actionManager, const QString &name)
0085     : QWidget(parent)
0086     , m_feedList()
0087     , m_actionManager(actionManager)
0088     , m_feedListManagementInterface(new FeedListManagementImpl)
0089 {
0090     setObjectName(name);
0091 
0092     FeedListManagementInterface::setInstance(m_feedListManagementInterface);
0093 
0094     m_actionManager->initMainWidget(this);
0095     m_part = part;
0096     m_shuttingDown = false;
0097     m_displayingAboutPage = false;
0098     setFocusPolicy(Qt::StrongFocus);
0099 
0100     connect(m_part, &Part::signalSettingsChanged, m_actionManager, &ActionManagerImpl::slotSettingsChanged);
0101 
0102     auto topLayout = new QVBoxLayout(this);
0103     topLayout->setContentsMargins({});
0104     topLayout->setSpacing(0);
0105     if (PimCommon::NeedUpdateVersionUtils::checkVersion()) {
0106         const auto status = PimCommon::NeedUpdateVersionUtils::obsoleteVersionStatus(KAboutData::applicationData().version(), QDate::currentDate());
0107         if (status != PimCommon::NeedUpdateVersionUtils::ObsoleteVersion::NotObsoleteYet) {
0108             auto needUpdateVersionWidget = new PimCommon::NeedUpdateVersionWidget(this);
0109             topLayout->addWidget(needUpdateVersionWidget);
0110             needUpdateVersionWidget->setObsoleteVersion(status);
0111         }
0112     }
0113 
0114     m_horizontalSplitter = new QSplitter(Qt::Horizontal, this);
0115 
0116     m_horizontalSplitter->setOpaqueResize(true);
0117     m_horizontalSplitter->setChildrenCollapsible(false);
0118     topLayout->addWidget(m_horizontalSplitter);
0119 
0120     connect(Kernel::self()->fetchQueue(), &FetchQueue::signalStarted, this, &MainWidget::slotFetchingStarted);
0121     connect(Kernel::self()->fetchQueue(), &FetchQueue::signalStopped, this, &MainWidget::slotFetchingStopped);
0122 
0123     m_feedListView = new SubscriptionListView(m_horizontalSplitter);
0124     m_feedListView->setObjectName(QLatin1StringView("feedtree"));
0125     m_actionManager->initSubscriptionListView(m_feedListView);
0126 
0127     connect(m_feedListView, &SubscriptionListView::userActionTakingPlace, this, &MainWidget::ensureArticleTabVisible);
0128 
0129     m_tabWidget = new TabWidget(m_horizontalSplitter);
0130     m_actionManager->initTabWidget(m_tabWidget);
0131 
0132     connect(m_part, &Part::signalSettingsChanged, this, &MainWidget::slotSettingsChanged);
0133 
0134     connect(m_tabWidget, &TabWidget::signalCurrentFrameChanged, this, &MainWidget::slotCurrentFrameChanged);
0135 
0136     connect(m_tabWidget, &TabWidget::signalRemoveFrameRequest, Kernel::self()->frameManager(), &FrameManager::slotRemoveFrame);
0137 
0138     connect(m_tabWidget,
0139             SIGNAL(signalOpenUrlRequest(Akregator::OpenUrlRequest &)),
0140             Kernel::self()->frameManager(),
0141             SLOT(slotOpenUrlRequest(Akregator::OpenUrlRequest &)));
0142 
0143     connect(Kernel::self()->frameManager(), &FrameManager::signalFrameAdded, m_tabWidget, &TabWidget::slotAddFrame);
0144 
0145     connect(Kernel::self()->frameManager(), &FrameManager::signalSelectFrame, m_tabWidget, &TabWidget::slotSelectFrame);
0146 
0147     connect(Kernel::self()->frameManager(), &FrameManager::signalFrameRemoved, m_tabWidget, &TabWidget::slotRemoveFrame);
0148 
0149     connect(Kernel::self()->frameManager(), &FrameManager::signalRequestNewFrame, this, &MainWidget::slotRequestNewFrame);
0150 
0151     connect(Kernel::self()->frameManager(), &FrameManager::signalFrameRemoved, this, &MainWidget::slotFramesChanged);
0152     connect(Kernel::self()->frameManager(), &FrameManager::signalCompleted, this, &MainWidget::slotFramesChanged);
0153 
0154     connect(PimCommon::NetworkManager::self(), &PimCommon::NetworkManager::networkStatusChanged, this, &MainWidget::slotNetworkStatusChanged);
0155 
0156     m_tabWidget->setWhatsThis(i18n("You can view multiple articles in several open tabs."));
0157 
0158     m_mainTab = new QWidget(this);
0159     m_mainTab->setObjectName(QLatin1StringView("Article Tab"));
0160     m_mainTab->setWhatsThis(i18n("Articles list."));
0161 
0162     auto mainTabLayout = new QVBoxLayout(m_mainTab);
0163     mainTabLayout->setContentsMargins({});
0164 
0165     m_searchBar = new SearchBar(m_mainTab);
0166     if (!Settings::showQuickFilter()) {
0167         m_searchBar->hide();
0168     }
0169     connect(m_searchBar, &SearchBar::forceLostFocus, this, &MainWidget::slotSetFocusToViewer);
0170     m_articleSplitter = new QSplitter(Qt::Vertical, m_mainTab);
0171     m_articleSplitter->setObjectName(QLatin1StringView("panner2"));
0172     m_articleSplitter->setChildrenCollapsible(false);
0173 
0174     m_articleWidget = new QWidget(m_articleSplitter);
0175     auto articleWidgetLayout = new QVBoxLayout;
0176     m_articleWidget->setLayout(articleWidgetLayout);
0177     articleWidgetLayout->setContentsMargins({});
0178     articleWidgetLayout->setSpacing(0);
0179 
0180     m_articleListView = new ArticleListView;
0181     articleWidgetLayout->addWidget(m_searchBar);
0182     articleWidgetLayout->addWidget(m_articleListView);
0183     connect(m_articleListView, &ArticleListView::userActionTakingPlace, this, &MainWidget::ensureArticleTabVisible);
0184 
0185     m_selectionController = new SelectionController(this);
0186     m_selectionController->setArticleLister(m_articleListView);
0187     m_selectionController->setFeedSelector(m_feedListView);
0188 
0189     connect(m_searchBar, &SearchBar::signalSearch, m_selectionController, &AbstractSelectionController::setFilters);
0190 
0191     connect(m_part, &Part::signalSettingsChanged, m_selectionController, &AbstractSelectionController::settingsChanged);
0192 
0193     auto expansionHandler = new FolderExpansionHandler(this);
0194     connect(m_feedListView, &QTreeView::expanded, expansionHandler, &FolderExpansionHandler::itemExpanded);
0195     connect(m_feedListView, &QTreeView::collapsed, expansionHandler, &FolderExpansionHandler::itemCollapsed);
0196 
0197     m_selectionController->setFolderExpansionHandler(expansionHandler);
0198 
0199     connect(m_selectionController, &AbstractSelectionController::currentSubscriptionChanged, this, &MainWidget::slotNodeSelected);
0200 
0201     connect(m_selectionController, &AbstractSelectionController::currentArticleChanged, this, &MainWidget::slotArticleSelected);
0202 
0203     connect(m_selectionController, &AbstractSelectionController::articleDoubleClicked, this, &MainWidget::slotOpenArticleInBrowser);
0204 
0205     m_actionManager->initArticleListView(m_articleListView);
0206 
0207     connect(m_articleListView, &ArticleListView::signalMouseButtonPressed, this, &MainWidget::slotMouseButtonPressed);
0208 
0209     m_articleViewer = new ArticleViewerWidget(Settings::grantleeDirectory(), m_actionManager->actionCollection(), m_articleSplitter);
0210     m_articleListView->setFocusProxy(m_articleViewer);
0211     setFocusProxy(m_articleViewer);
0212 
0213     connect(m_articleViewer, &ArticleViewerWidget::showStatusBarMessage, this, &MainWidget::slotShowStatusBarMessage);
0214     connect(m_articleViewer,
0215             SIGNAL(signalOpenUrlRequest(Akregator::OpenUrlRequest &)),
0216             Kernel::self()->frameManager(),
0217             SLOT(slotOpenUrlRequest(Akregator::OpenUrlRequest &)));
0218     connect(m_searchBar, &SearchBar::signalSearch, m_articleViewer, &ArticleViewerWidget::setFilters);
0219     mainTabLayout->addWidget(m_articleSplitter);
0220 
0221     m_mainFrame = new MainFrame(this, m_mainTab);
0222     m_mainFrame->slotSetTitle(i18n("Articles"));
0223     m_mainFrame->setArticleViewer(m_articleViewer);
0224     connect(m_articleViewer->articleViewerWidgetNg()->articleViewerNg(), &ArticleViewerWebEngine::articleAction, this, &MainWidget::slotArticleAction);
0225     connect(m_tabWidget, &TabWidget::signalCopyInFrame, m_mainFrame, &MainFrame::slotCopyInFrame);
0226     connect(m_tabWidget, &TabWidget::signalPrintInFrame, m_mainFrame, &MainFrame::slotPrintInFrame);
0227     connect(m_tabWidget, &TabWidget::signalZoomChangedInFrame, m_mainFrame, &MainFrame::slotZoomChangeInFrame);
0228     connect(m_tabWidget, &TabWidget::signalPrintPreviewInFrame, m_mainFrame, &MainFrame::slotPrintPreviewInFrame);
0229     connect(m_tabWidget, &TabWidget::signalFindTextInFrame, m_mainFrame, &MainFrame::slotFindTextInFrame);
0230     connect(m_tabWidget, &TabWidget::signalTextToSpeechInFrame, m_mainFrame, &MainFrame::slotTextToSpeechInFrame);
0231     connect(m_tabWidget, &TabWidget::signalSaveLinkAsInFrame, m_mainFrame, &MainFrame::slotSaveLinkAsInFrame);
0232     connect(m_tabWidget, &TabWidget::signalCopyLinkAsInFrame, m_mainFrame, &MainFrame::slotCopyLinkAsInFrame);
0233     connect(m_tabWidget, &TabWidget::signalCopyImageLocation, m_mainFrame, &MainFrame::slotCopyImageLocationInFrame);
0234     connect(m_tabWidget, &TabWidget::signalSaveImageOnDisk, m_mainFrame, &MainFrame::slotSaveImageOnDiskInFrame);
0235     connect(m_tabWidget, &TabWidget::signalMute, m_mainFrame, &MainFrame::slotMute);
0236 
0237     Kernel::self()->frameManager()->slotAddFrame(m_mainFrame);
0238 
0239     const QList<int> sp1sizes = Settings::splitter1Sizes();
0240     if (sp1sizes.count() >= m_horizontalSplitter->count()) {
0241         m_horizontalSplitter->setSizes(sp1sizes);
0242     }
0243     const QList<int> sp2sizes = Settings::splitter2Sizes();
0244     if (sp2sizes.count() >= m_articleSplitter->count()) {
0245         m_articleSplitter->setSizes(sp2sizes);
0246     }
0247 
0248     if (!Settings::self()->disableIntroduction()) {
0249         m_articleWidget->hide();
0250         m_articleViewer->displayAboutPage();
0251         m_mainFrame->slotSetTitle(i18n("About"));
0252         m_displayingAboutPage = true;
0253     }
0254 
0255     m_fetchTimer = new QTimer(this);
0256     connect(m_fetchTimer, &QTimer::timeout, this, &MainWidget::slotDoIntervalFetches);
0257     m_fetchTimer->start(1min);
0258 
0259     // delete expired articles once per hour
0260     m_expiryTimer = new QTimer(this);
0261     connect(m_expiryTimer, &QTimer::timeout, this, &MainWidget::slotDeleteExpiredArticles);
0262     m_expiryTimer->start(1h);
0263 
0264     m_markReadTimer = new QTimer(this);
0265     m_markReadTimer->setSingleShot(true);
0266     connect(m_markReadTimer, &QTimer::timeout, this, &MainWidget::slotSetCurrentArticleReadDelayed);
0267 
0268     setFeedList(QSharedPointer<FeedList>(new FeedList(Kernel::self()->storage())));
0269 
0270     switch (Settings::viewMode()) {
0271     case CombinedView:
0272         slotCombinedView();
0273         break;
0274     case WidescreenView:
0275         slotWidescreenView();
0276         break;
0277     default:
0278         slotNormalView();
0279     }
0280 
0281     if (!Settings::resetQuickFilterOnNodeChange()) {
0282         m_searchBar->slotSetStatus(Settings::statusFilter());
0283         m_searchBar->slotSetText(Settings::textFilter());
0284     }
0285 }
0286 
0287 void MainWidget::slotSettingsChanged()
0288 {
0289     m_tabWidget->slotSettingsChanged();
0290     m_articleViewer->updateAfterConfigChanged();
0291 }
0292 
0293 void MainWidget::slotSetFocusToViewer()
0294 {
0295     m_articleViewer->setFocus();
0296 }
0297 
0298 void MainWidget::slotOnShutdown()
0299 {
0300     disconnect(m_tabWidget, &TabWidget::signalCurrentFrameChanged, this, &MainWidget::slotCurrentFrameChanged);
0301 
0302     m_shuttingDown = true;
0303 
0304     // close all pageviewers in a controlled way
0305     // fixes bug 91660, at least when no part loading data
0306     while (m_tabWidget->count() > 1) { // remove frames until only the main frame remains
0307         m_tabWidget->setCurrentIndex(m_tabWidget->count() - 1); // select last page
0308         m_tabWidget->slotRemoveCurrentFrame();
0309     }
0310 
0311     Kernel::self()->fetchQueue()->slotAbort();
0312     setFeedList(QSharedPointer<FeedList>());
0313 
0314     delete m_feedListManagementInterface;
0315     delete m_feedListView; // call delete here, so that the header settings will get saved
0316     delete m_articleListView; // same for this one
0317 
0318     delete m_mainTab;
0319     delete m_mainFrame;
0320     m_mainFrame = nullptr;
0321 
0322     Settings::self()->save();
0323 }
0324 
0325 void MainWidget::saveSettings()
0326 {
0327     const QList<int> spl1 = m_horizontalSplitter->sizes();
0328     if (std::count(spl1.begin(), spl1.end(), 0) == 0) {
0329         Settings::setSplitter1Sizes(spl1);
0330     }
0331     const QList<int> spl2 = m_articleSplitter->sizes();
0332     if (std::count(spl2.begin(), spl2.end(), 0) == 0) {
0333         Settings::setSplitter2Sizes(spl2);
0334     }
0335     Settings::setViewMode(m_viewMode);
0336     Settings::self()->save();
0337 }
0338 
0339 void MainWidget::connectFrame(Akregator::WebEngineFrame *frame)
0340 {
0341     connect(m_tabWidget, &TabWidget::signalCopyInFrame, frame, &WebEngineFrame::slotCopyInFrame);
0342     connect(m_tabWidget, &TabWidget::signalPrintInFrame, frame, &WebEngineFrame::slotPrintInFrame);
0343     connect(m_tabWidget, &TabWidget::signalZoomChangedInFrame, frame, &WebEngineFrame::slotZoomChangeInFrame);
0344     connect(m_tabWidget, &TabWidget::signalPrintPreviewInFrame, frame, &WebEngineFrame::slotPrintPreviewInFrame);
0345     connect(m_tabWidget, &TabWidget::signalFindTextInFrame, frame, &WebEngineFrame::slotFindTextInFrame);
0346     connect(m_tabWidget, &TabWidget::signalTextToSpeechInFrame, frame, &WebEngineFrame::slotTextToSpeechInFrame);
0347     connect(m_tabWidget, &TabWidget::signalSaveLinkAsInFrame, frame, &WebEngineFrame::slotSaveLinkAsInFrame);
0348     connect(m_tabWidget, &TabWidget::signalCopyLinkAsInFrame, frame, &WebEngineFrame::slotCopyLinkAsInFrame);
0349     connect(m_tabWidget, &TabWidget::signalCopyImageLocation, frame, &WebEngineFrame::slotCopyImageLocationInFrame);
0350     connect(m_tabWidget, &TabWidget::signalSaveImageOnDisk, frame, &WebEngineFrame::slotSaveImageOnDiskInFrame);
0351     connect(m_tabWidget, &TabWidget::signalMute, frame, &WebEngineFrame::slotMute);
0352 
0353     connect(frame, &WebEngineFrame::showStatusBarMessage, this, &MainWidget::slotShowStatusBarMessage);
0354     connect(frame, &WebEngineFrame::signalIconChanged, m_tabWidget, &TabWidget::slotSetIcon);
0355     connect(frame, &WebEngineFrame::webPageMutedOrAudibleChanged, m_tabWidget, &TabWidget::slotWebPageMutedOrAudibleChanged);
0356 }
0357 
0358 void MainWidget::slotRequestNewFrame(int &frameId)
0359 {
0360     auto frame = new WebEngineFrame(m_actionManager->actionCollection(), m_tabWidget);
0361     connectFrame(frame);
0362 
0363     Kernel::self()->frameManager()->slotAddFrame(frame);
0364 
0365     frameId = frame->id();
0366 }
0367 
0368 void MainWidget::sendArticle(bool attach)
0369 {
0370     QByteArray text;
0371     QString title;
0372 
0373     Frame *frame = Kernel::self()->frameManager()->currentFrame();
0374 
0375     if (frame && frame->id() > 0) { // are we in some other tab than the articlelist?
0376         text = frame->url().toString().toLatin1();
0377         title = frame->title();
0378     } else { // nah, we're in articlelist..
0379         const Article article = m_selectionController->currentArticle();
0380         if (!article.isNull()) {
0381             text = article.link().toDisplayString().toLatin1();
0382             title = Akregator::Utils::convertHtmlTags(article.title());
0383         }
0384     }
0385 
0386     if (text.isEmpty()) {
0387         return;
0388     }
0389     sendArticle(text, title, attach);
0390 }
0391 
0392 void MainWidget::cleanUpDownloadFile()
0393 {
0394     for (QPointer<Akregator::DownloadArticleJob> job : std::as_const(mListDownloadArticleJobs)) {
0395         if (job) {
0396             job->forceCleanupTemporaryFile();
0397         }
0398     }
0399 }
0400 
0401 void MainWidget::sendArticle(const QByteArray &text, const QString &title, bool attach)
0402 {
0403     if (attach) {
0404         QPointer<Akregator::DownloadArticleJob> download = new Akregator::DownloadArticleJob(this);
0405         download->setArticleUrl(QUrl(QString::fromUtf8(text)));
0406         download->setText(QString::fromUtf8(text));
0407         download->setTitle(title);
0408         mListDownloadArticleJobs.append(download);
0409         download->start();
0410     } else {
0411         QUrlQuery query;
0412         query.addQueryItem(QStringLiteral("subject"), title);
0413         query.addQueryItem(QStringLiteral("body"), QString::fromUtf8(text));
0414         QUrl url;
0415         url.setScheme(QStringLiteral("mailto"));
0416         url.setQuery(query);
0417         QDesktopServices::openUrl(url);
0418     }
0419 }
0420 
0421 void MainWidget::importFeedList(const QDomDocument &doc)
0422 {
0423     auto cmd = new ImportFeedListCommand;
0424     cmd->setParentWidget(this);
0425     cmd->setFeedListDocument(doc);
0426     cmd->setTargetList(m_feedList);
0427     cmd->start();
0428 }
0429 
0430 void MainWidget::setFeedList(const QSharedPointer<FeedList> &list)
0431 {
0432     if (list == m_feedList) {
0433         return;
0434     }
0435     const QSharedPointer<FeedList> oldList = m_feedList;
0436 
0437     m_feedList = list;
0438     if (m_feedList) {
0439         connect(m_feedList.data(), &FeedList::unreadCountChanged, this, &MainWidget::slotSetTotalUnread);
0440     }
0441 
0442     slotSetTotalUnread();
0443 
0444     m_feedListManagementInterface->setFeedList(m_feedList);
0445     Kernel::self()->setFeedList(m_feedList);
0446     ProgressManager::self()->setFeedList(m_feedList);
0447     m_selectionController->setFeedList(m_feedList);
0448 
0449     if (oldList) {
0450         oldList->disconnect(this);
0451     }
0452 
0453     slotDeleteExpiredArticles();
0454 }
0455 
0456 void MainWidget::deleteExpiredArticles(const QSharedPointer<FeedList> &list)
0457 {
0458     if (!list) {
0459         return;
0460     }
0461     auto cmd = new ExpireItemsCommand(this);
0462     cmd->setParentWidget(this);
0463     cmd->setFeedList(list);
0464     cmd->setFeeds(list->feedIds());
0465     cmd->start();
0466 }
0467 
0468 void MainWidget::slotDeleteExpiredArticles()
0469 {
0470     deleteExpiredArticles(m_feedList);
0471 }
0472 
0473 QDomDocument MainWidget::feedListToOPML()
0474 {
0475     QDomDocument dom;
0476     if (m_feedList) {
0477         dom = m_feedList->toOpml();
0478     }
0479     return dom;
0480 }
0481 
0482 void MainWidget::addFeedToGroup(const QString &url, const QString &groupName)
0483 {
0484     // Locate the group.
0485     const QList<TreeNode *> namedGroups = m_feedList->findByTitle(groupName);
0486     Folder *group = nullptr;
0487     for (TreeNode *const candidate : namedGroups) {
0488         if (candidate->isGroup()) {
0489             group = static_cast<Folder *>(candidate);
0490             break;
0491         }
0492     }
0493 
0494     if (!group) {
0495         auto g = new Folder(groupName);
0496         m_feedList->allFeedsFolder()->appendChild(g);
0497         group = g;
0498     }
0499 
0500     // Invoke the Add Feed dialog with url filled in.
0501     addFeed(url, nullptr, group, true);
0502 }
0503 
0504 void MainWidget::slotNormalView()
0505 {
0506     if (m_viewMode == NormalView) {
0507         return;
0508     }
0509 
0510     if (m_viewMode == CombinedView) {
0511         m_articleWidget->show();
0512 
0513         const Article article = m_selectionController->currentArticle();
0514 
0515         if (!article.isNull()) {
0516             m_articleViewer->showArticle(article);
0517         } else {
0518             m_articleViewer->slotShowSummary(m_selectionController->selectedSubscription());
0519         }
0520     }
0521 
0522     m_articleSplitter->setOrientation(Qt::Vertical);
0523     m_viewMode = NormalView;
0524 
0525     Settings::setViewMode(m_viewMode);
0526 }
0527 
0528 void MainWidget::slotWidescreenView()
0529 {
0530     if (m_viewMode == WidescreenView) {
0531         return;
0532     }
0533 
0534     if (m_viewMode == CombinedView) {
0535         m_articleWidget->show();
0536 
0537         Article article = m_selectionController->currentArticle();
0538 
0539         if (!article.isNull()) {
0540             m_articleViewer->showArticle(article);
0541         } else {
0542             m_articleViewer->slotShowSummary(m_selectionController->selectedSubscription());
0543         }
0544     }
0545 
0546     m_articleSplitter->setOrientation(Qt::Horizontal);
0547     m_viewMode = WidescreenView;
0548 
0549     Settings::setViewMode(m_viewMode);
0550 }
0551 
0552 void MainWidget::slotCombinedView()
0553 {
0554     if (m_viewMode == CombinedView) {
0555         return;
0556     }
0557 
0558     m_articleListView->slotClear();
0559     m_articleWidget->hide();
0560     m_viewMode = CombinedView;
0561 
0562     Settings::setViewMode(m_viewMode);
0563 }
0564 
0565 void MainWidget::slotMoveCurrentNodeUp()
0566 {
0567     TreeNode *current = m_selectionController->selectedSubscription();
0568     if (!current) {
0569         return;
0570     }
0571     TreeNode *prev = current->prevSibling();
0572     Folder *parent = current->parent();
0573 
0574     if (!prev || !parent) {
0575         return;
0576     }
0577 
0578     parent->removeChild(prev);
0579     parent->insertChild(prev, current);
0580     m_feedListView->ensureNodeVisible(current);
0581 }
0582 
0583 void MainWidget::slotMoveCurrentNodeDown()
0584 {
0585     TreeNode *current = m_selectionController->selectedSubscription();
0586     if (!current) {
0587         return;
0588     }
0589     TreeNode *next = current->nextSibling();
0590     Folder *parent = current->parent();
0591 
0592     if (!next || !parent) {
0593         return;
0594     }
0595 
0596     parent->removeChild(current);
0597     parent->insertChild(current, next);
0598     m_feedListView->ensureNodeVisible(current);
0599 }
0600 
0601 void MainWidget::slotMoveCurrentNodeLeft()
0602 {
0603     TreeNode *current = m_selectionController->selectedSubscription();
0604     if (!current || !current->parent() || !current->parent()->parent()) {
0605         return;
0606     }
0607 
0608     Folder *parent = current->parent();
0609     Folder *grandparent = current->parent()->parent();
0610 
0611     parent->removeChild(current);
0612     grandparent->insertChild(current, parent);
0613     m_feedListView->ensureNodeVisible(current);
0614 }
0615 
0616 void MainWidget::slotMoveCurrentNodeRight()
0617 {
0618     TreeNode *current = m_selectionController->selectedSubscription();
0619     if (!current || !current->parent()) {
0620         return;
0621     }
0622     TreeNode *prev = current->prevSibling();
0623 
0624     if (prev && prev->isGroup()) {
0625         auto fg = static_cast<Folder *>(prev);
0626         current->parent()->removeChild(current);
0627         fg->appendChild(current);
0628         m_feedListView->ensureNodeVisible(current);
0629     }
0630 }
0631 
0632 void MainWidget::slotSendLink()
0633 {
0634     sendArticle();
0635 }
0636 
0637 void MainWidget::slotSendFile()
0638 {
0639     sendArticle(true);
0640 }
0641 
0642 void MainWidget::slotNodeSelected(TreeNode *node)
0643 {
0644     m_markReadTimer->stop();
0645 
0646     if (m_displayingAboutPage) {
0647         m_mainFrame->slotSetTitle(i18n("Articles"));
0648         if (m_viewMode != CombinedView) {
0649             m_articleWidget->show();
0650         }
0651         if (Settings::showQuickFilter()) {
0652             m_searchBar->show();
0653         }
0654         m_displayingAboutPage = false;
0655     }
0656 
0657     m_tabWidget->setCurrentWidget(m_mainFrame);
0658     if (Settings::resetQuickFilterOnNodeChange()) {
0659         m_searchBar->slotClearSearch();
0660     }
0661 
0662     if (m_viewMode == CombinedView) {
0663         m_articleViewer->showNode(node);
0664     } else {
0665         m_articleViewer->slotShowSummary(node);
0666     }
0667 
0668     if (node) {
0669         m_mainFrame->setWindowTitle(node->title());
0670     }
0671 
0672     m_actionManager->slotNodeSelected(node);
0673 }
0674 
0675 void MainWidget::slotFeedAdd()
0676 {
0677     Folder *group = nullptr;
0678     if (!m_selectionController->selectedSubscription()) {
0679         group = m_feedList->allFeedsFolder();
0680     } else {
0681         if (m_selectionController->selectedSubscription()->isGroup()) {
0682             group = static_cast<Folder *>(m_selectionController->selectedSubscription());
0683         } else {
0684             group = m_selectionController->selectedSubscription()->parent();
0685         }
0686     }
0687 
0688     TreeNode *const lastChild = !group->children().isEmpty() ? group->children().constLast() : nullptr;
0689 
0690     addFeed(QString(), lastChild, group, false);
0691 }
0692 
0693 void MainWidget::addFeed(const QString &url, TreeNode *after, Folder *parent, bool autoExec)
0694 {
0695     auto cmd(new CreateFeedCommand(this));
0696     cmd->setParentWidget(this);
0697     cmd->setPosition(parent, after);
0698     cmd->setRootFolder(m_feedList->allFeedsFolder());
0699     cmd->setAutoExecute(autoExec);
0700     cmd->setUrl(url);
0701     cmd->setSubscriptionListView(m_feedListView);
0702     cmd->start();
0703 }
0704 
0705 void MainWidget::slotFeedAddGroup()
0706 {
0707     auto cmd = new CreateFolderCommand(this);
0708     cmd->setParentWidget(this);
0709     cmd->setSelectedSubscription(m_selectionController->selectedSubscription());
0710     cmd->setRootFolder(m_feedList->allFeedsFolder());
0711     cmd->setSubscriptionListView(m_feedListView);
0712     cmd->start();
0713 }
0714 
0715 void MainWidget::slotFeedRemove()
0716 {
0717     TreeNode *selectedNode = m_selectionController->selectedSubscription();
0718 
0719     // don't delete root element! (safety valve)
0720     if (!selectedNode || selectedNode == m_feedList->allFeedsFolder()) {
0721         return;
0722     }
0723 
0724     auto cmd = new DeleteSubscriptionCommand(this);
0725     cmd->setParentWidget(this);
0726     cmd->setSubscription(m_feedList, selectedNode->id());
0727     cmd->start();
0728 }
0729 
0730 void MainWidget::slotFeedModify()
0731 {
0732     TreeNode *const node = m_selectionController->selectedSubscription();
0733     if (!node) {
0734         return;
0735     }
0736     auto cmd = new EditSubscriptionCommand(this);
0737     cmd->setParentWidget(this);
0738     cmd->setSubscription(m_feedList, node->id());
0739     cmd->setSubscriptionListView(m_feedListView);
0740     cmd->start();
0741 }
0742 
0743 void MainWidget::slotNextUnreadArticle()
0744 {
0745     ensureArticleTabVisible();
0746     if (m_viewMode == CombinedView) {
0747         m_feedListView->slotNextUnreadFeed();
0748         return;
0749     }
0750     TreeNode *sel = m_selectionController->selectedSubscription();
0751     if (sel && sel->unread() > 0) {
0752         m_articleListView->slotNextUnreadArticle();
0753     } else {
0754         m_feedListView->slotNextUnreadFeed();
0755     }
0756 }
0757 
0758 void MainWidget::slotPrevUnreadArticle()
0759 {
0760     ensureArticleTabVisible();
0761     if (m_viewMode == CombinedView) {
0762         m_feedListView->slotPrevUnreadFeed();
0763         return;
0764     }
0765     TreeNode *sel = m_selectionController->selectedSubscription();
0766     if (sel && sel->unread() > 0) {
0767         m_articleListView->slotPreviousUnreadArticle();
0768     } else {
0769         m_feedListView->slotPrevUnreadFeed();
0770     }
0771 }
0772 
0773 bool MainWidget::confirmMarkFeedAsRead(bool isSingleFeed, bool isGroup)
0774 {
0775     QString msg, caption;
0776     if (isSingleFeed && !isGroup) {
0777         msg = i18n("<qt>Are you sure you want to mark <b>all articles in the feed</b> as read?</qt>");
0778         caption = i18nc("@title:window", "Mark Feed as Read");
0779     } else {
0780         if (!isGroup) {
0781             msg = i18n("<qt>Are you sure you want to mark <b>all feeds</b> as read?</qt>");
0782         } else {
0783             msg = i18n("<qt>Are you sure you want to mark <b>all feeds in the folder</b> as read?</qt>");
0784         }
0785         caption = i18nc("@title:window", "Mark Feeds as Read");
0786     }
0787     if (KMessageBox::warningContinueCancel(this,
0788                                            msg,
0789                                            caption,
0790                                            KStandardGuiItem::cont(),
0791                                            KStandardGuiItem::cancel(),
0792                                            QStringLiteral("Disable Mark Feed As Read Confirmation"))
0793         != KMessageBox::Continue) {
0794         return false;
0795     }
0796     return true;
0797 }
0798 
0799 void MainWidget::slotMarkAllFeedsRead()
0800 {
0801     if (!confirmMarkFeedAsRead(false, false)) {
0802         return;
0803     }
0804     KJob *job = m_feedList->createMarkAsReadJob();
0805     connect(job, &KJob::finished, m_selectionController, &AbstractSelectionController::forceFilterUpdate);
0806     job->start();
0807 }
0808 
0809 void MainWidget::slotMarkAllRead()
0810 {
0811     TreeNode *current = m_selectionController->selectedSubscription();
0812     if (!current || !confirmMarkFeedAsRead(true, current->isGroup())) {
0813         return;
0814     }
0815     KJob *job = current->createMarkAsReadJob();
0816     connect(job, &KJob::finished, m_selectionController, &AbstractSelectionController::forceFilterUpdate);
0817     job->start();
0818 }
0819 
0820 void MainWidget::slotSetTotalUnread()
0821 {
0822     Q_EMIT signalUnreadCountChanged(m_feedList ? m_feedList->unread() : 0);
0823 }
0824 
0825 void MainWidget::slotDoIntervalFetches()
0826 {
0827     if (!m_feedList) {
0828         return;
0829     }
0830 #if 0
0831     // the following solid check apparently doesn't work reliably and causes
0832     // interval fetching not working although the user is actually online (but solid reports he's not
0833     const Networking::Status status = Solid::Networking::status();
0834     if (status != Networking::Connected && status != Networking::Unknown) {
0835         return;
0836     }
0837 #endif
0838     m_feedList->addToFetchQueue(Kernel::self()->fetchQueue(), true);
0839 }
0840 
0841 void MainWidget::slotFetchCurrentFeed()
0842 {
0843     if (!m_selectionController->selectedSubscription()) {
0844         return;
0845     }
0846     if (isNetworkAvailable()) {
0847         m_selectionController->selectedSubscription()->slotAddToFetchQueue(Kernel::self()->fetchQueue());
0848     } else {
0849         m_mainFrame->slotSetStatusText(i18n("Networking is not available."));
0850     }
0851 }
0852 
0853 void MainWidget::slotFetchAllFeeds()
0854 {
0855     if (m_feedList && isNetworkAvailable()) {
0856         m_feedList->addToFetchQueue(Kernel::self()->fetchQueue());
0857     } else if (m_feedList) {
0858         m_mainFrame->slotSetStatusText(i18n("Networking is not available."));
0859     }
0860 }
0861 
0862 void MainWidget::slotFetchingStarted()
0863 {
0864     m_mainFrame->slotSetState(Frame::Started);
0865     m_actionManager->action(QStringLiteral("feed_stop"))->setEnabled(true);
0866     m_mainFrame->slotSetStatusText(i18n("Fetching Feeds..."));
0867 }
0868 
0869 void MainWidget::slotFetchingStopped()
0870 {
0871     m_mainFrame->slotSetState(Frame::Completed);
0872     m_actionManager->action(QStringLiteral("feed_stop"))->setEnabled(false);
0873     m_mainFrame->slotSetStatusText(QString());
0874 }
0875 
0876 void MainWidget::slotArticleSelected(const Akregator::Article &article)
0877 {
0878     if (m_viewMode == CombinedView) {
0879         return;
0880     }
0881 
0882     m_markReadTimer->stop();
0883 
0884     Q_ASSERT(article.isNull() || article.feed());
0885 
0886     QList<Article> articles = m_selectionController->selectedArticles();
0887     Q_EMIT signalArticlesSelected(articles);
0888 
0889     auto const maai = qobject_cast<KToggleAction *>(m_actionManager->action(QStringLiteral("article_set_status_important")));
0890     Q_ASSERT(maai);
0891     maai->setChecked(article.keep());
0892 
0893     m_articleViewer->showArticle(article);
0894     if (m_selectionController->selectedArticles().isEmpty()) {
0895         m_articleListView->setCurrentIndex(m_selectionController->currentArticleIndex());
0896     }
0897 
0898     if (article.isNull() || article.status() == Akregator::Read) {
0899         return;
0900     }
0901 
0902     if (!Settings::useMarkReadDelay()) {
0903         return;
0904     }
0905 
0906     const int delay = Settings::markReadDelay();
0907 
0908     if (delay > 0) {
0909         m_markReadTimer->start(delay * 1000);
0910     } else {
0911         auto job = new Akregator::ArticleModifyJob;
0912         const Akregator::ArticleId aid = {article.feed()->xmlUrl(), article.guid()};
0913         job->setStatus(aid, Akregator::Read);
0914         job->start();
0915     }
0916 }
0917 
0918 void MainWidget::slotMouseButtonPressed(int button, const QUrl &url)
0919 {
0920     if (button != Qt::MiddleButton) {
0921         return;
0922     }
0923 
0924     if (!url.isValid()) {
0925         return;
0926     }
0927 
0928     OpenUrlRequest req(url);
0929 
0930     switch (Settings::mMBBehaviour()) {
0931     case Settings::EnumMMBBehaviour::OpenInExternalBrowser:
0932         req.setOptions(OpenUrlRequest::ExternalBrowser);
0933         break;
0934     case Settings::EnumMMBBehaviour::OpenInBackground:
0935         req.setOptions(OpenUrlRequest::NewTab);
0936         req.setOpenInBackground(true);
0937         break;
0938     default:
0939         req.setOptions(OpenUrlRequest::NewTab);
0940         req.setOpenInBackground(false);
0941     }
0942 
0943     Kernel::self()->frameManager()->slotOpenUrlRequest(req);
0944 }
0945 
0946 void MainWidget::slotOpenHomepage()
0947 {
0948     Feed *feed = dynamic_cast<Feed *>(m_selectionController->selectedSubscription());
0949 
0950     if (!feed) {
0951         return;
0952     }
0953 
0954     QUrl url(feed->htmlUrl());
0955 
0956     if (url.isValid()) {
0957         OpenUrlRequest req(url);
0958         req.setOptions(OpenUrlRequest::ExternalBrowser);
0959         Kernel::self()->frameManager()->slotOpenUrlRequest(req);
0960     }
0961 }
0962 
0963 void MainWidget::slotOpenSelectedArticlesInBrowser()
0964 {
0965     const QList<Article> articles = m_selectionController->selectedArticles();
0966 
0967     for (const Akregator::Article &article : articles) {
0968         slotOpenArticleInBrowser(article);
0969     }
0970 }
0971 
0972 void MainWidget::slotOpenArticleInBrowser(const Akregator::Article &article)
0973 {
0974     if (!article.isNull() && article.link().isValid()) {
0975         OpenUrlRequest req(article.link());
0976         req.setOptions(OpenUrlRequest::ExternalBrowser);
0977         Kernel::self()->frameManager()->slotOpenUrlRequest(req);
0978     }
0979 }
0980 
0981 void MainWidget::openSelectedArticles(bool openInBackground)
0982 {
0983     const QList<Article> articles = m_selectionController->selectedArticles();
0984 
0985     for (const Akregator::Article &article : articles) {
0986         const QUrl url = article.link();
0987         if (!url.isValid()) {
0988             continue;
0989         }
0990 
0991         OpenUrlRequest req(url);
0992         req.setOptions(OpenUrlRequest::NewTab);
0993         if (openInBackground) {
0994             req.setOpenInBackground(true);
0995             Kernel::self()->frameManager()->slotOpenUrlRequest(req, false /*don't use settings for open in background*/);
0996         } else {
0997             Kernel::self()->frameManager()->slotOpenUrlRequest(req);
0998         }
0999     }
1000 }
1001 
1002 void MainWidget::currentArticleInfo(QString &link, QString &title)
1003 {
1004     const Article article = m_selectionController->currentArticle();
1005 
1006     if (article.isNull()) {
1007         return;
1008     }
1009     if (article.link().isValid()) {
1010         link = article.link().url();
1011         title = Utils::convertHtmlTags(article.title());
1012     }
1013 }
1014 
1015 void MainWidget::updateQuickSearchLineText()
1016 {
1017     m_searchBar->updateQuickSearchLineText(m_actionManager->quickSearchLineText());
1018 }
1019 
1020 void MainWidget::slotCopyLinkAddress()
1021 {
1022     const Article article = m_selectionController->currentArticle();
1023 
1024     if (article.isNull()) {
1025         return;
1026     }
1027 
1028     QString link;
1029     if (article.link().isValid()) {
1030         link = article.link().url();
1031         QClipboard *cb = QApplication::clipboard();
1032         cb->setText(link, QClipboard::Clipboard);
1033         // don't set url to selection as it's a no-no according to a fd.o spec
1034         // cb->setText(link, QClipboard::Selection);
1035     }
1036 }
1037 
1038 void MainWidget::slotToggleShowQuickFilter()
1039 {
1040     if (Settings::showQuickFilter()) {
1041         Settings::setShowQuickFilter(false);
1042         m_searchBar->slotClearSearch();
1043         m_searchBar->hide();
1044     } else {
1045         Settings::setShowQuickFilter(true);
1046         if (!m_displayingAboutPage) {
1047             m_searchBar->show();
1048         }
1049     }
1050 }
1051 
1052 void MainWidget::slotArticleDelete()
1053 {
1054     if (m_viewMode == CombinedView) {
1055         return;
1056     }
1057 
1058     const QList<Article> articles = m_selectionController->selectedArticles();
1059 
1060     QString msg;
1061     switch (articles.count()) {
1062     case 0:
1063         return;
1064     case 1:
1065         msg = i18n("<qt>Are you sure you want to delete article <b>%1</b>?</qt>", articles.first().title());
1066         break;
1067     default:
1068         msg = i18np("<qt>Are you sure you want to delete the selected article?</qt>",
1069                     "<qt>Are you sure you want to delete the %1 selected articles?</qt>",
1070                     articles.count());
1071     }
1072 
1073     if (KMessageBox::warningContinueCancel(this,
1074                                            msg,
1075                                            i18nc("@title:window", "Delete Article"),
1076                                            KStandardGuiItem::del(),
1077                                            KStandardGuiItem::cancel(),
1078                                            QStringLiteral("Disable delete article confirmation"))
1079         != KMessageBox::Continue) {
1080         return;
1081     }
1082 
1083     TreeNode *const selected = m_selectionController->selectedSubscription();
1084 
1085     if (selected) {
1086         selected->setNotificationMode(false);
1087     }
1088 
1089     auto job = new Akregator::ArticleDeleteJob;
1090     for (const Akregator::Article &i : articles) {
1091         Feed *const feed = i.feed();
1092         if (!feed) {
1093             continue;
1094         }
1095         const Akregator::ArticleId aid = {feed->xmlUrl(), i.guid()};
1096         job->appendArticleId(aid);
1097     }
1098 
1099     job->start();
1100 
1101     if (selected) {
1102         selected->setNotificationMode(true);
1103     }
1104 }
1105 
1106 void MainWidget::slotFramesChanged()
1107 {
1108     // We need to wait till the frame is fully loaded
1109     QMetaObject::invokeMethod(m_part, &Akregator::Part::slotAutoSave, Qt::QueuedConnection);
1110 }
1111 
1112 void MainWidget::slotArticleToggleKeepFlag(bool)
1113 {
1114     const QList<Article> articles = m_selectionController->selectedArticles();
1115 
1116     if (articles.isEmpty()) {
1117         return;
1118     }
1119 
1120     bool allFlagsSet = true;
1121     for (const Akregator::Article &i : articles) {
1122         allFlagsSet = allFlagsSet && i.keep();
1123         if (!allFlagsSet) {
1124             break;
1125         }
1126     }
1127 
1128     auto job = new Akregator::ArticleModifyJob;
1129     for (const Akregator::Article &i : articles) {
1130         const Akregator::ArticleId aid = {i.feed()->xmlUrl(), i.guid()};
1131         job->setKeep(aid, !allFlagsSet);
1132     }
1133     job->start();
1134 }
1135 
1136 namespace
1137 {
1138 void setArticleStatus(const QString &feedUrl, const QString &articleId, int status)
1139 {
1140     if (!feedUrl.isEmpty() && !articleId.isEmpty()) {
1141         auto job = new Akregator::ArticleModifyJob;
1142         const Akregator::ArticleId aid = {feedUrl, articleId};
1143         job->setStatus(aid, status);
1144         job->start();
1145     }
1146 }
1147 
1148 void setSelectedArticleStatus(const Akregator::AbstractSelectionController *controller, int status)
1149 {
1150     const QList<Akregator::Article> articles = controller->selectedArticles();
1151 
1152     if (articles.isEmpty()) {
1153         return;
1154     }
1155 
1156     auto job = new Akregator::ArticleModifyJob;
1157     for (const Akregator::Article &i : articles) {
1158         const Akregator::ArticleId aid = {i.feed()->xmlUrl(), i.guid()};
1159         job->setStatus(aid, status);
1160     }
1161     job->start();
1162 }
1163 }
1164 
1165 void MainWidget::slotSetSelectedArticleRead()
1166 {
1167     ::setSelectedArticleStatus(m_selectionController, Akregator::Read);
1168 }
1169 
1170 void MainWidget::slotSetSelectedArticleUnread()
1171 {
1172     ::setSelectedArticleStatus(m_selectionController, Akregator::Unread);
1173 }
1174 
1175 void MainWidget::slotSetSelectedArticleNew()
1176 {
1177     ::setSelectedArticleStatus(m_selectionController, Akregator::New);
1178 }
1179 
1180 void MainWidget::slotSetCurrentArticleReadDelayed()
1181 {
1182     const Article article = m_selectionController->currentArticle();
1183 
1184     if (article.isNull()) {
1185         return;
1186     }
1187 
1188     auto const job = new Akregator::ArticleModifyJob;
1189     const Akregator::ArticleId aid = {article.feed()->xmlUrl(), article.guid()};
1190     job->setStatus(aid, Akregator::Read);
1191     job->start();
1192 }
1193 
1194 void MainWidget::slotShowStatusBarMessage(const QString &msg)
1195 {
1196     PimCommon::BroadcastStatus::instance()->setStatusMsg(msg);
1197 }
1198 
1199 void MainWidget::readProperties(const KConfigGroup &config)
1200 {
1201     if (!Settings::resetQuickFilterOnNodeChange()) {
1202         // read filter settings
1203         m_searchBar->slotSetText(config.readEntry("searchLine"));
1204         m_searchBar->slotSetStatus(config.readEntry("searchCombo").toInt());
1205     }
1206     const QString currentTabName = config.readEntry("CurrentTab");
1207     // Reopen tabs
1208     const QStringList childList = config.readEntry(QStringLiteral("Children"), QStringList());
1209     int currentFrameId = -1;
1210     for (const QString &framePrefix : childList) {
1211         auto const frame = new WebEngineFrame(m_actionManager->actionCollection(), m_tabWidget);
1212         frame->loadConfig(config, framePrefix + QLatin1Char('_'));
1213 
1214         connectFrame(frame);
1215         Kernel::self()->frameManager()->slotAddFrame(frame);
1216         if (currentTabName == framePrefix) {
1217             currentFrameId = frame->id();
1218         }
1219     }
1220     if (currentFrameId != -1) {
1221         m_tabWidget->slotSelectFrame(currentFrameId);
1222     }
1223 }
1224 
1225 void MainWidget::saveProperties(KConfigGroup &config)
1226 {
1227     // save filter settings
1228     const QString searchStr(m_searchBar->text());
1229     if (searchStr.isEmpty()) {
1230         config.deleteEntry("searchLine");
1231     } else {
1232         config.writeEntry("searchLine", m_searchBar->text());
1233     }
1234     config.writeEntry("searchCombo", static_cast<int>(m_searchBar->status()));
1235 
1236     Kernel::self()->frameManager()->saveProperties(config);
1237 }
1238 
1239 void MainWidget::ensureArticleTabVisible()
1240 {
1241     m_tabWidget->setCurrentWidget(m_mainFrame);
1242 }
1243 
1244 void MainWidget::slotReloadAllTabs()
1245 {
1246     m_tabWidget->slotReloadAllTabs();
1247 }
1248 
1249 bool MainWidget::isNetworkAvailable() const
1250 {
1251     return PimCommon::NetworkManager::self()->isOnline();
1252 }
1253 
1254 void MainWidget::slotNetworkStatusChanged(bool status)
1255 {
1256     if (status) {
1257         m_mainFrame->slotSetStatusText(i18n("Networking is available now."));
1258         this->slotFetchAllFeeds();
1259     } else {
1260         m_mainFrame->slotSetStatusText(i18n("Networking is not available."));
1261     }
1262 }
1263 
1264 void MainWidget::slotOpenSelectedArticles()
1265 {
1266     openSelectedArticles(false);
1267 }
1268 
1269 void MainWidget::slotOpenSelectedArticlesInBackground()
1270 {
1271     openSelectedArticles(true);
1272 }
1273 
1274 void MainWidget::slotCurrentFrameChanged(int frameId)
1275 {
1276     Kernel::self()->frameManager()->slotChangeFrame(frameId);
1277     m_actionManager->zoomActionMenu()->setZoomFactor(Kernel::self()->frameManager()->currentFrame()->zoomFactor() * 100);
1278 }
1279 
1280 void MainWidget::slotFocusQuickSearch()
1281 {
1282     m_searchBar->setFocusSearchLine();
1283 }
1284 
1285 void MainWidget::slotArticleAction(Akregator::ArticleViewerWebEngine::ArticleAction type, const QString &articleId, const QString &feed)
1286 {
1287     switch (type) {
1288     case ArticleViewerWebEngine::DeleteAction: {
1289         auto job = new Akregator::ArticleDeleteJob;
1290         const Akregator::ArticleId aid = {feed, articleId};
1291         job->appendArticleId(aid);
1292         job->start();
1293         break;
1294     }
1295     case ArticleViewerWebEngine::MarkAsRead:
1296         ::setArticleStatus(feed, articleId, Akregator::Read);
1297         break;
1298     case ArticleViewerWebEngine::MarkAsUnRead:
1299         ::setArticleStatus(feed, articleId, Akregator::Unread);
1300         break;
1301 
1302     case ArticleViewerWebEngine::MarkAsImportant: {
1303         auto job = new Akregator::ArticleModifyJob;
1304         const Akregator::Article article = m_feedList->findArticle(feed, articleId);
1305         const Akregator::ArticleId aid = {feed, articleId};
1306         job->setKeep(aid, !article.keep());
1307         job->start();
1308         break;
1309     }
1310     case ArticleViewerWebEngine::SendUrlArticle: {
1311     case ArticleViewerWebEngine::SendFileArticle:
1312         const Article article = m_feedList->findArticle(feed, articleId);
1313         const QByteArray text = article.link().toDisplayString().toLatin1();
1314         const QString title = Akregator::Utils::convertHtmlTags(article.title());
1315         if (text.isEmpty()) {
1316             return;
1317         }
1318         sendArticle(text, title, (type == ArticleViewerWebEngine::SendFileArticle));
1319         break;
1320     }
1321     case ArticleViewerWebEngine::OpenInBackgroundTab: {
1322         const Akregator::Article article = m_feedList->findArticle(feed, articleId);
1323         const QUrl url = article.link();
1324         if (url.isValid()) {
1325             OpenUrlRequest req(url);
1326             req.setOptions(OpenUrlRequest::NewTab);
1327             req.setOpenInBackground(true);
1328             Kernel::self()->frameManager()->slotOpenUrlRequest(req, false /*don't use settings for open in background*/);
1329         }
1330         break;
1331     }
1332     case ArticleViewerWebEngine::OpenInExternalBrowser: {
1333         const Akregator::Article article = m_feedList->findArticle(feed, articleId);
1334         slotOpenArticleInBrowser(article);
1335         break;
1336     }
1337     }
1338 }
1339 
1340 #include "moc_mainwidget.cpp"