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 ¬ifications 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
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 @@
-
+
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