diff options
Diffstat (limited to 'src/quicktemplates/qquickheaderview.cpp')
-rw-r--r-- | src/quicktemplates/qquickheaderview.cpp | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/src/quicktemplates/qquickheaderview.cpp b/src/quicktemplates/qquickheaderview.cpp new file mode 100644 index 0000000000..b94280865f --- /dev/null +++ b/src/quicktemplates/qquickheaderview.cpp @@ -0,0 +1,443 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtQuickTemplates2/private/qquickheaderview_p_p.h> +#include <algorithm> + +/*! + \qmltype HorizontalHeaderView + \inqmlmodule QtQuick.Controls + \ingroup qtquickcontrols-containers + \inherits TableView + \brief Provides a horizontal header view to accompany a \l TableView. + + \include qquickheaderview.qdocinc {detailed-description} {HorizontalHeaderView} + + \sa VerticalHeaderView +*/ + +/*! + \qmltype VerticalHeaderView + \inqmlmodule QtQuick.Controls + \ingroup qtquickcontrols-containers + \inherits TableView + \brief Offers a vertical header view to accompany a \l TableView. + + \include qquickheaderview.qdocinc {detailed-description} {VerticalHeaderView} + + \sa HorizontalHeaderView +*/ + +/*! + \qmlproperty TableView QtQuick.Controls::HorizontalHeaderView::syncView + + \include qquickheaderview.qdocinc {syncView} {horizontally} +*/ + +/*! + \qmlproperty TableView QtQuick.Controls::VerticalHeaderView::syncView + + \include qquickheaderview.qdocinc {syncView} {vertically} +*/ + +/*! + \qmlproperty QVariant QtQuick.Controls::HorizontalHeaderView::model + + \include qquickheaderview.qdocinc {model} {horizontal} +*/ + +/*! + \qmlproperty QVariant QtQuick.Controls::VerticalHeaderView::model + + \include qquickheaderview.qdocinc {model} {vertical} +*/ + +/*! + \qmlproperty QString QtQuick.Controls::HorizontalHeaderView::textRole + + \include qquickheaderview.qdocinc {textRole} +*/ + +/*! + \qmlproperty QString QtQuick.Controls::VerticalHeaderView::textRole + + \include qquickheaderview.qdocinc {textRole} +*/ + +QT_BEGIN_NAMESPACE + +QQuickHeaderViewBasePrivate::QQuickHeaderViewBasePrivate() + : QQuickTableViewPrivate() +{ +} + +QQuickHeaderViewBasePrivate::~QQuickHeaderViewBasePrivate() +{ +} + +const QPointer<QQuickItem> QQuickHeaderViewBasePrivate::delegateItemAt(int row, int col) const +{ + return loadedTableItem(QPoint(col, row))->item; +} + +QVariant QQuickHeaderViewBasePrivate::modelImpl() const +{ + if (auto model = m_headerDataProxyModel.sourceModel()) + return QVariant::fromValue(model.data()); +#if QT_CONFIG(transposeproxymodel) + if (auto model = m_transposeProxyModel.sourceModel()) + return QVariant::fromValue(model); +#endif + return QQuickTableViewPrivate::modelImpl(); +} + +template <typename P, typename M> +inline bool proxyModelSetter(QQuickHeaderViewBase *const q, P &proxyModel, M *model) +{ + if (model) { + if (model == proxyModel.sourceModel()) + return true; + proxyModel.setSourceModel(model); + const auto &modelVariant = QVariant::fromValue(std::addressof(proxyModel)); + bool isProxyModelChanged = (modelVariant != QQuickTableViewPrivate::get(q)->QQuickTableViewPrivate::modelImpl()); + QQuickTableViewPrivate::get(q)->QQuickTableViewPrivate::setModelImpl(modelVariant); + //Necessary, since TableView's assigned model not changed, but proxy's source changed + if (!isProxyModelChanged) + emit q->modelChanged(); + return true; + } + proxyModel.setSourceModel(nullptr); + return false; +} + +void QQuickHeaderViewBasePrivate::setModelImpl(const QVariant &newModel) +{ + Q_Q(QQuickHeaderViewBase); + m_modelExplicitlySetByUser = true; + // Case 1: newModel is QAbstractTableModel + if (proxyModelSetter(q, m_headerDataProxyModel, newModel.value<QAbstractTableModel *>())) + return; +#if QT_CONFIG(transposeproxymodel) + // Case 2: newModel is QAbstractItemModel but not QAbstractTableModel + if (orientation() == Qt::Horizontal + && proxyModelSetter(q, m_transposeProxyModel, newModel.value<QAbstractItemModel *>())) + return; +#endif + + QQuickTableViewPrivate::setModelImpl(newModel); +} + +void QQuickHeaderViewBasePrivate::syncModel() +{ + Q_Q(QQuickHeaderViewBase); + + if (assignedSyncView && !m_modelExplicitlySetByUser) { + auto newModel = assignedSyncView->model(); + if (auto m = newModel.value<QAbstractItemModel *>()) + proxyModelSetter(q, m_headerDataProxyModel, m); + } + + QQuickTableViewPrivate::syncModel(); + + isTransposed = false; + const auto aim = model->abstractItemModel(); + if (orientation() == Qt::Horizontal) { + // For models that are just a list or a number, and especially not a + // table, we transpose the view when the orientation is horizontal. + // The model (list) will then be laid out horizontally rather than + // vertically, which is the otherwise the default. + isTransposed = !aim || aim->columnCount() == 1; + } + if (m_textRole.isEmpty() && aim) + m_textRole = QLatin1String("display"); +} + +void QQuickHeaderViewBasePrivate::syncSyncView() +{ + if (assignedSyncDirection != orientation()) { + qmlWarning(q_func()) << "Setting syncDirection other than Qt::" + << QVariant::fromValue(orientation()).toString() + << " is invalid."; + assignedSyncDirection = orientation(); + } + QQuickTableViewPrivate::syncSyncView(); +} + +QAbstractItemModel *QQuickHeaderViewBasePrivate::selectionSourceModel() +{ + // Our proxy model shares no common model items with HeaderView.model. So + // selections done in HeaderView cannot be represented in an ItemSelectionModel + // that is shared with the syncView (and for the same reason, the mapping functions + // modelIndex(cell) and cellAtIndex(index) have not been overridden either). + // Instead, we set the internal proxy model as selection source model. + return &m_headerDataProxyModel; +} + +QQuickHeaderViewBase::QQuickHeaderViewBase(Qt::Orientation orient, QQuickItem *parent) + : QQuickTableView(*(new QQuickHeaderViewBasePrivate), parent) +{ + Q_D(QQuickHeaderViewBase); + d->m_headerDataProxyModel.m_headerView = this; + d->setSizePolicy(orient == Qt::Horizontal ? QLayoutPolicy::Preferred : QLayoutPolicy::Fixed, + orient == Qt::Horizontal ? QLayoutPolicy::Fixed : QLayoutPolicy::Preferred); + d->setOrientation(orient); + setSyncDirection(orient); +} + +QQuickHeaderViewBase::QQuickHeaderViewBase(QQuickHeaderViewBasePrivate &dd, QQuickItem *parent) + : QQuickTableView(dd, parent) +{ + Q_D(QQuickHeaderViewBase); + d->m_headerDataProxyModel.m_headerView = this; +} + +QQuickHeaderViewBase::~QQuickHeaderViewBase() +{ +} + +QString QQuickHeaderViewBase::textRole() const +{ + Q_D(const QQuickHeaderViewBase); + return d->m_textRole; +} + +void QQuickHeaderViewBase::setTextRole(const QString &role) +{ + Q_D(QQuickHeaderViewBase); + if (d->m_textRole == role) + return; + + d->m_textRole = role; + emit textRoleChanged(); +} + +Qt::Orientation QQuickHeaderViewBasePrivate::orientation() const +{ + return m_headerDataProxyModel.orientation(); +} + +void QQuickHeaderViewBasePrivate::setOrientation(Qt::Orientation orientation) +{ + if (QQuickHeaderViewBasePrivate::orientation() == orientation) + return; + m_headerDataProxyModel.setOrientation(orientation); +} + +QQuickVerticalHeaderView::QQuickVerticalHeaderView(QQuickVerticalHeaderViewPrivate &dd, QQuickItem *parent) + : QQuickHeaderViewBase(dd, parent) +{ +} + +/*! \internal + \class QHeaderDataProxyModel + \brief + QHeaderDataProxyModel is a proxy AbstractItemModel type that maps + source model's headerData() to correspondent data() + */ +QHeaderDataProxyModel::QHeaderDataProxyModel(QObject *parent) + : QAbstractItemModel(parent) +{ +} + +QHeaderDataProxyModel::~QHeaderDataProxyModel() = default; + +void QHeaderDataProxyModel::setSourceModel(QAbstractItemModel *newSourceModel) +{ + if (m_model == newSourceModel) + return; + beginResetModel(); + disconnectFromModel(); + m_model = newSourceModel; + connectToModel(); + endResetModel(); +} + +QModelIndex QHeaderDataProxyModel::index(int row, int column, const QModelIndex &parent) const +{ + return hasIndex(row, column, parent) ? createIndex(row, column) : QModelIndex(); +} + +QModelIndex QHeaderDataProxyModel::parent(const QModelIndex &child) const +{ + Q_UNUSED(child); + return QModelIndex(); +} + +QModelIndex QHeaderDataProxyModel::sibling(int row, int column, const QModelIndex &) const +{ + return index(row, column); +} + +int QHeaderDataProxyModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return m_model.isNull() ? -1 : (m_orientation == Qt::Horizontal ? 1 : m_model->rowCount(parent)); +} + +int QHeaderDataProxyModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return m_model.isNull() ? -1 : (m_orientation == Qt::Vertical ? 1 : m_model->columnCount(parent)); +} + +QVariant QHeaderDataProxyModel::data(const QModelIndex &index, int role) const +{ + if (m_model.isNull()) + return QVariant(); + if (!hasIndex(index.row(), index.column())) + return QModelIndex(); + auto section = m_orientation == Qt::Vertical ? index.row() : index.column(); + return m_model->headerData(section, m_orientation, role); +} + +bool QHeaderDataProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!hasIndex(index.row(), index.column())) + return false; + auto section = m_orientation == Qt::Vertical ? index.row() : index.column(); + auto ret = m_model->setHeaderData(section, m_orientation, value, role); + emit dataChanged(index, index, { role }); + return ret; +} + +bool QHeaderDataProxyModel::hasChildren(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return rowCount(parent) > 0 && columnCount(parent) > 0; + return false; +} + +QHash<int, QByteArray> QHeaderDataProxyModel::roleNames() const +{ + using namespace Qt::Literals::StringLiterals; + + auto names = m_model ? m_model->roleNames() : QAbstractItemModel::roleNames(); + if (m_headerView) { + QString textRole = m_headerView->textRole(); + if (textRole.isEmpty()) + textRole = u"display"_s; + if (!names.values().contains(textRole.toUtf8().constData())) { + qmlWarning(m_headerView).nospace() << "The 'textRole' property contains a role that doesn't exist in the model: " + << textRole << ". Check your model's roleNames() implementation"; + } + } + + return names; +} + +QVariant QHeaderDataProxyModel::variantValue() const +{ + return QVariant::fromValue(static_cast<QObject *>(const_cast<QHeaderDataProxyModel *>(this))); +} + +void QHeaderDataProxyModel::setOrientation(Qt::Orientation o) +{ + if (o == m_orientation) + return; + beginResetModel(); + m_orientation = o; + endResetModel(); +} + +Qt::Orientation QHeaderDataProxyModel::orientation() const +{ + return m_orientation; +} + +QPointer<QAbstractItemModel> QHeaderDataProxyModel::sourceModel() const +{ + return m_model; +} + +void QHeaderDataProxyModel::connectToModel() +{ + if (m_model.isNull()) + return; + connect(m_model, &QAbstractItemModel::headerDataChanged, + this, [this](Qt::Orientation orient, int first, int last) { + if (orient != orientation()) + return; + if (orient == Qt::Horizontal) { + emit dataChanged(createIndex(0, first), createIndex(0, last)); + } else { + emit dataChanged(createIndex(first, 0), createIndex(last, 0)); + } + }); + connect(m_model, &QAbstractItemModel::modelAboutToBeReset, + this, &QHeaderDataProxyModel::modelAboutToBeReset, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::modelReset, + this, &QHeaderDataProxyModel::modelReset, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsAboutToBeMoved, + this, &QHeaderDataProxyModel::rowsAboutToBeMoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsMoved, + this, &QHeaderDataProxyModel::rowsMoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsAboutToBeInserted, + this, &QHeaderDataProxyModel::rowsAboutToBeInserted, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsInserted, + this, &QHeaderDataProxyModel::rowsInserted, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &QHeaderDataProxyModel::rowsAboutToBeRemoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::rowsRemoved, + this, &QHeaderDataProxyModel::rowsRemoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsAboutToBeMoved, + this, &QHeaderDataProxyModel::columnsAboutToBeMoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsMoved, + this, &QHeaderDataProxyModel::columnsMoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsAboutToBeInserted, + this, &QHeaderDataProxyModel::columnsAboutToBeInserted, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsInserted, + this, &QHeaderDataProxyModel::columnsInserted, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsAboutToBeRemoved, + this, &QHeaderDataProxyModel::columnsAboutToBeRemoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::columnsRemoved, + this, &QHeaderDataProxyModel::columnsRemoved, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::layoutAboutToBeChanged, + this, &QHeaderDataProxyModel::layoutAboutToBeChanged, Qt::UniqueConnection); + connect(m_model, &QAbstractItemModel::layoutChanged, + this, &QHeaderDataProxyModel::layoutChanged, Qt::UniqueConnection); +} + +void QHeaderDataProxyModel::disconnectFromModel() +{ + if (m_model.isNull()) + return; + m_model->disconnect(this); +} + +QQuickHorizontalHeaderView::QQuickHorizontalHeaderView(QQuickItem *parent) + : QQuickHeaderViewBase(Qt::Horizontal, parent) +{ + setFlickableDirection(FlickableDirection::HorizontalFlick); + setResizableColumns(true); +} + +QQuickHorizontalHeaderView::~QQuickHorizontalHeaderView() +{ +} + +QQuickVerticalHeaderView::QQuickVerticalHeaderView(QQuickItem *parent) + : QQuickHeaderViewBase(Qt::Vertical, parent) +{ + setFlickableDirection(FlickableDirection::VerticalFlick); + setResizableRows(true); +} + +QQuickVerticalHeaderView::~QQuickVerticalHeaderView() +{ +} + +QQuickHorizontalHeaderViewPrivate::QQuickHorizontalHeaderViewPrivate() = default; + +QQuickHorizontalHeaderViewPrivate::~QQuickHorizontalHeaderViewPrivate() = default; + +QQuickVerticalHeaderViewPrivate::QQuickVerticalHeaderViewPrivate() = default; + +QQuickVerticalHeaderViewPrivate::~QQuickVerticalHeaderViewPrivate() = default; + +QT_END_NAMESPACE + +#include "moc_qquickheaderview_p_p.cpp" + +#include "moc_qquickheaderview_p.cpp" |