diff --git a/configuration/ui/settings_general.ui b/configuration/ui/settings_general.ui index c12d264b..fa057ebd 100644 --- a/configuration/ui/settings_general.ui +++ b/configuration/ui/settings_general.ui @@ -1,185 +1,192 @@ Teemu Rytilahti Akregator::SettingsGeneral 0 0 362 377 0 0 0 0 Global Show tra&y icon + + + + Hide feeds with no unread articles + + + Select this if you want to get notified when there are new articles. Use &notifications for all feeds &Use interval fetching Qt::Horizontal QSizePolicy::Fixed 40 20 false Fetch feeds every: false 1 3600 10 Qt::Horizontal 40 20 Startup Mark &all feeds as read on startup Fetch all fee&ds on startup Network Use the &browser cache (less network traffic) Qt::Vertical 20 1 KPluralHandlingSpinBox QSpinBox
kpluralhandlingspinbox.h
diff --git a/interfaces/akregator.kcfg b/interfaces/akregator.kcfg index 6f397abf..bf3fe639 100644 --- a/interfaces/akregator.kcfg +++ b/interfaces/akregator.kcfg @@ -1,244 +1,249 @@ + + + Hides feeds with no unread articles + false + Show Quick Filter Bar true Stores the last status filter setting 0 Stores the last search line text Article display mode. 0 First (usually vertical) splitter widget sizes. 225,650 Second (usually horizontal) splitter widget sizes. 50,350 false false #0000FF #FF0000 5.2 12 8 true keepAllArticles Save an unlimited number of articles. Limit the number of articles in a feed Delete expired articles - + Do not save any articles Default expiry age for articles in days. 60 Number of articles to keep per feed. 1000 When this option is enabled, articles you marked as important will not be removed when limit the archive size by either age or number of the articles. true Number of concurrent fetches 6 Use the KDE-wide HTML cache settings when downloading feeds, to avoid unnecessary traffic. Disable only when necessary. true This option allows user to specify custom user-agent string instead of using the default one. This is here because some proxies may interrupt the connection because of having "gator" in the name. Fetch feedlist on startup. false Mark all feeds as read on startup. false Fetch all feeds every %1 minutes. true Interval for autofetching in minutes. 30 Specifies if the balloon notifications are used or not. false Specifies if the tray icon is shown or not. true false Always show the tab bar, even when only one tab is open - false + false Show close buttons on tabs instead of icons - false + false Open a link which would normally open in a new window (external browser) in a new tab instead - false + false Use KDE web browser when opening in external browser. true Use the specified command when opening in external browser. false Command to launch external browser. URL will substitute for %u. firefox %u What the click with left mouse button should do. OpenInInternalBrowser What the click with middle mouse button should do. OpenInExternalBrowser 0 0 metakit Whether to delay before marking an article as read upon selecting it. true Configurable delay between selecting an article and it being marked as read. 0 Resets the quick filter when changing feeds. false diff --git a/src/abstractselectioncontroller.h b/src/abstractselectioncontroller.h index dceecead..10d082a6 100644 --- a/src/abstractselectioncontroller.h +++ b/src/abstractselectioncontroller.h @@ -1,137 +1,139 @@ /* This file is part of Akregator. Copyright (C) 2007 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_ABSTRACTSELECTIONCONTROLLER_H #define AKREGATOR_ABSTRACTSELECTIONCONTROLLER_H #include #include class QAbstractItemView; class QItemSelectionModel; class QPoint; class QModelIndex; namespace Akregator { class Article; class ArticleModel; class FeedList; class FolderExpansionHandler; class SubscriptionListModel; class TreeNode; namespace Filters { class AbstractMatcher; } class ArticleLister { public: virtual ~ArticleLister() { } virtual void setArticleModel(Akregator::ArticleModel *model) = 0; virtual QItemSelectionModel *articleSelectionModel() const = 0; virtual void setIsAggregation(bool isAggregation) = 0; virtual void setFilters(const std::vector > &) = 0; virtual void forceFilterUpdate() = 0; virtual QPoint scrollBarPositions() const = 0; virtual void setScrollBarPositions(const QPoint &p) = 0; virtual const QAbstractItemView *itemView() const = 0; virtual QAbstractItemView *itemView() = 0; }; class SingleArticleDisplay { public: virtual ~SingleArticleDisplay() { } virtual void showArticle(const Akregator::Article &article) = 0; }; class SubscriptionLister { public: virtual ~SubscriptionLister() { } virtual void setSubscriptionListModel(Akregator::SubscriptionListModel *model) = 0; virtual QItemSelectionModel *subscriptionSelectionModel() const = 0; }; class AbstractSelectionController : public QObject { Q_OBJECT public: explicit AbstractSelectionController(QObject *parent = nullptr); virtual ~AbstractSelectionController(); virtual void setFeedList(const QSharedPointer &list) = 0; virtual void setFeedSelector(QAbstractItemView *feedSelector) = 0; virtual void setArticleLister(Akregator::ArticleLister *lister) = 0; virtual void setFolderExpansionHandler(Akregator::FolderExpansionHandler *handler) = 0; virtual void setSingleArticleDisplay(Akregator::SingleArticleDisplay *display) = 0; virtual Akregator::Article currentArticle() const = 0; virtual QModelIndex currentArticleIndex() const = 0; virtual QVector selectedArticles() const = 0; virtual Akregator::TreeNode *selectedSubscription() const = 0; public Q_SLOTS: + virtual void settingsChanged() = 0; + virtual void setFilters(const std::vector > &) = 0; virtual void forceFilterUpdate() = 0; Q_SIGNALS: void currentSubscriptionChanged(Akregator::TreeNode *node); void currentArticleChanged(const Akregator::Article &); void articleDoubleClicked(const Akregator::Article &); }; } // namespace Akregator #endif // AKREGATOR_ABSTRACTSELECTIONCONTROLLER_H diff --git a/src/actions/actionmanagerimpl.cpp b/src/actions/actionmanagerimpl.cpp index fb139137..05383e4d 100644 --- a/src/actions/actionmanagerimpl.cpp +++ b/src/actions/actionmanagerimpl.cpp @@ -1,671 +1,687 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "actionmanagerimpl.h" #include "akregatorconfig.h" #include "akregator_part.h" #include "articlelistview.h" #include "feed.h" #include "fetchqueue.h" #include "folder.h" #include "kernel.h" #include "mainwidget.h" #include "subscriptionlistview.h" #include "tabwidget.h" #include "trayicon.h" #include "treenode.h" #include "treenodevisitor.h" #include #include #include #include #include #include #include "akregator_debug.h" #include #include #include #include #include #include #include #include #include using namespace Akregator; class ActionManagerImpl::NodeSelectVisitor : public TreeNodeVisitor { public: NodeSelectVisitor(ActionManagerImpl *manager) : m_manager(manager) { } bool visitFeed(Feed *node) override { QAction *remove = m_manager->action(QStringLiteral("feed_remove")); if (remove) { remove->setEnabled(true); } QAction *hp = m_manager->action(QStringLiteral("feed_homepage")); if (hp) { hp->setEnabled(!node->htmlUrl().isEmpty()); } m_manager->action(QStringLiteral("feed_fetch"))->setText(i18n("&Fetch Feed")); m_manager->action(QStringLiteral("feed_remove"))->setText(i18n("&Delete Feed")); m_manager->action(QStringLiteral("feed_modify"))->setText(i18n("&Edit Feed...")); m_manager->action(QStringLiteral("feed_mark_all_as_read"))->setText(i18n("&Mark Feed as Read")); return true; } bool visitFolder(Folder *node) override { QAction *remove = m_manager->action(QStringLiteral("feed_remove")); if (remove) { remove->setEnabled(node->parent()); // root nodes must not be deleted } QAction *hp = m_manager->action(QStringLiteral("feed_homepage")); if (hp) { hp->setEnabled(false); } m_manager->action(QStringLiteral("feed_fetch"))->setText(i18n("&Fetch Feeds")); m_manager->action(QStringLiteral("feed_remove"))->setText(i18n("&Delete Folder")); m_manager->action(QStringLiteral("feed_modify"))->setText(i18n("&Rename Folder")); m_manager->action(QStringLiteral("feed_mark_all_as_read"))->setText(i18n("&Mark Feeds as Read")); return true; } private: ActionManagerImpl *m_manager = nullptr; }; class ActionManagerImpl::ActionManagerImplPrivate { public: QString quickSearchLineText() const; NodeSelectVisitor *nodeSelectVisitor = nullptr; ArticleListView *articleList = nullptr; SubscriptionListView *subscriptionListView = nullptr; MainWidget *mainWidget = nullptr; Part *part = nullptr; TrayIcon *trayIcon = nullptr; KActionMenu *tagMenu = nullptr; KActionCollection *actionCollection = nullptr; TabWidget *tabWidget = nullptr; PimCommon::ShareServiceUrlManager *shareServiceManager = nullptr; WebEngineViewer::ZoomActionMenu *zoomActionMenu = nullptr; QAction *mQuickSearchAction = nullptr; }; +void ActionManagerImpl::slotSettingsChanged() +{ + QAction *a = action(QStringLiteral("feed_hide_read")); + if (!a) { + qCCritical(AKREGATOR_LOG) << "Action not found"; + return; + } + a->setChecked(Settings::hideReadFeeds()); +} + void ActionManagerImpl::slotNodeSelected(TreeNode *node) { if (node != 0) { d->nodeSelectVisitor->visit(node); } } ActionManagerImpl::ActionManagerImpl(Part *part, QObject *parent) : ActionManager(parent) , d(new ActionManagerImplPrivate) { d->nodeSelectVisitor = new NodeSelectVisitor(this); d->part = part; d->subscriptionListView = 0; d->articleList = 0; d->trayIcon = 0; d->mainWidget = 0; d->tabWidget = 0; d->tagMenu = 0; d->actionCollection = part->actionCollection(); d->shareServiceManager = new PimCommon::ShareServiceUrlManager(this); initPart(); } ActionManagerImpl::~ActionManagerImpl() { delete d->nodeSelectVisitor; delete d; d = 0; } void ActionManagerImpl::setTrayIcon(TrayIcon *trayIcon) { if (trayIcon == 0) { d->trayIcon = 0; return; } if (d->trayIcon) { return; } else { d->trayIcon = trayIcon; } QMenu *traypop = trayIcon->contextMenu(); if (QAction *act = actionCollection()->action(QStringLiteral("feed_fetch_all"))) { traypop->addAction(act); } if (QAction *act = actionCollection()->action(QStringLiteral("options_configure"))) { traypop->addAction(act); } } void ActionManagerImpl::initPart() { QAction *action = d->actionCollection->addAction(QStringLiteral("file_import")); action->setText(i18n("&Import Feeds...")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); connect(action, &QAction::triggered, d->part, &Part::fileImport); action = d->actionCollection->addAction(QStringLiteral("file_export")); action->setText(i18n("&Export Feeds...")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); connect(action, &QAction::triggered, d->part, &Part::fileExport); QAction *configure = d->actionCollection->addAction(QStringLiteral("options_configure")); configure->setText(i18n("&Configure Akregator...")); configure->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(configure, &QAction::triggered, d->part, &Part::showOptions); KStandardAction::configureNotifications(d->part, SLOT(showNotificationOptions()), d->actionCollection); // options_configure_notifications } void ActionManagerImpl::initMainWidget(MainWidget *mainWidget) { if (d->mainWidget) { return; } d->mainWidget = mainWidget; KActionCollection *coll = actionCollection(); // Feed/Feed Group popup menu QAction *action = coll->addAction(QStringLiteral("feed_homepage")); action->setText(i18n("&Open Homepage")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotOpenHomepage); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_H)); action = coll->addAction(QStringLiteral("reload_all_tabs")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); action->setText(i18n("Reload All Tabs")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotReloadAllTabs); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::Key_F5)); action = coll->addAction(QStringLiteral("feed_add")); action->setIcon(QIcon::fromTheme(QStringLiteral("feed-subscribe"))); action->setText(i18n("&Add Feed...")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFeedAdd); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_Insert)); action = coll->addAction(QStringLiteral("feed_add_group")); action->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); action->setText(i18n("Ne&w Folder...")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFeedAddGroup); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::Key_Insert)); action = coll->addAction(QStringLiteral("feed_remove")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action->setText(i18n("&Delete Feed")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFeedRemove); coll->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Delete)); action = coll->addAction(QStringLiteral("feed_modify")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); action->setText(i18n("&Edit Feed...")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFeedModify); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_F2)); // toolbar / View const MainWidget::ViewMode viewMode = static_cast(Settings::viewMode()); QActionGroup *group = new QActionGroup(this); action = coll->addAction(QStringLiteral("normal_view")); action->setCheckable(true); action->setChecked(viewMode == MainWidget::NormalView); group->addAction(action); action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom"))); action->setText(i18n("&Normal View")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotNormalView); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_1)); action = coll->addAction(QStringLiteral("widescreen_view")); action->setCheckable(true); action->setChecked(viewMode == MainWidget::WidescreenView); group->addAction(action); action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); action->setText(i18n("&Widescreen View")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotWidescreenView); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_2)); action = coll->addAction(QStringLiteral("combined_view")); action->setCheckable(true); action->setChecked(viewMode == MainWidget::CombinedView); group->addAction(action); action->setIcon(QIcon::fromTheme(QStringLiteral("view-list-text"))); action->setText(i18n("C&ombined View")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotCombinedView); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3)); // toolbar / feed menu action = coll->addAction(QStringLiteral("feed_fetch")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); action->setText(i18n("&Fetch Feed")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFetchCurrentFeed); coll->setDefaultShortcuts(action, KStandardShortcut::shortcut(KStandardShortcut::Reload)); action = coll->addAction(QStringLiteral("feed_fetch_all")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-bottom"))); action->setText(i18n("Fe&tch All Feeds")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFetchAllFeeds); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L)); QAction *stopAction = coll->addAction(QStringLiteral("feed_stop")); stopAction->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); stopAction->setText(i18n("C&ancel Feed Fetches")); connect(stopAction, &QAction::triggered, Kernel::self()->fetchQueue(), &FetchQueue::slotAbort); coll->setDefaultShortcut(stopAction, QKeySequence(Qt::Key_Escape)); stopAction->setEnabled(false); action = coll->addAction(QStringLiteral("feed_mark_all_as_read")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-read"))); action->setText(i18n("&Mark Feed as Read")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotMarkAllRead); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_R)); action = coll->addAction(QStringLiteral("feed_mark_all_feeds_as_read")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-read"))); action->setText(i18n("Ma&rk All Feeds as Read")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotMarkAllFeedsRead); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_R)); // Settings menu KToggleAction *sqf = coll->add(QStringLiteral("show_quick_filter")); sqf->setText(i18n("Show Quick Filter")); connect(sqf, &QAction::triggered, d->mainWidget, &MainWidget::slotToggleShowQuickFilter); sqf->setChecked(Settings::showQuickFilter()); action = coll->addAction(QStringLiteral("article_open")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); action->setText(i18n("Open in Tab")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotOpenSelectedArticles); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::Key_Return)); action = coll->addAction(QStringLiteral("article_open_in_background")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); action->setText(i18n("Open in Background Tab")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotOpenSelectedArticlesInBackground); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_Return)); action = coll->addAction(QStringLiteral("article_open_external")); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); action->setText(i18n("Open in External Browser")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotOpenSelectedArticlesInBrowser); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Return)); action = coll->addAction(QStringLiteral("article_copy_link_address")); action->setText(i18n("Copy Link Address")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotCopyLinkAddress); action = coll->addAction(QStringLiteral("go_prev_unread_article")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); action->setText(i18n("Pre&vious Unread Article")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotPrevUnreadArticle); coll->setDefaultShortcuts(action, QList() << QKeySequence(Qt::Key_Minus) << QKeySequence(Qt::Key_Minus + Qt::KeypadModifier)); action = coll->addAction(QStringLiteral("go_next_unread_article")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); action->setText(i18n("Ne&xt Unread Article")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotNextUnreadArticle); coll->setDefaultShortcuts(action, QList() << QKeySequence(Qt::Key_Plus) << QKeySequence(Qt::Key_Plus + Qt::KeypadModifier) << QKeySequence(Qt::Key_Equal) << QKeySequence(Qt::Key_Equal + Qt::KeypadModifier)); action = coll->addAction(QStringLiteral("article_delete")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action->setText(i18n("&Delete")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotArticleDelete); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_Delete)); KActionMenu *statusMenu = coll->add(QStringLiteral("article_set_status")); statusMenu->setText(i18n("&Mark As")); statusMenu->setEnabled(false); action = coll->addAction(QStringLiteral("article_set_status_read")); action->setText(i18nc("as in: mark as read", "&Read")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-read"))); action->setToolTip(i18n("Mark selected article as read")); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_E)); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotSetSelectedArticleRead); statusMenu->addAction(action); action = coll->addAction(QStringLiteral("article_set_status_new")); action->setText(i18nc("as in: mark as new", "&New")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-unread-new"))); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_N)); action->setToolTip(i18n("Mark selected article as new")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotSetSelectedArticleNew); statusMenu->addAction(action); action = coll->addAction(QStringLiteral("article_set_status_unread")); action->setText(i18nc("as in: mark as unread", "&Unread")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-unread"))); action->setToolTip(i18n("Mark selected article as unread")); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_U)); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotSetSelectedArticleUnread); statusMenu->addAction(action); KToggleAction *importantAction = coll->add(QStringLiteral("article_set_status_important")); importantAction->setText(i18n("&Mark as Important")); importantAction->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-important"))); const QList importantSC = {QKeySequence(Qt::CTRL + Qt::Key_I), QKeySequence(Qt::Key_I)}; coll->setDefaultShortcuts(importantAction, importantSC); importantAction->setCheckedState(KGuiItem(i18n("Remove &Important Mark"))); connect(importantAction, &QAction::triggered, d->mainWidget, &MainWidget::slotArticleToggleKeepFlag); action = coll->addAction(QStringLiteral("feedstree_move_up")); action->setText(i18n("Move Node Up")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotMoveCurrentNodeUp); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Up)); action = coll->addAction(QStringLiteral("feedstree_move_down")); action->setText(i18n("Move Node Down")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotMoveCurrentNodeDown); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Down)); action = coll->addAction(QStringLiteral("move_node_left")); action->setText(i18n("Move Node Left")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotMoveCurrentNodeLeft); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Left)); action = coll->addAction(QStringLiteral("feedstree_move_right")); action->setText(i18n("Move Node Right")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotMoveCurrentNodeRight); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Right)); action = coll->addAction(QStringLiteral("file_sendlink")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-message-new"))); action->setText(i18n("Send &Link Address...")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotSendLink); action = coll->addAction(QStringLiteral("file_sendfile")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-message-new"))); action->setText(i18n("Send &File...")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotSendFile); coll->addAction(QStringLiteral("share_serviceurl"), d->shareServiceManager->menu()); connect(d->shareServiceManager, &PimCommon::ShareServiceUrlManager::serviceUrlSelected, this, &ActionManagerImpl::slotServiceUrlSelected); d->mQuickSearchAction = new QAction(i18n("Set Focus to Quick Search"), this); //If change shortcut change Panel::setQuickSearchClickMessage(...) message actionCollection()->setDefaultShortcut(d->mQuickSearchAction, QKeySequence(Qt::ALT + Qt::Key_Q)); actionCollection()->addAction(QStringLiteral("focus_to_quickseach"), d->mQuickSearchAction); connect(d->mQuickSearchAction, &QAction::triggered, mainWidget, &MainWidget::slotFocusQuickSearch); setArticleActionsEnabled(false); } void ActionManagerImpl::slotServiceUrlSelected(PimCommon::ShareServiceUrlManager::ServiceType type) { if (d->mainWidget) { QString title; QString link; d->mainWidget->currentArticleInfo(link, title); const QUrl url = d->shareServiceManager->generateServiceUrl(link, title, type); d->shareServiceManager->openUrl(url); } } void ActionManagerImpl::initArticleListView(ArticleListView *articleList) { if (d->articleList) { return; } else { d->articleList = articleList; } QAction *action = actionCollection()->addAction(QStringLiteral("go_previous_article")); action->setText(i18n("&Previous Article")); connect(action, &QAction::triggered, articleList, &ArticleListView::slotPreviousArticle); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Left)); action = actionCollection()->addAction(QStringLiteral("go_next_article")); action->setText(i18n("&Next Article")); connect(action, &QAction::triggered, articleList, &ArticleListView::slotNextArticle); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Right)); } void ActionManagerImpl::initSubscriptionListView(SubscriptionListView *subscriptionListView) { if (d->subscriptionListView) { return; } else { d->subscriptionListView = subscriptionListView; } KActionCollection *coll = actionCollection(); QAction *action = coll->addAction(QStringLiteral("go_prev_feed")); action->setText(i18n("&Previous Feed")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotPrevFeed); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_P)); action = coll->addAction(QStringLiteral("go_next_feed")); action->setText(i18n("&Next Feed")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotNextFeed); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_N)); action = coll->addAction(QStringLiteral("go_next_unread_feed")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); action->setText(i18n("N&ext Unread Feed")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotNextUnreadFeed); coll->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Plus)); action = coll->addAction(QStringLiteral("go_prev_unread_feed")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); action->setText(i18n("Prev&ious Unread Feed")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotPrevUnreadFeed); coll->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Minus)); action = coll->addAction(QStringLiteral("feedstree_home")); action->setText(i18n("Go to Top of Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemBegin); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Home)); action = coll->addAction(QStringLiteral("feedstree_end")); action->setText(i18n("Go to Bottom of Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemEnd); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_End)); action = coll->addAction(QStringLiteral("feedstree_left")); action->setText(i18n("Go Left in Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemLeft); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Left)); action = coll->addAction(QStringLiteral("feedstree_right")); action->setText(i18n("Go Right in Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemRight); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Right)); action = coll->addAction(QStringLiteral("feedstree_up")); action->setText(i18n("Go Up in Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemUp); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Up)); action = coll->addAction(QStringLiteral("feedstree_down")); action->setText(i18n("Go Down in Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemDown); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Down)); + + action = coll->addAction(QStringLiteral("feed_hide_read")); + action->setCheckable(true); + action->setText(i18n("Hide Read Feeds")); + action->setChecked(Settings::hideReadFeeds()); + connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotSetHideReadFeeds); } void ActionManagerImpl::initTabWidget(TabWidget *tabWidget) { if (d->tabWidget) { return; } else { d->tabWidget = tabWidget; } KActionCollection *coll = actionCollection(); QAction *action = coll->addAction(QStringLiteral("select_next_tab")); action->setText(i18n("Select Next Tab")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotNextTab); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Period)); action = coll->addAction(QStringLiteral("select_previous_tab")); action->setText(i18n("Select Previous Tab")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotPreviousTab); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_Comma + Qt::CTRL)); action = coll->addAction(QStringLiteral("tab_detach")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); action->setText(i18n("Detach Tab")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotDetachTab); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B)); action = KStandardAction::copy(d->tabWidget, &TabWidget::slotCopy, coll); coll->addAction(QStringLiteral("viewer_copy"), action); action = KStandardAction::print(d->tabWidget, &TabWidget::slotPrint, coll); coll->addAction(QStringLiteral("viewer_print"), action); action = KStandardAction::printPreview(d->tabWidget, &TabWidget::slotPrintPreview, coll); coll->addAction(QStringLiteral("viewer_printpreview"), action); action = coll->addAction(QStringLiteral("tab_mute")); action->setText(i18n("Mute")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotMute); action = coll->addAction(QStringLiteral("tab_unmute")); action->setText(i18n("Unmute")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotUnMute); action = new QAction(i18n("Speak Text"), this); action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech"))); coll->addAction(QStringLiteral("speak_text"), action); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotTextToSpeech); action = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("&Find in Message..."), this); coll->addAction(QStringLiteral("find_in_messages"), action); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotFindTextInHtml); coll->setDefaultShortcut(action, KStandardShortcut::find().first()); action = coll->addAction(QStringLiteral("tab_copylinkaddress")); action->setText(i18n("Copy Link Address")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotCopyLinkAddress); action = coll->addAction(QStringLiteral("tab_remove")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); action->setText(i18n("Close Tab")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotCloseTab); coll->setDefaultShortcuts(action, KStandardShortcut::close()); d->zoomActionMenu = new WebEngineViewer::ZoomActionMenu(this); connect(d->zoomActionMenu, &WebEngineViewer::ZoomActionMenu::zoomChanged, d->tabWidget, &TabWidget::slotZoomChanged); d->zoomActionMenu->setActionCollection(coll); d->zoomActionMenu->createZoomActions(); QString actionname; for (int i = 1; i < 10; ++i) { actionname.sprintf("activate_tab_%02d", i); action = new QAction(i18n("Activate Tab %1", i), this); coll->addAction(actionname, action); coll->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+%1").arg(i))); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotActivateTab); } action = coll->addAction(QStringLiteral("savelinkas")); action->setText(i18n("&Save Link As...")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotSaveLinkAs); action = coll->addAction(QStringLiteral("copylinkaddress")); action->setText(i18n("Copy &Link Address")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotCopyLinkAddress); action = new QAction(i18n("Copy Image Location"), this); action->setIcon(QIcon::fromTheme(QStringLiteral("view-media-visualization"))); coll->addAction(QStringLiteral("copy_image_location"), action); coll->setShortcutsConfigurable(action, false); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotCopyImageLocation); // save Image On Disk action = new QAction(i18n("Save Image On Disk..."), this); coll->addAction(QStringLiteral("saveas_imageurl"), action); coll->setShortcutsConfigurable(action, false); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotSaveImageOnDisk); } QWidget *ActionManagerImpl::container(const QString &name) { if (d->part->factory()) { return d->part->factory()->container(name, d->part); } else { return 0; } } KActionCollection *ActionManagerImpl::actionCollection() const { return d->actionCollection; } QAction *ActionManagerImpl::action(const QString &name) { return d->actionCollection != nullptr ? d->actionCollection->action(name) : nullptr; } void ActionManagerImpl::setArticleActionsEnabled(bool enabled) { #undef setActionEnabled #define setActionEnabled(name) { QAction *const a = action(name); if (a) {a->setEnabled(enabled);}} setActionEnabled(QStringLiteral("article_open")) setActionEnabled(QStringLiteral("article_open_external")) setActionEnabled(QStringLiteral("article_set_status_important")) setActionEnabled(QStringLiteral("article_set_status")) setActionEnabled(QStringLiteral("article_delete")) setActionEnabled(QStringLiteral("file_sendlink")) setActionEnabled(QStringLiteral("file_sendfile")) setActionEnabled(QStringLiteral("article_open_in_background")) setActionEnabled(QStringLiteral("share_serviceurl")) #undef setActionEnabled } WebEngineViewer::ZoomActionMenu *ActionManagerImpl::zoomActionMenu() const { return d->zoomActionMenu; } QString ActionManagerImpl::quickSearchLineText() const { return d->quickSearchLineText(); } QString ActionManagerImpl::ActionManagerImplPrivate::quickSearchLineText() const { return mQuickSearchAction->shortcut().toString(); } diff --git a/src/actions/actionmanagerimpl.h b/src/actions/actionmanagerimpl.h index 29b6f7f2..6fff2c58 100644 --- a/src/actions/actionmanagerimpl.h +++ b/src/actions/actionmanagerimpl.h @@ -1,93 +1,94 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_ACTIONMANAGERIMPL_H #define AKREGATOR_ACTIONMANAGERIMPL_H #include "actionmanager.h" #include "PimCommon/ShareServiceUrlManager" class QAction; class KActionCollection; class QWidget; namespace WebEngineViewer { class ZoomActionMenu; } namespace Akregator { class ArticleListView; class MainWidget; class Part; class SubscriptionListView; class TabWidget; class TreeNode; class TrayIcon; /** * Akregator-specific implementation of the ActionManager interface */ class ActionManagerImpl : public ActionManager { Q_OBJECT public: explicit ActionManagerImpl(Part *part, QObject *parent = nullptr); virtual ~ActionManagerImpl(); QAction *action(const QString &name) override; QWidget *container(const QString &name) override; void initMainWidget(MainWidget *mainWidget); void initArticleListView(ArticleListView *articleList); void initSubscriptionListView(SubscriptionListView *subscriptionListView); void initTabWidget(TabWidget *tabWidget); void setArticleActionsEnabled(bool enabled) override; void setTrayIcon(TrayIcon *trayIcon); KActionCollection *actionCollection() const; WebEngineViewer::ZoomActionMenu *zoomActionMenu() const override; QString quickSearchLineText() const; public Q_SLOTS: void slotNodeSelected(Akregator::TreeNode *node); + void slotSettingsChanged(); private Q_SLOTS: void slotServiceUrlSelected(PimCommon::ShareServiceUrlManager::ServiceType type); private: void initPart(); friend class NodeSelectVisitor; class NodeSelectVisitor; class ActionManagerImplPrivate; ActionManagerImplPrivate *d; }; } // namespace Akregator #endif // AKREGATOR_ACTIONMANAGERIMPL_H diff --git a/src/data/akregator_part.rc b/src/data/akregator_part.rc index 2a27d45e..b9f5dc14 100644 --- a/src/data/akregator_part.rc +++ b/src/data/akregator_part.rc @@ -1,174 +1,176 @@ - + &Edit &View + + &Go Fee&d &Article &Settings Main Toolbar diff --git a/src/mainwidget.cpp b/src/mainwidget.cpp index d4b943f2..e2dacce5 100644 --- a/src/mainwidget.cpp +++ b/src/mainwidget.cpp @@ -1,1339 +1,1345 @@ /* This file is part of Akregator. Copyright (C) 2004 Stanislav Karchebny 2004 Sashmit Bhaduri 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "mainwidget.h" #include "utils.h" #include "actionmanagerimpl.h" #include "addfeeddialog.h" #include "articlelistview.h" #include "articleviewerwidget.h" #include "abstractselectioncontroller.h" #include "articlejobs.h" #include "articlematcher.h" #include "akregatorconfig.h" #include "akregator_part.h" #include "Libkdepim/BroadcastStatus" #include "createfeedcommand.h" #include "createfoldercommand.h" #include "deletesubscriptioncommand.h" #include "editsubscriptioncommand.h" #include "expireitemscommand.h" #include "importfeedlistcommand.h" #include "feed.h" #include "feedlist.h" #include "feedpropertiesdialog.h" #include "fetchqueue.h" #include "folder.h" #include "framemanager.h" #include "kernel.h" #include "notificationmanager.h" #include "openurlrequest.h" #include "progressmanager.h" #include "widgets/searchbar.h" #include "selectioncontroller.h" #include "subscriptionlistjobs.h" #include "subscriptionlistmodel.h" #include "subscriptionlistview.h" #include "tabwidget.h" #include "treenode.h" #include "treenodevisitor.h" #include "types.h" #include "mainframe.h" #include #include "job/downloadarticlejob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "articleviewer-ng/webengine/articleviewerwebenginewidgetng.h" using namespace Akregator; MainWidget::~MainWidget() { // if m_shuttingDown is false, slotOnShutdown was not called. That // means that not the whole app is shutdown, only the part. So it // should be no risk to do the cleanups now if (!m_shuttingDown) { slotOnShutdown(); } } MainWidget::MainWidget(Part *part, QWidget *parent, ActionManagerImpl *actionManager, const QString &name) : QWidget(parent) , m_feedList() , m_viewMode(NormalView) , m_actionManager(actionManager) , m_feedListManagementInterface(new FeedListManagementImpl) { setObjectName(name); FeedListManagementInterface::setInstance(m_feedListManagementInterface); m_actionManager->initMainWidget(this); m_part = part; m_shuttingDown = false; m_displayingAboutPage = false; setFocusPolicy(Qt::StrongFocus); + connect(m_part, &Part::signalSettingsChanged, + m_actionManager, &ActionManagerImpl::slotSettingsChanged); + QVBoxLayout *lt = new QVBoxLayout(this); lt->setMargin(0); m_horizontalSplitter = new QSplitter(Qt::Horizontal, this); m_horizontalSplitter->setOpaqueResize(true); m_horizontalSplitter->setChildrenCollapsible(false); lt->addWidget(m_horizontalSplitter); connect(Kernel::self()->fetchQueue(), &FetchQueue::signalStarted, this, &MainWidget::slotFetchingStarted); connect(Kernel::self()->fetchQueue(), &FetchQueue::signalStopped, this, &MainWidget::slotFetchingStopped); m_feedListView = new SubscriptionListView(m_horizontalSplitter); m_feedListView->setObjectName(QStringLiteral("feedtree")); m_actionManager->initSubscriptionListView(m_feedListView); connect(m_feedListView, &SubscriptionListView::userActionTakingPlace, this, &MainWidget::ensureArticleTabVisible); m_tabWidget = new TabWidget(m_horizontalSplitter); m_actionManager->initTabWidget(m_tabWidget); connect(m_part, &Part::signalSettingsChanged, this, &MainWidget::slotSettingsChanged); connect(m_tabWidget, &TabWidget::signalCurrentFrameChanged, this, &MainWidget::slotCurrentFrameChanged); connect(m_tabWidget, &TabWidget::signalRemoveFrameRequest, Kernel::self()->frameManager(), &FrameManager::slotRemoveFrame); connect(m_tabWidget, SIGNAL(signalOpenUrlRequest(Akregator::OpenUrlRequest&)), Kernel::self()->frameManager(), SLOT(slotOpenUrlRequest(Akregator::OpenUrlRequest&))); connect(Kernel::self()->frameManager(), &FrameManager::signalFrameAdded, m_tabWidget, &TabWidget::slotAddFrame); connect(Kernel::self()->frameManager(), &FrameManager::signalSelectFrame, m_tabWidget, &TabWidget::slotSelectFrame); connect(Kernel::self()->frameManager(), &FrameManager::signalFrameRemoved, m_tabWidget, &TabWidget::slotRemoveFrame); connect(Kernel::self()->frameManager(), &FrameManager::signalRequestNewFrame, this, &MainWidget::slotRequestNewFrame); connect(Kernel::self()->frameManager(), &FrameManager::signalFrameRemoved, this, &MainWidget::slotFramesChanged); connect(Kernel::self()->frameManager(), &FrameManager::signalCompleted, this, &MainWidget::slotFramesChanged); connect(PimCommon::NetworkManager::self()->networkConfigureManager(), &QNetworkConfigurationManager::onlineStateChanged, this, &MainWidget::slotNetworkStatusChanged); m_tabWidget->setWhatsThis(i18n("You can view multiple articles in several open tabs.")); m_mainTab = new QWidget(this); m_mainTab->setObjectName(QStringLiteral("Article Tab")); m_mainTab->setWhatsThis(i18n("Articles list.")); QVBoxLayout *mainTabLayout = new QVBoxLayout(m_mainTab); mainTabLayout->setMargin(0); m_searchBar = new SearchBar(m_mainTab); if (!Settings::showQuickFilter()) { m_searchBar->hide(); } m_articleSplitter = new QSplitter(Qt::Vertical, m_mainTab); m_articleSplitter->setObjectName(QStringLiteral("panner2")); m_articleSplitter->setChildrenCollapsible(false); m_articleWidget = new QWidget(m_articleSplitter); QVBoxLayout *articleWidgetLayout = new QVBoxLayout; m_articleWidget->setLayout(articleWidgetLayout); articleWidgetLayout->setMargin(0); articleWidgetLayout->setSpacing(0); m_articleListView = new ArticleListView; articleWidgetLayout->addWidget(m_searchBar); articleWidgetLayout->addWidget(m_articleListView); connect(m_articleListView, &ArticleListView::userActionTakingPlace, this, &MainWidget::ensureArticleTabVisible); m_selectionController = new SelectionController(this); m_selectionController->setArticleLister(m_articleListView); m_selectionController->setFeedSelector(m_feedListView); connect(m_searchBar, &SearchBar::signalSearch, m_selectionController, &AbstractSelectionController::setFilters); + connect(m_part, &Part::signalSettingsChanged, + m_selectionController, &AbstractSelectionController::settingsChanged); + FolderExpansionHandler *expansionHandler = new FolderExpansionHandler(this); connect(m_feedListView, &QTreeView::expanded, expansionHandler, &FolderExpansionHandler::itemExpanded); connect(m_feedListView, &QTreeView::collapsed, expansionHandler, &FolderExpansionHandler::itemCollapsed); m_selectionController->setFolderExpansionHandler(expansionHandler); connect(m_selectionController, &AbstractSelectionController::currentSubscriptionChanged, this, &MainWidget::slotNodeSelected); connect(m_selectionController, &AbstractSelectionController::currentArticleChanged, this, &MainWidget::slotArticleSelected); connect(m_selectionController, &AbstractSelectionController::articleDoubleClicked, this, &MainWidget::slotOpenArticleInBrowser); m_actionManager->initArticleListView(m_articleListView); connect(m_articleListView, &ArticleListView::signalMouseButtonPressed, this, &MainWidget::slotMouseButtonPressed); m_articleViewer = new ArticleViewerWidget(Settings::grantleeDirectory(), m_actionManager->actionCollection(), m_articleSplitter); m_articleListView->setFocusProxy(m_articleViewer); setFocusProxy(m_articleViewer); connect(m_articleViewer, &ArticleViewerWidget::showStatusBarMessage, this, &MainWidget::slotShowStatusBarMessage); connect(m_articleViewer, SIGNAL(signalOpenUrlRequest(Akregator::OpenUrlRequest&)), Kernel::self()->frameManager(), SLOT(slotOpenUrlRequest(Akregator::OpenUrlRequest&))); connect(m_searchBar, &SearchBar::signalSearch, m_articleViewer, &ArticleViewerWidget::setFilters); mainTabLayout->addWidget(m_articleSplitter); m_mainFrame = new MainFrame(this, m_mainTab); m_mainFrame->slotSetTitle(i18n("Articles")); m_mainFrame->setArticleViewer(m_articleViewer); connect(m_articleViewer->articleViewerWidgetNg()->articleViewerNg(), &ArticleViewerWebEngine::articleAction, this, &MainWidget::slotArticleAction); connect(m_tabWidget, &TabWidget::signalCopyInFrame, m_mainFrame, &MainFrame::slotCopyInFrame); connect(m_tabWidget, &TabWidget::signalPrintInFrame, m_mainFrame, &MainFrame::slotPrintInFrame); connect(m_tabWidget, &TabWidget::signalZoomChangedInFrame, m_mainFrame, &MainFrame::slotZoomChangeInFrame); connect(m_tabWidget, &TabWidget::signalPrintPreviewInFrame, m_mainFrame, &MainFrame::slotPrintPreviewInFrame); connect(m_tabWidget, &TabWidget::signalFindTextInFrame, m_mainFrame, &MainFrame::slotFindTextInFrame); connect(m_tabWidget, &TabWidget::signalTextToSpeechInFrame, m_mainFrame, &MainFrame::slotTextToSpeechInFrame); connect(m_tabWidget, &TabWidget::signalSaveLinkAsInFrame, m_mainFrame, &MainFrame::slotSaveLinkAsInFrame); connect(m_tabWidget, &TabWidget::signalCopyLinkAsInFrame, m_mainFrame, &MainFrame::slotCopyLinkAsInFrame); connect(m_tabWidget, &TabWidget::signalCopyImageLocation, m_mainFrame, &MainFrame::slotCopyImageLocationInFrame); connect(m_tabWidget, &TabWidget::signalSaveImageOnDisk, m_mainFrame, &MainFrame::slotSaveImageOnDiskInFrame); connect(m_tabWidget, &TabWidget::signalMute, m_mainFrame, &MainFrame::slotMute); Kernel::self()->frameManager()->slotAddFrame(m_mainFrame); const QList sp1sizes = Settings::splitter1Sizes(); if (sp1sizes.count() >= m_horizontalSplitter->count()) { m_horizontalSplitter->setSizes(sp1sizes); } const QList sp2sizes = Settings::splitter2Sizes(); if (sp2sizes.count() >= m_articleSplitter->count()) { m_articleSplitter->setSizes(sp2sizes); } if (!Settings::self()->disableIntroduction()) { m_articleWidget->hide(); m_articleViewer->displayAboutPage(); m_mainFrame->slotSetTitle(i18n("About")); m_displayingAboutPage = true; } m_fetchTimer = new QTimer(this); connect(m_fetchTimer, &QTimer::timeout, this, &MainWidget::slotDoIntervalFetches); m_fetchTimer->start(1000 * 60); // delete expired articles once per hour m_expiryTimer = new QTimer(this); connect(m_expiryTimer, &QTimer::timeout, this, &MainWidget::slotDeleteExpiredArticles); m_expiryTimer->start(3600 * 1000); m_markReadTimer = new QTimer(this); m_markReadTimer->setSingleShot(true); connect(m_markReadTimer, &QTimer::timeout, this, &MainWidget::slotSetCurrentArticleReadDelayed); setFeedList(QSharedPointer(new FeedList(Kernel::self()->storage()))); switch (Settings::viewMode()) { case CombinedView: slotCombinedView(); break; case WidescreenView: slotWidescreenView(); break; default: slotNormalView(); } if (!Settings::resetQuickFilterOnNodeChange()) { m_searchBar->slotSetStatus(Settings::statusFilter()); m_searchBar->slotSetText(Settings::textFilter()); } } void MainWidget::slotSettingsChanged() { m_tabWidget->slotSettingsChanged(); m_articleViewer->updateAfterConfigChanged(); } void MainWidget::slotOnShutdown() { disconnect(m_tabWidget, &TabWidget::signalCurrentFrameChanged, this, &MainWidget::slotCurrentFrameChanged); m_shuttingDown = true; // close all pageviewers in a controlled way // fixes bug 91660, at least when no part loading data while (m_tabWidget->count() > 1) { // remove frames until only the main frame remains m_tabWidget->setCurrentIndex(m_tabWidget->count() - 1); // select last page m_tabWidget->slotRemoveCurrentFrame(); } Kernel::self()->fetchQueue()->slotAbort(); setFeedList(QSharedPointer()); delete m_feedListManagementInterface; delete m_feedListView; // call delete here, so that the header settings will get saved delete m_articleListView; // same for this one delete m_mainTab; delete m_mainFrame; m_mainFrame = 0; Settings::self()->save(); } void MainWidget::saveSettings() { const QList spl1 = m_horizontalSplitter->sizes(); if (std::count(spl1.begin(), spl1.end(), 0) == 0) { Settings::setSplitter1Sizes(spl1); } const QList spl2 = m_articleSplitter->sizes(); if (std::count(spl2.begin(), spl2.end(), 0) == 0) { Settings::setSplitter2Sizes(spl2); } Settings::setViewMode(m_viewMode); Settings::self()->save(); } void MainWidget::connectFrame(Akregator::WebEngineFrame *frame) { connect(m_tabWidget, &TabWidget::signalCopyInFrame, frame, &WebEngineFrame::slotCopyInFrame); connect(m_tabWidget, &TabWidget::signalPrintInFrame, frame, &WebEngineFrame::slotPrintInFrame); connect(m_tabWidget, &TabWidget::signalZoomChangedInFrame, frame, &WebEngineFrame::slotZoomChangeInFrame); connect(m_tabWidget, &TabWidget::signalPrintPreviewInFrame, frame, &WebEngineFrame::slotPrintPreviewInFrame); connect(m_tabWidget, &TabWidget::signalFindTextInFrame, frame, &WebEngineFrame::slotFindTextInFrame); connect(m_tabWidget, &TabWidget::signalTextToSpeechInFrame, frame, &WebEngineFrame::slotTextToSpeechInFrame); connect(m_tabWidget, &TabWidget::signalSaveLinkAsInFrame, frame, &WebEngineFrame::slotSaveLinkAsInFrame); connect(m_tabWidget, &TabWidget::signalCopyLinkAsInFrame, frame, &WebEngineFrame::slotCopyLinkAsInFrame); connect(m_tabWidget, &TabWidget::signalCopyImageLocation, frame, &WebEngineFrame::slotCopyImageLocationInFrame); connect(m_tabWidget, &TabWidget::signalSaveImageOnDisk, frame, &WebEngineFrame::slotSaveImageOnDiskInFrame); connect(m_tabWidget, &TabWidget::signalMute, frame, &WebEngineFrame::slotMute); connect(frame, &WebEngineFrame::showStatusBarMessage, this, &MainWidget::slotShowStatusBarMessage); connect(frame, &WebEngineFrame::signalIconChanged, m_tabWidget, &TabWidget::slotSetIcon); connect(frame, &WebEngineFrame::webPageMutedOrAudibleChanged, m_tabWidget, &TabWidget::slotWebPageMutedOrAudibleChanged); } void MainWidget::slotRequestNewFrame(int &frameId) { WebEngineFrame *frame = new WebEngineFrame(m_actionManager->actionCollection(), m_tabWidget); connectFrame(frame); Kernel::self()->frameManager()->slotAddFrame(frame); frameId = frame->id(); } void MainWidget::sendArticle(bool attach) { QByteArray text; QString title; Frame *frame = Kernel::self()->frameManager()->currentFrame(); if (frame && frame->id() > 0) { // are we in some other tab than the articlelist? text = frame->url().toString().toLatin1(); title = frame->title(); } else { // nah, we're in articlelist.. const Article article = m_selectionController->currentArticle(); if (!article.isNull()) { text = article.link().toDisplayString().toLatin1(); title = Akregator::Utils::convertHtmlTags(article.title()); } } if (text.isEmpty()) { return; } sendArticle(text, title, attach); } void MainWidget::cleanUpDownloadFile() { for (QPointer job : qAsConst(mListDownloadArticleJobs)) { if (job) { job->forceCleanupTemporaryFile(); } } } void MainWidget::sendArticle(const QByteArray &text, const QString &title, bool attach) { if (attach) { QPointer download = new Akregator::DownloadArticleJob(this); download->setArticleUrl(QUrl(QString::fromUtf8(text))); download->setText(QString::fromUtf8(text)); download->setTitle(title); mListDownloadArticleJobs.append(download); download->start(); } else { QUrlQuery query; query.addQueryItem(QStringLiteral("subject"), title); query.addQueryItem(QStringLiteral("body"), QString::fromUtf8(text)); QUrl url; url.setScheme(QStringLiteral("mailto")); url.setQuery(query); QDesktopServices::openUrl(url); } } void MainWidget::importFeedList(const QDomDocument &doc) { ImportFeedListCommand *cmd = new ImportFeedListCommand; cmd->setParentWidget(this); cmd->setFeedListDocument(doc); cmd->setTargetList(m_feedList); cmd->start(); } void MainWidget::setFeedList(const QSharedPointer &list) { if (list == m_feedList) { return; } const QSharedPointer oldList = m_feedList; m_feedList = list; if (m_feedList) { connect(m_feedList.data(), &FeedList::unreadCountChanged, this, &MainWidget::slotSetTotalUnread); } slotSetTotalUnread(); m_feedListManagementInterface->setFeedList(m_feedList); Kernel::self()->setFeedList(m_feedList); ProgressManager::self()->setFeedList(m_feedList); m_selectionController->setFeedList(m_feedList); if (oldList) { oldList->disconnect(this); } slotDeleteExpiredArticles(); } void MainWidget::deleteExpiredArticles(const QSharedPointer &list) { if (!list) { return; } ExpireItemsCommand *cmd = new ExpireItemsCommand(this); cmd->setParentWidget(this); cmd->setFeedList(list); cmd->setFeeds(list->feedIds()); cmd->start(); } void MainWidget::slotDeleteExpiredArticles() { deleteExpiredArticles(m_feedList); } QDomDocument MainWidget::feedListToOPML() { QDomDocument dom; if (m_feedList) { dom = m_feedList->toOpml(); } return dom; } void MainWidget::addFeedToGroup(const QString &url, const QString &groupName) { // Locate the group. const QList namedGroups = m_feedList->findByTitle(groupName); Folder *group = nullptr; for (TreeNode *const candidate : namedGroups) { if (candidate->isGroup()) { group = static_cast(candidate); break; } } if (!group) { Folder *g = new Folder(groupName); m_feedList->allFeedsFolder()->appendChild(g); group = g; } // Invoke the Add Feed dialog with url filled in. addFeed(url, 0, group, true); } void MainWidget::slotNormalView() { if (m_viewMode == NormalView) { return; } if (m_viewMode == CombinedView) { m_articleWidget->show(); const Article article = m_selectionController->currentArticle(); if (!article.isNull()) { m_articleViewer->showArticle(article); } else { m_articleViewer->slotShowSummary(m_selectionController->selectedSubscription()); } } m_articleSplitter->setOrientation(Qt::Vertical); m_viewMode = NormalView; Settings::setViewMode(m_viewMode); } void MainWidget::slotWidescreenView() { if (m_viewMode == WidescreenView) { return; } if (m_viewMode == CombinedView) { m_articleWidget->show(); Article article = m_selectionController->currentArticle(); if (!article.isNull()) { m_articleViewer->showArticle(article); } else { m_articleViewer->slotShowSummary(m_selectionController->selectedSubscription()); } } m_articleSplitter->setOrientation(Qt::Horizontal); m_viewMode = WidescreenView; Settings::setViewMode(m_viewMode); } void MainWidget::slotCombinedView() { if (m_viewMode == CombinedView) { return; } m_articleListView->slotClear(); m_articleWidget->hide(); m_viewMode = CombinedView; Settings::setViewMode(m_viewMode); } void MainWidget::slotMoveCurrentNodeUp() { TreeNode *current = m_selectionController->selectedSubscription(); if (!current) { return; } TreeNode *prev = current->prevSibling(); Folder *parent = current->parent(); if (!prev || !parent) { return; } parent->removeChild(prev); parent->insertChild(prev, current); m_feedListView->ensureNodeVisible(current); } void MainWidget::slotMoveCurrentNodeDown() { TreeNode *current = m_selectionController->selectedSubscription(); if (!current) { return; } TreeNode *next = current->nextSibling(); Folder *parent = current->parent(); if (!next || !parent) { return; } parent->removeChild(current); parent->insertChild(current, next); m_feedListView->ensureNodeVisible(current); } void MainWidget::slotMoveCurrentNodeLeft() { TreeNode *current = m_selectionController->selectedSubscription(); if (!current || !current->parent() || !current->parent()->parent()) { return; } Folder *parent = current->parent(); Folder *grandparent = current->parent()->parent(); parent->removeChild(current); grandparent->insertChild(current, parent); m_feedListView->ensureNodeVisible(current); } void MainWidget::slotMoveCurrentNodeRight() { TreeNode *current = m_selectionController->selectedSubscription(); if (!current || !current->parent()) { return; } TreeNode *prev = current->prevSibling(); if (prev && prev->isGroup()) { Folder *fg = static_cast(prev); current->parent()->removeChild(current); fg->appendChild(current); m_feedListView->ensureNodeVisible(current); } } void MainWidget::slotSendLink() { sendArticle(); } void MainWidget::slotSendFile() { sendArticle(true); } void MainWidget::slotNodeSelected(TreeNode *node) { m_markReadTimer->stop(); if (m_displayingAboutPage) { m_mainFrame->slotSetTitle(i18n("Articles")); if (m_viewMode != CombinedView) { m_articleWidget->show(); } if (Settings::showQuickFilter()) { m_searchBar->show(); } m_displayingAboutPage = false; } m_tabWidget->setCurrentWidget(m_mainFrame); if (Settings::resetQuickFilterOnNodeChange()) { m_searchBar->slotClearSearch(); } if (m_viewMode == CombinedView) { m_articleViewer->showNode(node); } else { m_articleViewer->slotShowSummary(node); } if (node) { m_mainFrame->setWindowTitle(node->title()); } m_actionManager->slotNodeSelected(node); } void MainWidget::slotFeedAdd() { Folder *group = nullptr; if (!m_selectionController->selectedSubscription()) { group = m_feedList->allFeedsFolder(); } else { if (m_selectionController->selectedSubscription()->isGroup()) { group = static_cast(m_selectionController->selectedSubscription()); } else { group = m_selectionController->selectedSubscription()->parent(); } } TreeNode *const lastChild = !group->children().isEmpty() ? group->children().last() : nullptr; addFeed(QString(), lastChild, group, false); } void MainWidget::addFeed(const QString &url, TreeNode *after, Folder *parent, bool autoExec) { CreateFeedCommand *cmd(new CreateFeedCommand(this)); cmd->setParentWidget(this); cmd->setPosition(parent, after); cmd->setRootFolder(m_feedList->allFeedsFolder()); cmd->setAutoExecute(autoExec); cmd->setUrl(url); cmd->setSubscriptionListView(m_feedListView); cmd->start(); } void MainWidget::slotFeedAddGroup() { CreateFolderCommand *cmd = new CreateFolderCommand(this); cmd->setParentWidget(this); cmd->setSelectedSubscription(m_selectionController->selectedSubscription()); cmd->setRootFolder(m_feedList->allFeedsFolder()); cmd->setSubscriptionListView(m_feedListView); cmd->start(); } void MainWidget::slotFeedRemove() { TreeNode *selectedNode = m_selectionController->selectedSubscription(); // don't delete root element! (safety valve) if (!selectedNode || selectedNode == m_feedList->allFeedsFolder()) { return; } DeleteSubscriptionCommand *cmd = new DeleteSubscriptionCommand(this); cmd->setParentWidget(this); cmd->setSubscription(m_feedList, selectedNode->id()); cmd->start(); } void MainWidget::slotFeedModify() { TreeNode *const node = m_selectionController->selectedSubscription(); if (!node) { return; } EditSubscriptionCommand *cmd = new EditSubscriptionCommand(this); cmd->setParentWidget(this); cmd->setSubscription(m_feedList, node->id()); cmd->setSubscriptionListView(m_feedListView); cmd->start(); } void MainWidget::slotNextUnreadArticle() { ensureArticleTabVisible(); if (m_viewMode == CombinedView) { m_feedListView->slotNextUnreadFeed(); return; } TreeNode *sel = m_selectionController->selectedSubscription(); if (sel && sel->unread() > 0) { m_articleListView->slotNextUnreadArticle(); } else { m_feedListView->slotNextUnreadFeed(); } } void MainWidget::slotPrevUnreadArticle() { ensureArticleTabVisible(); if (m_viewMode == CombinedView) { m_feedListView->slotPrevUnreadFeed(); return; } TreeNode *sel = m_selectionController->selectedSubscription(); if (sel && sel->unread() > 0) { m_articleListView->slotPreviousUnreadArticle(); } else { m_feedListView->slotPrevUnreadFeed(); } } void MainWidget::slotMarkAllFeedsRead() { KJob *job = m_feedList->createMarkAsReadJob(); connect(job, &KJob::finished, m_selectionController, &AbstractSelectionController::forceFilterUpdate); job->start(); } void MainWidget::slotMarkAllRead() { if (!m_selectionController->selectedSubscription()) { return; } KJob *job = m_selectionController->selectedSubscription()->createMarkAsReadJob(); connect(job, &KJob::finished, m_selectionController, &AbstractSelectionController::forceFilterUpdate); job->start(); } void MainWidget::slotSetTotalUnread() { Q_EMIT signalUnreadCountChanged(m_feedList ? m_feedList->unread() : 0); } void MainWidget::slotDoIntervalFetches() { if (!m_feedList) { return; } #if 0 // the following solid check apparently doesn't work reliably and causes // interval fetching not working although the user is actually online (but solid reports he's not const Networking::Status status = Solid::Networking::status(); if (status != Networking::Connected && status != Networking::Unknown) { return; } #endif m_feedList->addToFetchQueue(Kernel::self()->fetchQueue(), true); } void MainWidget::slotFetchCurrentFeed() { if (!m_selectionController->selectedSubscription()) { return; } if (isNetworkAvailable()) { m_selectionController->selectedSubscription()->slotAddToFetchQueue(Kernel::self()->fetchQueue()); } else { m_mainFrame->slotSetStatusText(i18n("Networking is not available.")); } } void MainWidget::slotFetchAllFeeds() { if (m_feedList && isNetworkAvailable()) { m_feedList->addToFetchQueue(Kernel::self()->fetchQueue()); } else if (m_feedList) { m_mainFrame->slotSetStatusText(i18n("Networking is not available.")); } } void MainWidget::slotFetchingStarted() { m_mainFrame->slotSetState(Frame::Started); m_actionManager->action(QStringLiteral("feed_stop"))->setEnabled(true); m_mainFrame->slotSetStatusText(i18n("Fetching Feeds...")); } void MainWidget::slotFetchingStopped() { m_mainFrame->slotSetState(Frame::Completed); m_actionManager->action(QStringLiteral("feed_stop"))->setEnabled(false); m_mainFrame->slotSetStatusText(QString()); } void MainWidget::slotArticleSelected(const Akregator::Article &article) { if (m_viewMode == CombinedView) { return; } m_markReadTimer->stop(); Q_ASSERT(article.isNull() || article.feed()); QVector
articles = m_selectionController->selectedArticles(); Q_EMIT signalArticlesSelected(articles); KToggleAction *const maai = qobject_cast(m_actionManager->action(QStringLiteral("article_set_status_important"))); Q_ASSERT(maai); maai->setChecked(article.keep()); m_articleViewer->showArticle(article); if (m_selectionController->selectedArticles().isEmpty()) { m_articleListView->setCurrentIndex(m_selectionController->currentArticleIndex()); } if (article.isNull() || article.status() == Akregator::Read) { return; } if (!Settings::useMarkReadDelay()) { return; } const int delay = Settings::markReadDelay(); if (delay > 0) { m_markReadTimer->start(delay * 1000); } else { Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob; const Akregator::ArticleId aid = { article.feed()->xmlUrl(), article.guid() }; job->setStatus(aid, Akregator::Read); job->start(); } } void MainWidget::slotMouseButtonPressed(int button, const QUrl &url) { if (button != Qt::MidButton) { return; } if (!url.isValid()) { return; } OpenUrlRequest req(url); switch (Settings::mMBBehaviour()) { case Settings::EnumMMBBehaviour::OpenInExternalBrowser: req.setOptions(OpenUrlRequest::ExternalBrowser); break; case Settings::EnumMMBBehaviour::OpenInBackground: req.setOptions(OpenUrlRequest::NewTab); req.setOpenInBackground(true); break; default: req.setOptions(OpenUrlRequest::NewTab); req.setOpenInBackground(false); } Kernel::self()->frameManager()->slotOpenUrlRequest(req); } void MainWidget::slotOpenHomepage() { Feed *feed = dynamic_cast(m_selectionController->selectedSubscription()); if (!feed) { return; } QUrl url(feed->htmlUrl()); if (url.isValid()) { OpenUrlRequest req(url); req.setOptions(OpenUrlRequest::ExternalBrowser); Kernel::self()->frameManager()->slotOpenUrlRequest(req); } } void MainWidget::slotOpenSelectedArticlesInBrowser() { const QVector
articles = m_selectionController->selectedArticles(); for (const Akregator::Article &article : articles) { slotOpenArticleInBrowser(article); } } void MainWidget::slotOpenArticleInBrowser(const Akregator::Article &article) { if (!article.isNull() && article.link().isValid()) { OpenUrlRequest req(article.link()); req.setOptions(OpenUrlRequest::ExternalBrowser); Kernel::self()->frameManager()->slotOpenUrlRequest(req); } } void MainWidget::openSelectedArticles(bool openInBackground) { const QVector
articles = m_selectionController->selectedArticles(); for (const Akregator::Article &article : articles) { const QUrl url = article.link(); if (!url.isValid()) { continue; } OpenUrlRequest req(url); req.setOptions(OpenUrlRequest::NewTab); if (openInBackground) { req.setOpenInBackground(true); Kernel::self()->frameManager()->slotOpenUrlRequest(req, false /*don't use settings for open in background*/); } else { Kernel::self()->frameManager()->slotOpenUrlRequest(req); } } } void MainWidget::currentArticleInfo(QString &link, QString &title) { const Article article = m_selectionController->currentArticle(); if (article.isNull()) { return; } if (article.link().isValid()) { link = article.link().url(); title = Utils::convertHtmlTags(article.title()); } } void MainWidget::updateQuickSearchLineText() { m_searchBar->updateQuickSearchLineText(m_actionManager->quickSearchLineText()); } void MainWidget::slotCopyLinkAddress() { const Article article = m_selectionController->currentArticle(); if (article.isNull()) { return; } QString link; if (article.link().isValid()) { link = article.link().url(); QClipboard *cb = QApplication::clipboard(); cb->setText(link, QClipboard::Clipboard); // don't set url to selection as it's a no-no according to a fd.o spec //cb->setText(link, QClipboard::Selection); } } void MainWidget::slotToggleShowQuickFilter() { if (Settings::showQuickFilter()) { Settings::setShowQuickFilter(false); m_searchBar->slotClearSearch(); m_searchBar->hide(); } else { Settings::setShowQuickFilter(true); if (!m_displayingAboutPage) { m_searchBar->show(); } } } void MainWidget::slotArticleDelete() { if (m_viewMode == CombinedView) { return; } const QVector
articles = m_selectionController->selectedArticles(); QString msg; switch (articles.count()) { case 0: return; case 1: msg = i18n("Are you sure you want to delete article %1?", articles.first().title()); break; default: msg = i18np("Are you sure you want to delete the selected article?", "Are you sure you want to delete the %1 selected articles?", articles.count()); } if (KMessageBox::warningContinueCancel(this, msg, i18n("Delete Article"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QStringLiteral("Disable delete article confirmation")) != KMessageBox::Continue) { return; } TreeNode *const selected = m_selectionController->selectedSubscription(); if (selected) { selected->setNotificationMode(false); } Akregator::ArticleDeleteJob *job = new Akregator::ArticleDeleteJob; for (const Akregator::Article &i : articles) { Feed *const feed = i.feed(); if (!feed) { continue; } const Akregator::ArticleId aid = { feed->xmlUrl(), i.guid() }; job->appendArticleId(aid); } job->start(); if (selected) { selected->setNotificationMode(true); } } void MainWidget::slotFramesChanged() { // We need to wait till the frame is fully loaded QMetaObject::invokeMethod(m_part, "slotAutoSave", Qt::QueuedConnection); } void MainWidget::slotArticleToggleKeepFlag(bool) { const QVector
articles = m_selectionController->selectedArticles(); if (articles.isEmpty()) { return; } bool allFlagsSet = true; for (const Akregator::Article &i : articles) { allFlagsSet = allFlagsSet && i.keep(); if (!allFlagsSet) { break; } } Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob; for (const Akregator::Article &i : articles) { const Akregator::ArticleId aid = { i.feed()->xmlUrl(), i.guid() }; job->setKeep(aid, !allFlagsSet); } job->start(); } namespace { void setArticleStatus(const QString &feedUrl, const QString &articleId, int status) { if (!feedUrl.isEmpty() && !articleId.isEmpty()) { Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob; const Akregator::ArticleId aid = { feedUrl, articleId }; job->setStatus(aid, status); job->start(); } } void setSelectedArticleStatus(const Akregator::AbstractSelectionController *controller, int status) { const QVector articles = controller->selectedArticles(); if (articles.isEmpty()) { return; } Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob; for (const Akregator::Article &i : articles) { const Akregator::ArticleId aid = { i.feed()->xmlUrl(), i.guid() }; job->setStatus(aid, status); } job->start(); } } void MainWidget::slotSetSelectedArticleRead() { ::setSelectedArticleStatus(m_selectionController, Akregator::Read); } void MainWidget::slotSetSelectedArticleUnread() { ::setSelectedArticleStatus(m_selectionController, Akregator::Unread); } void MainWidget::slotSetSelectedArticleNew() { ::setSelectedArticleStatus(m_selectionController, Akregator::New); } void MainWidget::slotSetCurrentArticleReadDelayed() { const Article article = m_selectionController->currentArticle(); if (article.isNull()) { return; } Akregator::ArticleModifyJob *const job = new Akregator::ArticleModifyJob; const Akregator::ArticleId aid = { article.feed()->xmlUrl(), article.guid() }; job->setStatus(aid, Akregator::Read); job->start(); } void MainWidget::slotShowStatusBarMessage(const QString &msg) { KPIM::BroadcastStatus::instance()->setStatusMsg(msg); } void MainWidget::readProperties(const KConfigGroup &config) { if (!Settings::resetQuickFilterOnNodeChange()) { // read filter settings m_searchBar->slotSetText(config.readEntry("searchLine")); m_searchBar->slotSetStatus(config.readEntry("searchCombo").toInt()); } const QString currentTabName = config.readEntry("CurrentTab"); // Reopen tabs const QStringList childList = config.readEntry(QStringLiteral("Children"), QStringList()); int currentFrameId = -1; for (const QString &framePrefix : childList) { WebEngineFrame *const frame = new WebEngineFrame(m_actionManager->actionCollection(), m_tabWidget); frame->loadConfig(config, framePrefix + QLatin1Char('_')); connectFrame(frame); Kernel::self()->frameManager()->slotAddFrame(frame); if (currentTabName == framePrefix) { currentFrameId = frame->id(); } } if (currentFrameId != -1) { m_tabWidget->slotSelectFrame(currentFrameId); } } void MainWidget::saveProperties(KConfigGroup &config) { // save filter settings const QString searchStr(m_searchBar->text()); if (searchStr.isEmpty()) { config.deleteEntry("searchLine"); } else { config.writeEntry("searchLine", m_searchBar->text()); } config.writeEntry("searchCombo", m_searchBar->status()); Kernel::self()->frameManager()->saveProperties(config); } void MainWidget::ensureArticleTabVisible() { m_tabWidget->setCurrentWidget(m_mainFrame); } void MainWidget::slotReloadAllTabs() { m_tabWidget->slotReloadAllTabs(); } bool MainWidget::isNetworkAvailable() const { return PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline(); } void MainWidget::slotNetworkStatusChanged(bool status) { if (status) { m_mainFrame->slotSetStatusText(i18n("Networking is available now.")); this->slotFetchAllFeeds(); } else { m_mainFrame->slotSetStatusText(i18n("Networking is not available.")); } } void MainWidget::slotOpenSelectedArticles() { openSelectedArticles(false); } void MainWidget::slotOpenSelectedArticlesInBackground() { openSelectedArticles(true); } void MainWidget::slotCurrentFrameChanged(int frameId) { Kernel::self()->frameManager()->slotChangeFrame(frameId); m_actionManager->zoomActionMenu()->setZoomFactor(Kernel::self()->frameManager()->currentFrame()->zoomFactor() * 100); } void MainWidget::slotFocusQuickSearch() { m_searchBar->setFocusSearchLine(); } void MainWidget::slotArticleAction(Akregator::ArticleViewerWebEngine::ArticleAction type, const QString &articleId, const QString &feed) { switch (type) { case ArticleViewerWebEngine::DeleteAction: { Akregator::ArticleDeleteJob *job = new Akregator::ArticleDeleteJob; const Akregator::ArticleId aid = { feed, articleId }; job->appendArticleId(aid); job->start(); break; } case ArticleViewerWebEngine::MarkAsRead: ::setArticleStatus(feed, articleId, Akregator::Read); break; case ArticleViewerWebEngine::MarkAsUnRead: ::setArticleStatus(feed, articleId, Akregator::Unread); break; case ArticleViewerWebEngine::MarkAsImportant: { Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob; const Akregator::Article article = m_feedList->findArticle(feed, articleId); const Akregator::ArticleId aid = { feed, articleId }; job->setKeep(aid, !article.keep()); job->start(); break; } case ArticleViewerWebEngine::SendUrlArticle: { case ArticleViewerWebEngine::SendFileArticle: const Article article = m_feedList->findArticle(feed, articleId); const QByteArray text = article.link().toDisplayString().toLatin1(); const QString title = Akregator::Utils::convertHtmlTags(article.title()); if (text.isEmpty()) { return; } sendArticle(text, title, (type == ArticleViewerWebEngine::SendFileArticle)); break; } case ArticleViewerWebEngine::OpenInBackgroundTab: { const Akregator::Article article = m_feedList->findArticle(feed, articleId); const QUrl url = article.link(); if (url.isValid()) { OpenUrlRequest req(url); req.setOptions(OpenUrlRequest::NewTab); req.setOpenInBackground(true); Kernel::self()->frameManager()->slotOpenUrlRequest(req, false /*don't use settings for open in background*/); } break; } case ArticleViewerWebEngine::OpenInExternalBrowser: { const Akregator::Article article = m_feedList->findArticle(feed, articleId); slotOpenArticleInBrowser(article); break; } case ArticleViewerWebEngine::Share: const Akregator::Article article = m_feedList->findArticle(feed, articleId); const QUrl url = article.link(); if (url.isValid()) { //TODO } break; } } diff --git a/src/selectioncontroller.cpp b/src/selectioncontroller.cpp index 2c3f4a83..80b19e2a 100644 --- a/src/selectioncontroller.cpp +++ b/src/selectioncontroller.cpp @@ -1,317 +1,332 @@ /* This file is part of Akregator. Copyright (C) 2007 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "selectioncontroller.h" +#include "akregatorconfig.h" #include "actionmanager.h" #include "article.h" #include "articlejobs.h" #include "articlemodel.h" #include "feedlist.h" #include "subscriptionlistmodel.h" #include "treenode.h" #include "akregator_debug.h" #include #include #include using namespace Akregator; namespace { static Akregator::Article articleForIndex(const QModelIndex &index, FeedList *feedList) { if (!index.isValid()) { return Akregator::Article(); } const QString guid = index.data(ArticleModel::GuidRole).toString(); const QString feedId = index.data(ArticleModel::FeedIdRole).toString(); return feedList->findArticle(feedId, guid); } static QVector articlesForIndexes(const QModelIndexList &indexes, FeedList *feedList) { QVector articles; for (const QModelIndex &i : indexes) { const Article a = articleForIndex(i, feedList); if (a.isNull()) { continue; } articles.append(articleForIndex(i, feedList)); } return articles; } static Akregator::TreeNode *subscriptionForIndex(const QModelIndex &index, Akregator::FeedList *feedList) { if (!index.isValid()) { return nullptr; } return feedList->findByID(index.data(Akregator::SubscriptionListModel::SubscriptionIdRole).toInt()); } } // anon namespace Akregator::SelectionController::SelectionController(QObject *parent) : AbstractSelectionController(parent) , m_feedList() , m_feedSelector() , m_articleLister(0) , m_singleDisplay(0) - , m_subscriptionModel(new SubscriptionListModel(QSharedPointer(), this)) + , m_subscriptionModel(new FilterUnreadProxyModel(this)) , m_folderExpansionHandler(0) , m_articleModel(0) , m_selectedSubscription() { + m_subscriptionModel->setDoFilter(Settings::hideReadFeeds()); + m_subscriptionModel->setSourceModel(new SubscriptionListModel(QSharedPointer(), this)); } Akregator::SelectionController::~SelectionController() { delete m_articleModel; } void Akregator::SelectionController::setFeedSelector(QAbstractItemView *feedSelector) { if (m_feedSelector == feedSelector) { return; } if (m_feedSelector) { m_feedSelector->disconnect(this); m_feedSelector->selectionModel()->disconnect(this); + m_feedSelector->selectionModel()->disconnect(m_subscriptionModel); } m_feedSelector = feedSelector; if (!m_feedSelector) { return; } m_feedSelector->setModel(m_subscriptionModel); + m_subscriptionModel->clearCache(); connect(m_feedSelector.data(), &QAbstractItemView::customContextMenuRequested, this, &SelectionController::subscriptionContextMenuRequested); connect(m_feedSelector->selectionModel(), &QItemSelectionModel::currentChanged, this, &SelectionController::selectedSubscriptionChanged); connect(m_feedSelector.data(), &QAbstractItemView::activated, this, &SelectionController::selectedSubscriptionChanged); + connect(m_feedSelector->selectionModel(), &QItemSelectionModel::selectionChanged, m_subscriptionModel, &FilterUnreadProxyModel::selectionChanged); } void Akregator::SelectionController::setArticleLister(Akregator::ArticleLister *lister) { if (m_articleLister == lister) { return; } if (m_articleLister) { m_articleLister->articleSelectionModel()->disconnect(this); } if (m_articleLister && m_articleLister->itemView()) { m_articleLister->itemView()->disconnect(this); } m_articleLister = lister; if (m_articleLister && m_articleLister->itemView()) { connect(m_articleLister->itemView(), &QAbstractItemView::doubleClicked, this, &SelectionController::articleIndexDoubleClicked); } } void Akregator::SelectionController::setSingleArticleDisplay(Akregator::SingleArticleDisplay *display) { m_singleDisplay = display; } Akregator::Article Akregator::SelectionController::currentArticle() const { if (!m_articleLister || !m_articleLister->articleSelectionModel()) { return Article(); } return ::articleForIndex(m_articleLister->articleSelectionModel()->currentIndex(), m_feedList.data()); } QModelIndex SelectionController::currentArticleIndex() const { return m_articleLister->articleSelectionModel()->currentIndex(); } QVector Akregator::SelectionController::selectedArticles() const { if (!m_articleLister || !m_articleLister->articleSelectionModel()) { return QVector(); } return ::articlesForIndexes(m_articleLister->articleSelectionModel()->selectedRows(), m_feedList.data()); } Akregator::TreeNode *Akregator::SelectionController::selectedSubscription() const { return ::subscriptionForIndex(m_feedSelector->selectionModel()->currentIndex(), m_feedList.data()); } void Akregator::SelectionController::setFeedList(const QSharedPointer &list) { if (m_feedList == list) { return; } m_feedList = list; - std::unique_ptr oldModel(m_subscriptionModel); - m_subscriptionModel = new SubscriptionListModel(m_feedList, this); + SubscriptionListModel *m = qobject_cast(m_subscriptionModel->sourceModel()); + std::unique_ptr oldModel(m); + m_subscriptionModel->setSourceModel(new SubscriptionListModel(m_feedList, this)); if (m_folderExpansionHandler) { m_folderExpansionHandler->setFeedList(m_feedList); m_folderExpansionHandler->setModel(m_subscriptionModel); } if (m_feedSelector) { if (m_feedList) { m_feedSelector->setModel(m_subscriptionModel); disconnect(m_feedSelector->selectionModel(), &QItemSelectionModel::currentChanged, this, &SelectionController::selectedSubscriptionChanged); connect(m_feedSelector->selectionModel(), &QItemSelectionModel::currentChanged, this, &SelectionController::selectedSubscriptionChanged); } else { disconnect(m_feedSelector->selectionModel(), &QItemSelectionModel::currentChanged, this, &SelectionController::selectedSubscriptionChanged); } } } void Akregator::SelectionController::setFolderExpansionHandler(Akregator::FolderExpansionHandler *handler) { if (handler == m_folderExpansionHandler) { return; } m_folderExpansionHandler = handler; if (!m_folderExpansionHandler) { return; } handler->setFeedList(m_feedList); handler->setModel(m_subscriptionModel); } void Akregator::SelectionController::articleHeadersAvailable(KJob *job) { Q_ASSERT(job); Q_ASSERT(job == m_listJob); if (job->error()) { qCWarning(AKREGATOR_LOG) << job->errorText(); return; } TreeNode *const node = m_listJob->node(); Q_ASSERT(node); // if there was no error, the node must still exist Q_ASSERT(node == m_selectedSubscription); //...and equal the previously selected node ArticleModel *const newModel = new ArticleModel(m_listJob->articles()); connect(node, &QObject::destroyed, newModel, &ArticleModel::clear); connect(node, &TreeNode::signalArticlesAdded, newModel, &ArticleModel::articlesAdded); connect(node, &TreeNode::signalArticlesRemoved, newModel, &ArticleModel::articlesRemoved); connect(node, &TreeNode::signalArticlesUpdated, newModel, &ArticleModel::articlesUpdated); m_articleLister->setIsAggregation(node->isAggregation()); m_articleLister->setArticleModel(newModel); delete m_articleModel; //order is important: do not delete the old model before the new model is set in the view m_articleModel = newModel; disconnect(m_articleLister->articleSelectionModel(), &QItemSelectionModel::selectionChanged, this, &SelectionController::articleSelectionChanged); connect(m_articleLister->articleSelectionModel(), &QItemSelectionModel::selectionChanged, this, &SelectionController::articleSelectionChanged); if (node) { m_articleLister->setScrollBarPositions(node->listViewScrollBarPositions()); } } void Akregator::SelectionController::selectedSubscriptionChanged(const QModelIndex &index) { if (!index.isValid()) { return; } if (m_selectedSubscription && m_articleLister) { m_selectedSubscription->setListViewScrollBarPositions(m_articleLister->scrollBarPositions()); } m_selectedSubscription = selectedSubscription(); Q_EMIT currentSubscriptionChanged(m_selectedSubscription); // using a timer here internally to simulate async data fetching (which is still synchronous), // to ensure the UI copes with async behavior later on if (m_listJob) { m_listJob->disconnect(this); //Ignore if ~KJob() emits finished() delete m_listJob; } if (!m_selectedSubscription) { return; } ArticleListJob *const job(new ArticleListJob(m_selectedSubscription)); connect(job, &KJob::finished, this, &SelectionController::articleHeadersAvailable); m_listJob = job; m_listJob->start(); } void Akregator::SelectionController::subscriptionContextMenuRequested(const QPoint &point) { Q_ASSERT(m_feedSelector); const TreeNode *const node = ::subscriptionForIndex(m_feedSelector->indexAt(point), m_feedList.data()); if (!node) { return; } QWidget *w = ActionManager::getInstance()->container(node->isGroup() ? QStringLiteral("feedgroup_popup") : QStringLiteral("feeds_popup")); QMenu *popup = qobject_cast(w); if (popup) { const QPoint globalPos = m_feedSelector->viewport()->mapToGlobal(point); popup->exec(globalPos); } } void Akregator::SelectionController::articleSelectionChanged() { const Akregator::Article article = currentArticle(); if (m_singleDisplay) { m_singleDisplay->showArticle(article); } Q_EMIT currentArticleChanged(article); } void Akregator::SelectionController::articleIndexDoubleClicked(const QModelIndex &index) { const Akregator::Article article = ::articleForIndex(index, m_feedList.data()); Q_EMIT articleDoubleClicked(article); } +/** + * Called when the applications settings are changed; sets whether we apply a the filter or not. + */ +void Akregator::SelectionController::settingsChanged() +{ + m_subscriptionModel->setDoFilter(Settings::hideReadFeeds()); +} + void SelectionController::setFilters(const std::vector > &matchers) { Q_ASSERT(m_articleLister); m_articleLister->setFilters(matchers); } void SelectionController::forceFilterUpdate() { Q_ASSERT(m_articleLister); m_articleLister->forceFilterUpdate(); } diff --git a/src/selectioncontroller.h b/src/selectioncontroller.h index 2435d471..cfc6124c 100644 --- a/src/selectioncontroller.h +++ b/src/selectioncontroller.h @@ -1,105 +1,113 @@ /* This file is part of Akregator. Copyright (C) 2007 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_SELECTIONCONTROLLER_H #define AKREGATOR_SELECTIONCONTROLLER_H #include "abstractselectioncontroller.h" #include +#include class QModelIndex; class QPoint; class KJob; namespace Akregator { class ArticleListJob; +class FilterUnreadProxyModel; class SelectionController : public AbstractSelectionController { Q_OBJECT public: explicit SelectionController(QObject *parent = nullptr); ~SelectionController(); //impl void setFeedSelector(QAbstractItemView *feedSelector) override; //impl void setArticleLister(Akregator::ArticleLister *lister) override; //impl Akregator::Article currentArticle() const override; //impl QModelIndex currentArticleIndex() const override; //impl QVector selectedArticles() const override; //impl void setSingleArticleDisplay(Akregator::SingleArticleDisplay *display) override; //impl Akregator::TreeNode *selectedSubscription() const override; //impl void setFeedList(const QSharedPointer &list) override; //impl void setFolderExpansionHandler(Akregator::FolderExpansionHandler *handler) override; public Q_SLOTS: + //impl + void settingsChanged() override; + //impl void setFilters(const std::vector > &) override; //impl void forceFilterUpdate() override; private Q_SLOTS: void selectedSubscriptionChanged(const QModelIndex &index); void articleSelectionChanged(); void articleIndexDoubleClicked(const QModelIndex &index); void subscriptionContextMenuRequested(const QPoint &point); void articleHeadersAvailable(KJob *); private: + void setCurrentSubscriptionModel(); + QSharedPointer m_feedList; QPointer m_feedSelector; Akregator::ArticleLister *m_articleLister = nullptr; Akregator::SingleArticleDisplay *m_singleDisplay = nullptr; - Akregator::SubscriptionListModel *m_subscriptionModel = nullptr; + Akregator::FilterUnreadProxyModel *m_subscriptionModel = nullptr; + QAbstractItemModel *m_currentModel = nullptr; Akregator::FolderExpansionHandler *m_folderExpansionHandler = nullptr; Akregator::ArticleModel *m_articleModel = nullptr; QPointer m_selectedSubscription; QPointer m_listJob; }; } // namespace Akregator #endif // AKREGATOR_SELECTIONCONTROLLER_H diff --git a/src/subscription/subscriptionlistmodel.cpp b/src/subscription/subscriptionlistmodel.cpp index 14b25295..f433c9d3 100644 --- a/src/subscription/subscriptionlistmodel.cpp +++ b/src/subscription/subscriptionlistmodel.cpp @@ -1,491 +1,576 @@ /* This file is part of Akregator. Copyright (C) 2007 Frank Osterfeld This program is free software you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "subscriptionlistmodel.h" #include "feed.h" #include "feedlist.h" #include "folder.h" #include "subscriptionlistjobs.h" #include "treenode.h" #include "akregator_debug.h" #include #include #include #include #include #include #include #include #include +#include #include using namespace Akregator; using namespace Syndication; #define AKREGATOR_TREENODE_MIMETYPE QStringLiteral("akregator/treenode-id") namespace { +static uint nodeIdForIndex(const QModelIndex &idx) +{ + return idx.isValid() ? idx.internalId() : 0; +} + static QString errorCodeToString(Syndication::ErrorCode err) { switch (err) { case Timeout: return i18n("Timeout on remote server"); case UnknownHost: return i18n("Unknown host"); case FileNotFound: return i18n("Feed file not found on remote server"); case InvalidXml: return i18n("Could not read feed (invalid XML)"); case XmlNotAccepted: return i18n("Could not read feed (unknown format)"); case InvalidFormat: return i18n("Could not read feed (invalid feed)"); case Success: case Aborted: default: return QString(); } } static const Akregator::TreeNode *nodeForIndex(const QModelIndex &index, const FeedList *feedList) { return (!index.isValid() || !feedList) ? 0 : feedList->findByID(index.internalId()); } } +Akregator::FilterUnreadProxyModel::FilterUnreadProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) + , m_doFilter(false) + , m_selectedHierarchy() +{ + setDynamicSortFilter(true); +} + +bool Akregator::FilterUnreadProxyModel::doFilter() const +{ + return m_doFilter; +} + +void Akregator::FilterUnreadProxyModel::setDoFilter(bool v) +{ + m_doFilter = v; + invalidateFilter(); +} + +void Akregator::FilterUnreadProxyModel::setSourceModel(QAbstractItemModel *src) +{ + clearCache(); + QSortFilterProxyModel::setSourceModel(src); +} + +bool Akregator::FilterUnreadProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + if (!m_doFilter) { + return true; + } + + QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); + + if (m_selectedHierarchy.contains(idx)) + return true; + + QVariant v = idx.data(SubscriptionListModel::HasUnreadRole); + if (v.isNull()) + return true; + + return v.toBool(); +} + +/** + * This caches the hierarchy of the selected node. Its purpose is to allow + * feeds/folders with no unread content not to be filtered out immediately, + * which would occur otherwise (we'd select the last article to read, it would + * become unread, and disappear from the list without letting us view it). + **/ +void Akregator::FilterUnreadProxyModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + QModelIndexList desel = mapSelectionToSource(deselected).indexes(); + //calling invalidateFilter causes refiltering at the call point, so we should + //call it ONLY after we recreate our node cache + bool doInvalidate = false; + + //if we're deselecting an empty feed/folder, we need to hide it + if (!desel.isEmpty()) { + if (m_selectedHierarchy.contains(desel.at(0))) { + doInvalidate = true; + } + } + + clearCache(); + + QModelIndexList sel = mapSelectionToSource(selected).indexes(); + if (!sel.isEmpty()) { + //XXX add support for multiple selections? this doesn't generally make sense in this case honestly + QModelIndex current = sel.at(0); + while (current.isValid()) { + m_selectedHierarchy.insert(current); + current = current.parent(); + } + } + + if (doInvalidate && doFilter()) + invalidateFilter(); +} + +void Akregator::FilterUnreadProxyModel::clearCache() +{ + m_selectedHierarchy.clear(); +} + Akregator::SubscriptionListModel::SubscriptionListModel(const QSharedPointer &feedList, QObject *parent) : QAbstractItemModel(parent) , m_feedList(feedList) , m_beganRemoval(false) { if (!m_feedList) { return; } connect(m_feedList.data(), &FeedList::signalNodeAdded, this, &SubscriptionListModel::subscriptionAdded); connect(m_feedList.data(), &FeedList::signalAboutToRemoveNode, this, &SubscriptionListModel::aboutToRemoveSubscription); connect(m_feedList.data(), &FeedList::signalNodeRemoved, this, &SubscriptionListModel::subscriptionRemoved); connect(m_feedList.data(), &FeedList::signalNodeChanged, this, &SubscriptionListModel::subscriptionChanged); connect(m_feedList.data(), &FeedList::fetchStarted, this, &SubscriptionListModel::fetchStarted); connect(m_feedList.data(), &FeedList::fetched, this, &SubscriptionListModel::fetched); connect(m_feedList.data(), &FeedList::fetchAborted, this, &SubscriptionListModel::fetchAborted); } int Akregator::SubscriptionListModel::columnCount(const QModelIndex &) const { return 3; } int Akregator::SubscriptionListModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return 1; } const Akregator::TreeNode *const node = nodeForIndex(parent, m_feedList.data()); return node ? node->children().count() : 0; } -uint Akregator::SubscriptionListModel::nodeIdForIndex(const QModelIndex &idx) const -{ - return idx.isValid() ? idx.internalId() : 0; -} - QVariant Akregator::SubscriptionListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } const Akregator::TreeNode *const node = nodeForIndex(index, m_feedList.data()); if (!node) { return QVariant(); } switch (role) { case Qt::EditRole: case Qt::DisplayRole: switch (index.column()) { case TitleColumn: return node->title(); case UnreadCountColumn: return node->unread(); case TotalCountColumn: return node->totalCount(); } break; case Qt::ToolTipRole: { if (node->isGroup() || node->isAggregation()) { return node->title(); } const Feed *const feed = qobject_cast(node); if (!feed) { return QString(); } if (feed->fetchErrorOccurred()) { return i18n("Could not fetch feed: %1", errorCodeToString(feed->fetchErrorCode())); } return feed->title(); } case Qt::DecorationRole: { if (index.column() != TitleColumn) { return QVariant(); } const Feed *const feed = qobject_cast(node); return feed && feed->isFetching() ? node->icon().pixmap(KIconLoader::SizeSmall, QIcon::Active) : node->icon(); } case SubscriptionIdRole: return node->id(); case IsGroupRole: return node->isGroup(); case IsFetchableRole: return !node->isGroup() && !node->isAggregation(); case IsAggregationRole: return node->isAggregation(); case LinkRole: { const Feed *const feed = qobject_cast(node); return feed ? feed->xmlUrl() : QVariant(); } case IsOpenRole: { if (!node->isGroup()) { return false; } const Akregator::Folder *const folder = qobject_cast(node); Q_ASSERT(folder); return folder->isOpen(); } case HasUnreadRole: return node->unread() > 0; } return QVariant(); } QVariant Akregator::SubscriptionListModel::headerData(int section, Qt::Orientation, int role) const { if (role != Qt::DisplayRole) { return QVariant(); } switch (section) { case TitleColumn: return i18nc("Feedlist's column header", "Feeds"); case UnreadCountColumn: return i18nc("Feedlist's column header", "Unread"); case TotalCountColumn: return i18nc("Feedlist's column header", "Total"); } return QVariant(); } QModelIndex Akregator::SubscriptionListModel::parent(const QModelIndex &index) const { const Akregator::TreeNode *const node = nodeForIndex(index, m_feedList.data()); if (!node || !node->parent()) { return QModelIndex(); } const Akregator::Folder *parent = node->parent(); if (!parent->parent()) { return createIndex(0, 0, parent->id()); } const Akregator::Folder *const grandparent = parent->parent(); const int row = grandparent->indexOf(parent); Q_ASSERT(row != -1); return createIndex(row, 0, parent->id()); } QModelIndex Akregator::SubscriptionListModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { return (row == 0 && m_feedList) ? createIndex(row, column, m_feedList->allFeedsFolder()->id()) : QModelIndex(); } const Akregator::TreeNode *const parentNode = nodeForIndex(parent, m_feedList.data()); if (!parentNode) { return QModelIndex(); } const Akregator::TreeNode *const childNode = parentNode->childAt(row); return childNode ? createIndex(row, column, childNode->id()) : QModelIndex(); } QModelIndex SubscriptionListModel::indexForNode(const TreeNode *node) const { if (!node || !m_feedList) { return QModelIndex(); } const Folder *const parent = node->parent(); if (!parent) { return index(0, 0); } const int row = parent->indexOf(node); Q_ASSERT(row >= 0); const QModelIndex idx = index(row, 0, indexForNode(parent)); Q_ASSERT(idx.internalId() == node->id()); return idx; } void Akregator::SubscriptionListModel::subscriptionAdded(Akregator::TreeNode *subscription) { const Folder *const parent = subscription->parent(); const int row = parent ? parent->indexOf(subscription) : 0; Q_ASSERT(row >= 0); beginInsertRows(indexForNode(parent), row, row); endInsertRows(); } void Akregator::SubscriptionListModel::aboutToRemoveSubscription(Akregator::TreeNode *subscription) { qCDebug(AKREGATOR_LOG) << subscription->id(); const Folder *const parent = subscription->parent(); const int row = parent ? parent->indexOf(subscription) : -1; if (row < 0) { return; } beginRemoveRows(indexForNode(parent), row, row); m_beganRemoval = true; } void Akregator::SubscriptionListModel::subscriptionRemoved(TreeNode *subscription) { qCDebug(AKREGATOR_LOG) << subscription->id(); if (m_beganRemoval) { m_beganRemoval = false; endRemoveRows(); } } void Akregator::SubscriptionListModel::subscriptionChanged(TreeNode *node) { const QModelIndex idx = indexForNode(node); if (!idx.isValid()) { return; } Q_EMIT dataChanged(index(idx.row(), 0, idx.parent()), index(idx.row(), ColumnCount - 1, idx.parent())); } void SubscriptionListModel::fetchStarted(Akregator::Feed *node) { subscriptionChanged(node); } void SubscriptionListModel::fetched(Akregator::Feed *node) { subscriptionChanged(node); } void SubscriptionListModel::fetchError(Akregator::Feed *node) { subscriptionChanged(node); } void SubscriptionListModel::fetchAborted(Akregator::Feed *node) { subscriptionChanged(node); } void Akregator::FolderExpansionHandler::itemExpanded(const QModelIndex &idx) { setExpanded(idx, true); } void Akregator::FolderExpansionHandler::itemCollapsed(const QModelIndex &idx) { setExpanded(idx, false); } void Akregator::FolderExpansionHandler::setExpanded(const QModelIndex &idx, bool expanded) { if (!m_feedList || !m_model) { return; } - Akregator::TreeNode *const node = m_feedList->findByID(m_model->nodeIdForIndex(idx)); + Akregator::TreeNode *const node = m_feedList->findByID(nodeIdForIndex(idx)); if (!node || !node->isGroup()) { return; } Akregator::Folder *const folder = qobject_cast(node); Q_ASSERT(folder); folder->setOpen(expanded); } FolderExpansionHandler::FolderExpansionHandler(QObject *parent) : QObject(parent) , m_feedList() , m_model(0) { } -void FolderExpansionHandler::setModel(Akregator::SubscriptionListModel *model) +void FolderExpansionHandler::setModel(QAbstractItemModel *model) { m_model = model; } void FolderExpansionHandler::setFeedList(const QSharedPointer &feedList) { m_feedList = feedList; } Qt::ItemFlags SubscriptionListModel::flags(const QModelIndex &idx) const { const Qt::ItemFlags flags = QAbstractItemModel::flags(idx); if (!idx.isValid() || (idx.column() != TitleColumn)) { return flags; } if (!idx.parent().isValid()) { // the root folder is neither draggable nor editable return flags | Qt::ItemIsDropEnabled; } return flags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; } QStringList SubscriptionListModel::mimeTypes() const { QStringList types; types << QStringLiteral("text/uri-list") << AKREGATOR_TREENODE_MIMETYPE; return types; } QMimeData *SubscriptionListModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData; QList urls; for (const QModelIndex &i : indexes) { const QUrl url(i.data(LinkRole).toString()); if (!url.isEmpty()) { urls << url; } } mimeData->setUrls(urls); QByteArray idList; QDataStream idStream(&idList, QIODevice::WriteOnly); for (const QModelIndex &i : indexes) { if (i.isValid()) { idStream << i.data(SubscriptionIdRole).toInt(); } } mimeData->setData(AKREGATOR_TREENODE_MIMETYPE, idList); return mimeData; } bool SubscriptionListModel::setData(const QModelIndex &idx, const QVariant &value, int role) { if (!idx.isValid() || idx.column() != TitleColumn || role != Qt::EditRole) { return false; } const TreeNode *const node = nodeForIndex(idx, m_feedList.data()); if (!node) { return false; } RenameSubscriptionJob *job = new RenameSubscriptionJob(this); job->setSubscriptionId(node->id()); job->setName(value.toString()); job->start(); return true; } bool SubscriptionListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(column) if (action == Qt::IgnoreAction) { return true; } //if ( column != TitleColumn ) // return false; if (!data->hasFormat(AKREGATOR_TREENODE_MIMETYPE)) { return false; } const TreeNode *const droppedOnNode = qobject_cast(nodeForIndex(parent, m_feedList.data())); if (!droppedOnNode) { return false; } const Folder *const destFolder = droppedOnNode->isGroup() ? qobject_cast(droppedOnNode) : droppedOnNode->parent(); if (!destFolder) { return false; } QByteArray idData = data->data(AKREGATOR_TREENODE_MIMETYPE); QList ids; QDataStream stream(&idData, QIODevice::ReadOnly); while (!stream.atEnd()) { int id; stream >> id; ids << id; } //don't drop nodes into their own subtree for (const int id : qAsConst(ids)) { const Folder *const asFolder = qobject_cast(m_feedList->findByID(id)); if (asFolder && (asFolder == destFolder || asFolder->subtreeContains(destFolder))) { return false; } } const TreeNode *const after = droppedOnNode->isGroup() ? destFolder->childAt(row) : droppedOnNode; for (const int id : qAsConst(ids)) { const TreeNode *const node = m_feedList->findByID(id); if (!node) { continue; } MoveSubscriptionJob *job = new MoveSubscriptionJob(this); job->setSubscriptionId(node->id()); job->setDestination(destFolder->id(), after ? after->id() : -1); job->start(); } return true; } diff --git a/src/subscription/subscriptionlistmodel.h b/src/subscription/subscriptionlistmodel.h index 016bb21d..3dbb557b 100644 --- a/src/subscription/subscriptionlistmodel.h +++ b/src/subscription/subscriptionlistmodel.h @@ -1,140 +1,168 @@ /* This file is part of Akregator. Copyright (C) 2007 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_SUBSCRIPTIONLISTMODEL_H #define AKREGATOR_SUBSCRIPTIONLISTMODEL_H #include "akregatorpart_export.h" #include +#include +#include #include namespace Akregator { class Feed; class FeedList; class Folder; class TreeNode; +/** + * Filters feeds with unread counts. + */ +class FilterUnreadProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + explicit FilterUnreadProxyModel(QObject* parent = nullptr); + + bool doFilter() const; + + void setDoFilter(bool v); + + void setSourceModel(QAbstractItemModel *src) override; + +public slots: + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void clearCache(); + +private: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + + typedef QSet SelectionHierarchy; + + bool m_doFilter; + SelectionHierarchy m_selectedHierarchy; +}; + class AKREGATORPART_EXPORT SubscriptionListModel : public QAbstractItemModel { Q_OBJECT public: enum Role { SubscriptionIdRole = Qt::UserRole, IsFetchableRole, IsGroupRole, IsAggregationRole, LinkRole, IdRole, IsOpenRole, HasUnreadRole }; enum Column { TitleColumn = 0, UnreadCountColumn = 1, TotalCountColumn = 2, ColumnCount = 3 }; explicit SubscriptionListModel(const QSharedPointer &feedList, QObject *parent = nullptr); int columnCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QStringList mimeTypes() const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; bool setData(const QModelIndex &idx, const QVariant &value, int role = Qt::EditRole) override; - uint nodeIdForIndex(const QModelIndex &index) const; - private: QModelIndex indexForNode(const TreeNode *node) const; private Q_SLOTS: void subscriptionAdded(Akregator::TreeNode *); void aboutToRemoveSubscription(Akregator::TreeNode *); void subscriptionRemoved(Akregator::TreeNode *); void subscriptionChanged(Akregator::TreeNode *); void fetchStarted(Akregator::Feed *); void fetched(Akregator::Feed *); void fetchError(Akregator::Feed *); void fetchAborted(Akregator::Feed *); private: QSharedPointer m_feedList; bool m_beganRemoval; }; } namespace Akregator { class AKREGATORPART_EXPORT FolderExpansionHandler : public QObject { Q_OBJECT public: explicit FolderExpansionHandler(QObject *parent = nullptr); void setFeedList(const QSharedPointer &feedList); - void setModel(Akregator::SubscriptionListModel *model); + void setModel(QAbstractItemModel *model); public Q_SLOTS: void itemExpanded(const QModelIndex &index); void itemCollapsed(const QModelIndex &index); private: void setExpanded(const QModelIndex &index, bool expanded); private: QSharedPointer m_feedList; - SubscriptionListModel *m_model = nullptr; + QAbstractItemModel *m_model = nullptr; }; } // namespace Akregator #endif // AKREGATOR_SUBSCRIPTIONLISTMODEL_H diff --git a/src/subscription/subscriptionlistview.cpp b/src/subscription/subscriptionlistview.cpp index 6fa7e677..1a30003c 100644 --- a/src/subscription/subscriptionlistview.cpp +++ b/src/subscription/subscriptionlistview.cpp @@ -1,402 +1,420 @@ /* This file is part of Akregator. Copyright (C) 2007 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "subscriptionlistview.h" #include "subscriptionlistmodel.h" #include "subscriptionlistdelegate.h" #include "akregatorconfig.h" +#include "akregator_debug.h" #include #include #include #include #include #include #include using namespace Akregator; static QModelIndex prevIndex(const QModelIndex &idx) { if (!idx.isValid()) { return QModelIndex(); } const QAbstractItemModel *const model = idx.model(); Q_ASSERT(model); if (idx.row() > 0) { QModelIndex i = idx.sibling(idx.row() - 1, idx.column()); while (model->hasChildren(i)) { i = i.child(model->rowCount(i) - 1, i.column()); } return i; } else { return idx.parent(); } } static QModelIndex prevFeedIndex(const QModelIndex &idx, bool allowPassed = false) { QModelIndex prev = allowPassed ? idx : prevIndex(idx); while (prev.isValid() && prev.data(SubscriptionListModel::IsAggregationRole).toBool()) { prev = prevIndex(prev); } return prev; } static QModelIndex prevUnreadFeedIndex(const QModelIndex &idx, bool allowPassed = false) { QModelIndex prev = allowPassed ? idx : prevIndex(idx); while (prev.isValid() && (prev.data(SubscriptionListModel::IsAggregationRole).toBool() || prev.sibling(prev.row(), SubscriptionListModel::UnreadCountColumn).data().toInt() == 0)) { prev = prevIndex(prev); } return prev; } static QModelIndex lastLeaveChild(const QAbstractItemModel *const model) { Q_ASSERT(model); if (model->rowCount() == 0) { return QModelIndex(); } QModelIndex idx = model->index(model->rowCount() - 1, 0); while (model->hasChildren(idx)) { idx = idx.child(model->rowCount(idx) - 1, idx.column()); } return idx; } static QModelIndex nextIndex(const QModelIndex &idx) { if (!idx.isValid()) { return QModelIndex(); } const QAbstractItemModel *const model = idx.model(); Q_ASSERT(model); if (model->hasChildren(idx)) { return idx.child(0, idx.column()); } QModelIndex i = idx; while (true) { if (!i.isValid()) { return i; } const int siblings = model->rowCount(i.parent()); if (i.row() + 1 < siblings) { return i.sibling(i.row() + 1, i.column()); } i = i.parent(); } } static QModelIndex nextFeedIndex(const QModelIndex &idx) { QModelIndex next = nextIndex(idx); while (next.isValid() && next.data(SubscriptionListModel::IsAggregationRole).toBool()) { next = nextIndex(next); } return next; } static QModelIndex nextUnreadFeedIndex(const QModelIndex &idx) { QModelIndex next = nextIndex(idx); while (next.isValid() && (next.data(SubscriptionListModel::IsAggregationRole).toBool() || next.sibling(next.row(), SubscriptionListModel::UnreadCountColumn).data().toInt() == 0)) { next = nextIndex(next); } return next; } Akregator::SubscriptionListView::SubscriptionListView(QWidget *parent) : QTreeView(parent) { setFocusPolicy(Qt::NoFocus); setSelectionMode(QAbstractItemView::SingleSelection); setRootIsDecorated(false); setAlternatingRowColors(true); setContextMenuPolicy(Qt::CustomContextMenu); setDragDropMode(QAbstractItemView::DragDrop); setDropIndicatorShown(true); setAcceptDrops(true); setUniformRowHeights(true); setItemDelegate(new SubscriptionListDelegate(this)); connect(header(), &QWidget::customContextMenuRequested, this, &SubscriptionListView::showHeaderMenu); loadHeaderSettings(); } Akregator::SubscriptionListView::~SubscriptionListView() { saveHeaderSettings(); } void Akregator::SubscriptionListView::setModel(QAbstractItemModel *m) { Q_ASSERT(m); if (model()) { m_headerState = header()->saveState(); } QTreeView::setModel(m); restoreHeaderState(); QStack stack; stack.push(rootIndex()); while (!stack.isEmpty()) { const QModelIndex i = stack.pop(); const int childCount = m->rowCount(i); for (int j = 0; j < childCount; ++j) { const QModelIndex child = m->index(j, 0, i); if (child.isValid()) { stack.push(child); } } setExpanded(i, i.data(Akregator::SubscriptionListModel::IsOpenRole).toBool()); } header()->setContextMenuPolicy(Qt::CustomContextMenu); } void Akregator::SubscriptionListView::showHeaderMenu(const QPoint &pos) { if (!model()) { return; } QPointer menu = new QMenu(this); menu->setTitle(i18n("Columns")); menu->setAttribute(Qt::WA_DeleteOnClose); connect(menu.data(), &QMenu::triggered, this, &SubscriptionListView::headerMenuItemTriggered); for (int i = 0; i < model()->columnCount(); ++i) { if (SubscriptionListModel::TitleColumn == i) { continue; } QString col = model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); QAction *act = menu->addAction(col); act->setCheckable(true); act->setChecked(!header()->isSectionHidden(i)); act->setData(i); } menu->popup(header()->mapToGlobal(pos)); } void Akregator::SubscriptionListView::headerMenuItemTriggered(QAction *act) { Q_ASSERT(act); const int col = act->data().toInt(); if (act->isChecked()) { header()->showSection(col); } else { header()->hideSection(col); } } void Akregator::SubscriptionListView::saveHeaderSettings() { if (model()) { m_headerState = header()->saveState(); } KConfigGroup conf(Settings::self()->config(), "General"); conf.writeEntry("SubscriptionListHeaders", m_headerState.toBase64()); } void Akregator::SubscriptionListView::loadHeaderSettings() { const KConfigGroup conf(Settings::self()->config(), "General"); m_headerState = QByteArray::fromBase64(conf.readEntry("SubscriptionListHeaders").toLatin1()); restoreHeaderState(); } void Akregator::SubscriptionListView::restoreHeaderState() { header()->restoreState(m_headerState); // needed, even with Qt 4.5 // Always shows the title column header()->showSection(SubscriptionListModel::TitleColumn); if (m_headerState.isEmpty()) { // Default configuration: only show the title column header()->hideSection(SubscriptionListModel::UnreadCountColumn); header()->hideSection(SubscriptionListModel::TotalCountColumn); } } void Akregator::SubscriptionListView::slotPrevFeed() { if (!model()) { return; } const QModelIndex current = currentIndex(); QModelIndex prev = prevFeedIndex(current); if (!prev.isValid()) { prev = prevFeedIndex(lastLeaveChild(model()), true); } if (prev.isValid()) { setCurrentIndex(prev); } } void Akregator::SubscriptionListView::slotNextFeed() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); const QModelIndex current = currentIndex(); QModelIndex next = nextFeedIndex(current); if (!next.isValid()) { next = nextFeedIndex(model()->index(0, 0)); } if (next.isValid()) { setCurrentIndex(next); } } void Akregator::SubscriptionListView::slotPrevUnreadFeed() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); const QModelIndex current = currentIndex(); QModelIndex prev = prevUnreadFeedIndex(current); if (!prev.isValid()) { prev = prevUnreadFeedIndex(lastLeaveChild(model()), true); } if (prev.isValid()) { setCurrentIndex(prev); } } void Akregator::SubscriptionListView::slotNextUnreadFeed() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); const QModelIndex current = currentIndex(); QModelIndex next = nextUnreadFeedIndex(current); if (!next.isValid()) { next = nextUnreadFeedIndex(model()->index(0, 0)); } if (next.isValid()) { setCurrentIndex(next); } } void SubscriptionListView::slotItemBegin() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); setCurrentIndex(nextFeedIndex(model()->index(0, 0))); } void SubscriptionListView::slotItemEnd() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); setCurrentIndex(lastLeaveChild(model())); } void SubscriptionListView::slotItemLeft() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); const QModelIndex current = currentIndex(); if (!current.isValid()) { setCurrentIndex(nextFeedIndex(model()->index(0, 0))); return; } if (current.parent().isValid()) { setCurrentIndex(current.parent()); } } void SubscriptionListView::slotItemRight() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); const QModelIndex current = currentIndex(); if (!current.isValid()) { setCurrentIndex(nextFeedIndex(model()->index(0, 0))); return; } if (model()->rowCount(current) > 0) { setCurrentIndex(current.child(0, 0)); } } void SubscriptionListView::slotItemUp() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); const QModelIndex current = currentIndex(); QModelIndex prev = current.row() > 0 ? current.sibling(current.row() - 1, current.column()) : current.parent(); if (!prev.isValid()) { prev = lastLeaveChild(model()); } if (prev.isValid()) { setCurrentIndex(prev); } } void SubscriptionListView::slotItemDown() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); const QModelIndex current = currentIndex(); if (current.row() >= model()->rowCount(current.parent())) { return; } setCurrentIndex(current.sibling(current.row() + 1, current.column())); } +void SubscriptionListView::slotSetHideReadFeeds(bool setting) +{ + QAbstractItemModel *m = model(); + if (!m) { + return; + } + + FilterUnreadProxyModel *filter = qobject_cast(m); + if (!filter) { + qCCritical(AKREGATOR_LOG) << "Unable to cast model to FilterUnreadProxyModel*"; + return; + } + + Settings::setHideReadFeeds(setting); + filter->setDoFilter(setting); +} + void Akregator::SubscriptionListView::ensureNodeVisible(Akregator::TreeNode *) { } void Akregator::SubscriptionListView::startNodeRenaming(Akregator::TreeNode *node) { Q_UNUSED(node); const QModelIndex current = currentIndex(); if (!current.isValid()) { return; } edit(current); } diff --git a/src/subscription/subscriptionlistview.h b/src/subscription/subscriptionlistview.h index 9ebf0b72..e622e1b1 100644 --- a/src/subscription/subscriptionlistview.h +++ b/src/subscription/subscriptionlistview.h @@ -1,91 +1,93 @@ /* This file is part of Akregator. Copyright (C) 2007 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_SUBSCRIPTIONLISTVIEW_H #define AKREGATOR_SUBSCRIPTIONLISTVIEW_H #include #include #include namespace Akregator { class TreeNode; class SubscriptionListView : public QTreeView { Q_OBJECT public: explicit SubscriptionListView(QWidget *parent = nullptr); ~SubscriptionListView(); // the following is all transitional, for easier porting from the item-based views void startNodeRenaming(TreeNode *node); void ensureNodeVisible(TreeNode *node); //override void setModel(QAbstractItemModel *model) override; void triggerUpdate() { } enum Column { TitleColumn = 0, UnreadColumn = 1, TotalColumn = 2 }; public Q_SLOTS: void slotPrevFeed(); void slotNextFeed(); void slotPrevUnreadFeed(); void slotNextUnreadFeed(); void slotItemBegin(); void slotItemEnd(); void slotItemLeft(); void slotItemRight(); void slotItemUp(); void slotItemDown(); + void slotSetHideReadFeeds(bool setting); + Q_SIGNALS: void userActionTakingPlace(); private: void saveHeaderSettings(); void loadHeaderSettings(); void restoreHeaderState(); private: void showHeaderMenu(const QPoint &pos); void headerMenuItemTriggered(QAction *action); QByteArray m_headerState; }; } #endif // AKREGATOR_SUBSCRIPTIONLISTVIEW_H