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
177QT_BEGIN_NAMESPACE
178
179QQuickHeaderViewBasePrivate::QQuickHeaderViewBasePrivate()
180 : QQuickTableViewPrivate()
181{
182}
183
184QQuickHeaderViewBasePrivate::~QQuickHeaderViewBasePrivate()
185{
186}
187
188const QPointer<QQuickItem> QQuickHeaderViewBasePrivate::delegateItemAt(int row, int col) const
189{
190 return loadedTableItem(cell: QPoint(col, row))->item;
191}
192
193QVariant QQuickHeaderViewBasePrivate::modelImpl() 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
202template <typename P, typename M>
203inline bool proxyModelSetter(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
221void QQuickHeaderViewBasePrivate::setModelImpl(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
236void QQuickHeaderViewBasePrivate::syncModel()
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
261void QQuickHeaderViewBasePrivate::syncSyncView()
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
283QQuickHeaderViewBase::QQuickHeaderViewBase(Qt::Orientation orient, QQuickItem *parent)
284 : QQuickTableView(*(new QQuickHeaderViewBasePrivate), parent)
285{
286 d_func()->setOrientation(orient);
287 setSyncDirection(orient);
288}
289
290QQuickHeaderViewBase::QQuickHeaderViewBase(QQuickHeaderViewBasePrivate &dd, QQuickItem *parent)
291 : QQuickTableView(dd, parent)
292{
293}
294
295QQuickHeaderViewBase::~QQuickHeaderViewBase()
296{
297}
298
299QString QQuickHeaderViewBase::textRole() const
300{
301 Q_D(const QQuickHeaderViewBase);
302 return d->m_textRole;
303}
304
305void QQuickHeaderViewBase::setTextRole(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
315Qt::Orientation QQuickHeaderViewBasePrivate::orientation() const
316{
317 return m_headerDataProxyModel.orientation();
318}
319
320void QQuickHeaderViewBasePrivate::setOrientation(Qt::Orientation orientation)
321{
322 if (QQuickHeaderViewBasePrivate::orientation() == orientation)
323 return;
324 m_headerDataProxyModel.setOrientation(orientation);
325}
326
327QQuickVerticalHeaderView::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 */
338QHeaderDataProxyModel::QHeaderDataProxyModel(QObject *parent)
339 : QAbstractItemModel(parent)
340{
341}
342
343QHeaderDataProxyModel::~QHeaderDataProxyModel() = default;
344
345void QHeaderDataProxyModel::setSourceModel(QAbstractItemModel *newSourceModel)
346{
347 if (m_model == newSourceModel)
348 return;
349 beginResetModel();
350 disconnectFromModel();
351 m_model = newSourceModel;
352 connectToModel();
353 endResetModel();
354}
355
356QModelIndex QHeaderDataProxyModel::index(int row, int column, const QModelIndex &parent) const
357{
358 return hasIndex(row, column, parent) ? createIndex(arow: row, acolumn: column) : QModelIndex();
359}
360
361QModelIndex QHeaderDataProxyModel::parent(const QModelIndex &child) const
362{
363 Q_UNUSED(child)
364 return QModelIndex();
365}
366
367QModelIndex QHeaderDataProxyModel::sibling(int row, int column, const QModelIndex &) const
368{
369 return index(row, column);
370}
371
372int QHeaderDataProxyModel::rowCount(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
379int 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
386QVariant QHeaderDataProxyModel::data(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
396bool QHeaderDataProxyModel::setData(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
406bool QHeaderDataProxyModel::hasChildren(const QModelIndex &parent) const
407{
408 if (!parent.isValid())
409 return rowCount(parent) > 0 && columnCount(parent) > 0;
410 return false;
411}
412
413QVariant QHeaderDataProxyModel::variantValue() const
414{
415 return QVariant::fromValue(value: static_cast<QObject *>(const_cast<QHeaderDataProxyModel *>(this)));
416}
417
418void QHeaderDataProxyModel::setOrientation(Qt::Orientation o)
419{
420 if (o == m_orientation)
421 return;
422 beginResetModel();
423 m_orientation = o;
424 endResetModel();
425}
426
427Qt::Orientation QHeaderDataProxyModel::orientation() const
428{
429 return m_orientation;
430}
431
432QPointer<QAbstractItemModel> QHeaderDataProxyModel::sourceModel() const
433{
434 return m_model;
435}
436
437void QHeaderDataProxyModel::connectToModel()
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
485void QHeaderDataProxyModel::disconnectFromModel()
486{
487 if (m_model.isNull())
488 return;
489 m_model->disconnect(receiver: this);
490}
491
492QQuickHorizontalHeaderView::QQuickHorizontalHeaderView(QQuickItem *parent)
493 : QQuickHeaderViewBase(Qt::Horizontal, parent)
494{
495 setFlickableDirection(FlickableDirection::HorizontalFlick);
496}
497
498QQuickHorizontalHeaderView::~QQuickHorizontalHeaderView()
499{
500}
501
502QQuickVerticalHeaderView::QQuickVerticalHeaderView(QQuickItem *parent)
503 : QQuickHeaderViewBase(Qt::Vertical, parent)
504{
505 setFlickableDirection(FlickableDirection::VerticalFlick);
506}
507
508QQuickVerticalHeaderView::~QQuickVerticalHeaderView()
509{
510}
511
512QQuickHorizontalHeaderViewPrivate::QQuickHorizontalHeaderViewPrivate() = default;
513
514QQuickHorizontalHeaderViewPrivate::~QQuickHorizontalHeaderViewPrivate() = default;
515
516QQuickVerticalHeaderViewPrivate::QQuickVerticalHeaderViewPrivate() = default;
517
518QQuickVerticalHeaderViewPrivate::~QQuickVerticalHeaderViewPrivate() = default;
519
520QT_END_NAMESPACE
521

source code of qtquickcontrols2/src/quicktemplates2/qquickheaderview.cpp