1 | // Copyright (C) 2018 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qqmldelegatecomponent_p.h" |
5 | #include <QtQmlModels/private/qqmladaptormodel_p.h> |
6 | |
7 | QT_BEGIN_NAMESPACE |
8 | |
9 | /*! |
10 | \qmltype DelegateChoice |
11 | //! \instantiates QQmlDelegateChoice |
12 | \inqmlmodule Qt.labs.qmlmodels |
13 | \brief Encapsulates a delegate and when to use it. |
14 | |
15 | The DelegateChoice type wraps a delegate and defines the circumstances |
16 | in which it should be chosen. |
17 | |
18 | DelegateChoices can be nested inside a DelegateChooser. |
19 | |
20 | \sa DelegateChooser |
21 | */ |
22 | |
23 | /*! |
24 | \qmlproperty variant Qt.labs.qmlmodels::DelegateChoice::roleValue |
25 | This property holds the value used to match the role data for the role provided by \l DelegateChooser::role. |
26 | */ |
27 | QVariant QQmlDelegateChoice::roleValue() const |
28 | { |
29 | return m_value; |
30 | } |
31 | |
32 | void QQmlDelegateChoice::setRoleValue(const QVariant &value) |
33 | { |
34 | if (m_value == value) |
35 | return; |
36 | m_value = value; |
37 | emit roleValueChanged(); |
38 | emit changed(); |
39 | } |
40 | |
41 | /*! |
42 | \qmlproperty int Qt.labs.qmlmodels::DelegateChoice::row |
43 | This property holds the value used to match the row value of model elements. |
44 | With models that have only the index property (and thus only one column), this property |
45 | should be intended as an index, and set to the desired index value. |
46 | |
47 | \note Setting both row and index has undefined behavior. The two are equivalent and only |
48 | one should be used. |
49 | |
50 | \sa index |
51 | */ |
52 | |
53 | /*! |
54 | \qmlproperty int Qt.labs.qmlmodels::DelegateChoice::index |
55 | This property holds the value used to match the index value of model elements. |
56 | This is effectively an alias for \l row. |
57 | |
58 | \sa row |
59 | */ |
60 | int QQmlDelegateChoice::row() const |
61 | { |
62 | return m_row; |
63 | } |
64 | |
65 | void QQmlDelegateChoice::setRow(int r) |
66 | { |
67 | if (m_row == r) |
68 | return; |
69 | m_row = r; |
70 | emit rowChanged(); |
71 | emit indexChanged(); |
72 | emit changed(); |
73 | } |
74 | |
75 | /*! |
76 | \qmlproperty int Qt.labs.qmlmodels::DelegateChoice::column |
77 | This property holds the value used to match the column value of model elements. |
78 | */ |
79 | int QQmlDelegateChoice::column() const |
80 | { |
81 | return m_column; |
82 | } |
83 | |
84 | void QQmlDelegateChoice::setColumn(int c) |
85 | { |
86 | if (m_column == c) |
87 | return; |
88 | m_column = c; |
89 | emit columnChanged(); |
90 | emit changed(); |
91 | } |
92 | |
93 | QQmlComponent *QQmlDelegateChoice::delegate() const |
94 | { |
95 | return m_delegate; |
96 | } |
97 | |
98 | /*! |
99 | \qmlproperty Component Qt.labs.qmlmodels::DelegateChoice::delegate |
100 | This property holds the delegate to use if this choice matches the model item. |
101 | */ |
102 | void QQmlDelegateChoice::setDelegate(QQmlComponent *delegate) |
103 | { |
104 | if (m_delegate == delegate) |
105 | return; |
106 | QQmlAbstractDelegateComponent *adc = static_cast<QQmlAbstractDelegateComponent *>(m_delegate); |
107 | if (adc) |
108 | disconnect(sender: adc, signal: &QQmlAbstractDelegateComponent::delegateChanged, receiver: this, slot: &QQmlDelegateChoice::delegateChanged); |
109 | m_delegate = delegate; |
110 | adc = static_cast<QQmlAbstractDelegateComponent *>(delegate); |
111 | if (adc) |
112 | connect(sender: adc, signal: &QQmlAbstractDelegateComponent::delegateChanged, context: this, slot: &QQmlDelegateChoice::delegateChanged); |
113 | emit delegateChanged(); |
114 | emit changed(); |
115 | } |
116 | |
117 | bool QQmlDelegateChoice::match(int row, int column, const QVariant &value) const |
118 | { |
119 | if (!m_value.isValid() && m_row < 0 && m_column < 0) |
120 | return true; |
121 | |
122 | bool roleMatched = true; |
123 | if (m_value.isValid()) { |
124 | roleMatched = (value == m_value); |
125 | if (!roleMatched) { |
126 | bool valueOk = false; |
127 | bool mValueOk = false; |
128 | roleMatched = (value.toInt(ok: &valueOk) == m_value.toInt(ok: &mValueOk) && valueOk && mValueOk); |
129 | } |
130 | if (!roleMatched) |
131 | roleMatched = (value.toString() == m_value.toString()); |
132 | } |
133 | const bool rowMatched = (m_row < 0 ) ? true : m_row == row; |
134 | const bool columnMatched = (m_column < 0 ) ? true : m_column == column; |
135 | return roleMatched && rowMatched && columnMatched; |
136 | } |
137 | |
138 | /*! |
139 | \qmltype DelegateChooser |
140 | //! \instantiates QQmlDelegateChooser |
141 | \inqmlmodule Qt.labs.qmlmodels |
142 | \brief Allows a view to use different delegates for different types of items in the model. |
143 | |
144 | The DelegateChooser is a special \l Component type intended for those scenarios where a |
145 | Component is required by a view and used as a delegate. DelegateChooser encapsulates a set of \l |
146 | {DelegateChoice}s. These choices are used to determine the delegate that will be instantiated for |
147 | each item in the model. The selection of the choice is performed based on the value that a model |
148 | item has for \l role, and also based on index. |
149 | |
150 | DelegateChooser is commonly used when a view needs to display a set of delegates that are |
151 | significantly different from each other. For example, a typical phone settings view might include |
152 | toggle switches, sliders, radio buttons, and other visualizations based on the type of each |
153 | setting. In this case, DelegateChooser could provide an easy way to associate a different type of |
154 | delegate with each setting: |
155 | |
156 | \qml |
157 | import QtQuick |
158 | import QtQuick.Controls |
159 | import Qt.labs.qmlmodels |
160 | |
161 | ListView { |
162 | width: 200; height: 400 |
163 | |
164 | ListModel { |
165 | id: listModel |
166 | ListElement { type: "info"; ... } |
167 | ListElement { type: "switch"; ... } |
168 | ListElement { type: "swipe"; ... } |
169 | ListElement { type: "switch"; ... } |
170 | } |
171 | |
172 | DelegateChooser { |
173 | id: chooser |
174 | role: "type" |
175 | DelegateChoice { roleValue: "info"; ItemDelegate { ... } } |
176 | DelegateChoice { roleValue: "switch"; SwitchDelegate { ... } } |
177 | DelegateChoice { roleValue: "swipe"; SwipeDelegate { ... } } |
178 | } |
179 | |
180 | model: listModel |
181 | delegate: chooser |
182 | } |
183 | \endqml |
184 | |
185 | \note This type is intended to transparently work only with TableView and any |
186 | DelegateModel-based view. Views (including user-defined views) that aren't internally based on a |
187 | DelegateModel need to explicitly support this type of component to make it function as described. |
188 | |
189 | \sa DelegateChoice |
190 | */ |
191 | |
192 | /*! |
193 | \qmlproperty string Qt.labs.qmlmodels::DelegateChooser::role |
194 | This property holds the role or the property name used to determine the delegate for a given model item. |
195 | |
196 | \sa DelegateChoice |
197 | */ |
198 | void QQmlDelegateChooser::setRole(const QString &role) |
199 | { |
200 | if (m_role == role) |
201 | return; |
202 | m_role = role; |
203 | emit roleChanged(); |
204 | } |
205 | |
206 | /*! |
207 | \qmlproperty list<DelegateChoice> Qt.labs.qmlmodels::DelegateChooser::choices |
208 | \qmldefault |
209 | |
210 | The list of DelegateChoices for the chooser. |
211 | |
212 | The list is treated as an ordered list, where the first DelegateChoice to match |
213 | will be used be a view. |
214 | |
215 | It should not generally be necessary to refer to the \c choices property, |
216 | as it is the default property for DelegateChooser and thus all child items are |
217 | automatically assigned to this property. |
218 | */ |
219 | |
220 | QQmlListProperty<QQmlDelegateChoice> QQmlDelegateChooser::choices() |
221 | { |
222 | return QQmlListProperty<QQmlDelegateChoice>(this, nullptr, |
223 | QQmlDelegateChooser::choices_append, |
224 | QQmlDelegateChooser::choices_count, |
225 | QQmlDelegateChooser::choices_at, |
226 | QQmlDelegateChooser::choices_clear, |
227 | QQmlDelegateChooser::choices_replace, |
228 | QQmlDelegateChooser::choices_removeLast); |
229 | } |
230 | |
231 | void QQmlDelegateChooser::choices_append(QQmlListProperty<QQmlDelegateChoice> *prop, QQmlDelegateChoice *choice) |
232 | { |
233 | QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser *>(prop->object); |
234 | q->m_choices.append(t: choice); |
235 | connect(sender: choice, signal: &QQmlDelegateChoice::changed, context: q, slot: &QQmlAbstractDelegateComponent::delegateChanged); |
236 | q->delegateChanged(); |
237 | } |
238 | |
239 | qsizetype QQmlDelegateChooser::choices_count(QQmlListProperty<QQmlDelegateChoice> *prop) |
240 | { |
241 | QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser*>(prop->object); |
242 | return q->m_choices.size(); |
243 | } |
244 | |
245 | QQmlDelegateChoice *QQmlDelegateChooser::choices_at(QQmlListProperty<QQmlDelegateChoice> *prop, qsizetype index) |
246 | { |
247 | QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser*>(prop->object); |
248 | return q->m_choices.at(i: index); |
249 | } |
250 | |
251 | void QQmlDelegateChooser::choices_clear(QQmlListProperty<QQmlDelegateChoice> *prop) |
252 | { |
253 | QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser *>(prop->object); |
254 | for (QQmlDelegateChoice *choice : q->m_choices) |
255 | disconnect(sender: choice, signal: &QQmlDelegateChoice::changed, receiver: q, slot: &QQmlAbstractDelegateComponent::delegateChanged); |
256 | q->m_choices.clear(); |
257 | q->delegateChanged(); |
258 | } |
259 | |
260 | void QQmlDelegateChooser::choices_replace(QQmlListProperty<QQmlDelegateChoice> *prop, |
261 | qsizetype index, QQmlDelegateChoice *choice) |
262 | { |
263 | QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser *>(prop->object); |
264 | disconnect(sender: q->m_choices[index], signal: &QQmlDelegateChoice::changed, |
265 | receiver: q, slot: &QQmlAbstractDelegateComponent::delegateChanged); |
266 | q->m_choices[index] = choice; |
267 | connect(sender: choice, signal: &QQmlDelegateChoice::changed, context: q, |
268 | slot: &QQmlAbstractDelegateComponent::delegateChanged); |
269 | q->delegateChanged(); |
270 | } |
271 | |
272 | void QQmlDelegateChooser::choices_removeLast(QQmlListProperty<QQmlDelegateChoice> *prop) |
273 | { |
274 | QQmlDelegateChooser *q = static_cast<QQmlDelegateChooser *>(prop->object); |
275 | disconnect(sender: q->m_choices.takeLast(), signal: &QQmlDelegateChoice::changed, |
276 | receiver: q, slot: &QQmlAbstractDelegateComponent::delegateChanged); |
277 | q->delegateChanged(); |
278 | } |
279 | |
280 | QQmlComponent *QQmlDelegateChooser::delegate(QQmlAdaptorModel *adaptorModel, int row, int column) const |
281 | { |
282 | QVariant v; |
283 | if (!m_role.isNull()) |
284 | v = value(adaptorModel, row, column, role: m_role); |
285 | if (!v.isValid()) { // check if the row only has modelData, for example if the row is a QVariantMap |
286 | v = value(adaptorModel, row, column, QStringLiteral("modelData" )); |
287 | |
288 | if (v.isValid()) { |
289 | if (v.canConvert(targetType: QMetaType(QMetaType::QVariantMap))) |
290 | v = v.toMap().value(key: m_role); |
291 | else if (v.canConvert(targetType: QMetaType(QMetaType::QObjectStar))) |
292 | v = v.value<QObject*>()->property(name: m_role.toUtf8()); |
293 | } |
294 | } |
295 | |
296 | // loop through choices, finding first one that fits |
297 | for (int i = 0; i < m_choices.size(); ++i) { |
298 | const QQmlDelegateChoice *choice = m_choices.at(i); |
299 | if (choice->match(row, column, value: v)) |
300 | return choice->delegate(); |
301 | } |
302 | |
303 | return nullptr; |
304 | } |
305 | |
306 | QT_END_NAMESPACE |
307 | |
308 | #include "moc_qqmldelegatecomponent_p.cpp" |
309 | |