1// Copyright (C) 2020 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 <QtQuickTemplates2/private/qquickheaderview_p_p.h>
5#include <algorithm>
6
7/*!
8 \qmltype HorizontalHeaderView
9 \inqmlmodule QtQuick.Controls
10 \ingroup qtquickcontrols-containers
11 \inherits TableView
12 \brief Provides a horizontal header view to accompany a \l TableView.
13
14 \include qquickheaderview.qdocinc {detailed-description} {HorizontalHeaderView}
15
16 \sa VerticalHeaderView
17*/
18
19/*!
20 \qmltype VerticalHeaderView
21 \inqmlmodule QtQuick.Controls
22 \ingroup qtquickcontrols-containers
23 \inherits TableView
24 \brief Offers a vertical header view to accompany a \l TableView.
25
26 \include qquickheaderview.qdocinc {detailed-description} {VerticalHeaderView}
27
28 \sa HorizontalHeaderView
29*/
30
31/*!
32 \qmlproperty TableView QtQuick.Controls::HorizontalHeaderView::syncView
33
34 \include qquickheaderview.qdocinc {syncView} {horizontally}
35*/
36
37/*!
38 \qmlproperty TableView QtQuick.Controls::VerticalHeaderView::syncView
39
40 \include qquickheaderview.qdocinc {syncView} {vertically}
41*/
42
43/*!
44 \qmlproperty QVariant QtQuick.Controls::HorizontalHeaderView::model
45
46 \include qquickheaderview.qdocinc {model} {horizontal}
47*/
48
49/*!
50 \qmlproperty QVariant QtQuick.Controls::VerticalHeaderView::model
51
52 \include qquickheaderview.qdocinc {model} {vertical}
53*/
54
55/*!
56 \qmlproperty string QtQuick.Controls::HorizontalHeaderView::textRole
57
58 \include qquickheaderview.qdocinc {textRole}
59*/
60
61/*!
62 \qmlproperty string QtQuick.Controls::VerticalHeaderView::textRole
63
64 \include qquickheaderview.qdocinc {textRole}
65*/
66
67/*!
68 \qmlproperty bool QtQuick.Controls::HorizontalHeaderView::movableColumns
69 \since 6.8
70
71 \include qquickheaderview.qdocinc {movableColumns}
72*/
73
74/*!
75 \qmlproperty bool QtQuick.Controls::VerticalHeaderView::movableRows
76 \since 6.8
77
78 \include qquickheaderview.qdocinc {movableRows}
79*/
80
81QT_BEGIN_NAMESPACE
82
83static const char *kRequiredProperty_headerView = "headerView";
84static const char *kRequiredProperty_model = "model";
85
86QQuickHeaderViewBasePrivate::QQuickHeaderViewBasePrivate()
87 : QQuickTableViewPrivate()
88{
89}
90
91QQuickHeaderViewBasePrivate::~QQuickHeaderViewBasePrivate()
92{
93}
94
95void QQuickHeaderViewBasePrivate::init()
96{
97 Q_Q(QQuickHeaderViewBase);
98 m_headerDataProxyModel.m_headerView = q;
99 setSizePolicy(horizontalPolicy: orientation() == Qt::Horizontal ? QLayoutPolicy::Preferred : QLayoutPolicy::Fixed,
100 verticalPolicy: orientation() == Qt::Horizontal ? QLayoutPolicy::Fixed : QLayoutPolicy::Preferred);
101 q->setSyncDirection(orientation());
102}
103
104const QPointer<QQuickItem> QQuickHeaderViewBasePrivate::delegateItemAt(int row, int col) const
105{
106 return loadedTableItem(cell: QPoint(col, row))->item;
107}
108
109QVariant QQuickHeaderViewBasePrivate::modelImpl() const
110{
111 // modelImpl is supposed to return the actual model that is
112 // is used (that is, the proxy model), and not the source model.
113 if (m_headerDataProxyModel.sourceModel())
114 return QVariant::fromValue(value: &m_headerDataProxyModel);
115
116 // If we're not using the proxy model, use the assigned model
117 return QQuickTableViewPrivate::modelImpl();
118}
119
120void QQuickHeaderViewBasePrivate::setModelImpl(const QVariant &newModel)
121{
122 m_modelExplicitlySet = newModel.isValid();
123
124 auto asTableModel = [](QAbstractItemModel *model) -> QAbstractItemModel* {
125 if (qobject_cast<QAbstractTableModel *>(object: model))
126 return model;
127
128 // Since QQmlTableModel is not derived from QAbstractTableModel (QATM),
129 // we have to check for this model separately. We want it to behave like
130 // QATM to keep existing code working. This model is in the Labs module,
131 // and we cannot link to it to make a qobject_cast. Therefore, we are checking
132 // the class name.
133 // TODO: When QQmlTableModel leaves Labs, change to qobject_cast?
134 if (model && QByteArrayView(model->metaObject()->className()) == "QQmlTableModel")
135 return model;
136 return nullptr;
137 };
138
139 if (auto qabstracttablemodel = asTableModel(qaim(modelAsVariant: newModel))) {
140 if (qabstracttablemodel != m_headerDataProxyModel.sourceModel()) {
141 m_headerDataProxyModel.setSourceModel(qabstracttablemodel);
142 assignedModel = QVariant::fromValue(value: std::addressof(r&: m_headerDataProxyModel));
143 scheduleRebuildTable(options: QQuickTableViewPrivate::RebuildOption::All);
144 emit q_func()->modelChanged();
145 }
146 return;
147 }
148
149 m_headerDataProxyModel.setSourceModel(nullptr);
150 QQuickTableViewPrivate::setModelImpl(newModel);
151}
152
153void QQuickHeaderViewBasePrivate::syncModel()
154{
155 if (!m_modelExplicitlySet && assignedSyncView) {
156 // When no model has been explicitly set, but we have a syncView, we
157 // should use the headerData() from the syncView's model as model.
158 // If the model is not a QAbstractItemModel, or if it doesn't contain
159 // any header data, header view will be empty.
160 auto syncView_d = QQuickTableViewPrivate::get(q: assignedSyncView);
161 auto syncViewModelAsQaim = qaim(modelAsVariant: syncView_d->assignedModel);
162 if (syncViewModelAsQaim != m_headerDataProxyModel.sourceModel()) {
163 m_headerDataProxyModel.setSourceModel(syncViewModelAsQaim); // can be nullptr
164 assignedModel = QVariant::fromValue(value: std::addressof(r&: m_headerDataProxyModel));
165 emit q_func()->modelChanged();
166 }
167 }
168
169 QQuickTableViewPrivate::syncModel();
170
171 // For models that are just a list or a number, and especially not a
172 // table, we transpose the view when the orientation is horizontal.
173 // The model (list) will then be laid out horizontally rather than
174 // vertically, which is otherwise the default.
175 isTransposed = false;
176 const auto aim = model->abstractItemModel();
177 if (orientation() == Qt::Horizontal)
178 isTransposed = !aim || aim->columnCount() == 1;
179
180 if (m_textRole.isEmpty() && aim)
181 m_textRole = QLatin1String("display");
182}
183
184void QQuickHeaderViewBasePrivate::syncSyncView()
185{
186 if (assignedSyncDirection != orientation()) {
187 qmlWarning(me: q_func()) << "Setting syncDirection other than Qt::"
188 << QVariant::fromValue(value: orientation()).toString()
189 << " is invalid.";
190 assignedSyncDirection = orientation();
191 }
192 QQuickTableViewPrivate::syncSyncView();
193}
194
195QAbstractItemModel *QQuickHeaderViewBasePrivate::selectionSourceModel()
196{
197 // Our proxy model shares no common model items with HeaderView.model. So
198 // selections done in HeaderView cannot be represented in an ItemSelectionModel
199 // that is shared with the syncView (and for the same reason, the mapping functions
200 // modelIndex(cell) and cellAtIndex(index) have not been overridden either).
201 // Instead, we set the internal proxy model as selection source model.
202 return &m_headerDataProxyModel;
203}
204
205void QQuickHeaderViewBasePrivate::initItemCallback(int modelIndex, QObject *object)
206{
207 Q_Q(QQuickHeaderViewBase);
208
209 QQuickTableViewPrivate::initItemCallback(modelIndex, item: object);
210
211 auto item = qobject_cast<QQuickItem *>(o: object);
212 if (!item)
213 return;
214
215 QQmlDelegateModelItem *modelItem = tableModel->getModelItem(index: modelIndex);
216
217 setRequiredProperty(property: kRequiredProperty_headerView, value: QVariant::fromValue(value: q),
218 serializedModelIndex: modelIndex, object: item, init: true);
219 setRequiredProperty(property: kRequiredProperty_model, value: QVariant::fromValue(value: modelItem->modelObject()),
220 serializedModelIndex: modelIndex, object: item, init: true);
221}
222
223int QQuickHeaderViewBasePrivate::logicalRowIndex(const int visualIndex) const
224{
225 return (m_headerDataProxyModel.orientation() == Qt::Horizontal) ? visualIndex : QQuickTableViewPrivate::logicalRowIndex(visualIndex);
226}
227
228int QQuickHeaderViewBasePrivate::logicalColumnIndex(const int visualIndex) const
229{
230 return (m_headerDataProxyModel.orientation() == Qt::Vertical) ? visualIndex : QQuickTableViewPrivate::logicalColumnIndex(visualIndex);
231}
232
233int QQuickHeaderViewBasePrivate::visualRowIndex(const int logicalIndex) const
234{
235 return (m_headerDataProxyModel.orientation() == Qt::Horizontal) ? logicalIndex : QQuickTableViewPrivate::visualRowIndex(logicalIndex);
236}
237
238int QQuickHeaderViewBasePrivate::visualColumnIndex(const int logicalIndex) const
239{
240 return (m_headerDataProxyModel.orientation() == Qt::Vertical) ? logicalIndex : QQuickTableViewPrivate::visualColumnIndex(logicalIndex);
241}
242
243QQuickHeaderViewBase::QQuickHeaderViewBase(Qt::Orientation orient, QQuickItem *parent)
244 : QQuickTableView(*(new QQuickHeaderViewBasePrivate), parent)
245{
246 Q_D(QQuickHeaderViewBase);
247 d->setOrientation(orient);
248 d->init();
249}
250
251QQuickHeaderViewBase::QQuickHeaderViewBase(QQuickHeaderViewBasePrivate &dd, QQuickItem *parent)
252 : QQuickTableView(dd, parent)
253{
254 Q_D(QQuickHeaderViewBase);
255 d->init();
256}
257
258QQuickHeaderViewBase::~QQuickHeaderViewBase()
259{
260}
261
262QString QQuickHeaderViewBase::textRole() const
263{
264 Q_D(const QQuickHeaderViewBase);
265 return d->m_textRole;
266}
267
268void QQuickHeaderViewBase::setTextRole(const QString &role)
269{
270 Q_D(QQuickHeaderViewBase);
271 if (d->m_textRole == role)
272 return;
273
274 d->m_textRole = role;
275 emit textRoleChanged();
276}
277
278Qt::Orientation QQuickHeaderViewBasePrivate::orientation() const
279{
280 return m_headerDataProxyModel.orientation();
281}
282
283void QQuickHeaderViewBasePrivate::setOrientation(Qt::Orientation orientation)
284{
285 if (QQuickHeaderViewBasePrivate::orientation() == orientation)
286 return;
287 m_headerDataProxyModel.setOrientation(orientation);
288}
289
290QQuickVerticalHeaderView::QQuickVerticalHeaderView(QQuickVerticalHeaderViewPrivate &dd, QQuickItem *parent)
291 : QQuickHeaderViewBase(dd, parent)
292{
293}
294
295/*! \internal
296 \class QHeaderDataProxyModel
297 \brief
298 QHeaderDataProxyModel is a proxy AbstractItemModel type that maps
299 source model's headerData() to correspondent data()
300 */
301QHeaderDataProxyModel::QHeaderDataProxyModel(QObject *parent)
302 : QAbstractItemModel(parent)
303{
304}
305
306QHeaderDataProxyModel::~QHeaderDataProxyModel() = default;
307
308void QHeaderDataProxyModel::setSourceModel(QAbstractItemModel *newSourceModel)
309{
310 if (m_model == newSourceModel)
311 return;
312 beginResetModel();
313 disconnectFromModel();
314 m_model = newSourceModel;
315 connectToModel();
316 endResetModel();
317}
318
319QModelIndex QHeaderDataProxyModel::index(int row, int column, const QModelIndex &parent) const
320{
321 return hasIndex(row, column, parent) ? createIndex(arow: row, acolumn: column) : QModelIndex();
322}
323
324QModelIndex QHeaderDataProxyModel::parent(const QModelIndex &child) const
325{
326 Q_UNUSED(child);
327 return QModelIndex();
328}
329
330QModelIndex QHeaderDataProxyModel::sibling(int row, int column, const QModelIndex &) const
331{
332 return index(row, column);
333}
334
335int QHeaderDataProxyModel::rowCount(const QModelIndex &parent) const
336{
337 if (parent.isValid())
338 return 0;
339 return m_model.isNull() ? -1 : (m_orientation == Qt::Horizontal ? 1 : m_model->rowCount(parent));
340}
341
342int QHeaderDataProxyModel::columnCount(const QModelIndex &parent) const
343{
344 if (parent.isValid())
345 return 0;
346 return m_model.isNull() ? -1 : (m_orientation == Qt::Vertical ? 1 : m_model->columnCount(parent));
347}
348
349QVariant QHeaderDataProxyModel::data(const QModelIndex &index, int role) const
350{
351 if (m_model.isNull())
352 return QVariant();
353 if (!hasIndex(row: index.row(), column: index.column()))
354 return QModelIndex();
355 auto section = m_orientation == Qt::Vertical ? index.row() : index.column();
356 return m_model->headerData(section, orientation: m_orientation, role);
357}
358
359bool QHeaderDataProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
360{
361 if (!hasIndex(row: index.row(), column: index.column()))
362 return false;
363 auto section = m_orientation == Qt::Vertical ? index.row() : index.column();
364 auto ret = m_model->setHeaderData(section, orientation: m_orientation, value, role);
365 emit dataChanged(topLeft: index, bottomRight: index, roles: { role });
366 return ret;
367}
368
369bool QHeaderDataProxyModel::hasChildren(const QModelIndex &parent) const
370{
371 if (!parent.isValid())
372 return rowCount(parent) > 0 && columnCount(parent) > 0;
373 return false;
374}
375
376QHash<int, QByteArray> QHeaderDataProxyModel::roleNames() const
377{
378 using namespace Qt::Literals::StringLiterals;
379
380 auto names = m_model ? m_model->roleNames() : QAbstractItemModel::roleNames();
381 if (m_headerView) {
382 QString textRole = m_headerView->textRole();
383 if (textRole.isEmpty())
384 textRole = u"display"_s;
385 if (!names.values().contains(t: textRole.toUtf8().constData())) {
386 qmlWarning(me: m_headerView).nospace() << "The 'textRole' property contains a role that doesn't exist in the model: "
387 << textRole << ". Check your model's roleNames() implementation";
388 }
389 }
390
391 return names;
392}
393
394QVariant QHeaderDataProxyModel::variantValue() const
395{
396 return QVariant::fromValue(value: static_cast<QObject *>(const_cast<QHeaderDataProxyModel *>(this)));
397}
398
399void QHeaderDataProxyModel::setOrientation(Qt::Orientation o)
400{
401 if (o == m_orientation)
402 return;
403 beginResetModel();
404 m_orientation = o;
405 endResetModel();
406}
407
408Qt::Orientation QHeaderDataProxyModel::orientation() const
409{
410 return m_orientation;
411}
412
413QPointer<QAbstractItemModel> QHeaderDataProxyModel::sourceModel() const
414{
415 return m_model;
416}
417
418void QHeaderDataProxyModel::connectToModel()
419{
420 if (m_model.isNull())
421 return;
422 connect(sender: m_model, signal: &QAbstractItemModel::headerDataChanged,
423 context: this, slot: [this](Qt::Orientation orient, int first, int last) {
424 if (orient != orientation())
425 return;
426 if (orient == Qt::Horizontal) {
427 emit dataChanged(topLeft: createIndex(arow: 0, acolumn: first), bottomRight: createIndex(arow: 0, acolumn: last));
428 } else {
429 emit dataChanged(topLeft: createIndex(arow: first, acolumn: 0), bottomRight: createIndex(arow: last, acolumn: 0));
430 }
431 });
432 connect(sender: m_model, signal: &QAbstractItemModel::modelAboutToBeReset,
433 context: this, slot: &QHeaderDataProxyModel::modelAboutToBeReset, type: Qt::UniqueConnection);
434 connect(sender: m_model, signal: &QAbstractItemModel::modelReset,
435 context: this, slot: &QHeaderDataProxyModel::modelReset, type: Qt::UniqueConnection);
436 connect(sender: m_model, signal: &QAbstractItemModel::rowsAboutToBeMoved,
437 context: this, slot: &QHeaderDataProxyModel::rowsAboutToBeMoved, type: Qt::UniqueConnection);
438 connect(sender: m_model, signal: &QAbstractItemModel::rowsMoved,
439 context: this, slot: &QHeaderDataProxyModel::rowsMoved, type: Qt::UniqueConnection);
440 connect(sender: m_model, signal: &QAbstractItemModel::rowsAboutToBeInserted,
441 context: this, slot: &QHeaderDataProxyModel::rowsAboutToBeInserted, type: Qt::UniqueConnection);
442 connect(sender: m_model, signal: &QAbstractItemModel::rowsInserted,
443 context: this, slot: &QHeaderDataProxyModel::rowsInserted, type: Qt::UniqueConnection);
444 connect(sender: m_model, signal: &QAbstractItemModel::rowsAboutToBeRemoved,
445 context: this, slot: &QHeaderDataProxyModel::rowsAboutToBeRemoved, type: Qt::UniqueConnection);
446 connect(sender: m_model, signal: &QAbstractItemModel::rowsRemoved,
447 context: this, slot: &QHeaderDataProxyModel::rowsRemoved, type: Qt::UniqueConnection);
448 connect(sender: m_model, signal: &QAbstractItemModel::columnsAboutToBeMoved,
449 context: this, slot: &QHeaderDataProxyModel::columnsAboutToBeMoved, type: Qt::UniqueConnection);
450 connect(sender: m_model, signal: &QAbstractItemModel::columnsMoved,
451 context: this, slot: &QHeaderDataProxyModel::columnsMoved, type: Qt::UniqueConnection);
452 connect(sender: m_model, signal: &QAbstractItemModel::columnsAboutToBeInserted,
453 context: this, slot: &QHeaderDataProxyModel::columnsAboutToBeInserted, type: Qt::UniqueConnection);
454 connect(sender: m_model, signal: &QAbstractItemModel::columnsInserted,
455 context: this, slot: &QHeaderDataProxyModel::columnsInserted, type: Qt::UniqueConnection);
456 connect(sender: m_model, signal: &QAbstractItemModel::columnsAboutToBeRemoved,
457 context: this, slot: &QHeaderDataProxyModel::columnsAboutToBeRemoved, type: Qt::UniqueConnection);
458 connect(sender: m_model, signal: &QAbstractItemModel::columnsRemoved,
459 context: this, slot: &QHeaderDataProxyModel::columnsRemoved, type: Qt::UniqueConnection);
460 connect(sender: m_model, signal: &QAbstractItemModel::layoutAboutToBeChanged,
461 context: this, slot: &QHeaderDataProxyModel::layoutAboutToBeChanged, type: Qt::UniqueConnection);
462 connect(sender: m_model, signal: &QAbstractItemModel::layoutChanged,
463 context: this, slot: &QHeaderDataProxyModel::layoutChanged, type: Qt::UniqueConnection);
464}
465
466void QHeaderDataProxyModel::disconnectFromModel()
467{
468 if (m_model.isNull())
469 return;
470 m_model->disconnect(receiver: this);
471}
472
473QQuickHorizontalHeaderView::QQuickHorizontalHeaderView(QQuickItem *parent)
474 : QQuickHeaderViewBase(*(new QQuickHorizontalHeaderViewPrivate), parent)
475{
476 setFlickableDirection(FlickableDirection::HorizontalFlick);
477 setResizableColumns(true);
478}
479
480QQuickHorizontalHeaderView::~QQuickHorizontalHeaderView()
481{
482#if QT_CONFIG(quick_draganddrop)
483 Q_D(QQuickHorizontalHeaderView);
484 d->destroySectionDragHandler();
485#endif
486}
487
488bool QQuickHorizontalHeaderView::movableColumns() const
489{
490 Q_D(const QQuickHorizontalHeaderView);
491 return d->m_movableColumns;
492}
493
494void QQuickHorizontalHeaderView::setMovableColumns(bool movableColumns)
495{
496 Q_D(QQuickHorizontalHeaderView);
497 if (d->m_movableColumns == movableColumns)
498 return;
499
500 d->m_movableColumns = movableColumns;
501
502#if QT_CONFIG(quick_draganddrop)
503 if (d->m_movableColumns)
504 d->initSectionDragHandler(orientation: Qt::Horizontal);
505 else
506 d->destroySectionDragHandler();
507#endif
508
509 emit movableColumnsChanged();
510}
511
512QQuickVerticalHeaderView::QQuickVerticalHeaderView(QQuickItem *parent)
513 : QQuickHeaderViewBase(*(new QQuickVerticalHeaderViewPrivate), parent)
514{
515 setFlickableDirection(FlickableDirection::VerticalFlick);
516 setResizableRows(true);
517}
518
519QQuickVerticalHeaderView::~QQuickVerticalHeaderView()
520{
521#if QT_CONFIG(quick_draganddrop)
522 Q_D(QQuickVerticalHeaderView);
523 d->destroySectionDragHandler();
524#endif
525}
526
527bool QQuickVerticalHeaderView::movableRows() const
528{
529 Q_D(const QQuickVerticalHeaderView);
530 return d->m_movableRows ;
531}
532
533void QQuickVerticalHeaderView::setMovableRows(bool movableRows)
534{
535 Q_D(QQuickVerticalHeaderView);
536 if (d->m_movableRows == movableRows)
537 return;
538
539 d->m_movableRows = movableRows;
540
541#if QT_CONFIG(quick_draganddrop)
542 if (d->m_movableRows)
543 d->initSectionDragHandler(orientation: Qt::Vertical);
544 else
545 d->destroySectionDragHandler();
546#endif
547
548 emit movableRowsChanged();
549}
550
551QQuickHorizontalHeaderViewPrivate::QQuickHorizontalHeaderViewPrivate()
552{
553 setOrientation(Qt::Horizontal);
554};
555
556QQuickHorizontalHeaderViewPrivate::~QQuickHorizontalHeaderViewPrivate() = default;
557
558QQuickVerticalHeaderViewPrivate::QQuickVerticalHeaderViewPrivate()
559{
560 setOrientation(Qt::Vertical);
561};
562
563QQuickVerticalHeaderViewPrivate::~QQuickVerticalHeaderViewPrivate() = default;
564
565QT_END_NAMESPACE
566
567#include "moc_qquickheaderview_p_p.cpp"
568
569#include "moc_qquickheaderview_p.cpp"
570

source code of qtdeclarative/src/quicktemplates/qquickheaderview.cpp