File indexing completed on 2024-04-21 05:10:37
0001 /* 0002 This file is part of Akregator. 0003 0004 SPDX-FileCopyrightText: 2007 Frank Osterfeld <osterfeld@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0007 */ 0008 0009 #include "selectioncontroller.h" 0010 0011 #include "actionmanager.h" 0012 #include "akregatorconfig.h" 0013 #include "article.h" 0014 #include "articlejobs.h" 0015 #include "articlemodel.h" 0016 #include "feedlist.h" 0017 #include "subscriptionlistmodel.h" 0018 #include "treenode.h" 0019 0020 #include "akregator_debug.h" 0021 0022 #include <QAbstractItemView> 0023 #include <QMenu> 0024 #include <QTreeView> 0025 #include <memory> 0026 using namespace Akregator; 0027 0028 namespace 0029 { 0030 static Akregator::Article articleForIndex(const QModelIndex &index, FeedList *feedList) 0031 { 0032 if (!index.isValid()) { 0033 return {}; 0034 } 0035 0036 const QString guid = index.data(ArticleModel::GuidRole).toString(); 0037 const QString feedId = index.data(ArticleModel::FeedIdRole).toString(); 0038 return feedList->findArticle(feedId, guid); 0039 } 0040 0041 static QList<Akregator::Article> articlesForIndexes(const QModelIndexList &indexes, FeedList *feedList) 0042 { 0043 QList<Akregator::Article> articles; 0044 for (const QModelIndex &i : indexes) { 0045 const Article a = articleForIndex(i, feedList); 0046 if (a.isNull()) { 0047 continue; 0048 } 0049 articles.append(articleForIndex(i, feedList)); 0050 } 0051 0052 return articles; 0053 } 0054 0055 static Akregator::TreeNode *subscriptionForIndex(const QModelIndex &index, Akregator::FeedList *feedList) 0056 { 0057 if (!index.isValid()) { 0058 return nullptr; 0059 } 0060 0061 return feedList->findByID(index.data(Akregator::SubscriptionListModel::SubscriptionIdRole).toInt()); 0062 } 0063 } // anon namespace 0064 0065 SelectionController::SelectionController(QObject *parent) 0066 : AbstractSelectionController(parent) 0067 , m_feedList() 0068 , m_feedSelector() 0069 , m_subscriptionModel(new FilterUnreadProxyModel(this)) 0070 , m_selectedSubscription() 0071 { 0072 m_subscriptionModel->setDoFilter(Settings::hideReadFeeds()); 0073 m_subscriptionModel->setSourceModel(new SubscriptionListModel(QSharedPointer<FeedList>(), this)); 0074 0075 connect(m_subscriptionModel, &FilterUnreadProxyModel::dataChanged, this, &SelectionController::subscriptionDataChanged); 0076 } 0077 0078 SelectionController::~SelectionController() 0079 { 0080 delete m_articleModel; 0081 } 0082 0083 void SelectionController::setFeedSelector(QAbstractItemView *feedSelector) 0084 { 0085 if (m_feedSelector == feedSelector) { 0086 return; 0087 } 0088 0089 if (m_feedSelector) { 0090 m_feedSelector->disconnect(this); 0091 m_feedSelector->selectionModel()->disconnect(this); 0092 m_feedSelector->selectionModel()->disconnect(m_subscriptionModel); 0093 } 0094 0095 m_feedSelector = feedSelector; 0096 0097 if (!m_feedSelector) { 0098 return; 0099 } 0100 0101 m_feedSelector->setModel(m_subscriptionModel); 0102 m_subscriptionModel->clearCache(); 0103 0104 connect(m_feedSelector.data(), &QAbstractItemView::customContextMenuRequested, this, &SelectionController::subscriptionContextMenuRequested); 0105 connect(m_feedSelector->selectionModel(), &QItemSelectionModel::currentChanged, this, &SelectionController::selectedSubscriptionChanged); 0106 connect(m_feedSelector.data(), &QAbstractItemView::activated, this, &SelectionController::selectedSubscriptionChanged); 0107 connect(m_feedSelector->selectionModel(), &QItemSelectionModel::selectionChanged, m_subscriptionModel, &FilterUnreadProxyModel::selectionChanged); 0108 } 0109 0110 void SelectionController::setArticleLister(Akregator::ArticleLister *lister) 0111 { 0112 if (m_articleLister == lister) { 0113 return; 0114 } 0115 0116 if (m_articleLister) { 0117 m_articleLister->articleSelectionModel()->disconnect(this); 0118 } 0119 if (m_articleLister && m_articleLister->itemView()) { 0120 m_articleLister->itemView()->disconnect(this); 0121 } 0122 0123 m_articleLister = lister; 0124 0125 if (m_articleLister && m_articleLister->itemView()) { 0126 connect(m_articleLister->itemView(), &QAbstractItemView::doubleClicked, this, &SelectionController::articleIndexDoubleClicked); 0127 } 0128 } 0129 0130 void SelectionController::setSingleArticleDisplay(Akregator::SingleArticleDisplay *display) 0131 { 0132 m_singleDisplay = display; 0133 } 0134 0135 Akregator::Article SelectionController::currentArticle() const 0136 { 0137 if (!m_articleLister || !m_articleLister->articleSelectionModel()) { 0138 return {}; 0139 } 0140 return ::articleForIndex(m_articleLister->articleSelectionModel()->currentIndex(), m_feedList.data()); 0141 } 0142 0143 QModelIndex SelectionController::currentArticleIndex() const 0144 { 0145 return m_articleLister->articleSelectionModel()->currentIndex(); 0146 } 0147 0148 QList<Akregator::Article> SelectionController::selectedArticles() const 0149 { 0150 if (!m_articleLister || !m_articleLister->articleSelectionModel()) { 0151 return {}; 0152 } 0153 return ::articlesForIndexes(m_articleLister->articleSelectionModel()->selectedRows(), m_feedList.data()); 0154 } 0155 0156 Akregator::TreeNode *SelectionController::selectedSubscription() const 0157 { 0158 return ::subscriptionForIndex(m_feedSelector->selectionModel()->currentIndex(), m_feedList.data()); 0159 } 0160 0161 void SelectionController::setFeedList(const QSharedPointer<FeedList> &list) 0162 { 0163 if (m_feedList == list) { 0164 return; 0165 } 0166 0167 m_feedList = list; 0168 auto m = qobject_cast<SubscriptionListModel *>(m_subscriptionModel->sourceModel()); 0169 std::unique_ptr<SubscriptionListModel> oldModel(m); 0170 m_subscriptionModel->setSourceModel(new SubscriptionListModel(m_feedList, this)); 0171 0172 if (m_folderExpansionHandler) { 0173 m_folderExpansionHandler->setFeedList(m_feedList); 0174 m_folderExpansionHandler->setModel(m_subscriptionModel); 0175 } 0176 0177 if (m_feedSelector) { 0178 if (m_feedList) { 0179 m_feedSelector->setModel(m_subscriptionModel); 0180 disconnect(m_feedSelector->selectionModel(), &QItemSelectionModel::currentChanged, this, &SelectionController::selectedSubscriptionChanged); 0181 connect(m_feedSelector->selectionModel(), &QItemSelectionModel::currentChanged, this, &SelectionController::selectedSubscriptionChanged); 0182 } else { 0183 disconnect(m_feedSelector->selectionModel(), &QItemSelectionModel::currentChanged, this, &SelectionController::selectedSubscriptionChanged); 0184 } 0185 } 0186 } 0187 0188 void SelectionController::setFolderExpansionHandler(Akregator::FolderExpansionHandler *handler) 0189 { 0190 if (handler == m_folderExpansionHandler) { 0191 return; 0192 } 0193 m_folderExpansionHandler = handler; 0194 if (!m_folderExpansionHandler) { 0195 return; 0196 } 0197 handler->setFeedList(m_feedList); 0198 handler->setModel(m_subscriptionModel); 0199 } 0200 0201 void SelectionController::articleHeadersAvailable(KJob *job) 0202 { 0203 Q_ASSERT(job); 0204 Q_ASSERT(job == m_listJob); 0205 0206 if (job->error()) { 0207 qCWarning(AKREGATOR_LOG) << job->errorText(); 0208 return; 0209 } 0210 TreeNode *const node = m_listJob->node(); 0211 0212 Q_ASSERT(node); // if there was no error, the node must still exist 0213 Q_ASSERT(node == m_selectedSubscription); //...and equal the previously selected node 0214 0215 auto const newModel = new ArticleModel(m_listJob->articles()); 0216 0217 connect(node, &QObject::destroyed, newModel, &ArticleModel::clear); 0218 connect(node, &TreeNode::signalArticlesAdded, newModel, &ArticleModel::articlesAdded); 0219 connect(node, &TreeNode::signalArticlesRemoved, newModel, &ArticleModel::articlesRemoved); 0220 connect(node, &TreeNode::signalArticlesUpdated, newModel, &ArticleModel::articlesUpdated); 0221 0222 m_articleLister->setIsAggregation(node->isAggregation()); 0223 m_articleLister->setArticleModel(newModel); 0224 delete m_articleModel; // order is important: do not delete the old model before the new model is set in the view 0225 m_articleModel = newModel; 0226 0227 disconnect(m_articleLister->articleSelectionModel(), &QItemSelectionModel::selectionChanged, this, &SelectionController::articleSelectionChanged); 0228 connect(m_articleLister->articleSelectionModel(), &QItemSelectionModel::selectionChanged, this, &SelectionController::articleSelectionChanged); 0229 0230 m_articleLister->setScrollBarPositions(node->listViewScrollBarPositions()); 0231 } 0232 0233 void SelectionController::subscriptionDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) 0234 { 0235 if (!Settings::autoExpandFolders()) { 0236 return; 0237 } 0238 0239 if (!m_subscriptionModel) { 0240 qCCritical(AKREGATOR_LOG) << "m_subscriptionModel is NULL"; 0241 return; 0242 } 0243 0244 // need access to setExpanded 0245 auto tv = qobject_cast<QTreeView *>(m_feedSelector); 0246 if (!tv) { 0247 qCCritical(AKREGATOR_LOG) << "Unable to cast m_feedSelector to QTreeView"; 0248 return; 0249 } 0250 0251 int startRow = topLeft.row(); 0252 int endRow = bottomRight.row(); 0253 QModelIndex parent = topLeft.parent(); 0254 0255 for (int row = startRow; row <= endRow; ++row) { 0256 QModelIndex idx = m_subscriptionModel->index(row, 0, parent); 0257 QVariant v = m_subscriptionModel->data(idx, SubscriptionListModel::HasUnreadRole); 0258 if (!v.toBool()) { 0259 return; 0260 } 0261 tv->setExpanded(idx, true); 0262 } 0263 } 0264 0265 void SelectionController::selectedSubscriptionChanged(const QModelIndex &index) 0266 { 0267 if (!index.isValid()) { 0268 return; 0269 } 0270 0271 if (m_selectedSubscription && m_articleLister) { 0272 m_selectedSubscription->setListViewScrollBarPositions(m_articleLister->scrollBarPositions()); 0273 } 0274 0275 m_selectedSubscription = selectedSubscription(); 0276 Q_EMIT currentSubscriptionChanged(m_selectedSubscription); 0277 0278 // using a timer here internally to simulate async data fetching (which is still synchronous), 0279 // to ensure the UI copes with async behavior later on 0280 0281 if (m_listJob) { 0282 m_listJob->disconnect(this); // Ignore if ~KJob() emits finished() 0283 delete m_listJob; 0284 } 0285 0286 if (!m_selectedSubscription) { 0287 return; 0288 } 0289 0290 auto const job(new ArticleListJob(m_selectedSubscription)); 0291 connect(job, &KJob::finished, this, &SelectionController::articleHeadersAvailable); 0292 m_listJob = job; 0293 m_listJob->start(); 0294 } 0295 0296 void SelectionController::subscriptionContextMenuRequested(const QPoint &point) 0297 { 0298 Q_ASSERT(m_feedSelector); 0299 const TreeNode *const node = ::subscriptionForIndex(m_feedSelector->indexAt(point), m_feedList.data()); 0300 if (!node) { 0301 return; 0302 } 0303 0304 QWidget *w = ActionManager::getInstance()->container(node->isGroup() ? QStringLiteral("feedgroup_popup") : QStringLiteral("feeds_popup")); 0305 auto popup = qobject_cast<QMenu *>(w); 0306 if (popup) { 0307 const QPoint globalPos = m_feedSelector->viewport()->mapToGlobal(point); 0308 popup->exec(globalPos); 0309 } 0310 } 0311 0312 void SelectionController::articleSelectionChanged() 0313 { 0314 const Akregator::Article article = currentArticle(); 0315 if (m_singleDisplay) { 0316 m_singleDisplay->showArticle(article); 0317 } 0318 Q_EMIT currentArticleChanged(article); 0319 } 0320 0321 void SelectionController::articleIndexDoubleClicked(const QModelIndex &index) 0322 { 0323 const Akregator::Article article = ::articleForIndex(index, m_feedList.data()); 0324 Q_EMIT articleDoubleClicked(article); 0325 } 0326 0327 /** 0328 * Called when the applications settings are changed; sets whether we apply a the filter or not. 0329 */ 0330 void SelectionController::settingsChanged() 0331 { 0332 m_subscriptionModel->setDoFilter(Settings::hideReadFeeds()); 0333 } 0334 0335 void SelectionController::setFilters(const std::vector<QSharedPointer<const Filters::AbstractMatcher>> &matchers) 0336 { 0337 Q_ASSERT(m_articleLister); 0338 m_articleLister->setFilters(matchers); 0339 } 0340 0341 void SelectionController::forceFilterUpdate() 0342 { 0343 Q_ASSERT(m_articleLister); 0344 m_articleLister->forceFilterUpdate(); 0345 } 0346 0347 #include "moc_selectioncontroller.cpp"