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 | |
17 | class 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 | */ |
76 | class 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) |
81 | public: |
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 (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 | |
266 | Q_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 | |
300 | protected: |
301 | QList<QPersistentModelIndex> sourceRootIndexes() const; |
302 | |
303 | private: |
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 | |