Make it compiles when deprecated method are disabled. Add more find package
[kdepim.git] / akregator / src / mainwidget.cpp
blob7ecc8805ef9f6fc24705add815f1feba768ec8dc
1 /*
2 This file is part of Akregator.
4 Copyright (C) 2004 Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
5 2004 Sashmit Bhaduri <smt@vfemail.net>
6 2005 Frank Osterfeld <osterfeld@kde.org>
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 As a special exception, permission is given to link this program
23 with any edition of Qt, and distribute the resulting executable,
24 without including the source code for Qt in the source distribution.
27 #include "mainwidget.h"
28 #include "utils.h"
29 #include "actionmanagerimpl.h"
30 #include "addfeeddialog.h"
31 #include "articlelistview.h"
32 #include "articleviewerwidget.h"
33 #include "abstractselectioncontroller.h"
34 #include "articlejobs.h"
35 #include "articlematcher.h"
36 #include "akregatorconfig.h"
37 #include "akregator_part.h"
38 #include "Libkdepim/BroadcastStatus"
39 #include "createfeedcommand.h"
40 #include "createfoldercommand.h"
41 #include "deletesubscriptioncommand.h"
42 #include "editsubscriptioncommand.h"
43 #include "expireitemscommand.h"
44 #include "importfeedlistcommand.h"
45 #include "feed.h"
46 #include "feedlist.h"
47 #include "feedpropertiesdialog.h"
48 #include "fetchqueue.h"
49 #include "folder.h"
50 #include "framemanager.h"
51 #include "kernel.h"
52 #include "notificationmanager.h"
53 #include "openurlrequest.h"
54 #include "progressmanager.h"
55 #include "widgets/searchbar.h"
56 #include "selectioncontroller.h"
57 #include "subscriptionlistjobs.h"
58 #include "subscriptionlistmodel.h"
59 #include "subscriptionlistview.h"
60 #include "tabwidget.h"
61 #include "treenode.h"
62 #include "treenodevisitor.h"
63 #include "types.h"
64 #include "mainframe.h"
65 #include <WebEngineViewer/ZoomActionMenu>
66 #include "job/downloadarticlejob.h"
68 #include <QAction>
69 #include <kfileitem.h>
70 #include <KLocalizedString>
71 #include <kmessagebox.h>
72 #include <krandom.h>
73 #include <kshell.h>
75 #include <ktoggleaction.h>
76 #include <QUrl>
78 #include <QClipboard>
79 #include <QNetworkConfigurationManager>
80 #include <QSplitter>
81 #include <QTextDocument>
82 #include <QDomDocument>
83 #include <QTimer>
84 #include <QDesktopServices>
85 #include <QUrlQuery>
87 #include <algorithm>
88 #include <memory>
89 #include <cassert>
91 #include <webengine/webengineframe.h>
92 #include "articleviewer-ng/webengine/articleviewerwebenginewidgetng.h"
94 using namespace Akregator;
96 MainWidget::~MainWidget()
98 // if m_shuttingDown is false, slotOnShutdown was not called. That
99 // means that not the whole app is shutdown, only the part. So it
100 // should be no risk to do the cleanups now
101 if (!m_shuttingDown) {
102 slotOnShutdown();
106 MainWidget::MainWidget(Part *part, QWidget *parent, ActionManagerImpl *actionManager, const QString &name)
107 : QWidget(parent),
108 m_feedList(),
109 m_viewMode(NormalView),
110 m_actionManager(actionManager),
111 m_feedListManagementInterface(new FeedListManagementImpl),
112 m_networkConfigManager(new QNetworkConfigurationManager(this))
114 setObjectName(name);
116 FeedListManagementInterface::setInstance(m_feedListManagementInterface);
118 m_actionManager->initMainWidget(this);
119 m_part = part;
120 m_shuttingDown = false;
121 m_displayingAboutPage = false;
122 setFocusPolicy(Qt::StrongFocus);
124 QVBoxLayout *lt = new QVBoxLayout(this);
125 lt->setMargin(0);
127 m_horizontalSplitter = new QSplitter(Qt::Horizontal, this);
129 m_horizontalSplitter->setOpaqueResize(true);
130 m_horizontalSplitter->setChildrenCollapsible(false);
131 lt->addWidget(m_horizontalSplitter);
133 connect(Kernel::self()->fetchQueue(), &FetchQueue::signalStarted,
134 this, &MainWidget::slotFetchingStarted);
135 connect(Kernel::self()->fetchQueue(), &FetchQueue::signalStopped,
136 this, &MainWidget::slotFetchingStopped);
138 m_feedListView = new SubscriptionListView(m_horizontalSplitter);
139 m_feedListView->setObjectName(QStringLiteral("feedtree"));
140 m_actionManager->initSubscriptionListView(m_feedListView);
142 connect(m_feedListView, &SubscriptionListView::userActionTakingPlace,
143 this, &MainWidget::ensureArticleTabVisible);
145 m_tabWidget = new TabWidget(m_horizontalSplitter);
146 m_actionManager->initTabWidget(m_tabWidget);
148 connect(m_part, &Part::signalSettingsChanged,
149 this, &MainWidget::slotSettingsChanged);
151 connect(m_tabWidget, &TabWidget::signalCurrentFrameChanged,
152 this, &MainWidget::slotCurrentFrameChanged);
154 connect(m_tabWidget, &TabWidget::signalRemoveFrameRequest,
155 Kernel::self()->frameManager(), &FrameManager::slotRemoveFrame);
157 connect(m_tabWidget, SIGNAL(signalOpenUrlRequest(Akregator::OpenUrlRequest&)),
158 Kernel::self()->frameManager(), SLOT(slotOpenUrlRequest(Akregator::OpenUrlRequest&)));
160 connect(Kernel::self()->frameManager(), &FrameManager::signalFrameAdded,
161 m_tabWidget, &TabWidget::slotAddFrame);
163 connect(Kernel::self()->frameManager(), &FrameManager::signalSelectFrame,
164 m_tabWidget, &TabWidget::slotSelectFrame);
166 connect(Kernel::self()->frameManager(), &FrameManager::signalFrameRemoved,
167 m_tabWidget, &TabWidget::slotRemoveFrame);
169 connect(Kernel::self()->frameManager(), &FrameManager::signalRequestNewFrame,
170 this, &MainWidget::slotRequestNewFrame);
172 connect(Kernel::self()->frameManager(), &FrameManager::signalFrameRemoved,
173 this, &MainWidget::slotFramesChanged);
174 connect(Kernel::self()->frameManager(), &FrameManager::signalCompleted,
175 this, &MainWidget::slotFramesChanged);
177 connect(m_networkConfigManager, &QNetworkConfigurationManager::onlineStateChanged,
178 this, &MainWidget::slotNetworkStatusChanged);
180 m_tabWidget->setWhatsThis(i18n("You can view multiple articles in several open tabs."));
182 m_mainTab = new QWidget(this);
183 m_mainTab->setObjectName(QStringLiteral("Article Tab"));
184 m_mainTab->setWhatsThis(i18n("Articles list."));
186 QVBoxLayout *mainTabLayout = new QVBoxLayout(m_mainTab);
187 mainTabLayout->setMargin(0);
189 m_searchBar = new SearchBar(m_mainTab);
190 if (!Settings::showQuickFilter()) {
191 m_searchBar->hide();
193 m_articleSplitter = new QSplitter(Qt::Vertical, m_mainTab);
194 m_articleSplitter->setObjectName(QStringLiteral("panner2"));
195 m_articleSplitter->setChildrenCollapsible(false);
197 m_articleWidget = new QWidget(m_articleSplitter);
198 QVBoxLayout *articleWidgetLayout = new QVBoxLayout;
199 m_articleWidget->setLayout(articleWidgetLayout);
200 articleWidgetLayout->setMargin(0);
201 articleWidgetLayout->setSpacing(0);
203 m_articleListView = new ArticleListView;
204 articleWidgetLayout->addWidget(m_searchBar);
205 articleWidgetLayout->addWidget(m_articleListView);
206 connect(m_articleListView, &ArticleListView::userActionTakingPlace,
207 this, &MainWidget::ensureArticleTabVisible);
209 m_selectionController = new SelectionController(this);
210 m_selectionController->setArticleLister(m_articleListView);
211 m_selectionController->setFeedSelector(m_feedListView);
213 connect(m_searchBar, &SearchBar::signalSearch,
214 m_selectionController, &AbstractSelectionController::setFilters);
216 FolderExpansionHandler *expansionHandler = new FolderExpansionHandler(this);
217 connect(m_feedListView, &QTreeView::expanded, expansionHandler, &FolderExpansionHandler::itemExpanded);
218 connect(m_feedListView, &QTreeView::collapsed, expansionHandler, &FolderExpansionHandler::itemCollapsed);
220 m_selectionController->setFolderExpansionHandler(expansionHandler);
222 connect(m_selectionController, &AbstractSelectionController::currentSubscriptionChanged,
223 this, &MainWidget::slotNodeSelected);
225 connect(m_selectionController, &AbstractSelectionController::currentArticleChanged,
226 this, &MainWidget::slotArticleSelected);
228 connect(m_selectionController, &AbstractSelectionController::articleDoubleClicked,
229 this, &MainWidget::slotOpenArticleInBrowser);
231 m_actionManager->initArticleListView(m_articleListView);
233 connect(m_articleListView, &ArticleListView::signalMouseButtonPressed,
234 this, &MainWidget::slotMouseButtonPressed);
236 m_articleViewer = new ArticleViewerWidget(Settings::grantleeDirectory(), m_actionManager->actionCollection(), m_articleSplitter);
237 m_articleListView->setFocusProxy(m_articleViewer);
238 setFocusProxy(m_articleViewer);
240 connect(m_articleViewer, &ArticleViewerWidget::showStatusBarMessage, this, &MainWidget::slotShowStatusBarMessage);
241 connect(m_articleViewer, SIGNAL(signalOpenUrlRequest(Akregator::OpenUrlRequest&)),
242 Kernel::self()->frameManager(), SLOT(slotOpenUrlRequest(Akregator::OpenUrlRequest&)));
243 connect(m_searchBar, &SearchBar::signalSearch,
244 m_articleViewer, &ArticleViewerWidget::setFilters);
245 mainTabLayout->addWidget(m_articleSplitter);
247 m_mainFrame = new MainFrame(this, m_mainTab);
248 m_mainFrame->slotSetTitle(i18n("Articles"));
249 m_mainFrame->setArticleViewer(m_articleViewer);
250 connect(m_articleViewer->articleViewerWidgetNg()->articleViewerNg(), &ArticleViewerWebEngine::articleAction, this, &MainWidget::slotArticleAction);
251 connect(m_tabWidget, &TabWidget::signalCopyInFrame, m_mainFrame, &MainFrame::slotCopyInFrame);
252 connect(m_tabWidget, &TabWidget::signalPrintInFrame, m_mainFrame, &MainFrame::slotPrintInFrame);
253 connect(m_tabWidget, &TabWidget::signalZoomChangedInFrame, m_mainFrame, &MainFrame::slotZoomChangeInFrame);
254 connect(m_tabWidget, &TabWidget::signalPrintPreviewInFrame, m_mainFrame, &MainFrame::slotPrintPreviewInFrame);
255 connect(m_tabWidget, &TabWidget::signalFindTextInFrame, m_mainFrame, &MainFrame::slotFindTextInFrame);
256 connect(m_tabWidget, &TabWidget::signalTextToSpeechInFrame, m_mainFrame, &MainFrame::slotTextToSpeechInFrame);
257 connect(m_tabWidget, &TabWidget::signalSaveLinkAsInFrame, m_mainFrame, &MainFrame::slotSaveLinkAsInFrame);
258 connect(m_tabWidget, &TabWidget::signalCopyLinkAsInFrame, m_mainFrame, &MainFrame::slotCopyLinkAsInFrame);
259 connect(m_tabWidget, &TabWidget::signalCopyImageLocation, m_mainFrame, &MainFrame::slotCopyImageLocationInFrame);
260 connect(m_tabWidget, &TabWidget::signalSaveImageOnDisk, m_mainFrame, &MainFrame::slotSaveImageOnDiskInFrame);
261 connect(m_tabWidget, &TabWidget::signalMute, m_mainFrame, &MainFrame::slotMute);
263 Kernel::self()->frameManager()->slotAddFrame(m_mainFrame);
265 const QList<int> sp1sizes = Settings::splitter1Sizes();
266 if (sp1sizes.count() >= m_horizontalSplitter->count()) {
267 m_horizontalSplitter->setSizes(sp1sizes);
269 const QList<int> sp2sizes = Settings::splitter2Sizes();
270 if (sp2sizes.count() >= m_articleSplitter->count()) {
271 m_articleSplitter->setSizes(sp2sizes);
274 if (!Settings::self()->disableIntroduction()) {
275 m_articleWidget->hide();
276 m_articleViewer->displayAboutPage();
277 m_mainFrame->slotSetTitle(i18n("About"));
278 m_displayingAboutPage = true;
281 m_fetchTimer = new QTimer(this);
282 connect(m_fetchTimer, &QTimer::timeout,
283 this, &MainWidget::slotDoIntervalFetches);
284 m_fetchTimer->start(1000 * 60);
286 // delete expired articles once per hour
287 m_expiryTimer = new QTimer(this);
288 connect(m_expiryTimer, &QTimer::timeout,
289 this, &MainWidget::slotDeleteExpiredArticles);
290 m_expiryTimer->start(3600 * 1000);
292 m_markReadTimer = new QTimer(this);
293 m_markReadTimer->setSingleShot(true);
294 connect(m_markReadTimer, &QTimer::timeout, this, &MainWidget::slotSetCurrentArticleReadDelayed);
296 setFeedList(QSharedPointer<FeedList>(new FeedList(Kernel::self()->storage())));
298 switch (Settings::viewMode()) {
299 case CombinedView:
300 slotCombinedView();
301 break;
302 case WidescreenView:
303 slotWidescreenView();
304 break;
305 default:
306 slotNormalView();
309 if (!Settings::resetQuickFilterOnNodeChange()) {
310 m_searchBar->slotSetStatus(Settings::statusFilter());
311 m_searchBar->slotSetText(Settings::textFilter());
315 void MainWidget::slotSettingsChanged()
317 m_tabWidget->slotSettingsChanged();
318 m_articleViewer->updateAfterConfigChanged();
321 void MainWidget::slotOnShutdown()
323 disconnect(m_tabWidget, &TabWidget::signalCurrentFrameChanged,
324 this, &MainWidget::slotCurrentFrameChanged);
326 m_shuttingDown = true;
328 // close all pageviewers in a controlled way
329 // fixes bug 91660, at least when no part loading data
330 while (m_tabWidget->count() > 1) { // remove frames until only the main frame remains
331 m_tabWidget->setCurrentIndex(m_tabWidget->count() - 1); // select last page
332 m_tabWidget->slotRemoveCurrentFrame();
335 Kernel::self()->fetchQueue()->slotAbort();
336 setFeedList(QSharedPointer<FeedList>());
338 delete m_feedListManagementInterface;
339 delete m_feedListView; // call delete here, so that the header settings will get saved
340 delete m_articleListView; // same for this one
342 delete m_mainTab;
343 delete m_mainFrame;
344 m_mainFrame = 0;
346 Settings::self()->save();
349 void MainWidget::saveSettings()
351 const QList<int> spl1 = m_horizontalSplitter->sizes();
352 if (std::count(spl1.begin(), spl1.end(), 0) == 0) {
353 Settings::setSplitter1Sizes(spl1);
355 const QList<int> spl2 = m_articleSplitter->sizes();
356 if (std::count(spl2.begin(), spl2.end(), 0) == 0) {
357 Settings::setSplitter2Sizes(spl2);
359 Settings::setViewMode(m_viewMode);
360 Settings::self()->save();
363 void MainWidget::connectFrame(Akregator::WebEngineFrame *frame)
365 connect(m_tabWidget, &TabWidget::signalCopyInFrame, frame, &WebEngineFrame::slotCopyInFrame);
366 connect(m_tabWidget, &TabWidget::signalPrintInFrame, frame, &WebEngineFrame::slotPrintInFrame);
367 connect(m_tabWidget, &TabWidget::signalZoomChangedInFrame, frame, &WebEngineFrame::slotZoomChangeInFrame);
368 connect(m_tabWidget, &TabWidget::signalPrintPreviewInFrame, frame, &WebEngineFrame::slotPrintPreviewInFrame);
369 connect(m_tabWidget, &TabWidget::signalFindTextInFrame, frame, &WebEngineFrame::slotFindTextInFrame);
370 connect(m_tabWidget, &TabWidget::signalTextToSpeechInFrame, frame, &WebEngineFrame::slotTextToSpeechInFrame);
371 connect(m_tabWidget, &TabWidget::signalSaveLinkAsInFrame, frame, &WebEngineFrame::slotSaveLinkAsInFrame);
372 connect(m_tabWidget, &TabWidget::signalCopyLinkAsInFrame, frame, &WebEngineFrame::slotCopyLinkAsInFrame);
373 connect(m_tabWidget, &TabWidget::signalCopyImageLocation, frame, &WebEngineFrame::slotCopyImageLocationInFrame);
374 connect(m_tabWidget, &TabWidget::signalSaveImageOnDisk, frame, &WebEngineFrame::slotSaveImageOnDiskInFrame);
375 connect(m_tabWidget, &TabWidget::signalMute, frame, &WebEngineFrame::slotMute);
377 connect(frame, &WebEngineFrame::showStatusBarMessage, this, &MainWidget::slotShowStatusBarMessage);
378 connect(frame, &WebEngineFrame::signalIconChanged, m_tabWidget, &TabWidget::slotSetIcon);
379 connect(frame, &WebEngineFrame::webPageMutedOrAudibleChanged, m_tabWidget, &TabWidget::slotWebPageMutedOrAudibleChanged);
382 void MainWidget::slotRequestNewFrame(int &frameId)
384 WebEngineFrame *frame = new WebEngineFrame(m_actionManager->actionCollection(), m_tabWidget);
385 connectFrame(frame);
387 Kernel::self()->frameManager()->slotAddFrame(frame);
389 frameId = frame->id();
392 void MainWidget::sendArticle(bool attach)
394 QByteArray text;
395 QString title;
397 Frame *frame = Kernel::self()->frameManager()->currentFrame();
399 if (frame && frame->id() > 0) { // are we in some other tab than the articlelist?
400 text = frame->url().toString().toLatin1();
401 title = frame->title();
402 } else { // nah, we're in articlelist..
403 const Article article = m_selectionController->currentArticle();
404 if (!article.isNull()) {
405 text = article.link().toDisplayString().toLatin1();
406 title = Akregator::Utils::convertHtmlTags(article.title());
410 if (text.isEmpty()) {
411 return;
413 sendArticle(text, title, attach);
416 void MainWidget::cleanUpDownloadFile()
418 Q_FOREACH (QPointer<Akregator::DownloadArticleJob> job, mListDownloadArticleJobs) {
419 if (job) {
420 job->forceCleanupTemporaryFile();
425 void MainWidget::sendArticle(const QByteArray &text, const QString &title, bool attach)
427 if (attach) {
428 QPointer<Akregator::DownloadArticleJob> download = new Akregator::DownloadArticleJob(this);
429 download->setArticleUrl(QUrl(QString::fromUtf8(text)));
430 download->setText(QString::fromUtf8(text));
431 download->setTitle(title);
432 mListDownloadArticleJobs.append(download);
433 download->start();
434 } else {
435 QUrlQuery query;
436 query.addQueryItem(QStringLiteral("subject"), title);
437 query.addQueryItem(QStringLiteral("body"), QString::fromUtf8(text));
438 QUrl url;
439 url.setScheme(QStringLiteral("mailto"));
440 url.setQuery(query);
441 QDesktopServices::openUrl(url);
445 void MainWidget::importFeedList(const QDomDocument &doc)
447 ImportFeedListCommand *cmd = new ImportFeedListCommand;
448 cmd->setParentWidget(this);
449 cmd->setFeedListDocument(doc);
450 cmd->setTargetList(m_feedList);
451 cmd->start();
454 void MainWidget::setFeedList(const QSharedPointer<FeedList> &list)
456 if (list == m_feedList) {
457 return;
459 const QSharedPointer<FeedList> oldList = m_feedList;
461 m_feedList = list;
462 if (m_feedList) {
463 connect(m_feedList.data(), &FeedList::unreadCountChanged,
464 this, &MainWidget::slotSetTotalUnread);
467 slotSetTotalUnread();
469 m_feedListManagementInterface->setFeedList(m_feedList);
470 Kernel::self()->setFeedList(m_feedList);
471 ProgressManager::self()->setFeedList(m_feedList);
472 m_selectionController->setFeedList(m_feedList);
474 if (oldList) {
475 oldList->disconnect(this);
478 slotDeleteExpiredArticles();
481 void MainWidget::deleteExpiredArticles(const QSharedPointer<FeedList> &list)
483 if (!list) {
484 return;
486 ExpireItemsCommand *cmd = new ExpireItemsCommand(this);
487 cmd->setParentWidget(this);
488 cmd->setFeedList(list);
489 cmd->setFeeds(list->feedIds());
490 cmd->start();
493 void MainWidget::slotDeleteExpiredArticles()
495 deleteExpiredArticles(m_feedList);
498 QDomDocument MainWidget::feedListToOPML()
500 QDomDocument dom;
501 if (m_feedList) {
502 dom = m_feedList->toOpml();
504 return dom;
507 void MainWidget::addFeedToGroup(const QString &url, const QString &groupName)
509 // Locate the group.
510 QList<TreeNode *> namedGroups = m_feedList->findByTitle(groupName);
511 Folder *group = 0;
512 foreach (TreeNode *const candidate, namedGroups) {
513 if (candidate->isGroup()) {
514 group = static_cast<Folder *>(candidate);
515 break;
519 if (!group) {
520 Folder *g = new Folder(groupName);
521 m_feedList->allFeedsFolder()->appendChild(g);
522 group = g;
525 // Invoke the Add Feed dialog with url filled in.
526 addFeed(url, 0, group, true);
529 void MainWidget::slotNormalView()
531 if (m_viewMode == NormalView) {
532 return;
535 if (m_viewMode == CombinedView) {
536 m_articleWidget->show();
538 const Article article = m_selectionController->currentArticle();
540 if (!article.isNull()) {
541 m_articleViewer->showArticle(article);
542 } else {
543 m_articleViewer->slotShowSummary(m_selectionController->selectedSubscription());
547 m_articleSplitter->setOrientation(Qt::Vertical);
548 m_viewMode = NormalView;
550 Settings::setViewMode(m_viewMode);
553 void MainWidget::slotWidescreenView()
555 if (m_viewMode == WidescreenView) {
556 return;
559 if (m_viewMode == CombinedView) {
560 m_articleWidget->show();
562 Article article = m_selectionController->currentArticle();
564 if (!article.isNull()) {
565 m_articleViewer->showArticle(article);
566 } else {
567 m_articleViewer->slotShowSummary(m_selectionController->selectedSubscription());
571 m_articleSplitter->setOrientation(Qt::Horizontal);
572 m_viewMode = WidescreenView;
574 Settings::setViewMode(m_viewMode);
577 void MainWidget::slotCombinedView()
579 if (m_viewMode == CombinedView) {
580 return;
583 m_articleListView->slotClear();
584 m_articleWidget->hide();
585 m_viewMode = CombinedView;
587 Settings::setViewMode(m_viewMode);
590 void MainWidget::slotMoveCurrentNodeUp()
592 TreeNode *current = m_selectionController->selectedSubscription();
593 if (!current) {
594 return;
596 TreeNode *prev = current->prevSibling();
597 Folder *parent = current->parent();
599 if (!prev || !parent) {
600 return;
603 parent->removeChild(prev);
604 parent->insertChild(prev, current);
605 m_feedListView->ensureNodeVisible(current);
608 void MainWidget::slotMoveCurrentNodeDown()
610 TreeNode *current = m_selectionController->selectedSubscription();
611 if (!current) {
612 return;
614 TreeNode *next = current->nextSibling();
615 Folder *parent = current->parent();
617 if (!next || !parent) {
618 return;
621 parent->removeChild(current);
622 parent->insertChild(current, next);
623 m_feedListView->ensureNodeVisible(current);
626 void MainWidget::slotMoveCurrentNodeLeft()
628 TreeNode *current = m_selectionController->selectedSubscription();
629 if (!current || !current->parent() || !current->parent()->parent()) {
630 return;
633 Folder *parent = current->parent();
634 Folder *grandparent = current->parent()->parent();
636 parent->removeChild(current);
637 grandparent->insertChild(current, parent);
638 m_feedListView->ensureNodeVisible(current);
641 void MainWidget::slotMoveCurrentNodeRight()
643 TreeNode *current = m_selectionController->selectedSubscription();
644 if (!current || !current->parent()) {
645 return;
647 TreeNode *prev = current->prevSibling();
649 if (prev && prev->isGroup()) {
650 Folder *fg = static_cast<Folder *>(prev);
651 current->parent()->removeChild(current);
652 fg->appendChild(current);
653 m_feedListView->ensureNodeVisible(current);
657 void MainWidget::slotSendLink()
659 sendArticle();
662 void MainWidget::slotSendFile()
664 sendArticle(true);
667 void MainWidget::slotNodeSelected(TreeNode *node)
669 m_markReadTimer->stop();
671 if (m_displayingAboutPage) {
672 m_mainFrame->slotSetTitle(i18n("Articles"));
673 if (m_viewMode != CombinedView) {
674 m_articleWidget->show();
676 if (Settings::showQuickFilter()) {
677 m_searchBar->show();
679 m_displayingAboutPage = false;
682 m_tabWidget->setCurrentWidget(m_mainFrame);
683 if (Settings::resetQuickFilterOnNodeChange()) {
684 m_searchBar->slotClearSearch();
687 if (m_viewMode == CombinedView) {
688 m_articleViewer->showNode(node);
689 } else {
690 m_articleViewer->slotShowSummary(node);
693 if (node) {
694 m_mainFrame->setWindowTitle(node->title());
697 m_actionManager->slotNodeSelected(node);
700 void MainWidget::slotFeedAdd()
702 Folder *group = 0;
703 if (!m_selectionController->selectedSubscription()) {
704 group = m_feedList->allFeedsFolder();
705 } else {
706 if (m_selectionController->selectedSubscription()->isGroup()) {
707 group = static_cast<Folder *>(m_selectionController->selectedSubscription());
708 } else {
709 group = m_selectionController->selectedSubscription()->parent();
714 TreeNode *const lastChild = !group->children().isEmpty() ? group->children().last() : Q_NULLPTR;
716 addFeed(QString(), lastChild, group, false);
719 void MainWidget::addFeed(const QString &url, TreeNode *after, Folder *parent, bool autoExec)
721 CreateFeedCommand *cmd(new CreateFeedCommand(this));
722 cmd->setParentWidget(this);
723 cmd->setPosition(parent, after);
724 cmd->setRootFolder(m_feedList->allFeedsFolder());
725 cmd->setAutoExecute(autoExec);
726 cmd->setUrl(url);
727 cmd->setSubscriptionListView(m_feedListView);
728 cmd->start();
731 void MainWidget::slotFeedAddGroup()
733 CreateFolderCommand *cmd = new CreateFolderCommand(this);
734 cmd->setParentWidget(this);
735 cmd->setSelectedSubscription(m_selectionController->selectedSubscription());
736 cmd->setRootFolder(m_feedList->allFeedsFolder());
737 cmd->setSubscriptionListView(m_feedListView);
738 cmd->start();
741 void MainWidget::slotFeedRemove()
743 TreeNode *selectedNode = m_selectionController->selectedSubscription();
745 // don't delete root element! (safety valve)
746 if (!selectedNode || selectedNode == m_feedList->allFeedsFolder()) {
747 return;
750 DeleteSubscriptionCommand *cmd = new DeleteSubscriptionCommand(this);
751 cmd->setParentWidget(this);
752 cmd->setSubscription(m_feedList, selectedNode->id());
753 cmd->start();
756 void MainWidget::slotFeedModify()
758 TreeNode *const node = m_selectionController->selectedSubscription();
759 if (!node) {
760 return;
762 EditSubscriptionCommand *cmd = new EditSubscriptionCommand(this);
763 cmd->setParentWidget(this);
764 cmd->setSubscription(m_feedList, node->id());
765 cmd->setSubscriptionListView(m_feedListView);
766 cmd->start();
769 void MainWidget::slotNextUnreadArticle()
771 ensureArticleTabVisible();
772 if (m_viewMode == CombinedView) {
773 m_feedListView->slotNextUnreadFeed();
774 return;
776 TreeNode *sel = m_selectionController->selectedSubscription();
777 if (sel && sel->unread() > 0) {
778 m_articleListView->slotNextUnreadArticle();
779 } else {
780 m_feedListView->slotNextUnreadFeed();
784 void MainWidget::slotPrevUnreadArticle()
786 ensureArticleTabVisible();
787 if (m_viewMode == CombinedView) {
788 m_feedListView->slotPrevUnreadFeed();
789 return;
791 TreeNode *sel = m_selectionController->selectedSubscription();
792 if (sel && sel->unread() > 0) {
793 m_articleListView->slotPreviousUnreadArticle();
794 } else {
795 m_feedListView->slotPrevUnreadFeed();
799 void MainWidget::slotMarkAllFeedsRead()
801 KJob *job = m_feedList->createMarkAsReadJob();
802 connect(job, &KJob::finished, m_selectionController, &AbstractSelectionController::forceFilterUpdate);
803 job->start();
806 void MainWidget::slotMarkAllRead()
808 if (!m_selectionController->selectedSubscription()) {
809 return;
811 KJob *job = m_selectionController->selectedSubscription()->createMarkAsReadJob();
812 connect(job, &KJob::finished, m_selectionController, &AbstractSelectionController::forceFilterUpdate);
813 job->start();
816 void MainWidget::slotSetTotalUnread()
818 Q_EMIT signalUnreadCountChanged(m_feedList ? m_feedList->unread() : 0);
821 void MainWidget::slotDoIntervalFetches()
823 if (!m_feedList) {
824 return;
826 #if 0
827 // the following solid check apparently doesn't work reliably and causes
828 // interval fetching not working although the user is actually online (but solid reports he's not
829 const Networking::Status status = Solid::Networking::status();
830 if (status != Networking::Connected && status != Networking::Unknown) {
831 return;
833 #endif
834 m_feedList->addToFetchQueue(Kernel::self()->fetchQueue(), true);
837 void MainWidget::slotFetchCurrentFeed()
839 if (!m_selectionController->selectedSubscription()) {
840 return;
842 if (isNetworkAvailable()) {
843 m_selectionController->selectedSubscription()->slotAddToFetchQueue(Kernel::self()->fetchQueue());
844 } else {
845 m_mainFrame->slotSetStatusText(i18n("Networking is not available."));
849 void MainWidget::slotFetchAllFeeds()
851 if (m_feedList && isNetworkAvailable()) {
852 m_feedList->addToFetchQueue(Kernel::self()->fetchQueue());
853 } else if (m_feedList) {
854 m_mainFrame->slotSetStatusText(i18n("Networking is not available."));
858 void MainWidget::slotFetchingStarted()
860 m_mainFrame->slotSetState(Frame::Started);
861 m_actionManager->action(QStringLiteral("feed_stop"))->setEnabled(true);
862 m_mainFrame->slotSetStatusText(i18n("Fetching Feeds..."));
865 void MainWidget::slotFetchingStopped()
867 m_mainFrame->slotSetState(Frame::Completed);
868 m_actionManager->action(QStringLiteral("feed_stop"))->setEnabled(false);
869 m_mainFrame->slotSetStatusText(QString());
872 void MainWidget::slotArticleSelected(const Akregator::Article &article)
874 if (m_viewMode == CombinedView) {
875 return;
878 m_markReadTimer->stop();
880 Q_ASSERT(article.isNull() || article.feed());
882 QVector<Article> articles = m_selectionController->selectedArticles();
883 Q_EMIT signalArticlesSelected(articles);
885 KToggleAction *const maai = qobject_cast<KToggleAction *>(m_actionManager->action(QStringLiteral("article_set_status_important")));
886 Q_ASSERT(maai);
887 maai->setChecked(article.keep());
889 m_articleViewer->showArticle(article);
890 if (m_selectionController->selectedArticles().count() == 0) {
891 m_articleListView->setCurrentIndex(m_selectionController->currentArticleIndex());
894 if (article.isNull() || article.status() == Akregator::Read) {
895 return;
898 if (!Settings::useMarkReadDelay()) {
899 return;
902 const int delay = Settings::markReadDelay();
904 if (delay > 0) {
905 m_markReadTimer->start(delay * 1000);
906 } else {
907 Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob;
908 const Akregator::ArticleId aid = { article.feed()->xmlUrl(), article.guid() };
909 job->setStatus(aid, Akregator::Read);
910 job->start();
914 void MainWidget::slotMouseButtonPressed(int button, const QUrl &url)
916 if (button != Qt::MidButton) {
917 return;
920 if (!url.isValid()) {
921 return;
924 OpenUrlRequest req(url);
926 switch (Settings::mMBBehaviour()) {
927 case Settings::EnumMMBBehaviour::OpenInExternalBrowser:
928 req.setOptions(OpenUrlRequest::ExternalBrowser);
929 break;
930 case Settings::EnumMMBBehaviour::OpenInBackground:
931 req.setOptions(OpenUrlRequest::NewTab);
932 req.setOpenInBackground(true);
933 break;
934 default:
935 req.setOptions(OpenUrlRequest::NewTab);
936 req.setOpenInBackground(false);
939 Kernel::self()->frameManager()->slotOpenUrlRequest(req);
942 void MainWidget::slotOpenHomepage()
944 Feed *feed = dynamic_cast<Feed *>(m_selectionController->selectedSubscription());
946 if (!feed) {
947 return;
950 QUrl url(feed->htmlUrl());
952 if (url.isValid()) {
953 OpenUrlRequest req(url);
954 req.setOptions(OpenUrlRequest::ExternalBrowser);
955 Kernel::self()->frameManager()->slotOpenUrlRequest(req);
959 void MainWidget::slotOpenSelectedArticlesInBrowser()
961 const QVector<Article> articles = m_selectionController->selectedArticles();
963 Q_FOREACH (const Akregator::Article &article, articles) {
964 slotOpenArticleInBrowser(article);
968 void MainWidget::slotOpenArticleInBrowser(const Akregator::Article &article)
970 if (!article.isNull() && article.link().isValid()) {
971 OpenUrlRequest req(article.link());
972 req.setOptions(OpenUrlRequest::ExternalBrowser);
973 Kernel::self()->frameManager()->slotOpenUrlRequest(req);
977 void MainWidget::openSelectedArticles(bool openInBackground)
979 const QVector<Article> articles = m_selectionController->selectedArticles();
981 Q_FOREACH (const Akregator::Article &article, articles) {
982 const QUrl url = article.link();
983 if (!url.isValid()) {
984 continue;
987 OpenUrlRequest req(url);
988 req.setOptions(OpenUrlRequest::NewTab);
989 if (openInBackground) {
990 req.setOpenInBackground(true);
991 Kernel::self()->frameManager()->slotOpenUrlRequest(req, false /*don't use settings for open in background*/);
992 } else {
993 Kernel::self()->frameManager()->slotOpenUrlRequest(req);
999 void MainWidget::currentArticleInfo(QString &link, QString &title)
1001 const Article article = m_selectionController->currentArticle();
1003 if (article.isNull()) {
1004 return;
1006 if (article.link().isValid()) {
1007 link = article.link().url();
1008 title = Utils::convertHtmlTags(article.title());
1012 void MainWidget::updateQuickSearchLineText()
1014 m_searchBar->updateQuickSearchLineText(m_actionManager->quickSearchLineText());
1017 void MainWidget::slotCopyLinkAddress()
1019 const Article article = m_selectionController->currentArticle();
1021 if (article.isNull()) {
1022 return;
1025 QString link;
1026 if (article.link().isValid()) {
1027 link = article.link().url();
1028 QClipboard *cb = QApplication::clipboard();
1029 cb->setText(link, QClipboard::Clipboard);
1030 // don't set url to selection as it's a no-no according to a fd.o spec
1031 //cb->setText(link, QClipboard::Selection);
1035 void MainWidget::slotToggleShowQuickFilter()
1037 if (Settings::showQuickFilter()) {
1038 Settings::setShowQuickFilter(false);
1039 m_searchBar->slotClearSearch();
1040 m_searchBar->hide();
1041 } else {
1042 Settings::setShowQuickFilter(true);
1043 if (!m_displayingAboutPage) {
1044 m_searchBar->show();
1050 void MainWidget::slotArticleDelete()
1053 if (m_viewMode == CombinedView) {
1054 return;
1057 const QVector<Article> articles = m_selectionController->selectedArticles();
1059 QString msg;
1060 switch (articles.count()) {
1061 case 0:
1062 return;
1063 case 1:
1064 msg = i18n("<qt>Are you sure you want to delete article <b>%1</b>?</qt>", articles.first().title());
1065 break;
1066 default:
1067 msg = i18np("<qt>Are you sure you want to delete the selected article?</qt>", "<qt>Are you sure you want to delete the %1 selected articles?</qt>", articles.count());
1070 if (KMessageBox::warningContinueCancel(this,
1071 msg, i18n("Delete Article"),
1072 KStandardGuiItem::del(),
1073 KStandardGuiItem::cancel(),
1074 QStringLiteral("Disable delete article confirmation")) != KMessageBox::Continue) {
1075 return;
1078 TreeNode *const selected = m_selectionController->selectedSubscription();
1080 if (selected) {
1081 selected->setNotificationMode(false);
1084 Akregator::ArticleDeleteJob *job = new Akregator::ArticleDeleteJob;
1085 Q_FOREACH (const Akregator::Article &i, articles) {
1086 Feed *const feed = i.feed();
1087 if (!feed) {
1088 continue;
1090 const Akregator::ArticleId aid = { feed->xmlUrl(), i.guid() };
1091 job->appendArticleId(aid);
1094 job->start();
1096 if (selected) {
1097 selected->setNotificationMode(true);
1101 void MainWidget::slotFramesChanged()
1103 // We need to wait till the frame is fully loaded
1104 QMetaObject::invokeMethod(m_part, "slotAutoSave", Qt::QueuedConnection);
1107 void MainWidget::slotArticleToggleKeepFlag(bool)
1109 const QVector<Article> articles = m_selectionController->selectedArticles();
1111 if (articles.isEmpty()) {
1112 return;
1115 bool allFlagsSet = true;
1116 Q_FOREACH (const Akregator::Article &i, articles) {
1117 allFlagsSet = allFlagsSet && i.keep();
1118 if (!allFlagsSet) {
1119 break;
1123 Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob;
1124 Q_FOREACH (const Akregator::Article &i, articles) {
1125 const Akregator::ArticleId aid = { i.feed()->xmlUrl(), i.guid() };
1126 job->setKeep(aid, !allFlagsSet);
1128 job->start();
1131 namespace
1134 void setArticleStatus(const QString &feedUrl, const QString &articleId, int status)
1136 if (!feedUrl.isEmpty() && !articleId.isEmpty()) {
1137 Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob;
1138 const Akregator::ArticleId aid = { feedUrl, articleId };
1139 job->setStatus(aid, status);
1140 job->start();
1144 void setSelectedArticleStatus(const Akregator::AbstractSelectionController *controller, int status)
1146 const QVector<Akregator::Article> articles = controller->selectedArticles();
1148 if (articles.isEmpty()) {
1149 return;
1152 Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob;
1153 Q_FOREACH (const Akregator::Article &i, articles) {
1154 const Akregator::ArticleId aid = { i.feed()->xmlUrl(), i.guid() };
1155 job->setStatus(aid, status);
1157 job->start();
1162 void MainWidget::slotSetSelectedArticleRead()
1164 ::setSelectedArticleStatus(m_selectionController, Akregator::Read);
1167 void MainWidget::slotSetSelectedArticleUnread()
1169 ::setSelectedArticleStatus(m_selectionController, Akregator::Unread);
1172 void MainWidget::slotSetSelectedArticleNew()
1174 ::setSelectedArticleStatus(m_selectionController, Akregator::New);
1177 void MainWidget::slotSetCurrentArticleReadDelayed()
1179 const Article article = m_selectionController->currentArticle();
1181 if (article.isNull()) {
1182 return;
1185 Akregator::ArticleModifyJob *const job = new Akregator::ArticleModifyJob;
1186 const Akregator::ArticleId aid = { article.feed()->xmlUrl(), article.guid() };
1187 job->setStatus(aid, Akregator::Read);
1188 job->start();
1191 void MainWidget::slotShowStatusBarMessage(const QString &msg)
1193 KPIM::BroadcastStatus::instance()->setStatusMsg(msg);
1196 void MainWidget::readProperties(const KConfigGroup &config)
1198 if (!Settings::resetQuickFilterOnNodeChange()) {
1199 // read filter settings
1200 m_searchBar->slotSetText(config.readEntry("searchLine"));
1201 m_searchBar->slotSetStatus(config.readEntry("searchCombo").toInt());
1203 const QString currentTabName = config.readEntry("CurrentTab");
1204 // Reopen tabs
1205 const QStringList childList = config.readEntry(QStringLiteral("Children"), QStringList());
1206 int currentFrameId = -1;
1207 Q_FOREACH (const QString &framePrefix, childList) {
1208 WebEngineFrame *const frame = new WebEngineFrame(m_actionManager->actionCollection(), m_tabWidget);
1209 frame->loadConfig(config, framePrefix + QLatin1Char('_'));
1211 connectFrame(frame);
1212 Kernel::self()->frameManager()->slotAddFrame(frame);
1213 if (currentTabName == framePrefix) {
1214 currentFrameId = frame->id();
1217 if (currentFrameId != -1) {
1218 m_tabWidget->slotSelectFrame(currentFrameId);
1222 void MainWidget::saveProperties(KConfigGroup &config)
1224 // save filter settings
1225 const QString searchStr(m_searchBar->text());
1226 if (searchStr.isEmpty()) {
1227 config.deleteEntry("searchLine");
1228 } else {
1229 config.writeEntry("searchLine", m_searchBar->text());
1231 config.writeEntry("searchCombo", m_searchBar->status());
1233 Kernel::self()->frameManager()->saveProperties(config);
1236 void MainWidget::ensureArticleTabVisible()
1238 m_tabWidget->setCurrentWidget(m_mainFrame);
1241 void MainWidget::slotReloadAllTabs()
1243 m_tabWidget->slotReloadAllTabs();
1246 bool MainWidget::isNetworkAvailable() const
1248 return m_networkConfigManager->isOnline();
1251 void MainWidget::slotNetworkStatusChanged(bool status)
1253 if (status) {
1254 m_mainFrame->slotSetStatusText(i18n("Networking is available now."));
1255 this->slotFetchAllFeeds();
1256 } else {
1257 m_mainFrame->slotSetStatusText(i18n("Networking is not available."));
1261 void MainWidget::slotOpenSelectedArticles()
1263 openSelectedArticles(false);
1266 void MainWidget::slotOpenSelectedArticlesInBackground()
1268 openSelectedArticles(true);
1271 void MainWidget::slotCurrentFrameChanged(int frameId)
1273 Kernel::self()->frameManager()->slotChangeFrame(frameId);
1274 m_actionManager->zoomActionMenu()->setZoomFactor(Kernel::self()->frameManager()->currentFrame()->zoomFactor() * 100);
1277 void MainWidget::slotFocusQuickSearch()
1279 m_searchBar->setFocusSearchLine();
1282 void MainWidget::slotArticleAction(Akregator::ArticleViewerWebEngine::ArticleAction type, const QString &articleId, const QString &feed)
1284 switch (type) {
1285 case ArticleViewerWebEngine::DeleteAction: {
1286 Akregator::ArticleDeleteJob *job = new Akregator::ArticleDeleteJob;
1287 const Akregator::ArticleId aid = { feed, articleId };
1288 job->appendArticleId(aid);
1289 job->start();
1290 break;
1292 case ArticleViewerWebEngine::MarkAsRead:
1293 ::setArticleStatus(feed, articleId, Akregator::Read);
1294 break;
1295 case ArticleViewerWebEngine::MarkAsUnRead:
1296 ::setArticleStatus(feed, articleId, Akregator::Unread);
1297 break;
1299 case ArticleViewerWebEngine::MarkAsImportant: {
1300 Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob;
1301 const Akregator::Article article = m_feedList->findArticle(feed, articleId);
1302 const Akregator::ArticleId aid = { feed, articleId };
1303 job->setKeep(aid, !article.keep());
1304 job->start();
1305 break;
1307 case ArticleViewerWebEngine::SendUrlArticle: {
1308 case ArticleViewerWebEngine::SendFileArticle:
1309 const Article article = m_feedList->findArticle(feed, articleId);
1310 const QByteArray text = article.link().toDisplayString().toLatin1();
1311 const QString title = Akregator::Utils::convertHtmlTags(article.title());
1312 if (text.isEmpty()) {
1313 return;
1315 sendArticle(text, title, (type == ArticleViewerWebEngine::SendFileArticle));
1316 break;
1318 case ArticleViewerWebEngine::OpenInBackgroundTab: {
1319 const Akregator::Article article = m_feedList->findArticle(feed, articleId);
1320 const QUrl url = article.link();
1321 if (url.isValid()) {
1322 OpenUrlRequest req(url);
1323 req.setOptions(OpenUrlRequest::NewTab);
1324 req.setOpenInBackground(true);
1325 Kernel::self()->frameManager()->slotOpenUrlRequest(req, false /*don't use settings for open in background*/);
1327 break;
1329 case ArticleViewerWebEngine::OpenInExternalBrowser: {
1330 const Akregator::Article article = m_feedList->findArticle(feed, articleId);
1331 slotOpenArticleInBrowser(article);
1332 break;
1334 case ArticleViewerWebEngine::Share:
1335 const Akregator::Article article = m_feedList->findArticle(feed, articleId);
1336 const QUrl url = article.link();
1337 if (url.isValid()) {
1338 //TODO
1340 break;