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
21 \inmodule KItemModels
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 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 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 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 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: \l {https://doc.qt.io/qt-6/model-view-programming.html#proxy-models}{Proxy Models in the Qt Documentation}
71
72 \since 4.4
73
74*/
75class KITEMMODELS_EXPORT KSelectionProxyModel : public QAbstractProxyModel
76{
77 Q_OBJECT
78
79 /*!
80 * \property KSelectionProxyModel::filterBehavior
81 */
82 Q_PROPERTY(FilterBehavior filterBehavior READ filterBehavior WRITE setFilterBehavior NOTIFY filterBehaviorChanged)
83
84 /*!
85 * \property KSelectionProxyModel::selectionModel
86 */
87 Q_PROPERTY(QItemSelectionModel *selectionModel READ selectionModel WRITE setSelectionModel NOTIFY selectionModelChanged)
88public:
89 // KF6: Remove the selectionModel from the constructor here.
90 /*!
91 Constructor.
92
93 \a selectionModel The selection model used to filter what is presented by the proxy.
94 */
95 explicit KSelectionProxyModel(QItemSelectionModel *selectionModel, QObject *parent = nullptr);
96
97 // KF6: Remove in favor of the constructor above.
98 /*!
99 Default constructor. Allow the creation of a KSelectionProxyModel in QML
100 code. QML will assign a parent after construction.
101 */
102 explicit KSelectionProxyModel();
103
104 ~KSelectionProxyModel() override;
105
106 void setSourceModel(QAbstractItemModel *sourceModel) override;
107
108 QItemSelectionModel *selectionModel() const;
109 void setSelectionModel(QItemSelectionModel *selectionModel);
110
111 enum FilterBehavior {
112 SubTrees,
113 SubTreeRoots,
114 SubTreesWithoutRoots,
115 ExactSelection,
116 ChildrenOfExactSelection,
117 InvalidBehavior,
118 };
119 Q_ENUM(FilterBehavior)
120
121 /*!
122 Set the filter behaviors of this model.
123 The filter behaviors of the model govern the content of the model based on the selection of the contained QItemSelectionModel.
124
125 See kdeui/proxymodeltestapp to try out the different proxy model behaviors.
126
127 The most useful behaviors are SubTrees, ExactSelection and ChildrenOfExactSelection.
128
129 The default behavior is SubTrees. This means that this proxy model will contain the roots of the items in the source model.
130 Any descendants which are also selected have no additional effect.
131 For example if the source model is like:
132
133 \code
134 (root)
135 - A
136 - B
137 - C
138 - D
139 - E
140 - F
141 - G
142 - H
143 - I
144 - J
145 - K
146 - L
147 \endcode
148
149 And A, B, C and D are selected, the proxy will contain:
150
151 \code
152 (root)
153 - A
154 - B
155 - C
156 - D
157 - E
158 - F
159 - G
160 \endcode
161
162 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:
163
164 \code
165 (root)
166 - A
167 - C
168 - D
169 - E
170 - F
171 - G
172 \endcode
173
174 This is the behavior used by KJots when rendering books.
175
176 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,
177
178 \code
179 (root)
180 - A
181 - B
182 \endcode
183
184 Note that although 'D' is selected, it is not part of the proxy model, because its parent 'B' is already selected.
185
186 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'
187 and 'I' are selected:
188
189 \code
190 (root)
191 - C
192 - D
193 - E
194 - F
195 - G
196 - J
197 - K
198 - L
199 \endcode
200
201 Note that 'A' has no children, so selecting it has no outward effect on the model.
202
203 ChildrenOfExactSelection causes the proxy model to contain the children of the selected indexes,but further descendants are omitted.
204 Additionally, if descendants of an already selected index are selected, their children are part of the proxy model.
205 For example, if 'A', 'B', 'D' and 'I' are selected:
206
207 \code
208 (root)
209 - C
210 - D
211 - E
212 - G
213 - J
214 - K
215 - L
216 \endcode
217
218 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
219 the proxy, but could be filtered out using a QSortfilterProxyModel.
220
221 The ExactSelection behavior causes the selected items to be part of the proxy model, even if their ancestors are already selected, but children of
222 selected items are not included.
223
224 Again, if 'A', 'B', 'D' and 'I' are selected:
225
226 \code
227 (root)
228 - A
229 - B
230 - D
231 - I
232 \endcode
233
234 This is the behavior used by the Favourite Folder View in KMail.
235
236 */
237 void setFilterBehavior(FilterBehavior behavior);
238 FilterBehavior filterBehavior() const;
239
240 QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
241 QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
242
243 QItemSelection mapSelectionFromSource(const QItemSelection &selection) const override;
244 QItemSelection mapSelectionToSource(const QItemSelection &selection) const override;
245
246 Qt::ItemFlags flags(const QModelIndex &index) const override;
247 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
248 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
249 QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
250
251 QMimeData *mimeData(const QModelIndexList &indexes) const override;
252 QStringList mimeTypes() const override;
253 Qt::DropActions supportedDropActions() const override;
254 bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
255
256 bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
257 QModelIndex index(int, int, const QModelIndex & = QModelIndex()) const override;
258 QModelIndex parent(const QModelIndex &) const override;
259 int columnCount(const QModelIndex & = QModelIndex()) const override;
260
261 virtual QModelIndexList match(const QModelIndex &start,
262 int role,
263 const QVariant &value,
264 int hits = 1,
265 Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const override;
266
267Q_SIGNALS:
268 /*!
269 \internal
270 Emitted before \a removeRootIndex, an index in the sourceModel is removed from
271 the root selected indexes. This may be unrelated to rows removed from the model,
272 depending on configuration.
273 */
274 void rootIndexAboutToBeRemoved(const QModelIndex &removeRootIndex, QPrivateSignal);
275
276 /*!
277 \internal
278 Emitted when \a newIndex, an index in the sourceModel is added to the root selected
279 indexes. This may be unrelated to rows inserted to the model,
280 depending on configuration.
281 */
282 void rootIndexAdded(const QModelIndex &newIndex, QPrivateSignal);
283
284 /*!
285 \internal
286 Emitted before \a selection, a selection in the sourceModel, is removed from
287 the root selection.
288 */
289 void rootSelectionAboutToBeRemoved(const QItemSelection &selection, QPrivateSignal);
290
291 /*!
292 \internal
293 Emitted after \a selection, a selection in the sourceModel, is added to
294 the root selection.
295 */
296 void rootSelectionAdded(const QItemSelection &selection, QPrivateSignal);
297
298 void selectionModelChanged(QPrivateSignal);
299 void filterBehaviorChanged(QPrivateSignal);
300
301protected:
302 QList<QPersistentModelIndex> sourceRootIndexes() const;
303
304private:
305 Q_DECLARE_PRIVATE(KSelectionProxyModel)
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
323#endif
324

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