diff --git a/configuration/ui/settings_general.ui b/configuration/ui/settings_general.ui index fa057ebd..1d595603 100644 --- a/configuration/ui/settings_general.ui +++ b/configuration/ui/settings_general.ui @@ -1,192 +1,199 @@ Teemu Rytilahti Akregator::SettingsGeneral 0 0 362 377 0 0 0 0 Global Show tra&y icon Hide feeds with no unread articles + + + + Auto-expand folders containing 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 bf3fe639..52c73151 100644 --- a/interfaces/akregator.kcfg +++ b/interfaces/akregator.kcfg @@ -1,249 +1,254 @@ Hides feeds with no unread articles false + + + Auto-expand folders containing 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 Show close buttons on tabs instead of icons false Open a link which would normally open in a new window (external browser) in a new tab instead 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/actions/actionmanagerimpl.cpp b/src/actions/actionmanagerimpl.cpp index 05383e4d..44d56fec 100644 --- a/src/actions/actionmanagerimpl.cpp +++ b/src/actions/actionmanagerimpl.cpp @@ -1,687 +1,700 @@ /* 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()); + + a = action(QStringLiteral("auto_expand_folders")); + if (!a) { + qCCritical(AKREGATOR_LOG) << "Action not found"; + return; + } + a->setChecked(Settings::autoExpandFolders()); } 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); + + action = coll->addAction(QStringLiteral("auto_expand_folders")); + action->setCheckable(true); + action->setText(i18n("Auto-expand folders with unread articles")); + action->setChecked(Settings::autoExpandFolders()); + connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotSetAutoExpandFolders); } 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/data/akregator_part.rc b/src/data/akregator_part.rc index b9f5dc14..9a8798a5 100644 --- a/src/data/akregator_part.rc +++ b/src/data/akregator_part.rc @@ -1,176 +1,177 @@ - + &Edit &View + &Go Fee&d &Article &Settings Main Toolbar diff --git a/src/selectioncontroller.cpp b/src/selectioncontroller.cpp index 80b19e2a..5090d74f 100644 --- a/src/selectioncontroller.cpp +++ b/src/selectioncontroller.cpp @@ -1,332 +1,368 @@ /* 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 #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 FilterUnreadProxyModel(this)) , m_folderExpansionHandler(0) , m_articleModel(0) , m_selectedSubscription() { m_subscriptionModel->setDoFilter(Settings::hideReadFeeds()); m_subscriptionModel->setSourceModel(new SubscriptionListModel(QSharedPointer(), this)); + + connect(m_subscriptionModel, &FilterUnreadProxyModel::dataChanged, + this, &SelectionController::subscriptionDataChanged); } 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; 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::subscriptionDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (!Settings::autoExpandFolders()) { + return; + } + + if (!m_subscriptionModel) { + qCCritical(AKREGATOR_LOG) << "m_subscriptionModel is NULL"; + return; + } + + //need access to setExpanded + QTreeView *tv = qobject_cast(m_feedSelector); + if (!tv) { + qCCritical(AKREGATOR_LOG) << "Unable to cast m_feedSelector to QTreeView"; + return; + } + + int startRow = topLeft.row(); + int endRow = bottomRight.row(); + QModelIndex parent = topLeft.parent(); + + for (int row = startRow; row <= endRow; ++row) { + QModelIndex idx = m_subscriptionModel->index(row, 0, parent); + QVariant v = m_subscriptionModel->data(idx, SubscriptionListModel::HasUnreadRole); + if (!v.toBool()) { + return; + } + tv->setExpanded(idx, true); + } +} + 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 cfc6124c..a4006bc0 100644 --- a/src/selectioncontroller.h +++ b/src/selectioncontroller.h @@ -1,113 +1,114 @@ /* 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 subscriptionDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); 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::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/subscriptionlistview.cpp b/src/subscription/subscriptionlistview.cpp index 1a30003c..312aecc7 100644 --- a/src/subscription/subscriptionlistview.cpp +++ b/src/subscription/subscriptionlistview.cpp @@ -1,420 +1,458 @@ /* 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 #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::slotSetAutoExpandFolders(bool setting) +{ + Settings::setAutoExpandFolders(setting); + if (!setting) { + return; + } + + //expand any current subscriptions with unread items + QQueue indexes; + //start at the root node + indexes.enqueue(QModelIndex()); + + QAbstractItemModel *m = model(); + if (!m) { + return; + } + + while (!indexes.isEmpty()) { + QModelIndex parent = indexes.dequeue(); + int rows = m->rowCount(parent); + + for (int row = 0; row < rows; ++row) { + QModelIndex current = m->index(row, 0, parent); + + if (m->hasChildren(current)) { + indexes.enqueue(current); + } + + if (!m->data(current, SubscriptionListModel::HasUnreadRole).toBool()) { + continue; + } + + setExpanded(current, true); + } + } +} + 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 e622e1b1..70948292 100644 --- a/src/subscription/subscriptionlistview.h +++ b/src/subscription/subscriptionlistview.h @@ -1,93 +1,94 @@ /* 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); + void slotSetAutoExpandFolders(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