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 
QQuickHeaderViewBasePrivate()179 QQuickHeaderViewBasePrivate::QQuickHeaderViewBasePrivate()
180     : QQuickTableViewPrivate()
181 {
182 }
183 
~QQuickHeaderViewBasePrivate()184 QQuickHeaderViewBasePrivate::~QQuickHeaderViewBasePrivate()
185 {
186 }
187 
delegateItemAt(int row,int col) const188 const QPointer<QQuickItem> QQuickHeaderViewBasePrivate::delegateItemAt(int row, int col) const
189 {
190     return loadedTableItem(QPoint(col, row))->item;
191 }
192 
modelImpl() const193 QVariant QQuickHeaderViewBasePrivate::modelImpl() const
194 {
195     if (auto model = m_headerDataProxyModel.sourceModel())
196         return QVariant::fromValue(model.data());
197     if (auto model = m_transposeProxyModel.sourceModel())
198         return QVariant::fromValue(model);
199     return QQuickTableViewPrivate::modelImpl();
200 }
201 
202 template <typename P, typename M>
proxyModelSetter(QQuickHeaderViewBase * const q,P & proxyModel,M * model)203 inline 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 
setModelImpl(const QVariant & newModel)221 void QQuickHeaderViewBasePrivate::setModelImpl(const QVariant &newModel)
222 {
223     Q_Q(QQuickHeaderViewBase);
224     m_modelExplicitlySetByUser = true;
225     // Case 1: newModel is QAbstractTableModel
226     if (proxyModelSetter(q, m_headerDataProxyModel, newModel.value<QAbstractTableModel *>()))
227         return;
228     // Case 2: newModel is QAbstractItemModel but not QAbstractTableModel
229     if (orientation() == Qt::Horizontal
230         && proxyModelSetter(q, m_transposeProxyModel, newModel.value<QAbstractItemModel *>()))
231         return;
232 
233     QQuickTableViewPrivate::setModelImpl(newModel);
234 }
235 
syncModel()236 void 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, m_headerDataProxyModel, 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 
syncSyncView()261 void QQuickHeaderViewBasePrivate::syncSyncView()
262 {
263     Q_Q(QQuickHeaderViewBase);
264     if (assignedSyncDirection != orientation()) {
265         qmlWarning(q_func()) << "Setting syncDirection other than Qt::"
266                              << QVariant::fromValue(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 
QQuickHeaderViewBase(Qt::Orientation orient,QQuickItem * parent)283 QQuickHeaderViewBase::QQuickHeaderViewBase(Qt::Orientation orient, QQuickItem *parent)
284     : QQuickTableView(*(new QQuickHeaderViewBasePrivate), parent)
285 {
286     d_func()->setOrientation(orient);
287     setSyncDirection(orient);
288 }
289 
QQuickHeaderViewBase(QQuickHeaderViewBasePrivate & dd,QQuickItem * parent)290 QQuickHeaderViewBase::QQuickHeaderViewBase(QQuickHeaderViewBasePrivate &dd, QQuickItem *parent)
291     : QQuickTableView(dd, parent)
292 {
293 }
294 
~QQuickHeaderViewBase()295 QQuickHeaderViewBase::~QQuickHeaderViewBase()
296 {
297 }
298 
textRole() const299 QString QQuickHeaderViewBase::textRole() const
300 {
301     Q_D(const QQuickHeaderViewBase);
302     return d->m_textRole;
303 }
304 
setTextRole(const QString & role)305 void 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 
orientation() const315 Qt::Orientation QQuickHeaderViewBasePrivate::orientation() const
316 {
317     return m_headerDataProxyModel.orientation();
318 }
319 
setOrientation(Qt::Orientation orientation)320 void QQuickHeaderViewBasePrivate::setOrientation(Qt::Orientation orientation)
321 {
322     if (QQuickHeaderViewBasePrivate::orientation() == orientation)
323         return;
324     m_headerDataProxyModel.setOrientation(orientation);
325 }
326 
QQuickVerticalHeaderView(QQuickVerticalHeaderViewPrivate & dd,QQuickItem * parent)327 QQuickVerticalHeaderView::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  */
QHeaderDataProxyModel(QObject * parent)338 QHeaderDataProxyModel::QHeaderDataProxyModel(QObject *parent)
339     : QAbstractItemModel(parent)
340 {
341 }
342 
343 QHeaderDataProxyModel::~QHeaderDataProxyModel() = default;
344 
setSourceModel(QAbstractItemModel * newSourceModel)345 void 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 
index(int row,int column,const QModelIndex & parent) const356 QModelIndex QHeaderDataProxyModel::index(int row, int column, const QModelIndex &parent) const
357 {
358     return hasIndex(row, column, parent) ? createIndex(row, column) : QModelIndex();
359 }
360 
parent(const QModelIndex & child) const361 QModelIndex QHeaderDataProxyModel::parent(const QModelIndex &child) const
362 {
363     Q_UNUSED(child)
364     return QModelIndex();
365 }
366 
sibling(int row,int column,const QModelIndex &) const367 QModelIndex QHeaderDataProxyModel::sibling(int row, int column, const QModelIndex &) const
368 {
369     return index(row, column);
370 }
371 
rowCount(const QModelIndex & parent) const372 int 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 
columnCount(const QModelIndex & parent) const379 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 
data(const QModelIndex & index,int role) const386 QVariant QHeaderDataProxyModel::data(const QModelIndex &index, int role) const
387 {
388     if (m_model.isNull())
389         return QVariant();
390     if (!hasIndex(index.row(), index.column()))
391         return QModelIndex();
392     auto section = m_orientation == Qt::Vertical ? index.row() : index.column();
393     return m_model->headerData(section, m_orientation, role);
394 }
395 
setData(const QModelIndex & index,const QVariant & value,int role)396 bool QHeaderDataProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
397 {
398     if (!hasIndex(index.row(), index.column()))
399         return false;
400     auto section = m_orientation == Qt::Vertical ? index.row() : index.column();
401     auto ret = m_model->setHeaderData(section, m_orientation, value, role);
402     emit dataChanged(index, index, { role });
403     return ret;
404 }
405 
hasChildren(const QModelIndex & parent) const406 bool QHeaderDataProxyModel::hasChildren(const QModelIndex &parent) const
407 {
408     if (!parent.isValid())
409         return rowCount(parent) > 0 && columnCount(parent) > 0;
410     return false;
411 }
412 
variantValue() const413 QVariant QHeaderDataProxyModel::variantValue() const
414 {
415     return QVariant::fromValue(static_cast<QObject *>(const_cast<QHeaderDataProxyModel *>(this)));
416 }
417 
setOrientation(Qt::Orientation o)418 void QHeaderDataProxyModel::setOrientation(Qt::Orientation o)
419 {
420     if (o == m_orientation)
421         return;
422     beginResetModel();
423     m_orientation = o;
424     endResetModel();
425 }
426 
orientation() const427 Qt::Orientation QHeaderDataProxyModel::orientation() const
428 {
429     return m_orientation;
430 }
431 
sourceModel() const432 QPointer<QAbstractItemModel> QHeaderDataProxyModel::sourceModel() const
433 {
434     return m_model;
435 }
436 
connectToModel()437 void QHeaderDataProxyModel::connectToModel()
438 {
439     if (m_model.isNull())
440         return;
441     connect(m_model, &QAbstractItemModel::headerDataChanged,
442         [this](Qt::Orientation orient, int first, int last) {
443             if (orient != orientation())
444                 return;
445             if (orient == Qt::Horizontal) {
446                 emit dataChanged(createIndex(0, first), createIndex(0, last));
447             } else {
448                 emit dataChanged(createIndex(first, 0), createIndex(last, 0));
449             }
450         });
451     connect(m_model, &QAbstractItemModel::modelAboutToBeReset,
452         this, &QHeaderDataProxyModel::modelAboutToBeReset, Qt::UniqueConnection);
453     connect(m_model, &QAbstractItemModel::modelReset,
454         this, &QHeaderDataProxyModel::modelReset, Qt::UniqueConnection);
455     connect(m_model, &QAbstractItemModel::rowsAboutToBeMoved,
456         this, &QHeaderDataProxyModel::rowsAboutToBeMoved, Qt::UniqueConnection);
457     connect(m_model, &QAbstractItemModel::rowsMoved,
458         this, &QHeaderDataProxyModel::rowsMoved, Qt::UniqueConnection);
459     connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted,
460         this, &QHeaderDataProxyModel::rowsAboutToBeInserted, Qt::UniqueConnection);
461     connect(m_model, &QAbstractItemModel::rowsInserted,
462         this, &QHeaderDataProxyModel::rowsInserted, Qt::UniqueConnection);
463     connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved,
464         this, &QHeaderDataProxyModel::rowsAboutToBeRemoved, Qt::UniqueConnection);
465     connect(m_model, &QAbstractItemModel::rowsRemoved,
466         this, &QHeaderDataProxyModel::rowsRemoved, Qt::UniqueConnection);
467     connect(m_model, &QAbstractItemModel::columnsAboutToBeMoved,
468         this, &QHeaderDataProxyModel::columnsAboutToBeMoved, Qt::UniqueConnection);
469     connect(m_model, &QAbstractItemModel::columnsMoved,
470         this, &QHeaderDataProxyModel::columnsMoved, Qt::UniqueConnection);
471     connect(m_model, &QAbstractItemModel::columnsAboutToBeInserted,
472         this, &QHeaderDataProxyModel::columnsAboutToBeInserted, Qt::UniqueConnection);
473     connect(m_model, &QAbstractItemModel::columnsInserted,
474         this, &QHeaderDataProxyModel::columnsInserted, Qt::UniqueConnection);
475     connect(m_model, &QAbstractItemModel::columnsAboutToBeRemoved,
476         this, &QHeaderDataProxyModel::columnsAboutToBeRemoved, Qt::UniqueConnection);
477     connect(m_model, &QAbstractItemModel::columnsRemoved,
478         this, &QHeaderDataProxyModel::columnsRemoved, Qt::UniqueConnection);
479     connect(m_model, &QAbstractItemModel::layoutAboutToBeChanged,
480         this, &QHeaderDataProxyModel::layoutAboutToBeChanged, Qt::UniqueConnection);
481     connect(m_model, &QAbstractItemModel::layoutChanged,
482         this, &QHeaderDataProxyModel::layoutChanged, Qt::UniqueConnection);
483 }
484 
disconnectFromModel()485 void QHeaderDataProxyModel::disconnectFromModel()
486 {
487     if (m_model.isNull())
488         return;
489     m_model->disconnect(this);
490 }
491 
QQuickHorizontalHeaderView(QQuickItem * parent)492 QQuickHorizontalHeaderView::QQuickHorizontalHeaderView(QQuickItem *parent)
493     : QQuickHeaderViewBase(Qt::Horizontal, parent)
494 {
495     setFlickableDirection(FlickableDirection::HorizontalFlick);
496 }
497 
~QQuickHorizontalHeaderView()498 QQuickHorizontalHeaderView::~QQuickHorizontalHeaderView()
499 {
500 }
501 
QQuickVerticalHeaderView(QQuickItem * parent)502 QQuickVerticalHeaderView::QQuickVerticalHeaderView(QQuickItem *parent)
503     : QQuickHeaderViewBase(Qt::Vertical, parent)
504 {
505     setFlickableDirection(FlickableDirection::VerticalFlick);
506 }
507 
~QQuickVerticalHeaderView()508 QQuickVerticalHeaderView::~QQuickVerticalHeaderView()
509 {
510 }
511 
512 QQuickHorizontalHeaderViewPrivate::QQuickHorizontalHeaderViewPrivate() = default;
513 
514 QQuickHorizontalHeaderViewPrivate::~QQuickHorizontalHeaderViewPrivate() = default;
515 
516 QQuickVerticalHeaderViewPrivate::QQuickVerticalHeaderViewPrivate() = default;
517 
518 QQuickVerticalHeaderViewPrivate::~QQuickVerticalHeaderViewPrivate() = default;
519 
520 QT_END_NAMESPACE
521