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 |
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 | */ |
75 | class 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) |
88 | public: |
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 (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 | |
267 | Q_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 | |
301 | protected: |
302 | QList<QPersistentModelIndex> sourceRootIndexes() const; |
303 | |
304 | private: |
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 | |