Commit 60678a70 authored by Laurent Montel's avatar Laurent Montel 😁

Patch from Kevin Tardif

Added Hide Read Feed option
This patch adds a new option, to hide feeds with zero unread count from the feed list. You can access the setting via either the View menubar item or via the General configuration panel, it defaults to being turned off. This is useful if you have many feeds, as it makes the feed list less crowded by hiding those with no new items.

subscriptionlistmodel:

    filter models are present in articlelistview, but controller only includes listmodel.h, so I put FilterUnreadProxyModel here
    added FilterUnreadProxyModel, a QSortFilerProxyModel subclass to filter out feeds with zero unread count
    uint nodeIdForIndex(const QModelIndex&) is now a file-specific method
    changed FolderExpansionHandler::m_model to be of type QAbstractItemModel* (since it no longer needs SubscriptionListModel::nodeIdForIndex)

selectioncontroller:

    hooked filter's selectionmodel's selectionChanged signal in SelectionController::setFeedSelector to the FilterUnreadProxyModel instance
    m_subscriptionModel is now a FilterUnreadProxyModel*
    added SelectionController::settingsChanged slot
    changed SelectionController::setFeedList to set the proxy's source
    model

actionmanagerimpl:

    added slotSettingsChanged slot for receiving MainWidget::signalSettingsChange signal and updating action state
    added a KAction to ActionManagerImpl:initSubscriptionListView, connected to SubscriptionListView::slotSetHideReadFeeds

subscriptionlistview:

    added a public slot to connect the added action to (slotSetHideReadFeeds)

mainwidget:

    hooked m_part's signalSettingsChanged() to m_selectionController::settingsChanged, m_actionManager::slotSettingsChanged

interfaces/akregator.kcfg:

    added View/HideReadFeeds entry

akregator_part.rc:

    add a separator and an action to <Menu name="view"> (name=feed_hide_read)

settings_general.ui:

    add kcfg_HideReadFeeds field under General->Global

