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"