File indexing completed on 2024-05-05 05:13:11
0001 /* 0002 This file is part of Akregator. 0003 0004 SPDX-FileCopyrightText: 2007 Frank Osterfeld <frank.osterfeld@kdemail.net> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0007 */ 0008 0009 #include "subscriptionlistview.h" 0010 #include "akregator_debug.h" 0011 #include "akregatorconfig.h" 0012 #include "subscriptionlistdelegate.h" 0013 #include "subscriptionlistmodel.h" 0014 0015 #include <QHeaderView> 0016 #include <QPointer> 0017 #include <QQueue> 0018 #include <QStack> 0019 0020 #include <KConfigGroup> 0021 #include <KLocalizedString> 0022 #include <QMenu> 0023 0024 using namespace Akregator; 0025 0026 static QModelIndex prevIndex(const QModelIndex &idx) 0027 { 0028 if (!idx.isValid()) { 0029 return {}; 0030 } 0031 const QAbstractItemModel *const model = idx.model(); 0032 Q_ASSERT(model); 0033 0034 if (idx.row() > 0) { 0035 QModelIndex i = idx.sibling(idx.row() - 1, idx.column()); 0036 while (model->hasChildren(i)) { 0037 i = model->index(model->rowCount(i) - 1, i.column(), i); 0038 } 0039 return i; 0040 } else { 0041 return idx.parent(); 0042 } 0043 } 0044 0045 static QModelIndex prevFeedIndex(const QModelIndex &idx, bool allowPassed = false) 0046 { 0047 QModelIndex prev = allowPassed ? idx : prevIndex(idx); 0048 while (prev.isValid() && prev.data(SubscriptionListModel::IsAggregationRole).toBool()) { 0049 prev = prevIndex(prev); 0050 } 0051 return prev; 0052 } 0053 0054 static QModelIndex prevUnreadFeedIndex(const QModelIndex &idx, bool allowPassed = false) 0055 { 0056 QModelIndex prev = allowPassed ? idx : prevIndex(idx); 0057 while (prev.isValid() 0058 && (prev.data(SubscriptionListModel::IsAggregationRole).toBool() 0059 || prev.sibling(prev.row(), SubscriptionListModel::UnreadCountColumn).data().toInt() == 0)) { 0060 prev = prevIndex(prev); 0061 } 0062 return prev; 0063 } 0064 0065 static QModelIndex lastLeaveChild(const QAbstractItemModel *const model) 0066 { 0067 Q_ASSERT(model); 0068 if (model->rowCount() == 0) { 0069 return {}; 0070 } 0071 QModelIndex idx = model->index(model->rowCount() - 1, 0); 0072 while (model->hasChildren(idx)) { 0073 idx = model->index(model->rowCount(idx) - 1, idx.column(), idx); 0074 } 0075 return idx; 0076 } 0077 0078 static QModelIndex nextIndex(const QModelIndex &idx) 0079 { 0080 if (!idx.isValid()) { 0081 return {}; 0082 } 0083 const QAbstractItemModel *const model = idx.model(); 0084 Q_ASSERT(model); 0085 if (model->hasChildren(idx)) { 0086 return model->index(0, idx.column(), idx); 0087 } 0088 QModelIndex i = idx; 0089 while (true) { 0090 if (!i.isValid()) { 0091 return i; 0092 } 0093 const int siblings = model->rowCount(i.parent()); 0094 if (i.row() + 1 < siblings) { 0095 return i.sibling(i.row() + 1, i.column()); 0096 } 0097 i = i.parent(); 0098 } 0099 } 0100 0101 static QModelIndex nextFeedIndex(const QModelIndex &idx) 0102 { 0103 QModelIndex next = nextIndex(idx); 0104 while (next.isValid() && next.data(SubscriptionListModel::IsAggregationRole).toBool()) { 0105 next = nextIndex(next); 0106 } 0107 return next; 0108 } 0109 0110 static QModelIndex nextUnreadFeedIndex(const QModelIndex &idx) 0111 { 0112 QModelIndex next = nextIndex(idx); 0113 while (next.isValid() 0114 && (next.data(SubscriptionListModel::IsAggregationRole).toBool() 0115 || next.sibling(next.row(), SubscriptionListModel::UnreadCountColumn).data().toInt() == 0)) { 0116 next = nextIndex(next); 0117 } 0118 return next; 0119 } 0120 0121 Akregator::SubscriptionListView::SubscriptionListView(QWidget *parent) 0122 : QTreeView(parent) 0123 { 0124 setFocusPolicy(Qt::NoFocus); 0125 setSelectionMode(QAbstractItemView::SingleSelection); 0126 setRootIsDecorated(false); 0127 setAlternatingRowColors(true); 0128 setContextMenuPolicy(Qt::CustomContextMenu); 0129 setDragDropMode(Settings::lockFeedsInPlace() ? QAbstractItemView::NoDragDrop : QAbstractItemView::DragDrop); 0130 setDropIndicatorShown(true); 0131 setAcceptDrops(true); 0132 setUniformRowHeights(true); 0133 setItemDelegate(new SubscriptionListDelegate(this)); 0134 connect(header(), &QWidget::customContextMenuRequested, this, &SubscriptionListView::showHeaderMenu); 0135 0136 loadHeaderSettings(); 0137 } 0138 0139 Akregator::SubscriptionListView::~SubscriptionListView() 0140 { 0141 saveHeaderSettings(); 0142 } 0143 0144 void Akregator::SubscriptionListView::setModel(QAbstractItemModel *m) 0145 { 0146 Q_ASSERT(m); 0147 0148 if (model()) { 0149 m_headerState = header()->saveState(); 0150 } 0151 0152 QTreeView::setModel(m); 0153 0154 restoreHeaderState(); 0155 0156 QStack<QModelIndex> stack; 0157 stack.push(rootIndex()); 0158 while (!stack.isEmpty()) { 0159 const QModelIndex i = stack.pop(); 0160 const int childCount = m->rowCount(i); 0161 for (int j = 0; j < childCount; ++j) { 0162 const QModelIndex child = m->index(j, 0, i); 0163 if (child.isValid()) { 0164 stack.push(child); 0165 } 0166 } 0167 setExpanded(i, i.data(Akregator::SubscriptionListModel::IsOpenRole).toBool()); 0168 } 0169 0170 header()->setContextMenuPolicy(Qt::CustomContextMenu); 0171 } 0172 0173 void Akregator::SubscriptionListView::showHeaderMenu(const QPoint &pos) 0174 { 0175 if (!model()) { 0176 return; 0177 } 0178 0179 QPointer<QMenu> menu = new QMenu(this); 0180 menu->setTitle(i18n("Columns")); 0181 menu->setAttribute(Qt::WA_DeleteOnClose); 0182 connect(menu.data(), &QMenu::triggered, this, &SubscriptionListView::headerMenuItemTriggered); 0183 0184 for (int i = 0; i < model()->columnCount(); ++i) { 0185 if (SubscriptionListModel::TitleColumn == i) { 0186 continue; 0187 } 0188 QString col = model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); 0189 QAction *act = menu->addAction(col); 0190 act->setCheckable(true); 0191 act->setChecked(!header()->isSectionHidden(i)); 0192 act->setData(i); 0193 } 0194 0195 menu->popup(header()->mapToGlobal(pos)); 0196 } 0197 0198 void Akregator::SubscriptionListView::headerMenuItemTriggered(QAction *act) 0199 { 0200 Q_ASSERT(act); 0201 const int col = act->data().toInt(); 0202 if (act->isChecked()) { 0203 header()->showSection(col); 0204 } else { 0205 header()->hideSection(col); 0206 } 0207 } 0208 0209 void Akregator::SubscriptionListView::saveHeaderSettings() 0210 { 0211 if (model()) { 0212 m_headerState = header()->saveState(); 0213 } 0214 KConfigGroup conf(Settings::self()->config(), QStringLiteral("General")); 0215 conf.writeEntry("SubscriptionListHeaders", m_headerState.toBase64()); 0216 } 0217 0218 void Akregator::SubscriptionListView::loadHeaderSettings() 0219 { 0220 const KConfigGroup conf(Settings::self()->config(), QStringLiteral("General")); 0221 m_headerState = QByteArray::fromBase64(conf.readEntry("SubscriptionListHeaders").toLatin1()); 0222 restoreHeaderState(); 0223 } 0224 0225 void Akregator::SubscriptionListView::restoreHeaderState() 0226 { 0227 header()->restoreState(m_headerState); // needed, even with Qt 4.5 0228 // Always shows the title column 0229 header()->showSection(SubscriptionListModel::TitleColumn); 0230 if (m_headerState.isEmpty()) { 0231 // Default configuration: only show the title column 0232 header()->hideSection(SubscriptionListModel::UnreadCountColumn); 0233 header()->hideSection(SubscriptionListModel::TotalCountColumn); 0234 } 0235 } 0236 0237 void Akregator::SubscriptionListView::slotPrevFeed() 0238 { 0239 if (!model()) { 0240 return; 0241 } 0242 const QModelIndex current = currentIndex(); 0243 QModelIndex prev = prevFeedIndex(current); 0244 if (!prev.isValid()) { 0245 prev = prevFeedIndex(lastLeaveChild(model()), true); 0246 } 0247 if (prev.isValid()) { 0248 setCurrentIndex(prev); 0249 } 0250 } 0251 0252 void Akregator::SubscriptionListView::slotNextFeed() 0253 { 0254 if (!model()) { 0255 return; 0256 } 0257 Q_EMIT userActionTakingPlace(); 0258 const QModelIndex current = currentIndex(); 0259 QModelIndex next = nextFeedIndex(current); 0260 if (!next.isValid()) { 0261 next = nextFeedIndex(model()->index(0, 0)); 0262 } 0263 if (next.isValid()) { 0264 setCurrentIndex(next); 0265 } 0266 } 0267 0268 void Akregator::SubscriptionListView::slotPrevUnreadFeed() 0269 { 0270 if (!model()) { 0271 return; 0272 } 0273 Q_EMIT userActionTakingPlace(); 0274 const QModelIndex current = currentIndex(); 0275 QModelIndex prev = prevUnreadFeedIndex(current); 0276 if (!prev.isValid()) { 0277 prev = prevUnreadFeedIndex(lastLeaveChild(model()), true); 0278 } 0279 if (prev.isValid()) { 0280 setCurrentIndex(prev); 0281 } 0282 } 0283 0284 void Akregator::SubscriptionListView::slotNextUnreadFeed() 0285 { 0286 if (!model()) { 0287 return; 0288 } 0289 Q_EMIT userActionTakingPlace(); 0290 const QModelIndex current = currentIndex(); 0291 QModelIndex next = nextUnreadFeedIndex(current); 0292 if (!next.isValid()) { 0293 next = nextUnreadFeedIndex(model()->index(0, 0)); 0294 } 0295 if (next.isValid()) { 0296 setCurrentIndex(next); 0297 } 0298 } 0299 0300 void SubscriptionListView::slotItemBegin() 0301 { 0302 if (!model()) { 0303 return; 0304 } 0305 Q_EMIT userActionTakingPlace(); 0306 setCurrentIndex(nextFeedIndex(model()->index(0, 0))); 0307 } 0308 0309 void SubscriptionListView::slotItemEnd() 0310 { 0311 if (!model()) { 0312 return; 0313 } 0314 Q_EMIT userActionTakingPlace(); 0315 setCurrentIndex(lastLeaveChild(model())); 0316 } 0317 0318 void SubscriptionListView::slotItemLeft() 0319 { 0320 if (!model()) { 0321 return; 0322 } 0323 Q_EMIT userActionTakingPlace(); 0324 const QModelIndex current = currentIndex(); 0325 if (!current.isValid()) { 0326 setCurrentIndex(nextFeedIndex(model()->index(0, 0))); 0327 return; 0328 } 0329 if (current.parent().isValid()) { 0330 setCurrentIndex(current.parent()); 0331 } 0332 } 0333 0334 void SubscriptionListView::slotItemRight() 0335 { 0336 if (!model()) { 0337 return; 0338 } 0339 Q_EMIT userActionTakingPlace(); 0340 const QModelIndex current = currentIndex(); 0341 if (!current.isValid()) { 0342 setCurrentIndex(nextFeedIndex(model()->index(0, 0))); 0343 return; 0344 } 0345 if (model()->rowCount(current) > 0) { 0346 setCurrentIndex(model()->index(0, 0, current)); 0347 } 0348 } 0349 0350 void SubscriptionListView::slotItemUp() 0351 { 0352 if (!model()) { 0353 return; 0354 } 0355 Q_EMIT userActionTakingPlace(); 0356 const QModelIndex current = currentIndex(); 0357 QModelIndex prev = current.row() > 0 ? current.sibling(current.row() - 1, current.column()) : current.parent(); 0358 if (!prev.isValid()) { 0359 prev = lastLeaveChild(model()); 0360 } 0361 if (prev.isValid()) { 0362 setCurrentIndex(prev); 0363 } 0364 } 0365 0366 void SubscriptionListView::slotItemDown() 0367 { 0368 if (!model()) { 0369 return; 0370 } 0371 Q_EMIT userActionTakingPlace(); 0372 const QModelIndex current = currentIndex(); 0373 if (current.row() >= model()->rowCount(current.parent())) { 0374 return; 0375 } 0376 setCurrentIndex(current.sibling(current.row() + 1, current.column())); 0377 } 0378 0379 void SubscriptionListView::slotSetHideReadFeeds(bool setting) 0380 { 0381 QAbstractItemModel *m = model(); 0382 if (!m) { 0383 return; 0384 } 0385 0386 auto filter = qobject_cast<FilterUnreadProxyModel *>(m); 0387 if (!filter) { 0388 qCCritical(AKREGATOR_LOG) << "Unable to cast model to FilterUnreadProxyModel*"; 0389 return; 0390 } 0391 0392 Settings::setHideReadFeeds(setting); 0393 filter->setDoFilter(setting); 0394 } 0395 0396 void SubscriptionListView::slotSetLockFeedsInPlace(bool setting) 0397 { 0398 if (!model()) { 0399 return; 0400 } 0401 0402 setDragDropMode(setting ? QAbstractItemView::NoDragDrop : QAbstractItemView::DragDrop); 0403 0404 Settings::setLockFeedsInPlace(setting); 0405 } 0406 0407 void Akregator::SubscriptionListView::slotSetAutoExpandFolders(bool setting) 0408 { 0409 Settings::setAutoExpandFolders(setting); 0410 if (!setting) { 0411 return; 0412 } 0413 0414 // expand any current subscriptions with unread items 0415 QQueue<QModelIndex> indexes; 0416 // start at the root node 0417 indexes.enqueue(QModelIndex()); 0418 0419 QAbstractItemModel *m = model(); 0420 if (!m) { 0421 return; 0422 } 0423 0424 while (!indexes.isEmpty()) { 0425 QModelIndex parent = indexes.dequeue(); 0426 int rows = m->rowCount(parent); 0427 0428 for (int row = 0; row < rows; ++row) { 0429 QModelIndex current = m->index(row, 0, parent); 0430 0431 if (m->hasChildren(current)) { 0432 indexes.enqueue(current); 0433 } 0434 0435 if (!m->data(current, SubscriptionListModel::HasUnreadRole).toBool()) { 0436 continue; 0437 } 0438 0439 setExpanded(current, true); 0440 } 0441 } 0442 } 0443 0444 void Akregator::SubscriptionListView::ensureNodeVisible(Akregator::TreeNode *) 0445 { 0446 } 0447 0448 void Akregator::SubscriptionListView::startNodeRenaming(Akregator::TreeNode *node) 0449 { 0450 Q_UNUSED(node) 0451 const QModelIndex current = currentIndex(); 0452 if (!current.isValid()) { 0453 return; 0454 } 0455 edit(current); 0456 } 0457 0458 #include "moc_subscriptionlistview.cpp"