Differential Revision: https://phabricator.kde.org/D8091
parent a9a392c6
......@@ -37,6 +37,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_HideReadFeeds">
<property name="text">
<string>Hide feeds with no unread articles</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_UseNotifications">
<property name="toolTip">
......
......@@ -5,6 +5,11 @@
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name="akregatorrc" />
<group name="View" >
<entry key="Hide Read Feeds" type="Bool">
<label>Hide feeds with no unread articles</label>
<whatsthis>Hides feeds with no unread articles</whatsthis>
<default>false</default>
</entry>
<entry key="Show Quick Filter" type="Bool" >
<label>Show Quick Filter</label>
<whatsthis>Show Quick Filter Bar</whatsthis>
......@@ -87,7 +92,7 @@
<label>Delete Expired Articles</label>
<whatsthis>Delete expired articles</whatsthis>
</choice>
<choice name="disableArchiving">
<choice name="disableArchiving">
<label>Disable Archiving</label>
<whatsthis>Do not save any articles</whatsthis>
</choice>
......@@ -164,17 +169,17 @@
<entry key="Always Show Tab Bar" type="Bool">
<label>Always show the tab bar</label>
<whatsthis>Always show the tab bar, even when only one tab is open</whatsthis>
<default>false</default>
<default>false</default>
</entry>
<entry key="Close Button On Tabs" type="Bool">
<label>Show close buttons on tabs</label>
<whatsthis>Show close buttons on tabs instead of icons</whatsthis>
<default>false</default>
<default>false</default>
</entry>
<entry key="New Window In Tab" type="Bool">
<label>Open links in new tab instead of in new window</label>
<whatsthis>Open a link which would normally open in a new window (external browser) in a new tab instead</whatsthis>
<default>false</default>
<default>false</default>
</entry>
<entry key="External Browser Use Kde Default" type="Bool" >
<label>Use default KDE web browser</label>
......
......@@ -121,6 +121,8 @@ public:
public Q_SLOTS:
virtual void settingsChanged() = 0;
virtual void setFilters(const std::vector<QSharedPointer<const Akregator::Filters::AbstractMatcher> > &) = 0;
virtual void forceFilterUpdate() = 0;
......
......@@ -124,6 +124,16 @@ public:
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) {
......@@ -520,6 +530,12 @@ void ActionManagerImpl::initSubscriptionListView(SubscriptionListView *subscript
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)
......
......@@ -74,6 +74,7 @@ public:
public Q_SLOTS:
void slotNodeSelected(Akregator::TreeNode *node);
void slotSettingsChanged();
private Q_SLOTS:
void slotServiceUrlSelected(PimCommon::ShareServiceUrlManager::ServiceType type);
......
<!DOCTYPE gui SYSTEM "kpartgui.dtd">
<gui name="akregator_part" version="425" translationDomain="akregator">
<gui name="akregator_part" version="426" translationDomain="akregator">
<MenuBar>
<Menu name="file">
<Action name="file_import"/>
......@@ -24,6 +24,8 @@
<Action name="combined_view"/>
<Separator/>
<Action name="zoom_menu"/>
<Separator/>
<Action name="feed_hide_read"/>
</Menu>
<Menu name="go">
......
......@@ -120,6 +120,9 @@ MainWidget::MainWidget(Part *part, QWidget *parent, ActionManagerImpl *actionMan
m_displayingAboutPage = false;
setFocusPolicy(Qt::StrongFocus);
connect(m_part, &Part::signalSettingsChanged,
m_actionManager, &ActionManagerImpl::slotSettingsChanged);
QVBoxLayout *lt = new QVBoxLayout(this);
lt->setMargin(0);
......@@ -212,6 +215,9 @@ MainWidget::MainWidget(Part *part, QWidget *parent, ActionManagerImpl *actionMan
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);
......
......@@ -24,6 +24,7 @@
#include "selectioncontroller.h"
#include "akregatorconfig.h"
#include "actionmanager.h"
#include "article.h"
#include "articlejobs.h"
......@@ -81,11 +82,13 @@ Akregator::SelectionController::SelectionController(QObject *parent)
, m_feedSelector()
, m_articleLister(0)
, m_singleDisplay(0)
, m_subscriptionModel(new SubscriptionListModel(QSharedPointer<FeedList>(), 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<FeedList>(), this));
}
Akregator::SelectionController::~SelectionController()
......@@ -102,6 +105,7 @@ void Akregator::SelectionController::setFeedSelector(QAbstractItemView *feedSele
if (m_feedSelector) {
m_feedSelector->disconnect(this);
m_feedSelector->selectionModel()->disconnect(this);
m_feedSelector->selectionModel()->disconnect(m_subscriptionModel);
}
m_feedSelector = feedSelector;
......@@ -111,10 +115,12 @@ void Akregator::SelectionController::setFeedSelector(QAbstractItemView *feedSele
}
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)
......@@ -175,8 +181,9 @@ void Akregator::SelectionController::setFeedList(const QSharedPointer<FeedList>
}
m_feedList = list;
std::unique_ptr<SubscriptionListModel> oldModel(m_subscriptionModel);
m_subscriptionModel = new SubscriptionListModel(m_feedList, this);
SubscriptionListModel *m = qobject_cast<SubscriptionListModel*>(m_subscriptionModel->sourceModel());
std::unique_ptr<SubscriptionListModel> oldModel(m);
m_subscriptionModel->setSourceModel(new SubscriptionListModel(m_feedList, this));
if (m_folderExpansionHandler) {
m_folderExpansionHandler->setFeedList(m_feedList);
......@@ -304,6 +311,14 @@ void Akregator::SelectionController::articleIndexDoubleClicked(const QModelIndex
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<QSharedPointer<const Filters::AbstractMatcher> > &matchers)
{
Q_ASSERT(m_articleLister);
......
......@@ -27,6 +27,7 @@
#include "abstractselectioncontroller.h"
#include <QPointer>
#include <QAbstractItemModel>
class QModelIndex;
class QPoint;
......@@ -35,6 +36,7 @@ class KJob;
namespace Akregator {
class ArticleListJob;
class FilterUnreadProxyModel;
class SelectionController : public AbstractSelectionController
{
......@@ -74,6 +76,9 @@ public:
public Q_SLOTS:
//impl
void settingsChanged() override;
//impl
void setFilters(const std::vector<QSharedPointer<const Akregator::Filters::AbstractMatcher> > &) override;
......@@ -90,11 +95,14 @@ private Q_SLOTS:
private:
void setCurrentSubscriptionModel();
QSharedPointer<FeedList> m_feedList;
QPointer<QAbstractItemView> 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<TreeNode> m_selectedSubscription;
......
......@@ -40,6 +40,7 @@
#include <QMimeData>
#include <QUrl>
#include <QVariant>
#include <QItemSelection>
#include <cassert>
......@@ -49,6 +50,11 @@ 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) {
......@@ -77,6 +83,90 @@ static const Akregator::TreeNode *nodeForIndex(const QModelIndex &index, const F
}
}
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<const FeedList> &feedList, QObject *parent) : QAbstractItemModel(parent)
, m_feedList(feedList)
, m_beganRemoval(false)
......@@ -115,11 +205,6 @@ int Akregator::SubscriptionListModel::rowCount(const QModelIndex &parent) const
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()) {
......@@ -343,7 +428,7 @@ void Akregator::FolderExpansionHandler::setExpanded(const QModelIndex &idx, bool
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;
}
......@@ -359,7 +444,7 @@ FolderExpansionHandler::FolderExpansionHandler(QObject *parent) : QObject(parent
{
}
void FolderExpansionHandler::setModel(Akregator::SubscriptionListModel *model)
void FolderExpansionHandler::setModel(QAbstractItemModel *model)
{
m_model = model;
}
......
......@@ -27,6 +27,8 @@
#include "akregatorpart_export.h"
#include <QAbstractItemModel>
#include <QSet>
#include <QSortFilterProxyModel>
#include <QSharedPointer>
......@@ -36,6 +38,34 @@ 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<QModelIndex> SelectionHierarchy;
bool m_doFilter;
SelectionHierarchy m_selectedHierarchy;
};
class AKREGATORPART_EXPORT SubscriptionListModel : public QAbstractItemModel
{
Q_OBJECT
......@@ -83,8 +113,6 @@ public:
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;
......@@ -122,7 +150,7 @@ public:
explicit FolderExpansionHandler(QObject *parent = nullptr);
void setFeedList(const QSharedPointer<FeedList> &feedList);
void setModel(Akregator::SubscriptionListModel *model);
void setModel(QAbstractItemModel *model);
public Q_SLOTS:
void itemExpanded(const QModelIndex &index);
......@@ -133,7 +161,7 @@ private:
private:
QSharedPointer<FeedList> m_feedList;
SubscriptionListModel *m_model = nullptr;
QAbstractItemModel *m_model = nullptr;
};
} // namespace Akregator
......
......@@ -26,6 +26,7 @@
#include "subscriptionlistmodel.h"
#include "subscriptionlistdelegate.h"
#include "akregatorconfig.h"
#include "akregator_debug.h"
#include <QHeaderView>
#include <QStack>
......@@ -387,6 +388,23 @@ void SubscriptionListView::slotItemDown()
setCurrentIndex(current.sibling(current.row() + 1, current.column()));
}
void SubscriptionListView::slotSetHideReadFeeds(bool setting)
{
QAbstractItemModel *m = model();
if (!m) {
return;
}
FilterUnreadProxyModel *filter = qobject_cast<FilterUnreadProxyModel*>(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 *)
{
}
......
......@@ -72,6 +72,8 @@ public Q_SLOTS:
void slotItemUp();
void slotItemDown();
void slotSetHideReadFeeds(bool setting);
Q_SIGNALS:
void userActionTakingPlace();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment