1/*
2 SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#ifndef KSELECTIONPROXYMODEL_H
8#define KSELECTIONPROXYMODEL_H
9
10#include <QAbstractProxyModel>
11#include <QItemSelectionModel>
12
13#include "kitemmodels_export.h"
14
15#include <memory>
16
17class KSelectionProxyModelPrivate;
18
19/**
20 @class KSelectionProxyModel kselectionproxymodel.h KSelectionProxyModel
21
22 @brief A Proxy Model which presents a subset of its source model to observers.
23
24 The KSelectionProxyModel is most useful as a convenience for displaying the selection in one view in
25 another view. The selectionModel of the initial view is used to create a proxied model which is filtered
26 based on the configuration of this class.
27
28 For example, when a user clicks a mail folder in one view in an email application, the contained emails
29 should be displayed in another view.
30
31 This takes away the need for the developer to handle the selection between the views, including all the
32 mapToSource, mapFromSource and setRootIndex calls.
33
34 @code
35 MyModel *sourceModel = new MyModel(this);
36 QTreeView *leftView = new QTreeView(this);
37 leftView->setModel(sourceModel);
38
39 KSelectionProxyModel *selectionProxy = new KSelectionProxyModel(leftView->selectionModel(), this);
40 selectionProxy->setSourceModel(sourceModel);
41
42 QTreeView *rightView = new QTreeView(this);
43 rightView->setModel(selectionProxy);
44 @endcode
45
46 \image html selectionproxymodelsimpleselection.png "A Selection in one view creating a model for use with another view."
47
48 The KSelectionProxyModel can handle complex selections.
49
50 \image html selectionproxymodelmultipleselection.png "Non-contiguous selection creating a new simple model in a second view."
51
52 The contents of the secondary view depends on the selection in the primary view, and the configuration of the proxy model.
53 See KSelectionProxyModel::setFilterBehavior for the different possible configurations.
54
55 For example, if the filterBehavior is SubTrees, selecting another item in an already selected subtree has no effect.
56
57 \image html selectionproxymodelmultipleselection-withdescendant.png "Selecting an item and its descendant."
58
59 See the test application in tests/proxymodeltestapp to try out the valid configurations.
60
61 \image html kselectionproxymodel-testapp.png "KSelectionProxyModel test application"
62
63 Obviously, the KSelectionProxyModel may be used in a view, or further processed with other proxy models.
64 See KAddressBook and AkonadiConsole in kdepim for examples which use a further KDescendantsProxyModel
65 and QSortFilterProxyModel on top of a KSelectionProxyModel.
66
67 Additionally, this class can be used to programmatically choose some items from the source model to display in the view. For example,
68 this is how the Favourite Folder View in KMail works, and is also used in unit testing.
69
70 See also: https://doc.qt.io/qt-5/model-view-programming.html#proxy-models
71
72 @since 4.4
73 @author Stephen Kelly <steveire@gmail.com>
74
75*/
76class KITEMMODELS_EXPORT KSelectionProxyModel : public QAbstractProxyModel
77{
78 Q_OBJECT
79 Q_PROPERTY(FilterBehavior filterBehavior READ filterBehavior WRITE setFilterBehavior NOTIFY filterBehaviorChanged)
80 Q_PROPERTY(QItemSelectionModel *selectionModel READ selectionModel WRITE setSelectionModel NOTIFY selectionModelChanged)
81public:
82 /**
83 ctor.
84
85 @p selectionModel The selection model used to filter what is presented by the proxy.
86 */
87 // KF6: Remove the selectionModel from the constructor here.
88 explicit KSelectionProxyModel(QItemSelectionModel *selectionModel, QObject *parent = nullptr);
89
90 /**
91 Default constructor. Allow the creation of a KSelectionProxyModel in QML
92 code. QML will assign a parent after construction.
93 */
94 // KF6: Remove in favor of the constructor above.
95 explicit KSelectionProxyModel();
96
97 /**
98 dtor
99 */
100 ~KSelectionProxyModel() override;
101
102 /**
103 reimp.
104 */
105 void setSourceModel(QAbstractItemModel *sourceModel) override;
106
107 QItemSelectionModel *selectionModel() const;
108 void setSelectionModel(QItemSelectionModel *selectionModel);
109
110 enum FilterBehavior {
111 SubTrees,
112 SubTreeRoots,
113 SubTreesWithoutRoots,
114 ExactSelection,
115 ChildrenOfExactSelection,
116 InvalidBehavior,
117 };
118 Q_ENUM(FilterBehavior)
119
120 /**
121 Set the filter behaviors of this model.
122 The filter behaviors of the model govern the content of the model based on the selection of the contained QItemSelectionModel.
123
124 See kdeui/proxymodeltestapp to try out the different proxy model behaviors.
125
126 The most useful behaviors are SubTrees, ExactSelection and ChildrenOfExactSelection.
127
128 The default behavior is SubTrees. This means that this proxy model will contain the roots of the items in the source model.
129 Any descendants which are also selected have no additional effect.
130 For example if the source model is like:
131
132 @verbatim
133 (root)
134 - A
135 - B
136 - C
137 - D
138 - E
139 - F
140 - G
141 - H
142 - I
143 - J
144 - K
145 - L
146 @endverbatim
147
148 And A, B, C and D are selected, the proxy will contain:
149
150 @verbatim
151 (root)
152 - A
153 - B
154 - C
155 - D
156 - E
157 - F
158 - G
159 @endverbatim
160
161 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:
162
163 @verbatim
164 (root)
165 - A
166 - C
167 - D
168 - E
169 - F
170 - G
171 @endverbatim
172
173 This is the behavior used by KJots when rendering books.
174
175 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,
176
177 @verbatim
178 (root)
179 - A
180 - B
181 @endverbatim
182
183 Note that although 'D' is selected, it is not part of the proxy model, because its parent 'B' is already selected.
184
185 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'
186 and 'I' are selected:
187
188 @verbatim
189 (root)
190 - C
191 - D
192 - E
193 - F
194 - G
195 - J
196 - K
197 - L
198 @endverbatim
199
200 Note that 'A' has no children, so selecting it has no outward effect on the model.
201
202 ChildrenOfExactSelection causes the proxy model to contain the children of the selected indexes,but further descendants are omitted.
203 Additionally, if descendants of an already selected index are selected, their children are part of the proxy model.
204 For example, if 'A', 'B', 'D' and 'I' are selected:
205
206 @verbatim
207 (root)
208 - C
209 - D
210 - E
211 - G
212 - J
213 - K
214 - L
215 @endverbatim
216
217 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
218 the proxy, but could be filtered out using a QSortfilterProxyModel.
219
220 The ExactSelection behavior causes the selected items to be part of the proxy model, even if their ancestors are already selected, but children of
221 selected items are not included.
222
223 Again, if 'A', 'B', 'D' and 'I' are selected:
224
225 @verbatim
226 (root)
227 - A
228 - B
229 - D
230 - I
231 @endverbatim
232
233 This is the behavior used by the Favourite Folder View in KMail.
234
235 */
236 void setFilterBehavior(FilterBehavior behavior);
237 FilterBehavior filterBehavior() const;
238
239 QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
240 QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
241
242 QItemSelection mapSelectionFromSource(const QItemSelection &selection) const override;
243 QItemSelection mapSelectionToSource(const QItemSelection &selection) const override;
244
245 Qt::ItemFlags flags(const QModelIndex &index) const override;
246 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
247 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
248 QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
249
250 QMimeData *mimeData(const QModelIndexList &indexes) const override;
251 QStringList mimeTypes() const override;
252 Qt::DropActions supportedDropActions() const override;
253 bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
254
255 bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
256 QModelIndex index(int, int, const QModelIndex & = QModelIndex()) const override;
257 QModelIndex parent(const QModelIndex &) const override;
258 int columnCount(const QModelIndex & = QModelIndex()) const override;
259
260 virtual QModelIndexList match(const QModelIndex &start,
261 int role,
262 const QVariant &value,
263 int hits = 1,
264 Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override;
265
266Q_SIGNALS:
267 /**
268 @internal
269 Emitted before @p removeRootIndex, an index in the sourceModel is removed from
270 the root selected indexes. This may be unrelated to rows removed from the model,
271 depending on configuration.
272 */
273 void rootIndexAboutToBeRemoved(const QModelIndex &removeRootIndex, QPrivateSignal);
274
275 /**
276 @internal
277 Emitted when @p newIndex, an index in the sourceModel is added to the root selected
278 indexes. This may be unrelated to rows inserted to the model,
279 depending on configuration.
280 */
281 void rootIndexAdded(const QModelIndex &newIndex, QPrivateSignal);
282
283 /**
284 @internal
285 Emitted before @p selection, a selection in the sourceModel, is removed from
286 the root selection.
287 */
288 void rootSelectionAboutToBeRemoved(const QItemSelection &selection, QPrivateSignal);
289
290 /**
291 @internal
292 Emitted after @p selection, a selection in the sourceModel, is added to
293 the root selection.
294 */
295 void rootSelectionAdded(const QItemSelection &selection, QPrivateSignal);
296
297 void selectionModelChanged(QPrivateSignal);
298 void filterBehaviorChanged(QPrivateSignal);
299
300protected:
301 QList<QPersistentModelIndex> sourceRootIndexes() const;
302
303private:
304 Q_DECLARE_PRIVATE(KSelectionProxyModel)
305 //@cond PRIVATE
306 std::unique_ptr<KSelectionProxyModelPrivate> const d_ptr;
307
308 Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeInserted(const QModelIndex &, int, int))
309 Q_PRIVATE_SLOT(d_func(), void sourceRowsInserted(const QModelIndex &, int, int))
310 Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeRemoved(const QModelIndex &, int, int))
311 Q_PRIVATE_SLOT(d_func(), void sourceRowsRemoved(const QModelIndex &, int, int))
312 Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int))
313 Q_PRIVATE_SLOT(d_func(), void sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int))
314 Q_PRIVATE_SLOT(d_func(), void sourceModelAboutToBeReset())
315 Q_PRIVATE_SLOT(d_func(), void sourceModelReset())
316 Q_PRIVATE_SLOT(d_func(), void sourceLayoutAboutToBeChanged())
317 Q_PRIVATE_SLOT(d_func(), void sourceLayoutChanged())
318 Q_PRIVATE_SLOT(d_func(), void sourceDataChanged(const QModelIndex &, const QModelIndex &))
319 Q_PRIVATE_SLOT(d_func(), void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected))
320 Q_PRIVATE_SLOT(d_func(), void sourceModelDestroyed())
321
322 //@endcond
323};
324
325#endif
326

source code of kitemmodels/src/core/kselectionproxymodel.h