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