1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2020 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <QtQuickTemplates2/private/qquickheaderview_p_p.h> |
41 | #include <algorithm> |
42 | |
43 | /*! |
44 | \qmltype HorizontalHeaderView |
45 | \inqmlmodule QtQuick.Controls |
46 | \ingroup qtquickcontrols2-containers |
47 | \inherits TableView |
48 | \brief Provides a horizontal header view to accompany a \l TableView. |
49 | |
50 | A HorizontalHeaderView provides labeling of the columns of a \l TableView. |
51 | To add a horizontal header to a TableView, bind the |
52 | \l {HorizontalHeaderView::syncView} {syncView} property to the TableView: |
53 | |
54 | \snippet qtquickcontrols2-headerview-simple.qml horizontal |
55 | |
56 | The header displays data from the {syncView}'s model by default, but can |
57 | also have its own model. If the model is a QAbstractTableModel, then |
58 | the header will display the model's horizontal headerData(); otherwise, |
59 | the model's data(). |
60 | */ |
61 | |
62 | /*! |
63 | \qmltype VerticalHeaderView |
64 | \inqmlmodule QtQuick.Controls |
65 | \ingroup qtquickcontrols2-containers |
66 | \inherits TableView |
67 | \brief Provides a vertical header view to accompany a \l TableView. |
68 | |
69 | A VerticalHeaderView provides labeling of the rows of a \l TableView. |
70 | To add a vertical header to a TableView, bind the |
71 | \l {VerticalHeaderView::syncView} {syncView} property to the TableView: |
72 | |
73 | \snippet qtquickcontrols2-headerview-simple.qml vertical |
74 | |
75 | The header displays data from the {syncView}'s model by default, but can |
76 | also have its own model. If the model is a QAbstractTableModel, then |
77 | the header will display the model's vertical headerData(); otherwise, |
78 | the model's data(). |
79 | */ |
80 | |
81 | /*! |
82 | \qmlproperty TableView QtQuick::HorizontalHeaderView::syncView |
83 | |
84 | This property holds the TableView to synchronize with. |
85 | |
86 | Once this property is bound to another TableView, both header and table |
87 | will synchronize with regard to column widths, column spacing, and flicking |
88 | horizontally. |
89 | |
90 | If the \l model is not explicitly set, then the header will use the syncView's |
91 | model to label the columns. |
92 | |
93 | \sa model TableView |
94 | */ |
95 | |
96 | /*! |
97 | \qmlproperty TableView QtQuick::VerticalHeaderView::syncView |
98 | |
99 | This property holds the TableView to synchronize with. |
100 | |
101 | Once this property is bound to another TableView, both header and table |
102 | will synchronize with regard to row heights, row spacing, and flicking |
103 | vertically. |
104 | |
105 | If the \l model is not explicitly set, then the header will use the syncView's |
106 | model to label the rows. |
107 | |
108 | \sa model TableView |
109 | */ |
110 | |
111 | /*! |
112 | \qmlproperty QVariant QtQuick::HorizontalHeaderView::model |
113 | |
114 | This property holds the model providing data for the horizontal header view. |
115 | |
116 | When model is not explicitly set, the header will use the syncView's |
117 | model once syncView is set. |
118 | |
119 | If model is a QAbstractTableModel, its horizontal headerData() will |
120 | be accessed. |
121 | |
122 | If model is a QAbstractItemModel other than QAbstractTableModel, model's data() |
123 | will be accessed. |
124 | |
125 | Otherwise, the behavior is same as setting TableView::model. |
126 | |
127 | \sa TableView {TableView::model} {model} QAbstractTableModel |
128 | */ |
129 | |
130 | /*! |
131 | \qmlproperty QVariant QtQuick::VerticalHeaderView::model |
132 | |
133 | This property holds the model providing data for the vertical header view. |
134 | |
135 | When model is not explicitly set, it will be synchronized with syncView's model |
136 | once syncView is set. |
137 | |
138 | If model is a QAbstractTableModel, its vertical headerData() will |
139 | be accessed. |
140 | |
141 | If model is a QAbstractItemModel other than QAbstractTableModel, model's data() |
142 | will be accessed. |
143 | |
144 | Otherwise, the behavior is same as setting TableView::model. |
145 | |
146 | \sa TableView {TableView::model} {model} QAbstractTableModel |
147 | */ |
148 | |
149 | /*! |
150 | \qmlproperty QString QtQuick::HorizontalHeaderView::textRole |
151 | |
152 | This property holds the model role used to display text in each header cell. |
153 | |
154 | When the model has multiple roles, textRole can be set to determine which |
155 | role should be displayed. |
156 | |
157 | If model is a QAbstractItemModel then it will default to "display"; otherwise |
158 | it is empty. |
159 | |
160 | \sa QAbstractItemModel::roleNames() |
161 | */ |
162 | |
163 | /*! |
164 | \qmlproperty QString QtQuick::VerticalHeaderView::textRole |
165 | |
166 | This property holds the model role used to display text in each header cell. |
167 | |
168 | When the model has multiple roles, textRole can be set to determine which |
169 | role should be displayed. |
170 | |
171 | If model is a QAbstractItemModel then it will default to "display"; otherwise |
172 | it is empty. |
173 | |
174 | \sa QAbstractItemModel::roleNames() |
175 | */ |
176 | |
177 | QT_BEGIN_NAMESPACE |
178 | |
179 | QQuickHeaderViewBasePrivate::() |
180 | : QQuickTableViewPrivate() |
181 | { |
182 | } |
183 | |
184 | QQuickHeaderViewBasePrivate::() |
185 | { |
186 | } |
187 | |
188 | const QPointer<QQuickItem> QQuickHeaderViewBasePrivate::(int row, int col) const |
189 | { |
190 | return loadedTableItem(cell: QPoint(col, row))->item; |
191 | } |
192 | |
193 | QVariant QQuickHeaderViewBasePrivate::() const |
194 | { |
195 | if (auto model = m_headerDataProxyModel.sourceModel()) |
196 | return QVariant::fromValue(value: model.data()); |
197 | if (auto model = m_transposeProxyModel.sourceModel()) |
198 | return QVariant::fromValue(value: model); |
199 | return QQuickTableViewPrivate::modelImpl(); |
200 | } |
201 | |
202 | template <typename P, typename M> |
203 | inline bool (QQuickHeaderViewBase *const q, P &proxyModel, M *model) |
204 | { |
205 | if (model) { |
206 | if (model == proxyModel.sourceModel()) |
207 | return true; |
208 | proxyModel.setSourceModel(model); |
209 | const auto &modelVariant = QVariant::fromValue(std::addressof(proxyModel)); |
210 | bool isProxyModelChanged = (modelVariant != QQuickTableViewPrivate::get(q)->QQuickTableViewPrivate::modelImpl()); |
211 | QQuickTableViewPrivate::get(q)->QQuickTableViewPrivate::setModelImpl(modelVariant); |
212 | //Necessary, since TableView's assigned model not changed, but proxy's source changed |
213 | if (!isProxyModelChanged) |
214 | emit q->modelChanged(); |
215 | return true; |
216 | } |
217 | proxyModel.setSourceModel(nullptr); |
218 | return false; |
219 | } |
220 | |
221 | void QQuickHeaderViewBasePrivate::(const QVariant &newModel) |
222 | { |
223 | Q_Q(QQuickHeaderViewBase); |
224 | m_modelExplicitlySetByUser = true; |
225 | // Case 1: newModel is QAbstractTableModel |
226 | if (proxyModelSetter(q, proxyModel&: m_headerDataProxyModel, model: newModel.value<QAbstractTableModel *>())) |
227 | return; |
228 | // Case 2: newModel is QAbstractItemModel but not QAbstractTableModel |
229 | if (orientation() == Qt::Horizontal |
230 | && proxyModelSetter(q, proxyModel&: m_transposeProxyModel, model: newModel.value<QAbstractItemModel *>())) |
231 | return; |
232 | |
233 | QQuickTableViewPrivate::setModelImpl(newModel); |
234 | } |
235 | |
236 | void QQuickHeaderViewBasePrivate::() |
237 | { |
238 | Q_Q(QQuickHeaderViewBase); |
239 | |
240 | if (assignedSyncView && !m_modelExplicitlySetByUser) { |
241 | auto newModel = assignedSyncView->model(); |
242 | if (auto m = newModel.value<QAbstractItemModel *>()) |
243 | proxyModelSetter(q, proxyModel&: m_headerDataProxyModel, model: m); |
244 | } |
245 | |
246 | QQuickTableViewPrivate::syncModel(); |
247 | |
248 | isTransposed = false; |
249 | const auto aim = model->abstractItemModel(); |
250 | if (orientation() == Qt::Horizontal) { |
251 | // For models that are just a list or a number, and especially not a |
252 | // table, we transpose the view when the orientation is horizontal. |
253 | // The model (list) will then be laid out horizontally rather than |
254 | // vertically, which is the otherwise the default. |
255 | isTransposed = !aim || aim->columnCount() == 1; |
256 | } |
257 | if (m_textRole.isEmpty() && aim) |
258 | m_textRole = QLatin1String("display" ); |
259 | } |
260 | |
261 | void QQuickHeaderViewBasePrivate::() |
262 | { |
263 | Q_Q(QQuickHeaderViewBase); |
264 | if (assignedSyncDirection != orientation()) { |
265 | qmlWarning(me: q_func()) << "Setting syncDirection other than Qt::" |
266 | << QVariant::fromValue(value: orientation()).toString() |
267 | << " is invalid." ; |
268 | assignedSyncDirection = orientation(); |
269 | } |
270 | if (assignedSyncView) { |
271 | QBoolBlocker fixupGuard(inUpdateContentSize, true); |
272 | if (orientation() == Qt::Horizontal) { |
273 | q->setLeftMargin(assignedSyncView->leftMargin()); |
274 | q->setRightMargin(assignedSyncView->rightMargin()); |
275 | } else { |
276 | q->setTopMargin(assignedSyncView->topMargin()); |
277 | q->setBottomMargin(assignedSyncView->bottomMargin()); |
278 | } |
279 | } |
280 | QQuickTableViewPrivate::syncSyncView(); |
281 | } |
282 | |
283 | QQuickHeaderViewBase::(Qt::Orientation orient, QQuickItem *parent) |
284 | : QQuickTableView(*(new QQuickHeaderViewBasePrivate), parent) |
285 | { |
286 | d_func()->setOrientation(orient); |
287 | setSyncDirection(orient); |
288 | } |
289 | |
290 | QQuickHeaderViewBase::(QQuickHeaderViewBasePrivate &dd, QQuickItem *parent) |
291 | : QQuickTableView(dd, parent) |
292 | { |
293 | } |
294 | |
295 | QQuickHeaderViewBase::() |
296 | { |
297 | } |
298 | |
299 | QString QQuickHeaderViewBase::() const |
300 | { |
301 | Q_D(const QQuickHeaderViewBase); |
302 | return d->m_textRole; |
303 | } |
304 | |
305 | void QQuickHeaderViewBase::(const QString &role) |
306 | { |
307 | Q_D(QQuickHeaderViewBase); |
308 | if (d->m_textRole == role) |
309 | return; |
310 | |
311 | d->m_textRole = role; |
312 | emit textRoleChanged(); |
313 | } |
314 | |
315 | Qt::Orientation QQuickHeaderViewBasePrivate::() const |
316 | { |
317 | return m_headerDataProxyModel.orientation(); |
318 | } |
319 | |
320 | void QQuickHeaderViewBasePrivate::(Qt::Orientation orientation) |
321 | { |
322 | if (QQuickHeaderViewBasePrivate::orientation() == orientation) |
323 | return; |
324 | m_headerDataProxyModel.setOrientation(orientation); |
325 | } |
326 | |
327 | QQuickVerticalHeaderView::(QQuickVerticalHeaderViewPrivate &dd, QQuickItem *parent) |
328 | : QQuickHeaderViewBase(dd, parent) |
329 | { |
330 | } |
331 | |
332 | /*! \internal |
333 | \class QHeaderDataProxyModel |
334 | \brief |
335 | QHeaderDataProxyModel is a proxy AbstractItemModel type that maps |
336 | source model's headerData() to correspondent data() |
337 | */ |
338 | QHeaderDataProxyModel::(QObject *parent) |
339 | : QAbstractItemModel(parent) |
340 | { |
341 | } |
342 | |
343 | QHeaderDataProxyModel::() = default; |
344 | |
345 | void QHeaderDataProxyModel::(QAbstractItemModel *newSourceModel) |
346 | { |
347 | if (m_model == newSourceModel) |
348 | return; |
349 | beginResetModel(); |
350 | disconnectFromModel(); |
351 | m_model = newSourceModel; |
352 | connectToModel(); |
353 | endResetModel(); |
354 | } |
355 | |
356 | QModelIndex QHeaderDataProxyModel::(int row, int column, const QModelIndex &parent) const |
357 | { |
358 | return hasIndex(row, column, parent) ? createIndex(arow: row, acolumn: column) : QModelIndex(); |
359 | } |
360 | |
361 | QModelIndex QHeaderDataProxyModel::(const QModelIndex &child) const |
362 | { |
363 | Q_UNUSED(child) |
364 | return QModelIndex(); |
365 | } |
366 | |
367 | QModelIndex QHeaderDataProxyModel::(int row, int column, const QModelIndex &) const |
368 | { |
369 | return index(row, column); |
370 | } |
371 | |
372 | int QHeaderDataProxyModel::(const QModelIndex &parent) const |
373 | { |
374 | if (parent.isValid()) |
375 | return 0; |
376 | return m_model.isNull() ? -1 : (m_orientation == Qt::Horizontal ? 1 : m_model->rowCount(parent)); |
377 | } |
378 | |
379 | int QHeaderDataProxyModel::columnCount(const QModelIndex &parent) const |
380 | { |
381 | if (parent.isValid()) |
382 | return 0; |
383 | return m_model.isNull() ? -1 : (m_orientation == Qt::Vertical ? 1 : m_model->columnCount(parent)); |
384 | } |
385 | |
386 | QVariant QHeaderDataProxyModel::(const QModelIndex &index, int role) const |
387 | { |
388 | if (m_model.isNull()) |
389 | return QVariant(); |
390 | if (!hasIndex(row: index.row(), column: index.column())) |
391 | return QModelIndex(); |
392 | auto section = m_orientation == Qt::Vertical ? index.row() : index.column(); |
393 | return m_model->headerData(section, orientation: m_orientation, role); |
394 | } |
395 | |
396 | bool QHeaderDataProxyModel::(const QModelIndex &index, const QVariant &value, int role) |
397 | { |
398 | if (!hasIndex(row: index.row(), column: index.column())) |
399 | return false; |
400 | auto section = m_orientation == Qt::Vertical ? index.row() : index.column(); |
401 | auto ret = m_model->setHeaderData(section, orientation: m_orientation, value, role); |
402 | emit dataChanged(topLeft: index, bottomRight: index, roles: { role }); |
403 | return ret; |
404 | } |
405 | |
406 | bool QHeaderDataProxyModel::(const QModelIndex &parent) const |
407 | { |
408 | if (!parent.isValid()) |
409 | return rowCount(parent) > 0 && columnCount(parent) > 0; |
410 | return false; |
411 | } |
412 | |
413 | QVariant QHeaderDataProxyModel::() const |
414 | { |
415 | return QVariant::fromValue(value: static_cast<QObject *>(const_cast<QHeaderDataProxyModel *>(this))); |
416 | } |
417 | |
418 | void QHeaderDataProxyModel::(Qt::Orientation o) |
419 | { |
420 | if (o == m_orientation) |
421 | return; |
422 | beginResetModel(); |
423 | m_orientation = o; |
424 | endResetModel(); |
425 | } |
426 | |
427 | Qt::Orientation QHeaderDataProxyModel::() const |
428 | { |
429 | return m_orientation; |
430 | } |
431 | |
432 | QPointer<QAbstractItemModel> QHeaderDataProxyModel::() const |
433 | { |
434 | return m_model; |
435 | } |
436 | |
437 | void QHeaderDataProxyModel::() |
438 | { |
439 | if (m_model.isNull()) |
440 | return; |
441 | connect(sender: m_model, signal: &QAbstractItemModel::headerDataChanged, |
442 | slot: [this](Qt::Orientation orient, int first, int last) { |
443 | if (orient != orientation()) |
444 | return; |
445 | if (orient == Qt::Horizontal) { |
446 | emit dataChanged(topLeft: createIndex(arow: 0, acolumn: first), bottomRight: createIndex(arow: 0, acolumn: last)); |
447 | } else { |
448 | emit dataChanged(topLeft: createIndex(arow: first, acolumn: 0), bottomRight: createIndex(arow: last, acolumn: 0)); |
449 | } |
450 | }); |
451 | connect(sender: m_model, signal: &QAbstractItemModel::modelAboutToBeReset, |
452 | receiver: this, slot: &QHeaderDataProxyModel::modelAboutToBeReset, type: Qt::UniqueConnection); |
453 | connect(sender: m_model, signal: &QAbstractItemModel::modelReset, |
454 | receiver: this, slot: &QHeaderDataProxyModel::modelReset, type: Qt::UniqueConnection); |
455 | connect(sender: m_model, signal: &QAbstractItemModel::rowsAboutToBeMoved, |
456 | receiver: this, slot: &QHeaderDataProxyModel::rowsAboutToBeMoved, type: Qt::UniqueConnection); |
457 | connect(sender: m_model, signal: &QAbstractItemModel::rowsMoved, |
458 | receiver: this, slot: &QHeaderDataProxyModel::rowsMoved, type: Qt::UniqueConnection); |
459 | connect(sender: m_model, signal: &QAbstractItemModel::rowsAboutToBeInserted, |
460 | receiver: this, slot: &QHeaderDataProxyModel::rowsAboutToBeInserted, type: Qt::UniqueConnection); |
461 | connect(sender: m_model, signal: &QAbstractItemModel::rowsInserted, |
462 | receiver: this, slot: &QHeaderDataProxyModel::rowsInserted, type: Qt::UniqueConnection); |
463 | connect(sender: m_model, signal: &QAbstractItemModel::rowsAboutToBeRemoved, |
464 | receiver: this, slot: &QHeaderDataProxyModel::rowsAboutToBeRemoved, type: Qt::UniqueConnection); |
465 | connect(sender: m_model, signal: &QAbstractItemModel::rowsRemoved, |
466 | receiver: this, slot: &QHeaderDataProxyModel::rowsRemoved, type: Qt::UniqueConnection); |
467 | connect(sender: m_model, signal: &QAbstractItemModel::columnsAboutToBeMoved, |
468 | receiver: this, slot: &QHeaderDataProxyModel::columnsAboutToBeMoved, type: Qt::UniqueConnection); |
469 | connect(sender: m_model, signal: &QAbstractItemModel::columnsMoved, |
470 | receiver: this, slot: &QHeaderDataProxyModel::columnsMoved, type: Qt::UniqueConnection); |
471 | connect(sender: m_model, signal: &QAbstractItemModel::columnsAboutToBeInserted, |
472 | receiver: this, slot: &QHeaderDataProxyModel::columnsAboutToBeInserted, type: Qt::UniqueConnection); |
473 | connect(sender: m_model, signal: &QAbstractItemModel::columnsInserted, |
474 | receiver: this, slot: &QHeaderDataProxyModel::columnsInserted, type: Qt::UniqueConnection); |
475 | connect(sender: m_model, signal: &QAbstractItemModel::columnsAboutToBeRemoved, |
476 | receiver: this, slot: &QHeaderDataProxyModel::columnsAboutToBeRemoved, type: Qt::UniqueConnection); |
477 | connect(sender: m_model, signal: &QAbstractItemModel::columnsRemoved, |
478 | receiver: this, slot: &QHeaderDataProxyModel::columnsRemoved, type: Qt::UniqueConnection); |
479 | connect(sender: m_model, signal: &QAbstractItemModel::layoutAboutToBeChanged, |
480 | receiver: this, slot: &QHeaderDataProxyModel::layoutAboutToBeChanged, type: Qt::UniqueConnection); |
481 | connect(sender: m_model, signal: &QAbstractItemModel::layoutChanged, |
482 | receiver: this, slot: &QHeaderDataProxyModel::layoutChanged, type: Qt::UniqueConnection); |
483 | } |
484 | |
485 | void QHeaderDataProxyModel::() |
486 | { |
487 | if (m_model.isNull()) |
488 | return; |
489 | m_model->disconnect(receiver: this); |
490 | } |
491 | |
492 | QQuickHorizontalHeaderView::(QQuickItem *parent) |
493 | : QQuickHeaderViewBase(Qt::Horizontal, parent) |
494 | { |
495 | setFlickableDirection(FlickableDirection::HorizontalFlick); |
496 | } |
497 | |
498 | QQuickHorizontalHeaderView::() |
499 | { |
500 | } |
501 | |
502 | QQuickVerticalHeaderView::(QQuickItem *parent) |
503 | : QQuickHeaderViewBase(Qt::Vertical, parent) |
504 | { |
505 | setFlickableDirection(FlickableDirection::VerticalFlick); |
506 | } |
507 | |
508 | QQuickVerticalHeaderView::() |
509 | { |
510 | } |
511 | |
512 | QQuickHorizontalHeaderViewPrivate::() = default; |
513 | |
514 | QQuickHorizontalHeaderViewPrivate::() = default; |
515 | |
516 | QQuickVerticalHeaderViewPrivate::() = default; |
517 | |
518 | QQuickVerticalHeaderViewPrivate::() = default; |
519 | |
520 | QT_END_NAMESPACE |
521 | |