File indexing completed on 2024-07-21 06:38:23

0001 /*
0002     SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #ifndef KSELECTIONPROXYMODEL_H
0008 #define KSELECTIONPROXYMODEL_H
0009 
0010 #include <QAbstractProxyModel>
0011 #include <QItemSelectionModel>
0012 
0013 #include "kitemmodels_export.h"
0014 
0015 #include <memory>
0016 
0017 class KSelectionProxyModelPrivate;
0018 
0019 /**
0020   @class KSelectionProxyModel kselectionproxymodel.h KSelectionProxyModel
0021 
0022   @brief A Proxy Model which presents a subset of its source model to observers.
0023 
0024   The KSelectionProxyModel is most useful as a convenience for displaying the selection in one view in
0025   another view. The selectionModel of the initial view is used to create a proxied model which is filtered
0026   based on the configuration of this class.
0027 
0028   For example, when a user clicks a mail folder in one view in an email application, the contained emails
0029   should be displayed in another view.
0030 
0031   This takes away the need for the developer to handle the selection between the views, including all the
0032   mapToSource, mapFromSource and setRootIndex calls.
0033 
0034   @code
0035   MyModel *sourceModel = new MyModel(this);
0036   QTreeView *leftView = new QTreeView(this);
0037   leftView->setModel(sourceModel);
0038 
0039   KSelectionProxyModel *selectionProxy = new KSelectionProxyModel(leftView->selectionModel(), this);
0040   selectionProxy->setSourceModel(sourceModel);
0041 
0042   QTreeView *rightView = new QTreeView(this);
0043   rightView->setModel(selectionProxy);
0044   @endcode
0045 
0046   \image html selectionproxymodelsimpleselection.png "A Selection in one view creating a model for use with another view."
0047 
0048   The KSelectionProxyModel can handle complex selections.
0049 
0050   \image html selectionproxymodelmultipleselection.png "Non-contiguous selection creating a new simple model in a second view."
0051 
0052   The contents of the secondary view depends on the selection in the primary view, and the configuration of the proxy model.
0053   See KSelectionProxyModel::setFilterBehavior for the different possible configurations.
0054 
0055   For example, if the filterBehavior is SubTrees, selecting another item in an already selected subtree has no effect.
0056 
0057   \image html selectionproxymodelmultipleselection-withdescendant.png "Selecting an item and its descendant."
0058 
0059   See the test application in tests/proxymodeltestapp to try out the valid configurations.
0060 
0061   \image html kselectionproxymodel-testapp.png "KSelectionProxyModel test application"
0062 
0063   Obviously, the KSelectionProxyModel may be used in a view, or further processed with other proxy models.
0064   See KAddressBook and AkonadiConsole in kdepim for examples which use a further KDescendantsProxyModel
0065   and QSortFilterProxyModel on top of a KSelectionProxyModel.
0066 
0067   Additionally, this class can be used to programmatically choose some items from the source model to display in the view. For example,
0068   this is how the Favourite Folder View in KMail works, and is also used in unit testing.
0069 
0070   See also: https://doc.qt.io/qt-5/model-view-programming.html#proxy-models
0071 
0072   @since 4.4
0073   @author Stephen Kelly <steveire@gmail.com>
0074 
0075 */
0076 class KITEMMODELS_EXPORT KSelectionProxyModel : public QAbstractProxyModel
0077 {
0078     Q_OBJECT
0079     Q_PROPERTY(FilterBehavior filterBehavior READ filterBehavior WRITE setFilterBehavior NOTIFY filterBehaviorChanged)
0080     Q_PROPERTY(QItemSelectionModel *selectionModel READ selectionModel WRITE setSelectionModel NOTIFY selectionModelChanged)
0081 public:
0082     /**
0083     ctor.
0084 
0085     @p selectionModel The selection model used to filter what is presented by the proxy.
0086     */
0087     // KF6: Remove the selectionModel from the constructor here.
0088     explicit KSelectionProxyModel(QItemSelectionModel *selectionModel, QObject *parent = nullptr);
0089 
0090     /**
0091      Default constructor. Allow the creation of a KSelectionProxyModel in QML
0092      code. QML will assign a parent after construction.
0093      */
0094     // KF6: Remove in favor of the constructor above.
0095     explicit KSelectionProxyModel();
0096 
0097     /**
0098     dtor
0099     */
0100     ~KSelectionProxyModel() override;
0101 
0102     /**
0103     reimp.
0104     */
0105     void setSourceModel(QAbstractItemModel *sourceModel) override;
0106 
0107     QItemSelectionModel *selectionModel() const;
0108     void setSelectionModel(QItemSelectionModel *selectionModel);
0109 
0110     enum FilterBehavior {
0111         SubTrees,
0112         SubTreeRoots,
0113         SubTreesWithoutRoots,
0114         ExactSelection,
0115         ChildrenOfExactSelection,
0116         InvalidBehavior,
0117     };
0118     Q_ENUM(FilterBehavior)
0119 
0120     /**
0121       Set the filter behaviors of this model.
0122       The filter behaviors of the model govern the content of the model based on the selection of the contained QItemSelectionModel.
0123 
0124       See kdeui/proxymodeltestapp to try out the different proxy model behaviors.
0125 
0126       The most useful behaviors are SubTrees, ExactSelection and ChildrenOfExactSelection.
0127 
0128       The default behavior is SubTrees. This means that this proxy model will contain the roots of the items in the source model.
0129       Any descendants which are also selected have no additional effect.
0130       For example if the source model is like:
0131 
0132       @verbatim
0133       (root)
0134         - A
0135         - B
0136           - C
0137           - D
0138             - E
0139               - F
0140             - G
0141         - H
0142         - I
0143           - J
0144           - K
0145           - L
0146       @endverbatim
0147 
0148       And A, B, C and D are selected, the proxy will contain:
0149 
0150       @verbatim
0151       (root)
0152         - A
0153         - B
0154           - C
0155           - D
0156             - E
0157               - F
0158             - G
0159       @endverbatim
0160 
0161       That is, selecting 'D' or 'C' if 'B' is also selected has no effect. If 'B' is de-selected, then 'C' amd 'D' become top-level items:
0162 
0163       @verbatim
0164       (root)
0165         - A
0166         - C
0167         - D
0168           - E
0169             - F
0170           - G
0171       @endverbatim
0172 
0173       This is the behavior used by KJots when rendering books.
0174 
0175       If the behavior is set to SubTreeRoots, then the children of selected indexes are not part of the model. If 'A', 'B' and 'D' are selected,
0176 
0177       @verbatim
0178       (root)
0179         - A
0180         - B
0181       @endverbatim
0182 
0183       Note that although 'D' is selected, it is not part of the proxy model, because its parent 'B' is already selected.
0184 
0185       SubTreesWithoutRoots has the effect of not making the selected items part of the model, but making their children part of the model instead. If 'A', 'B'
0186       and 'I' are selected:
0187 
0188       @verbatim
0189       (root)
0190         - C
0191         - D
0192           - E
0193             - F
0194           - G
0195         - J
0196         - K
0197         - L
0198       @endverbatim
0199 
0200       Note that 'A' has no children, so selecting it has no outward effect on the model.
0201 
0202       ChildrenOfExactSelection causes the proxy model to contain the children of the selected indexes,but further descendants are omitted.
0203       Additionally, if descendants of an already selected index are selected, their children are part of the proxy model.
0204       For example, if 'A', 'B', 'D' and 'I' are selected:
0205 
0206       @verbatim
0207       (root)
0208         - C
0209         - D
0210         - E
0211         - G
0212         - J
0213         - K
0214         - L
0215       @endverbatim
0216 
0217       This would be useful for example if showing containers (for example maildirs) in one view and their items in another. Sub-maildirs would still appear in
0218       the proxy, but could be filtered out using a QSortfilterProxyModel.
0219 
0220       The ExactSelection behavior causes the selected items to be part of the proxy model, even if their ancestors are already selected, but children of
0221       selected items are not included.
0222 
0223       Again, if 'A', 'B', 'D' and 'I' are selected:
0224 
0225       @verbatim
0226       (root)
0227         - A
0228         - B
0229         - D
0230         - I
0231       @endverbatim
0232 
0233       This is the behavior used by the Favourite Folder View in KMail.
0234 
0235     */
0236     void setFilterBehavior(FilterBehavior behavior);
0237     FilterBehavior filterBehavior() const;
0238 
0239     QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
0240     QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
0241 
0242     QItemSelection mapSelectionFromSource(const QItemSelection &selection) const override;
0243     QItemSelection mapSelectionToSource(const QItemSelection &selection) const override;
0244 
0245     Qt::ItemFlags flags(const QModelIndex &index) const override;
0246     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
0247     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
0248     QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
0249 
0250     QMimeData *mimeData(const QModelIndexList &indexes) const override;
0251     QStringList mimeTypes() const override;
0252     Qt::DropActions supportedDropActions() const override;
0253     bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
0254 
0255     bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
0256     QModelIndex index(int, int, const QModelIndex & = QModelIndex()) const override;
0257     QModelIndex parent(const QModelIndex &) const override;
0258     int columnCount(const QModelIndex & = QModelIndex()) const override;
0259 
0260     virtual QModelIndexList match(const QModelIndex &start,
0261                                   int role,
0262                                   const QVariant &value,
0263                                   int hits = 1,
0264                                   Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override;
0265 
0266 Q_SIGNALS:
0267     /**
0268       @internal
0269       Emitted before @p removeRootIndex, an index in the sourceModel is removed from
0270       the root selected indexes. This may be unrelated to rows removed from the model,
0271       depending on configuration.
0272     */
0273     void rootIndexAboutToBeRemoved(const QModelIndex &removeRootIndex, QPrivateSignal);
0274 
0275     /**
0276       @internal
0277       Emitted when @p newIndex, an index in the sourceModel is added to the root selected
0278       indexes. This may be unrelated to rows inserted to the model,
0279       depending on configuration.
0280     */
0281     void rootIndexAdded(const QModelIndex &newIndex, QPrivateSignal);
0282 
0283     /**
0284       @internal
0285       Emitted before @p selection, a selection in the sourceModel, is removed from
0286       the root selection.
0287     */
0288     void rootSelectionAboutToBeRemoved(const QItemSelection &selection, QPrivateSignal);
0289 
0290     /**
0291       @internal
0292       Emitted after @p selection, a selection in the sourceModel, is added to
0293       the root selection.
0294     */
0295     void rootSelectionAdded(const QItemSelection &selection, QPrivateSignal);
0296 
0297     void selectionModelChanged(QPrivateSignal);
0298     void filterBehaviorChanged(QPrivateSignal);
0299 
0300 protected:
0301     QList<QPersistentModelIndex> sourceRootIndexes() const;
0302 
0303 private:
0304     Q_DECLARE_PRIVATE(KSelectionProxyModel)
0305     //@cond PRIVATE
0306     std::unique_ptr<KSelectionProxyModelPrivate> const d_ptr;
0307 
0308     Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeInserted(const QModelIndex &, int, int))
0309     Q_PRIVATE_SLOT(d_func(), void sourceRowsInserted(const QModelIndex &, int, int))
0310     Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeRemoved(const QModelIndex &, int, int))
0311     Q_PRIVATE_SLOT(d_func(), void sourceRowsRemoved(const QModelIndex &, int, int))
0312     Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))
0313     Q_PRIVATE_SLOT(d_func(), void sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))
0314     Q_PRIVATE_SLOT(d_func(), void sourceModelAboutToBeReset())
0315     Q_PRIVATE_SLOT(d_func(), void sourceModelReset())
0316     Q_PRIVATE_SLOT(d_func(), void sourceLayoutAboutToBeChanged())
0317     Q_PRIVATE_SLOT(d_func(), void sourceLayoutChanged())
0318     Q_PRIVATE_SLOT(d_func(), void sourceDataChanged(const QModelIndex &, const QModelIndex &))
0319     Q_PRIVATE_SLOT(d_func(), void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected))
0320     Q_PRIVATE_SLOT(d_func(), void sourceModelDestroyed())
0321 
0322     //@endcond
0323 };
0324 
0325 #endif