File indexing completed on 2024-05-05 04:38:46
0001 /* 0002 SPDX-FileCopyrightText: 2012 Miha Čančula <miha@noughmad.eu> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "multilevellistview.h" 0008 0009 #include <QHBoxLayout> 0010 #include <QTreeView> 0011 #include <QSortFilterProxyModel> 0012 0013 #include <KSelectionProxyModel> 0014 0015 /** 0016 * Interface to set the label of a model. 0017 */ 0018 class LabeledProxy 0019 { 0020 public: 0021 virtual ~LabeledProxy() 0022 { 0023 } 0024 void setLabel(const QString& label) 0025 { 0026 m_label = label; 0027 } 0028 0029 QVariant header(QAbstractItemModel* model, int section, Qt::Orientation orientation, int role) const 0030 { 0031 if (model && section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { 0032 return m_label; 0033 } else { 0034 return QVariant(); 0035 } 0036 } 0037 0038 protected: 0039 QString m_label; 0040 }; 0041 0042 /** 0043 * The left-most view's model which only contains the root nodes of the source model. 0044 */ 0045 class RootProxyModel : public QSortFilterProxyModel 0046 , public LabeledProxy 0047 { 0048 Q_OBJECT 0049 0050 public: 0051 explicit RootProxyModel(QObject* parent = nullptr) 0052 : QSortFilterProxyModel(parent) 0053 { 0054 } 0055 bool filterAcceptsRow(int /*source_row*/, const QModelIndex& source_parent) const override 0056 { 0057 return !source_parent.isValid(); 0058 } 0059 QVariant headerData(int section, Qt::Orientation orientation, int role) const override 0060 { 0061 return header(sourceModel(), section, orientation, role); 0062 } 0063 }; 0064 0065 /** 0066 * A class that automatically updates its contents based on the selection in another view. 0067 */ 0068 class SubTreeProxyModel : public KSelectionProxyModel 0069 , public LabeledProxy 0070 { 0071 Q_OBJECT 0072 0073 public: 0074 explicit SubTreeProxyModel(QItemSelectionModel* selectionModel, QObject* parent = nullptr) 0075 : KSelectionProxyModel(selectionModel, parent) 0076 {} 0077 QVariant headerData(int section, Qt::Orientation orientation, int role) const override 0078 { 0079 return header(sourceModel(), section, orientation, role); 0080 } 0081 Qt::ItemFlags flags(const QModelIndex& index) const override 0082 { 0083 Qt::ItemFlags ret = KSelectionProxyModel::flags(index); 0084 if (filterBehavior() == KSelectionProxyModel::SubTreesWithoutRoots && hasChildren(index)) { 0085 // we want to select child items 0086 ret &= ~Qt::ItemIsSelectable; 0087 } 0088 return ret; 0089 } 0090 }; 0091 0092 using namespace KDevelop; 0093 0094 class KDevelop::MultiLevelListViewPrivate 0095 { 0096 public: 0097 explicit MultiLevelListViewPrivate(MultiLevelListView* view); 0098 ~MultiLevelListViewPrivate(); 0099 0100 void viewSelectionChanged(const QModelIndex& current, const QModelIndex& previous); 0101 void lastViewsContentsChanged(); 0102 void ensureViewSelected(QTreeView* view); 0103 0104 /** 0105 * @param index index in any of our proxy models 0106 * @return an index in the source model 0107 */ 0108 QModelIndex mapToSource(QModelIndex index) const; 0109 /** 0110 * @param index an index in the source model 0111 * @return an index in the view's model at level @p level 0112 */ 0113 QModelIndex mapFromSource(QModelIndex index, int level) const; 0114 0115 MultiLevelListView* view; 0116 0117 int levels; 0118 QList<QTreeView*> views; 0119 QList<LabeledProxy*> proxies; 0120 QList<QVBoxLayout*> layouts; 0121 0122 QAbstractItemModel* model; 0123 }; 0124 0125 MultiLevelListViewPrivate::MultiLevelListViewPrivate(MultiLevelListView* view_) 0126 : view(view_) 0127 , levels(0) 0128 , model(nullptr) 0129 { 0130 } 0131 0132 MultiLevelListViewPrivate::~MultiLevelListViewPrivate() 0133 { 0134 } 0135 0136 void MultiLevelListViewPrivate::viewSelectionChanged(const QModelIndex& current, 0137 const QModelIndex& previous) 0138 { 0139 if (!current.isValid()) { 0140 // ignore, as we should always have some kind of selection 0141 return; 0142 } 0143 0144 // figure out which proxy this signal belongs to 0145 auto* proxy = qobject_cast<QAbstractProxyModel*>( 0146 const_cast<QAbstractItemModel*>(current.model())); 0147 Q_ASSERT(proxy); 0148 0149 // what level is this proxy in 0150 int level = -1; 0151 for (int i = 0; i < levels; ++i) { 0152 if (views.at(i)->model() == proxy) { 0153 level = i; 0154 break; 0155 } 0156 } 0157 0158 Q_ASSERT(level >= 0 && level < levels); 0159 0160 if (level + 1 == levels) { 0161 // right-most view 0162 if (proxy->hasIndex(0, 0, current)) { 0163 // select the first leaf node for this view 0164 QModelIndex idx = current; 0165 QModelIndex child = proxy->index(0, 0, idx); 0166 while (child.isValid()) { 0167 idx = child; 0168 child = proxy->index(0, 0, idx); 0169 } 0170 views.last()->setCurrentIndex(idx); 0171 return; 0172 } 0173 // signal that our actual selection has changed 0174 emit view->currentIndexChanged(mapToSource(current), mapToSource(previous)); 0175 } else { 0176 // some leftish view 0177 // ensure the next view's first item is selected 0178 QTreeView* treeView = views.at(level + 1); 0179 0180 // we need to delay the call, because at this point the child view 0181 // will still have its old data which is going to be invalidated 0182 // right after this method exits 0183 // be we must not set the index to 0,0 here directly, since e.g. 0184 // MultiLevelListView::setCurrentIndex might have been used, which 0185 // sets a proper index already. 0186 QMetaObject::invokeMethod(view, "ensureViewSelected", Qt::QueuedConnection, 0187 Q_ARG(QTreeView*, treeView)); 0188 } 0189 } 0190 0191 void MultiLevelListViewPrivate::lastViewsContentsChanged() 0192 { 0193 views.last()->expandAll(); 0194 } 0195 0196 void MultiLevelListViewPrivate::ensureViewSelected(QTreeView* view) 0197 { 0198 if (!view->currentIndex().isValid()) { 0199 view->setCurrentIndex(view->model()->index(0, 0)); 0200 } 0201 } 0202 0203 QModelIndex MultiLevelListViewPrivate::mapToSource(QModelIndex index) const 0204 { 0205 if (!index.isValid()) { 0206 return index; 0207 } 0208 0209 while (index.model() != model) { 0210 auto* proxy = qobject_cast<QAbstractProxyModel*>( 0211 const_cast<QAbstractItemModel*>(index.model())); 0212 Q_ASSERT(proxy); 0213 index = proxy->mapToSource(index); 0214 Q_ASSERT(index.isValid()); 0215 } 0216 return index; 0217 } 0218 0219 QModelIndex MultiLevelListViewPrivate::mapFromSource(QModelIndex index, int level) const 0220 { 0221 if (!index.isValid()) { 0222 return index; 0223 } 0224 0225 Q_ASSERT(index.model() == model); 0226 0227 auto* proxy = qobject_cast<QAbstractProxyModel*>(views.at(level)->model()); 0228 Q_ASSERT(proxy); 0229 0230 // find all proxies between the source and our view 0231 QVector<QAbstractProxyModel*> proxies; 0232 proxies << proxy; 0233 while (true) { 0234 auto* child = qobject_cast<QAbstractProxyModel*>(proxy->sourceModel()); 0235 if (child) { 0236 proxy = child; 0237 proxies << proxy; 0238 } else { 0239 Q_ASSERT(proxy->sourceModel() == model); 0240 break; 0241 } 0242 } 0243 // iterate in reverse order to find the view's index 0244 for (int i = proxies.size() - 1; i >= 0; --i) { 0245 proxy = proxies.at(i); 0246 index = proxy->mapFromSource(index); 0247 Q_ASSERT(index.isValid()); 0248 } 0249 0250 return index; 0251 } 0252 0253 MultiLevelListView::MultiLevelListView(QWidget* parent, Qt::WindowFlags f) 0254 : QWidget(parent, f) 0255 , d_ptr(new MultiLevelListViewPrivate(this)) 0256 { 0257 setLayout(new QHBoxLayout()); 0258 layout()->setContentsMargins(0, 0, 0, 0); 0259 0260 qRegisterMetaType<QTreeView*>("QTreeView*"); 0261 } 0262 0263 MultiLevelListView::~MultiLevelListView() = default; 0264 0265 int MultiLevelListView::levels() const 0266 { 0267 Q_D(const MultiLevelListView); 0268 0269 return d->levels; 0270 } 0271 0272 void MultiLevelListView::setLevels(int levels) 0273 { 0274 Q_D(MultiLevelListView); 0275 0276 qDeleteAll(d->views); 0277 qDeleteAll(d->proxies); 0278 qDeleteAll(d->layouts); 0279 d->views.clear(); 0280 d->proxies.clear(); 0281 d->layouts.clear(); 0282 0283 d->levels = levels; 0284 d->views.reserve(levels); 0285 d->proxies.reserve(levels); 0286 d->layouts.reserve(levels); 0287 0288 QTreeView* previousView = nullptr; 0289 for (int i = 0; i < d->levels; ++i) { 0290 auto* levelLayout = new QVBoxLayout(); 0291 0292 auto* view = new QTreeView(this); 0293 view->setContentsMargins(0, 0, 0, 0); 0294 // only the right-most view is decorated 0295 view->setRootIsDecorated(i + 1 == d->levels); 0296 view->setHeaderHidden(false); 0297 view->setSelectionMode(QAbstractItemView::SingleSelection); 0298 0299 if (!previousView) { 0300 // the root, i.e. left-most view 0301 auto* root = new RootProxyModel(this); 0302 root->setDynamicSortFilter(true); 0303 d->proxies << root; 0304 root->setSourceModel(d->model); 0305 view->setModel(root); 0306 } else { 0307 auto* subTreeProxy = new SubTreeProxyModel(previousView->selectionModel(), this); 0308 if (i + 1 < d->levels) { 0309 // middel views only shows children of selection 0310 subTreeProxy->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); 0311 } else { 0312 // right-most view shows the rest 0313 subTreeProxy->setFilterBehavior(KSelectionProxyModel::SubTreesWithoutRoots); 0314 } 0315 d->proxies << subTreeProxy; 0316 subTreeProxy->setSourceModel(d->model); 0317 // sorting requires another proxy in-between 0318 auto* sortProxy = new QSortFilterProxyModel(subTreeProxy); 0319 sortProxy->setSourceModel(subTreeProxy); 0320 sortProxy->setDynamicSortFilter(true); 0321 view->setModel(sortProxy); 0322 } 0323 0324 // view->setModel creates the selection model 0325 connect(view->selectionModel(), &QItemSelectionModel::currentChanged, 0326 this, [this](const QModelIndex& current, const QModelIndex& previous) { 0327 Q_D(MultiLevelListView); 0328 d->viewSelectionChanged(current, previous); 0329 }); 0330 0331 if (i + 1 == d->levels) { 0332 connect(view->model(), &QAbstractItemModel::rowsInserted, 0333 this, [this] { 0334 Q_D(MultiLevelListView); 0335 d->lastViewsContentsChanged(); 0336 }); 0337 } 0338 0339 view->setSortingEnabled(true); 0340 view->sortByColumn(0, Qt::AscendingOrder); 0341 0342 levelLayout->addWidget(view); 0343 layout()->addItem(levelLayout); 0344 0345 d->layouts << levelLayout; 0346 d->views << view; 0347 0348 previousView = view; 0349 } 0350 0351 setModel(d->model); 0352 } 0353 0354 QAbstractItemModel* MultiLevelListView::model() const 0355 { 0356 Q_D(const MultiLevelListView); 0357 0358 return d->model; 0359 } 0360 0361 void MultiLevelListView::setModel(QAbstractItemModel* model) 0362 { 0363 Q_D(MultiLevelListView); 0364 0365 d->model = model; 0366 0367 for (LabeledProxy* proxy : qAsConst(d->proxies)) { 0368 dynamic_cast<QAbstractProxyModel*>(proxy)->setSourceModel(model); 0369 } 0370 0371 if (model && !d->views.isEmpty()) { 0372 d->views.first()->setCurrentIndex(d->views.first()->model()->index(0, 0)); 0373 } 0374 } 0375 0376 QTreeView* MultiLevelListView::viewForLevel(int level) const 0377 { 0378 Q_D(const MultiLevelListView); 0379 0380 return d->views.value(level); 0381 } 0382 0383 void MultiLevelListView::addWidget(int level, QWidget* widget) 0384 { 0385 Q_D(MultiLevelListView); 0386 0387 Q_ASSERT(level < d->levels); 0388 d->layouts[level]->addWidget(widget); 0389 } 0390 0391 QModelIndex MultiLevelListView::currentIndex() const 0392 { 0393 Q_D(const MultiLevelListView); 0394 0395 return d->mapToSource(d->views.last()->currentIndex()); 0396 } 0397 0398 void MultiLevelListView::setCurrentIndex(const QModelIndex& index) 0399 { 0400 Q_D(MultiLevelListView); 0401 0402 // incoming index is for the original model 0403 Q_ASSERT(!index.isValid() || index.model() == d->model); 0404 0405 const QModelIndex previous = currentIndex(); 0406 0407 QModelIndex idx(index); 0408 QVector<QModelIndex> indexes; 0409 0410 while (idx.isValid()) { 0411 indexes.prepend(idx); 0412 idx = idx.parent(); 0413 } 0414 0415 for (int i = 0; i < d->levels; ++i) { 0416 QTreeView* view = d->views.at(i); 0417 if (indexes.size() <= i) { 0418 // select first item by default 0419 view->setCurrentIndex(view->model()->index(0, 0)); 0420 continue; 0421 } 0422 0423 QModelIndex index; 0424 if (i + 1 == d->levels) { 0425 // select the very last index in the list (i.e. might be deep down in the actual tree) 0426 index = indexes.last(); 0427 } else { 0428 // select the first index for that level 0429 index = indexes.at(i); 0430 } 0431 view->setCurrentIndex(d->mapFromSource(index, i)); 0432 } 0433 0434 emit currentIndexChanged(index, previous); 0435 } 0436 0437 void MultiLevelListView::setRootIndex(const QModelIndex& index) 0438 { 0439 Q_D(MultiLevelListView); 0440 0441 Q_ASSERT(!index.isValid() || index.model() == d->model); 0442 d->views.first()->setRootIndex(index); 0443 } 0444 0445 void MultiLevelListView::setHeaderLabels(const QStringList& labels) 0446 { 0447 Q_D(MultiLevelListView); 0448 0449 int n = qMin(d->levels, labels.size()); 0450 for (int i = 0; i < n; ++i) { 0451 d->proxies.at(i)->setLabel(labels[i]); 0452 } 0453 } 0454 0455 static 0456 KSelectionProxyModel::FilterBehavior toSelectionProxyModelFilterBehavior(MultiLevelListView::LastLevelViewMode mode) 0457 { 0458 switch (mode) { 0459 case MultiLevelListView::SubTrees: 0460 return KSelectionProxyModel::SubTreesWithoutRoots; 0461 case MultiLevelListView::DirectChildren: 0462 return KSelectionProxyModel::ChildrenOfExactSelection; 0463 } 0464 Q_UNREACHABLE(); 0465 } 0466 0467 void MultiLevelListView::setLastLevelViewMode(LastLevelViewMode mode) 0468 { 0469 Q_D(MultiLevelListView); 0470 0471 if (d->proxies.isEmpty()) { 0472 return; 0473 } 0474 const auto filterBehavior = toSelectionProxyModelFilterBehavior(mode); 0475 dynamic_cast<KSelectionProxyModel*>(d->proxies.last())->setFilterBehavior(filterBehavior); 0476 } 0477 0478 #include "multilevellistview.moc" 0479 #include "moc_multilevellistview.cpp"