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"
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"
47 #include "feedpropertiesdialog.h"
48 #include "fetchqueue.h"
50 #include "framemanager.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"
62 #include "treenodevisitor.h"
64 #include "mainframe.h"
65 #include <WebEngineViewer/ZoomActionMenu>
66 #include "job/downloadarticlejob.h"
69 #include <kfileitem.h>
70 #include <KLocalizedString>
71 #include <kmessagebox.h>
75 #include <ktoggleaction.h>
79 #include <QNetworkConfigurationManager>
81 #include <QTextDocument>
82 #include <QDomDocument>
84 #include <QDesktopServices>
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
) {
106 MainWidget::MainWidget(Part
*part
, QWidget
*parent
, ActionManagerImpl
*actionManager
, const QString
&name
)
109 m_viewMode(NormalView
),
110 m_actionManager(actionManager
),
111 m_feedListManagementInterface(new FeedListManagementImpl
),
112 m_networkConfigManager(new QNetworkConfigurationManager(this))
116 FeedListManagementInterface::setInstance(m_feedListManagementInterface
);
118 m_actionManager
->initMainWidget(this);
120 m_shuttingDown
= false;
121 m_displayingAboutPage
= false;
122 setFocusPolicy(Qt::StrongFocus
);
124 QVBoxLayout
*lt
= new QVBoxLayout(this);
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()) {
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()) {
303 slotWidescreenView();
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
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
);
387 Kernel::self()->frameManager()->slotAddFrame(frame
);
389 frameId
= frame
->id();
392 void MainWidget::sendArticle(bool attach
)
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()) {
413 sendArticle(text
, title
, attach
);
416 void MainWidget::cleanUpDownloadFile()
418 Q_FOREACH (QPointer
<Akregator::DownloadArticleJob
> job
, mListDownloadArticleJobs
) {
420 job
->forceCleanupTemporaryFile();
425 void MainWidget::sendArticle(const QByteArray
&text
, const QString
&title
, bool 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
);
436 query
.addQueryItem(QStringLiteral("subject"), title
);
437 query
.addQueryItem(QStringLiteral("body"), QString::fromUtf8(text
));
439 url
.setScheme(QStringLiteral("mailto"));
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
);
454 void MainWidget::setFeedList(const QSharedPointer
<FeedList
> &list
)
456 if (list
== m_feedList
) {
459 const QSharedPointer
<FeedList
> oldList
= 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
);
475 oldList
->disconnect(this);
478 slotDeleteExpiredArticles();
481 void MainWidget::deleteExpiredArticles(const QSharedPointer
<FeedList
> &list
)
486 ExpireItemsCommand
*cmd
= new ExpireItemsCommand(this);
487 cmd
->setParentWidget(this);
488 cmd
->setFeedList(list
);
489 cmd
->setFeeds(list
->feedIds());
493 void MainWidget::slotDeleteExpiredArticles()
495 deleteExpiredArticles(m_feedList
);
498 QDomDocument
MainWidget::feedListToOPML()
502 dom
= m_feedList
->toOpml();
507 void MainWidget::addFeedToGroup(const QString
&url
, const QString
&groupName
)
510 QList
<TreeNode
*> namedGroups
= m_feedList
->findByTitle(groupName
);
512 foreach (TreeNode
*const candidate
, namedGroups
) {
513 if (candidate
->isGroup()) {
514 group
= static_cast<Folder
*>(candidate
);
520 Folder
*g
= new Folder(groupName
);
521 m_feedList
->allFeedsFolder()->appendChild(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
) {
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
);
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
) {
559 if (m_viewMode
== CombinedView
) {
560 m_articleWidget
->show();
562 Article article
= m_selectionController
->currentArticle();
564 if (!article
.isNull()) {
565 m_articleViewer
->showArticle(article
);
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
) {
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();
596 TreeNode
*prev
= current
->prevSibling();
597 Folder
*parent
= current
->parent();
599 if (!prev
|| !parent
) {
603 parent
->removeChild(prev
);
604 parent
->insertChild(prev
, current
);
605 m_feedListView
->ensureNodeVisible(current
);
608 void MainWidget::slotMoveCurrentNodeDown()
610 TreeNode
*current
= m_selectionController
->selectedSubscription();
614 TreeNode
*next
= current
->nextSibling();
615 Folder
*parent
= current
->parent();
617 if (!next
|| !parent
) {
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()) {
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()) {
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()
662 void MainWidget::slotSendFile()
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()) {
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
);
690 m_articleViewer
->slotShowSummary(node
);
694 m_mainFrame
->setWindowTitle(node
->title());
697 m_actionManager
->slotNodeSelected(node
);
700 void MainWidget::slotFeedAdd()
703 if (!m_selectionController
->selectedSubscription()) {
704 group
= m_feedList
->allFeedsFolder();
706 if (m_selectionController
->selectedSubscription()->isGroup()) {
707 group
= static_cast<Folder
*>(m_selectionController
->selectedSubscription());
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
);
727 cmd
->setSubscriptionListView(m_feedListView
);
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
);
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()) {
750 DeleteSubscriptionCommand
*cmd
= new DeleteSubscriptionCommand(this);
751 cmd
->setParentWidget(this);
752 cmd
->setSubscription(m_feedList
, selectedNode
->id());
756 void MainWidget::slotFeedModify()
758 TreeNode
*const node
= m_selectionController
->selectedSubscription();
762 EditSubscriptionCommand
*cmd
= new EditSubscriptionCommand(this);
763 cmd
->setParentWidget(this);
764 cmd
->setSubscription(m_feedList
, node
->id());
765 cmd
->setSubscriptionListView(m_feedListView
);
769 void MainWidget::slotNextUnreadArticle()
771 ensureArticleTabVisible();
772 if (m_viewMode
== CombinedView
) {
773 m_feedListView
->slotNextUnreadFeed();
776 TreeNode
*sel
= m_selectionController
->selectedSubscription();
777 if (sel
&& sel
->unread() > 0) {
778 m_articleListView
->slotNextUnreadArticle();
780 m_feedListView
->slotNextUnreadFeed();
784 void MainWidget::slotPrevUnreadArticle()
786 ensureArticleTabVisible();
787 if (m_viewMode
== CombinedView
) {
788 m_feedListView
->slotPrevUnreadFeed();
791 TreeNode
*sel
= m_selectionController
->selectedSubscription();
792 if (sel
&& sel
->unread() > 0) {
793 m_articleListView
->slotPreviousUnreadArticle();
795 m_feedListView
->slotPrevUnreadFeed();
799 void MainWidget::slotMarkAllFeedsRead()
801 KJob
*job
= m_feedList
->createMarkAsReadJob();
802 connect(job
, &KJob::finished
, m_selectionController
, &AbstractSelectionController::forceFilterUpdate
);
806 void MainWidget::slotMarkAllRead()
808 if (!m_selectionController
->selectedSubscription()) {
811 KJob
*job
= m_selectionController
->selectedSubscription()->createMarkAsReadJob();
812 connect(job
, &KJob::finished
, m_selectionController
, &AbstractSelectionController::forceFilterUpdate
);
816 void MainWidget::slotSetTotalUnread()
818 Q_EMIT
signalUnreadCountChanged(m_feedList
? m_feedList
->unread() : 0);
821 void MainWidget::slotDoIntervalFetches()
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
) {
834 m_feedList
->addToFetchQueue(Kernel::self()->fetchQueue(), true);
837 void MainWidget::slotFetchCurrentFeed()
839 if (!m_selectionController
->selectedSubscription()) {
842 if (isNetworkAvailable()) {
843 m_selectionController
->selectedSubscription()->slotAddToFetchQueue(Kernel::self()->fetchQueue());
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
) {
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")));
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
) {
898 if (!Settings::useMarkReadDelay()) {
902 const int delay
= Settings::markReadDelay();
905 m_markReadTimer
->start(delay
* 1000);
907 Akregator::ArticleModifyJob
*job
= new Akregator::ArticleModifyJob
;
908 const Akregator::ArticleId aid
= { article
.feed()->xmlUrl(), article
.guid() };
909 job
->setStatus(aid
, Akregator::Read
);
914 void MainWidget::slotMouseButtonPressed(int button
, const QUrl
&url
)
916 if (button
!= Qt::MidButton
) {
920 if (!url
.isValid()) {
924 OpenUrlRequest
req(url
);
926 switch (Settings::mMBBehaviour()) {
927 case Settings::EnumMMBBehaviour::OpenInExternalBrowser
:
928 req
.setOptions(OpenUrlRequest::ExternalBrowser
);
930 case Settings::EnumMMBBehaviour::OpenInBackground
:
931 req
.setOptions(OpenUrlRequest::NewTab
);
932 req
.setOpenInBackground(true);
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());
950 QUrl
url(feed
->htmlUrl());
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()) {
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*/);
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()) {
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()) {
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();
1042 Settings::setShowQuickFilter(true);
1043 if (!m_displayingAboutPage
) {
1044 m_searchBar
->show();
1050 void MainWidget::slotArticleDelete()
1053 if (m_viewMode
== CombinedView
) {
1057 const QVector
<Article
> articles
= m_selectionController
->selectedArticles();
1060 switch (articles
.count()) {
1064 msg
= i18n("<qt>Are you sure you want to delete article <b>%1</b>?</qt>", articles
.first().title());
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
) {
1078 TreeNode
*const selected
= m_selectionController
->selectedSubscription();
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();
1090 const Akregator::ArticleId aid
= { feed
->xmlUrl(), i
.guid() };
1091 job
->appendArticleId(aid
);
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()) {
1115 bool allFlagsSet
= true;
1116 Q_FOREACH (const Akregator::Article
&i
, articles
) {
1117 allFlagsSet
= allFlagsSet
&& i
.keep();
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
);
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
);
1144 void setSelectedArticleStatus(const Akregator::AbstractSelectionController
*controller
, int status
)
1146 const QVector
<Akregator::Article
> articles
= controller
->selectedArticles();
1148 if (articles
.isEmpty()) {
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
);
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()) {
1185 Akregator::ArticleModifyJob
*const job
= new Akregator::ArticleModifyJob
;
1186 const Akregator::ArticleId aid
= { article
.feed()->xmlUrl(), article
.guid() };
1187 job
->setStatus(aid
, Akregator::Read
);
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");
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");
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
)
1254 m_mainFrame
->slotSetStatusText(i18n("Networking is available now."));
1255 this->slotFetchAllFeeds();
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
)
1285 case ArticleViewerWebEngine::DeleteAction
: {
1286 Akregator::ArticleDeleteJob
*job
= new Akregator::ArticleDeleteJob
;
1287 const Akregator::ArticleId aid
= { feed
, articleId
};
1288 job
->appendArticleId(aid
);
1292 case ArticleViewerWebEngine::MarkAsRead
:
1293 ::setArticleStatus(feed
, articleId
, Akregator::Read
);
1295 case ArticleViewerWebEngine::MarkAsUnRead
:
1296 ::setArticleStatus(feed
, articleId
, Akregator::Unread
);
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());
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()) {
1315 sendArticle(text
, title
, (type
== ArticleViewerWebEngine::SendFileArticle
));
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*/);
1329 case ArticleViewerWebEngine::OpenInExternalBrowser
: {
1330 const Akregator::Article article
= m_feedList
->findArticle(feed
, articleId
);
1331 slotOpenArticleInBrowser(article
);
1334 case ArticleViewerWebEngine::Share
:
1335 const Akregator::Article article
= m_feedList
->findArticle(feed
, articleId
);
1336 const QUrl url
= article
.link();
1337 if (url
.isValid()) {