diff options
author | MohammadHossein Qanbari <[email protected]> | 2024-10-15 16:33:58 +0200 |
---|---|---|
committer | MohammadHossein Qanbari <[email protected]> | 2024-11-27 09:03:42 +0100 |
commit | 0edf656a2ea2c3fec34892b5f878855d0b094af7 (patch) | |
tree | 10e36c01eca41c44c6fd715e9e175cfe179597d4 | |
parent | 77a20c00e64010c661b9121e8c114065ae5346e0 (diff) |
Controls: Add TableViewDelegate
This patch introduces TableViewDelegate to the Controls module.
Key features:
- Ready-made delegate assignable to TableView
- Handles table drawing using predefined styles
- Usable without customization
- Implements all required properties set by TableView
- Provides API for changing background and label
Test Suite:
- Verifies TableViewDelegate properties and functionalities
- Tests include:
- Verification of selected, current, and content text properties
- Validation of selection behavior and item clicking
BLACKLISTing two tests (dragToSelect and pressAndHoldToSelect), which
will be removed from the BLACKLIST in the next patch (to fix cell
selection on the Android platform).
Implementation derived from TreeViewDelegate, adapted for TableView use.
TreeViewDelegate patch: 0ddb0d4b9b0c70c4fd4058ef4660e38fd933523e
[ChangeLog][Controls] New delegate added: TableViewDelegate
Fixes: QTBUG-114636
Change-Id: Ibb8b0a7622016e0c6fd58d696e507e7bb76daced
Reviewed-by: Mitch Curtis <[email protected]>
21 files changed, 1209 insertions, 14 deletions
diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index fa0746f48c..2c07c9eb1a 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -61,7 +61,11 @@ \snippet qml/tableview/cpp-tablemodel.h 0 - And then how to use it from QML: + And then the \l TableViewDelegate automatically uses the model to set/get data + to/from the model. The \l TableViewDelegate uses the \l {Qt::ItemDataRole}{Qt::DisplayRole} + for display text and \l {Qt::ItemDataRole}{Qt::EditRole} for editing data in the model. + + The following snippet shows how to use the model from QML in a custom delegate: \snippet qml/tableview/cpp-tablemodel.qml 0 @@ -72,6 +76,32 @@ \snippet qml/tableview/qml-tablemodel.qml 0 + As the \l TableViewDelegate uses the \l {Qt::ItemDataRole}{Qt::EditRole} to set + the data, it's necessary to specify the edit role in the \l TableModelColumn when + the delegate is \l TableViewDelegate: + + \code + model: TableModel { + TableModelColumn { display: "name", edit: "name" } + TableModelColumn { display: "color", edit: "color" } + + rows: [ + { + "name": "cat", + "color": "black" + }, + { + "name": "dog", + "color": "brown" + }, + { + "name": "bird", + "color": "white" + } + ] + } + \endcode + \section1 Reusing items TableView recycles delegate items by default, instead of instantiating from @@ -233,7 +263,8 @@ be allowed to select individual cells, rows, or columns. To find out whether a delegate is selected or current, declare the - following properties: + following properties (unless the delegate is a \l TableViewDelegate, + in which case the properties have are already been added): \code delegate: Item { @@ -278,8 +309,11 @@ You can also disable keyboard navigation fully (in case you want to implement your own key handlers) by setting \l keyNavigationEnabled to \c false. + \note By default, the \l TableViewDelegate renders the current and selected cells, + so there is no need to add these properties. + The following example demonstrates how you can use keyboard navigation together - with \c current and \c selected properties: + with \c current and \c selected properties in a custom delegate: \snippet qml/tableview/keyboard-navigation.qml 0 @@ -451,8 +485,16 @@ /*! \qmlproperty Component QtQuick::TableView::delegate - The delegate provides a template defining each cell item instantiated by the - view. The model index is exposed as an accessible \c index property. The same + The delegate provides a template defining each cell item instantiated by the view. + It can be any custom component, but it's recommended to use \l {TableViewDelegate}, + as it styled according to the application style, and offers out-of-the-box functionality. + + To use \l TableViewDelegate, simply set it as the delegate: + \code + delegate: TableViewDelegate { } + \endcode + + The model index is exposed as an accessible \c index property. The same applies to \c row and \c column. Properties of the model are also available depending upon the type of \l {qml-data-models}{Data Model}. @@ -461,9 +503,10 @@ information. Explicit width or height settings are ignored and overwritten. Inside the delegate, you can optionally add one or more of the following - properties. TableView modifies the values of these properties to inform the - delegate which state it's in. This can be used by the delegate to render - itself differently according on its own state. + properties (unless the delegate is a \l TableViewDelegate, in which case + the properties have already been added). TableView modifies the values + of these properties to inform the delegate which state it's in. This can be + used by the delegate to render itself differently according on its own state. \list \li required property bool current - \c true if the delegate is \l {Keyboard navigation}{current.} @@ -474,7 +517,7 @@ VerticalHeaderView. (since Qt 6.8) \endlist - The following example shows how to use these properties: + The following example shows how to use these properties in a custom delegate: \code delegate: Rectangle { required property bool current @@ -488,7 +531,8 @@ They are also reused if the \l reuseItems property is set to \c true. You should therefore avoid storing state information in the delegates. - \sa {Row heights and column widths}, {Reusing items}, {Required Properties} + \sa {Row heights and column widths}, {Reusing items}, {Required Properties}, + {TableViewDelegate}, {Customizing TableViewDelegate} */ /*! @@ -1496,7 +1540,7 @@ inside the \l {TableView::delegate}{TableView delegate.}. The latter can be done by defining a property \c {required property bool editing} inside it, that you bind to the \l {QQuickItem::}{visible} property of some of the child items. - The following snippet shows how to do that: + The following snippet shows how to do that in a custom delegate: \snippet qml/tableview/editdelegate.qml 1 @@ -1504,7 +1548,21 @@ on it. If you want active focus to be set on a child of the edit delegate instead, let the edit delegate be a \l FocusScope. - \sa editTriggers, TableView::commit, edit(), closeEditor(), {Editing cells} + By default, \l TableViewDelegate provides an \l {TableView::editDelegate}{edit delegate}, + and you can also set your own: + + \code + delegate: TableViewDelegate { + TableView.editDelegate: TextField { + width: parent.width + height: parent.height + text: display + TableView.onCommit: display = text + } + } + \endcode + + \sa editTriggers, TableView::commit, edit(), closeEditor(), {Editing cells}, TableViewDelegate */ QT_BEGIN_NAMESPACE @@ -1516,6 +1574,7 @@ Q_LOGGING_CATEGORY(lcTableViewDelegateLifecycle, "qt.quick.tableview.lifecycle") static const Qt::Edge allTableEdges[] = { Qt::LeftEdge, Qt::RightEdge, Qt::TopEdge, Qt::BottomEdge }; +static const char* kRequiredProperty_tableView = "tableView"; static const char* kRequiredProperties = "_qt_tableview_requiredpropertymask"; static const char* kRequiredProperty_selected = "selected"; static const char* kRequiredProperty_current = "current"; @@ -4365,6 +4424,8 @@ void QQuickTableViewPrivate::initItemCallback(int modelIndex, QObject *object) const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y())); const bool current = currentInSelectionModel(visualCell); const bool selected = selectedInSelectionModel(visualCell); + + setRequiredProperty(kRequiredProperty_tableView, QVariant::fromValue(q), modelIndex, item, true); setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, true); setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, true); setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), modelIndex, item, true); @@ -4384,10 +4445,14 @@ void QQuickTableViewPrivate::itemPooledCallback(int modelIndex, QObject *object) void QQuickTableViewPrivate::itemReusedCallback(int modelIndex, QObject *object) { + Q_Q(QQuickTableView); + const QPoint cell = cellAtModelIndex(modelIndex); const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y())); const bool current = currentInSelectionModel(visualCell); const bool selected = selectedInSelectionModel(visualCell); + + setRequiredProperty(kRequiredProperty_tableView, QVariant::fromValue(q), modelIndex, object, false); setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, false); setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, false); // Note: the edit item will never be reused, so no reason to set kRequiredProperty_editing diff --git a/src/quickcontrols/basic/CMakeLists.txt b/src/quickcontrols/basic/CMakeLists.txt index 4c97c00ec9..d1caae7b07 100644 --- a/src/quickcontrols/basic/CMakeLists.txt +++ b/src/quickcontrols/basic/CMakeLists.txt @@ -74,6 +74,11 @@ if (QT_FEATURE_quicktemplates2_calendar) "WeekNumberColumn.qml" ) endif() +if (QT_FEATURE_quick_tableview) + list(APPEND qml_files + "TableViewDelegate.qml" + ) +endif() if (QT_FEATURE_quick_treeview) list(APPEND qml_files "TreeViewDelegate.qml" diff --git a/src/quickcontrols/basic/TableViewDelegate.qml b/src/quickcontrols/basic/TableViewDelegate.qml new file mode 100644 index 0000000000..b560340a2d --- /dev/null +++ b/src/quickcontrols/basic/TableViewDelegate.qml @@ -0,0 +1,81 @@ +// Copyright (C) 2024 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 + +import QtQuick +import QtQuick.Controls.impl +import Qt.labs.qmlmodels as QtLabsQmlModels +import QtQuick.Templates as T + +T.TableViewDelegate { + id: control + + // same as AbstractButton.qml + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + highlighted: control.selected + + required property int column + required property int row + required property var model + + background: Rectangle { + border.width: control.current ? 2 : 0 + border.color: control.palette.highlight + color: control.highlighted + ? control.palette.highlight + : (control.tableView.alternatingRows && control.row % 2 !== 0 + ? control.palette.alternateBase : control.palette.base) + } + + contentItem: Label { + clip: false + text: control.model.display ?? "" + elide: Text.ElideRight + color: control.highlighted ? control.palette.highlightedText : control.palette.buttonText + visible: !control.editing + } + + // The edit delegate is a separate component, and doesn't need + // to follow the same strict rules that are applied to a control. + // qmllint disable attached-property-reuse + // qmllint disable controls-attached-property-reuse + // qmllint disable controls-sanity + TableView.editDelegate: FocusScope { + width: parent.width + height: parent.height + + TableView.onCommit: { + let model = control.tableView.model + if (!model) + return + // The setData() APIs are different in QAbstractItemModel and QQmlTableModel. + // This is an issue and will be fixed later, probably by deprecating the wrong + // API in QQmlTableModel. There is a ticket reported this issue and a workaround + // is provided in the description: https://bugreports.qt.io/browse/QTBUG-104733 + // But temporarily we need to manage this by checking the model's type. + let succeed = false + const index = model.index(control.row, control.column) + if (model instanceof QtLabsQmlModels.TableModel) + succeed = model.setData(index, "edit", textField.text) + else + succeed = model.setData(index, textField.text, Qt.EditRole) + if (!succeed) + console.warn("The model does not allow setting the EditRole data.") + } + + Component.onCompleted: textField.selectAll() + + TextField { + id: textField + anchors.fill: parent + text: control.model.edit ?? control.model.display ?? "" + focus: true + } + } + // qmllint enable attached-property-reuse + // qmllint enable controls-attached-property-reuse + // qmllint enable controls-sanity +} diff --git a/src/quickcontrols/doc/images/qtquickcontrols-tableviewdelegate-custom.png b/src/quickcontrols/doc/images/qtquickcontrols-tableviewdelegate-custom.png Binary files differnew file mode 100644 index 0000000000..94f27adaf9 --- /dev/null +++ b/src/quickcontrols/doc/images/qtquickcontrols-tableviewdelegate-custom.png diff --git a/src/quickcontrols/doc/images/qtquickcontrols-tableviewdelegate.png b/src/quickcontrols/doc/images/qtquickcontrols-tableviewdelegate.png Binary files differnew file mode 100644 index 0000000000..eb8f804837 --- /dev/null +++ b/src/quickcontrols/doc/images/qtquickcontrols-tableviewdelegate.png diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-tableviewdelegate-custom.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-tableviewdelegate-custom.qml new file mode 100644 index 0000000000..450c574a75 --- /dev/null +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-tableviewdelegate-custom.qml @@ -0,0 +1,149 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [file] +import QtQuick +import QtQuick.Controls.Basic +import QtQuick.Layouts +import Qt.labs.qmlmodels + +TableView { + width: 540 + height: 40 * rows + + columnWidthProvider: function(column) { + switch (column) { + case 0: return 220 + case 1: return 260 + case 2: return 60 + default: return -1 + } + } + + //! [delegate] + delegate: TableViewDelegate { + id: tableCell + + checked: column === 0 ? checkBox.checked : tableView.itemAtIndex(tableView.index(row, 0)).checked + selected: checked + + //! [background] + background: Item { + Rectangle { + anchors.fill: parent + anchors.margins: tableCell.current ? 3 : 1 + color: tableCell.selected ? "blue" : "white" + } + + Rectangle { + anchors.fill: parent + color: "transparent" + border.color: "darkblue" + border.width: tableCell.current ? 2 : 0 + } + } + //! [background] + + //! [contentItem] + contentItem: Item { + implicitHeight: 40 + visible: !tableCell.editing + + RowLayout { + anchors.fill: parent + + CheckBox { + id: checkBox + implicitWidth: height + Layout.fillHeight: true + checked: false + visible: tableCell.column === 0 + } + + Text { + Layout.leftMargin: 4 + Layout.fillWidth: true + Layout.fillHeight: true + verticalAlignment: Text.AlignVCenter + color: tableCell.selected ? "white" : "black" + text: tableCell.model.display + } + } + } + //! [contentItem] + + //! [editDelegate] + TableView.editDelegate: FocusScope { + width: parent.width + height: parent.height + + TableView.onCommit: { + let qaim = tableCell.tableView.model + if (!qaim) + return + const index = qaim.index(tableCell.row, tableCell.column) + // instead of the edit role, any custom role supported by the model can be checked + // e.g. if (!tableCell.checked || !tableCell.model.customRole) + if (!tableCell.checked || !tableCell.model.edit) + return + // instead of the edit role, any custom role supported by the model can be set + // e.g. tableCell.model.customRole = textField.text + tableCell.model.edit = textField.text + tableCell.model.display = textField.text + } + + Component.onCompleted: textField.selectAll() + + TextField { + id: textField + anchors.fill: parent + text: tableCell.model.edit ?? tableCell.model.display ?? "" + focus: true + } + } + //! [editDelegate] + } + //! [delegate] + + model: TableModel { + TableModelColumn { display: "name" } + TableModelColumn { display: "address" } + TableModelColumn { display: "quantity" } + + rows: [ + { + name: "Kristian Quan", + address: "123 Company Place, Big City", + quantity: 4, + }, + { + name: "Matthew Rand", + address: "The Orchard, Little Village", + quantity: 2, + }, + { + name: "Eirik Asaki", + address: "497 Park Skyway, Future City", + quantity: 29, + }, + { + name: "Jarek Hanssen", + address: "1023 RivieraDrive, Southern Precinct", + quantity: 45, + }, + { + name: "Charlos Hartmann", + address: "The Manor House, Country Estate", + quantity: 1, + }, + { + name: "Bea King", + address: "Floor 201, Sun Tower, Central City", + quantity: 32, + }, + ] + } + + selectionModel: ItemSelectionModel { } +} +//! [file] diff --git a/src/quickcontrols/doc/snippets/qtquickcontrols-tableviewdelegate.qml b/src/quickcontrols/doc/snippets/qtquickcontrols-tableviewdelegate.qml new file mode 100644 index 0000000000..7166c17cdb --- /dev/null +++ b/src/quickcontrols/doc/snippets/qtquickcontrols-tableviewdelegate.qml @@ -0,0 +1,74 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import Qt.labs.qmlmodels + +Window { + width: 460 + height: 198 + visible: true + title: qsTr("TableViewDelegate Example") + + TableView { + id: tableView + anchors.fill: parent + model: tableModel + delegate: tableDelegate + selectionModel: ItemSelectionModel {} + } + + Component { + id: tableDelegate + + TableViewDelegate { + topPadding: 8 + leftPadding: 12 + rightPadding: leftPadding + bottomPadding: topPadding + Component.onCompleted: contentItem.verticalAlignment = Text.AlignVCenter + } + } + + TableModel { + id: tableModel + + TableModelColumn { display: "name"; edit: "name" } + TableModelColumn { display: "address"; edit: "address" } + TableModelColumn { display: "quantity"; edit: "quantity" } + + rows: [ + { + name: "Kristian Quan", + address: "123 Company Place, Big City", + quantity: 4, + }, + { + name: "Matthew Rand", + address: "The Orchard, Little Village", + quantity: 2, + }, + { + name: "Eirik Asaki", + address: "497 Park Skyway, Future City", + quantity: 29, + }, + { + name: "Jarek Hanssen", + address: "1023 RivieraDrive, Southern Precinct", + quantity: 45, + }, + { + name: "Charlos Hartmann", + address: "The Manor House, Country Estate", + quantity: 1, + }, + { + name: "Bea King", + address: "Floor 201, Sun Tower, Central City", + quantity: 32, + }, + ] + } +} diff --git a/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc b/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc index df11f7c133..e569252b76 100644 --- a/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc +++ b/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc @@ -1040,4 +1040,20 @@ For a non-wrapping Tumbler, use ListView: \snippet qtquickcontrols-tumbler-listView.qml contentItem + + \section2 Customizing TableViewDelegate + + TableViewDelegate inherits \l ItemDelegate, which means that it's composed of two + visual items: + \l [QML]{Control::}{background} and + \l [QML]{Control::}{contentItem}. + + You can always assign your own custom edit delegate to + \l [QML]{TableView::}{editDelegate} if you have needs + outside what the default edit delegate offers. + + \image qtquickcontrols-tableviewdelegate-custom.png + + \snippet qtquickcontrols-tableviewdelegate-custom.qml delegate + */ diff --git a/src/quickcontrols/doc/src/qtquickcontrols-delegates.qdoc b/src/quickcontrols/doc/src/qtquickcontrols-delegates.qdoc index a3fee8b5ae..dd3096e8f8 100644 --- a/src/quickcontrols/doc/src/qtquickcontrols-delegates.qdoc +++ b/src/quickcontrols/doc/src/qtquickcontrols-delegates.qdoc @@ -58,6 +58,15 @@ \b {See also} \l {Switch Control}. + \section1 TableViewDelegate Control + + \image qtquickcontrols-tableviewdelegate.png + + A \l TableViewDelegate is a delegate that can be assigned to the + \l [QML]{TableView::}{delegate} property of a TableView. + + \b {See also} \l {TableView} + \section1 TreeViewDelegate Control \image qtquickcontrols-treeviewdelegate.png diff --git a/src/quicktemplates/CMakeLists.txt b/src/quicktemplates/CMakeLists.txt index 6a98ce2f89..fc051dfebe 100644 --- a/src/quicktemplates/CMakeLists.txt +++ b/src/quicktemplates/CMakeLists.txt @@ -170,6 +170,7 @@ qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_quick_tableview qquickheaderview_p_p.h qquickselectionrectangle.cpp qquickselectionrectangle_p.h qquickselectionrectangle_p_p.h + qquicktableviewdelegate.cpp qquicktableviewdelegate_p.h ) qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_quick_treeview diff --git a/src/quicktemplates/qquicktableviewdelegate.cpp b/src/quicktemplates/qquicktableviewdelegate.cpp new file mode 100644 index 0000000000..c62f0de810 --- /dev/null +++ b/src/quicktemplates/qquicktableviewdelegate.cpp @@ -0,0 +1,249 @@ +// Copyright (C) 2024 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 "qquicktableviewdelegate_p.h" + +#include <QtQuickTemplates2/private/qquickitemdelegate_p_p.h> +#include <QtQuick/private/qquicktaphandler_p.h> +#include <QtQuick/private/qquicktableview_p.h> + +#include <QtCore/qpointer.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype TableViewDelegate + \inherits ItemDelegate + \inqmlmodule QtQuick.Controls + \since 6.9 + \ingroup qtquickcontrols-delegates + \brief A delegate that can be assigned to a TableView. + + \image qtquickcontrols-tableviewdelegate.png + + A TableViewDelegate is a delegate that can be assigned to the + \l {TableView::delegate} {delegate property} of a \l TableView. + It renders each cell of the table in the view using the application style. + + \code + TableView { + anchors.fill: parent + delegate: TableViewDelegate {} + // model: yourModel + } + \endcode + + TableViewDelegate inherits \l ItemDelegate, which means that it's composed of + two items: a \l[QML]{Control::}{background} and a \l [QML]{Control::}{contentItem}. + + The position of the contentItem is controlled with \l [QML]{Control::}{padding}. + + \section2 Interacting with pointers + TableViewDelegate inherits \l ItemDelegate. This means that it will emit signals such as + \l {AbstractButton::clicked()}{clicked} when the user clicks on the delegate. + You can connect to this signal to implement application-specific functionality. + + However, the ItemDelegate API does not give you information about the position of the + click, or which modifiers are being held. If this is needed, a better approach would + be to use pointer handlers, for example: + + \code + TableView { + id: tableView + delegate: TableViewDelegate { + TapHandler { + acceptedButtons: Qt.RightButton + onTapped: someContextMenu.open() + } + + TapHandler { + acceptedModifiers: Qt.ControlModifier + onTapped: tableView.doSomethingToCell(row, column) + } + } + } + \endcode + + \note If you want to disable the default behavior that occurs when the + user clicks on the delegate (like changing the current index), you can set + \l {TableView::pointerNavigationEnabled}{pointerNavigationEnabled} to \c false. + + \section2 Editing cells in the table + TableViewDelegate has a default \l {TableView::editDelegate}{edit delegate} assigned. + If \l TableView has \l {TableView::editTriggers}{edit triggers} set, and + the \l {TableView::model}{model} has support for \l {Editing cells} {editing model items}, + then the user can activate any of the edit triggers to edit the text of + the \l {TableViewDelegate::current}{current} table cell. + + The default edit delegate will use the \c {Qt.EditRole} to read and write data to the + \l {TableView::model}{model}. If you need to use another role, or otherwise have needs + outside what the default edit delegate offers, you can always assign your own delegate + to \l {TableView::editDelegate}{TableView.editDelegate}. + + \sa {Customizing TableViewDelegate}, {TableView} +*/ + +/*! + \qmlproperty TableView QtQuick.Controls::TableViewDelegate::tableView + + This property points to the \l TableView that contains the delegate item. +*/ + +/*! + \qmlproperty bool QtQuick.Controls::TableViewDelegate::current + + This property holds whether or not the delegate represents the + \l {QItemSelectionModel::currentIndex()}{current index} + in the \l {TableView::selectionModel}{selection model}. +*/ + +/*! + \qmlproperty bool QtQuick.Controls::TableViewDelegate::selected + + This property holds whether or not the delegate represents a + \l {QItemSelectionModel::selection()}{selected index} + in the \l {TableView::selectionModel}{selection model}. +*/ + +/*! + \qmlproperty bool QtQuick.Controls::TableViewDelegate::editing + + This property holds whether or not the delegate is being \l {Editing cells}{edited}. +*/ + +using namespace Qt::Literals::StringLiterals; + +class QQuickTableViewDelegatePrivate : public QQuickItemDelegatePrivate +{ +public: + Q_DECLARE_PUBLIC(QQuickTableViewDelegate) + + QPalette defaultPalette() const override; + +public: + QPointer<QQuickTableView> m_tableView; + bool m_current = false; + bool m_selected = false; + bool m_editing = false; +}; + +QQuickTableViewDelegate::QQuickTableViewDelegate(QQuickItem *parent) + : QQuickItemDelegate(*(new QQuickTableViewDelegatePrivate), parent) +{ + Q_D(QQuickTableViewDelegate); + + auto tapHandler = new QQuickTapHandler(this); + tapHandler->setAcceptedModifiers(Qt::NoModifier); + + // Since we override mousePressEvent to avoid QQuickAbstractButton from blocking + // pointer handlers, we inform the button about its pressed state from the tap + // handler instead. This will ensure that we emit button signals like + // pressed, clicked, and doubleClicked. + connect(tapHandler, &QQuickTapHandler::pressedChanged, this, [this, d, tapHandler] { + auto view = tableView(); + if (!view || !view->pointerNavigationEnabled()) + return; + + const QQuickHandlerPoint p = tapHandler->point(); + if (tapHandler->isPressed()) + d->handlePress(p.position(), 0); + else if (tapHandler->tapCount() > 0) + d->handleRelease(p.position(), 0); + else + d->handleUngrab(); + + if (tapHandler->tapCount() > 1 && !tapHandler->isPressed()) + emit doubleClicked(); + }, Qt::DirectConnection); +} + +void QQuickTableViewDelegate::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickTableViewDelegate); + + const auto view = d->m_tableView; + if (view && view->pointerNavigationEnabled()) { + // Ignore mouse events so that we don't block our own pointer handlers, or + // pointer handlers in e.g TreeView, TableView, or SelectionRectangle. Instead + // we call out to the needed mouse handling functions in QQuickAbstractButton directly + // from our pointer handlers, to ensure that we continue to work as a button. + event->ignore(); + return; + } + + QQuickItemDelegate::mousePressEvent(event); +} + +QPalette QQuickTableViewDelegatePrivate::defaultPalette() const +{ + return QQuickTheme::palette(QQuickTheme::ItemView); +} + +QFont QQuickTableViewDelegate::defaultFont() const +{ + return QQuickTheme::font(QQuickTheme::ItemView); +} + +bool QQuickTableViewDelegate::current() const +{ + return d_func()->m_current; +} + +void QQuickTableViewDelegate::setCurrent(bool current) +{ + Q_D(QQuickTableViewDelegate); + if (d->m_current == current) + return; + + d->m_current = current; + emit currentChanged(); +} + +bool QQuickTableViewDelegate::selected() const +{ + return d_func()->m_selected; +} + +void QQuickTableViewDelegate::setSelected(bool selected) +{ + Q_D(QQuickTableViewDelegate); + if (d->m_selected == selected) + return; + + d->m_selected = selected; + emit selectedChanged(); +} + +bool QQuickTableViewDelegate::editing() const +{ + return d_func()->m_editing; +} + +void QQuickTableViewDelegate::setEditing(bool editing) +{ + Q_D(QQuickTableViewDelegate); + if (d->m_editing == editing) + return; + + d->m_editing = editing; + emit editingChanged(); +} + +QQuickTableView *QQuickTableViewDelegate::tableView() const +{ + return d_func()->m_tableView; +} + +void QQuickTableViewDelegate::setTableView(QQuickTableView *tableView) +{ + Q_D(QQuickTableViewDelegate); + if (d->m_tableView == tableView) + return; + + d->m_tableView = tableView; + emit tableViewChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qquicktableviewdelegate_p.cpp" diff --git a/src/quicktemplates/qquicktableviewdelegate_p.h b/src/quicktemplates/qquicktableviewdelegate_p.h new file mode 100644 index 0000000000..80667b425f --- /dev/null +++ b/src/quicktemplates/qquicktableviewdelegate_p.h @@ -0,0 +1,72 @@ +// Copyright (C) 2024 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 + +#ifndef QQUICKTABLEVIEWDELEGATE_P_H +#define QQUICKTABLEVIEWDELEGATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/qquickitem.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtQuickTemplates2/private/qquickitemdelegate_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickTableView; +class QQuickTableViewDelegatePrivate; + +class Q_QUICKTEMPLATES2_EXPORT QQuickTableViewDelegate : public QQuickItemDelegate +{ + Q_OBJECT + + // Required properties + Q_PROPERTY(QQuickTableView *tableView READ tableView WRITE setTableView NOTIFY tableViewChanged REQUIRED FINAL) + Q_PROPERTY(bool current READ current WRITE setCurrent NOTIFY currentChanged REQUIRED FINAL) + Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged REQUIRED FINAL) + Q_PROPERTY(bool editing READ editing WRITE setEditing NOTIFY editingChanged REQUIRED FINAL) + + QML_NAMED_ELEMENT(TableViewDelegate) + QML_ADDED_IN_VERSION(6, 9) + +public: + explicit QQuickTableViewDelegate(QQuickItem *parent = nullptr); + + bool current() const; + void setCurrent(bool current); + + bool selected() const; + void setSelected(bool selected); + + bool editing() const; + void setEditing(bool editing); + + QQuickTableView *tableView() const; + void setTableView(QQuickTableView *tableView); + +Q_SIGNALS: + void tableViewChanged(); + void currentChanged(); + void selectedChanged(); + void editingChanged(); + +protected: + QFont defaultFont() const override; + void mousePressEvent(QMouseEvent *event) override; + +private: + Q_DISABLE_COPY(QQuickTableViewDelegate) + Q_DECLARE_PRIVATE(QQuickTableViewDelegate) +}; + +QT_END_NAMESPACE + +#endif // QQUICKTABLEVIEWDELEGATE_P_H diff --git a/tests/auto/quickcontrols/CMakeLists.txt b/tests/auto/quickcontrols/CMakeLists.txt index b7669d4f54..7d3815b606 100644 --- a/tests/auto/quickcontrols/CMakeLists.txt +++ b/tests/auto/quickcontrols/CMakeLists.txt @@ -54,6 +54,10 @@ add_subdirectory(styleimportscompiletimematerial) add_subdirectory(styleimportscompiletimeqmlonly) add_subdirectory(translation) +if (QT_FEATURE_quick_tableview) + add_subdirectory(qquicktableviewdelegate) +endif() + if (QT_FEATURE_quick_treeview) add_subdirectory(qquicktreeviewdelegate) endif() diff --git a/tests/auto/quickcontrols/qquicktableviewdelegate/BLACKLIST b/tests/auto/quickcontrols/qquicktableviewdelegate/BLACKLIST new file mode 100644 index 0000000000..ed6d7fd2cf --- /dev/null +++ b/tests/auto/quickcontrols/qquicktableviewdelegate/BLACKLIST @@ -0,0 +1,6 @@ +# perhaps related to QTBUG-103072 +[dragToSelect] +android +# perhaps related to QTBUG-103064 +[pressAndHoldToSelect] +android diff --git a/tests/auto/quickcontrols/qquicktableviewdelegate/CMakeLists.txt b/tests/auto/quickcontrols/qquicktableviewdelegate/CMakeLists.txt new file mode 100644 index 0000000000..9f045719ac --- /dev/null +++ b/tests/auto/quickcontrols/qquicktableviewdelegate/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qquicktableviewdelegate LANGUAGES C CXX ASM) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +##################################################################### +## tst_qquicktableviewdelegate Test: +##################################################################### + +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + data/*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_qquicktableviewdelegate + SOURCES + testmodel.h testmodel.cpp + tst_qquicktableviewdelegate.cpp + LIBRARIES + Qt::QuickPrivate + Qt::QuickTestUtilsPrivate + Qt::QuickTemplates2Private + TESTDATA ${test_data} +) + +qt_internal_extend_target(tst_qquicktableviewdelegate CONDITION ANDROID OR IOS + DEFINES + QT_QMLTEST_DATADIR=":/data" +) + +qt_internal_extend_target(tst_qquicktableviewdelegate CONDITION NOT ANDROID AND NOT IOS + DEFINES + QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data" +) diff --git a/tests/auto/quickcontrols/qquicktableviewdelegate/data/unmodified.qml b/tests/auto/quickcontrols/qquicktableviewdelegate/data/unmodified.qml new file mode 100644 index 0000000000..44b3d09549 --- /dev/null +++ b/tests/auto/quickcontrols/qquicktableviewdelegate/data/unmodified.qml @@ -0,0 +1,32 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import TestModel +import QtQuick.Controls + +Item { + width: 800 + height: 600 + + property alias tableView: tableView + property alias selectionRectangle: selectionRectangle + + TableView { + id: tableView + anchors.fill:parent + anchors.margins: 10 + model: TestModel {} + clip: true + + delegate: TableViewDelegate { + readonly property string displayText: contentItem ? contentItem.text : null + } + selectionModel: ItemSelectionModel { model: tableView.model } + } + + SelectionRectangle { + id: selectionRectangle + target: tableView + } +} diff --git a/tests/auto/quickcontrols/qquicktableviewdelegate/testmodel.cpp b/tests/auto/quickcontrols/qquicktableviewdelegate/testmodel.cpp new file mode 100644 index 0000000000..ec72977683 --- /dev/null +++ b/tests/auto/quickcontrols/qquicktableviewdelegate/testmodel.cpp @@ -0,0 +1,63 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "testmodel.h" + +TestModel::TestModel(QObject *parent) + : QAbstractTableModel(parent) + , m_columnCount(1) + , m_rowCount(1) +{ + m_data.insert({0,0}, {{Qt::DisplayRole, "0,0"},}); +} + +void TestModel::appendColumn() +{ + beginInsertColumns(QModelIndex{}, m_columnCount, m_columnCount); + ++m_columnCount; + endInsertColumns(); +} + +void TestModel::appendRow() +{ + beginInsertRows(QModelIndex{}, m_rowCount, m_rowCount); + ++m_rowCount; + endInsertRows(); +} + +bool TestModel::setData(int row, int column, const QVariant &value, int role) +{ + return setData(index(row, column), value, role); +} + +QVariant TestModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant{}; + + auto it_table = m_data.find({index.row(), index.column()}); + if (it_table == m_data.end()) + return QVariant{}; + + const CellData &cellData = it_table.value(); + auto it_cell = cellData.find(role); + return (it_cell == cellData.end()) ? QVariant{} : it_cell.value(); +} + +bool TestModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + const Position pos{index.row(), index.column()}; + auto it_table = m_data.find(pos); + if (it_table != m_data.end()) + it_table.value().insert(role, value); + else + m_data.insert(pos, {{role, value},}); + + emit dataChanged(index, index); + + return true; +} + diff --git a/tests/auto/quickcontrols/qquicktableviewdelegate/testmodel.h b/tests/auto/quickcontrols/qquicktableviewdelegate/testmodel.h new file mode 100644 index 0000000000..83509b2cf2 --- /dev/null +++ b/tests/auto/quickcontrols/qquicktableviewdelegate/testmodel.h @@ -0,0 +1,43 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TESTMODEL_H +#define TESTMODEL_H + +#include <QtCore/qabstractitemmodel.h> + +class TestModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit TestModel(QObject *parent = nullptr); + + void appendColumn(); + void appendRow(); + + bool setData(int row, int column, const QVariant &value, int role); + + // QAbstractItemModel interface + int rowCount(const QModelIndex & = QModelIndex{}) const override { return m_rowCount; } + int columnCount(const QModelIndex & = QModelIndex{}) const override { return m_columnCount; } + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + +private: + // use appendRow() instead + using QAbstractTableModel::insertRow; + using QAbstractTableModel::insertRows; + // use appendColumn() instead + using QAbstractTableModel::insertColumn; + using QAbstractTableModel::insertColumns; + +private: + using CellData = QHash<int, QVariant>; + using Position = QPair<int, int>; + QHash<Position, CellData> m_data; + int m_columnCount = 0; + int m_rowCount = 0; +}; + +#endif // TESTMODEL_H diff --git a/tests/auto/quickcontrols/qquicktableviewdelegate/tst_qquicktableviewdelegate.cpp b/tests/auto/quickcontrols/qquicktableviewdelegate/tst_qquicktableviewdelegate.cpp new file mode 100644 index 0000000000..ecf0fe4bc6 --- /dev/null +++ b/tests/auto/quickcontrols/qquicktableviewdelegate/tst_qquicktableviewdelegate.cpp @@ -0,0 +1,285 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "testmodel.h" + +#include <QtQuickTest/quicktest.h> + +#include <QtQuick/qquickview.h> +#include <QtQuick/private/qquicktableview_p.h> +#include <QtQuick/private/qquicktableview_p_p.h> + +#include <QtQuickTemplates2/private/qquicktableviewdelegate_p.h> + +#include <QtQuickTestUtils/private/viewtestutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> + +#include <QtTest/QtTest> + +using namespace QQuickViewTestUtils; +using namespace QQuickVisualTestUtils; + +#define LOAD_TABLEVIEW(fileName) \ + view->setSource(testFileUrl(fileName)); \ + view->show(); \ + QVERIFY(QTest::qWaitForWindowActive(view.get())); \ + QQuickTableView *tableView = getTableView(); \ + QVERIFY(tableView); \ + [[maybe_unused]] auto tableViewPrivate = QQuickTableViewPrivate::get(tableView); \ + [[maybe_unused]] auto model = tableView->model().value<TestModel *>() + +// ######################################################## + +class tst_qquicktableviewdelegate : public QQmlDataTest +{ + Q_OBJECT + +public: + tst_qquicktableviewdelegate(); + +private slots: + void initTestCase() override; + void showTableView(); + void checkProperties(); + void checkCurrentIndex(); + void checkClickedSignal_data(); + void checkClickedSignal(); + void clearSelectionOnClick(); + void dragToSelect(); + void pressAndHoldToSelect(); + +private: + QQuickTableView *getTableView(); + QQuickTableViewDelegate *getDelegateItem(int row, int column); + +private: + std::unique_ptr<QQuickView> view = nullptr; +}; + +tst_qquicktableviewdelegate::tst_qquicktableviewdelegate() + : QQmlDataTest(QT_QMLTEST_DATADIR) +{ +} + +void tst_qquicktableviewdelegate::initTestCase() +{ + QQmlDataTest::initTestCase(); + qmlRegisterType<TestModel>("TestModel", 1, 0, "TestModel"); + view.reset(createView()); +} + +void tst_qquicktableviewdelegate::showTableView() +{ + LOAD_TABLEVIEW("unmodified.qml"); + QCOMPARE(tableViewPrivate->loadedRows.count(), 1); + QCOMPARE(tableViewPrivate->loadedColumns.count(), 1); +} + +void tst_qquicktableviewdelegate::checkProperties() +{ + LOAD_TABLEVIEW("unmodified.qml"); + QCOMPARE(tableViewPrivate->loadedRows.count(), 1); + QCOMPARE(tableViewPrivate->loadedColumns.count(), 1); + + const QQuickTableViewDelegate *item = getDelegateItem(0, 0); + QVERIFY(item); + QCOMPARE(item->property("displayText"), "0,0"); + QCOMPARE(item->tableView(), tableView); + + constexpr auto TEXT_0_1 = "0,1"; + model->appendColumn(); + model->setData(0, 1, TEXT_0_1, Qt::DisplayRole); + QVERIFY(QQuickTest::qWaitForPolish(tableView)); + + QCOMPARE(tableViewPrivate->loadedRows.count(), 1); + QCOMPARE(tableViewPrivate->loadedColumns.count(), 2); + + item = getDelegateItem(0, 1); + QVERIFY(item); + QCOMPARE(item->property("displayText"), TEXT_0_1); + QCOMPARE(item->tableView(), tableView); + + constexpr auto TEXT_1_0 = "1,0"; + constexpr auto TEXT_1_1 = "1,1"; + model->appendRow(); + model->setData(1, 0, TEXT_1_0, Qt::DisplayRole); + model->setData(1, 1, TEXT_1_1, Qt::DisplayRole); + QVERIFY(QQuickTest::qWaitForPolish(tableView)); + + QCOMPARE(tableViewPrivate->loadedRows.count(), 2); + QCOMPARE(tableViewPrivate->loadedColumns.count(), 2); + + item = getDelegateItem(1, 0); + QVERIFY(item); + QCOMPARE(item->property("displayText"), TEXT_1_0); + QCOMPARE(item->tableView(), tableView); + + item = getDelegateItem(1, 1); + QVERIFY(item); + QCOMPARE(item->property("displayText"), TEXT_1_1); + QCOMPARE(item->tableView(), tableView); +} + +void tst_qquicktableviewdelegate::checkCurrentIndex() +{ + // Check that that a cell becomes current when you click on it + LOAD_TABLEVIEW("unmodified.qml"); + QCOMPARE(tableViewPrivate->loadedRows.count(), 1); + + const QQuickTableViewDelegate *item = getDelegateItem(0, 0); + QVERIFY(item); + QVERIFY(!item->current()); + QVERIFY(!item->selected()); + QVERIFY(!item->editing()); + QVERIFY(!tableView->hasActiveFocus()); + + // Click on the label + const QPoint localPos = QPoint(item->width() / 2, item->height() / 2); + const QPoint pos = item->window()->contentItem()->mapFromItem(item, localPos).toPoint(); + QTest::mouseClick(item->window(), Qt::LeftButton, Qt::NoModifier, pos); + QVERIFY(item->current()); + QVERIFY(!item->selected()); + QVERIFY(!item->editing()); + QVERIFY(tableView->hasActiveFocus()); + + // Select the cell + tableView->selectionModel()->setCurrentIndex(tableView->modelIndex({0, 0}), QItemSelectionModel::Select); + QVERIFY(item->current()); + QVERIFY(item->selected()); + QVERIFY(!item->editing()); +} + +void tst_qquicktableviewdelegate::checkClickedSignal_data() +{ + QTest::addColumn<bool>("pointerNavigationEnabled"); + QTest::newRow("pointer navigation enabled") << true; + QTest::newRow("pointer navigation disabled") << false; +} + +void tst_qquicktableviewdelegate::checkClickedSignal() +{ + // Check that the delegate emits clicked when clicking on the + // label, but not when clicking on the indicator. This API is + // a part of the AbstractButton API, and should work with or + // without TableView.pointerNavigationEnabled set. + QFETCH(bool, pointerNavigationEnabled); + + LOAD_TABLEVIEW("unmodified.qml"); + tableView->setPointerNavigationEnabled(pointerNavigationEnabled); + + const auto item = tableView->itemAtIndex(tableView->index(0, 0)); + QVERIFY(item); + + QSignalSpy clickedSpy(item, SIGNAL(clicked())); + + // Click on the label + const QPoint localPos = QPoint(item->width() / 2, item->height() / 2); + const QPoint pos = item->window()->contentItem()->mapFromItem(item, localPos).toPoint(); + QTest::mouseClick(item->window(), Qt::LeftButton, Qt::NoModifier, pos); + QCOMPARE(clickedSpy.size(), 1); + clickedSpy.clear(); +} + +void tst_qquicktableviewdelegate::clearSelectionOnClick() +{ + LOAD_TABLEVIEW("unmodified.qml"); + + // Select root item + const auto index = tableView->selectionModel()->model()->index(0, 0); + tableView->selectionModel()->select(index, QItemSelectionModel::Select); + QCOMPARE(tableView->selectionModel()->selectedIndexes().size(), 1); + + // Click on a cell. This should remove the selection + const auto item = getDelegateItem(0, 0); + QVERIFY(item); + const QPoint localPos = QPoint(item->width() / 2, item->height() / 2); + const QPoint pos = item->window()->contentItem()->mapFromItem(item, localPos).toPoint(); + QTest::mouseClick(item->window(), Qt::LeftButton, Qt::NoModifier, pos); + QCOMPARE(tableView->selectionModel()->selectedIndexes().size(), 0); +} + +void tst_qquicktableviewdelegate::dragToSelect() +{ + // Check that the delegate is not blocking the user from + // being able to select cells using Drag. + LOAD_TABLEVIEW("unmodified.qml"); + + // When TableView is not interactive, SelectionRectangle + // will use Drag by default. + tableView->setInteractive(false); + + QVERIFY(!tableView->selectionModel()->hasSelection()); + QCOMPARE(tableView->selectionBehavior(), QQuickTableView::SelectCells); + + model->appendColumn(); + model->setData(0, 1, "0,1", Qt::DisplayRole); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->rowCount(), 1); + + QVERIFY(QQuickTest::qWaitForPolish(tableView)); + + // Drag on from cell 0,0 to 0,1 + const auto item0_0 = getDelegateItem(0, 0); + QVERIFY(item0_0); + const auto item0_1 = getDelegateItem(0, 1); + QVERIFY(item0_1); + + QQuickWindow *window = tableView->window(); + const QPoint localPos0_0(item0_0->width() / 2, item0_0->height() / 2); + const QPoint windowPos0_0 = window->contentItem()->mapFromItem(item0_0, localPos0_0).toPoint(); + const QPoint localPos0_1(item0_1->width() / 2, item0_1->height() / 2); + const QPoint windowPos0_1 = window->contentItem()->mapFromItem(item0_1, localPos0_1).toPoint(); + + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, windowPos0_0); + QTest::mouseMove(window, windowPos0_1); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, windowPos0_1); + + QCOMPARE(tableView->selectionModel()->selectedIndexes().size(), 2); + QVERIFY(item0_0->selected()); + QVERIFY(item0_1->selected()); +} + +void tst_qquicktableviewdelegate::pressAndHoldToSelect() +{ + // Check that the delegate is not blocking the user from + // being able to select cells using PressAndHold + LOAD_TABLEVIEW("unmodified.qml"); + + // When TableView is interactive, SelectionRectangle + // will use PressAndHold by default. + tableView->setInteractive(true); + + QVERIFY(!tableView->selectionModel()->hasSelection()); + QCOMPARE(tableView->selectionBehavior(), QQuickTableView::SelectCells); + + // PressAndHold on cell 0,0 + const auto item0_0 = getDelegateItem(0, 0); + QVERIFY(item0_0); + + QQuickWindow *window = tableView->window(); + const QPoint localPos0_0(item0_0->width() / 2, item0_0->height() / 2); + const QPoint windowPos0_0 = window->contentItem()->mapFromItem(item0_0, localPos0_0).toPoint(); + + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, windowPos0_0); + QTRY_VERIFY(tableView->selectionModel()->hasSelection()); + QCOMPARE(tableView->selectionModel()->selectedIndexes().size(), 1); + QVERIFY(item0_0->selected()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, windowPos0_0); +} + +QQuickTableView *tst_qquicktableviewdelegate::getTableView() +{ + return view->rootObject()->property("tableView").value<QQuickTableView *>(); +} + +QQuickTableViewDelegate *tst_qquicktableviewdelegate::getDelegateItem(int row, int column) +{ + auto tableView = getTableView(); + if (!tableView) + return nullptr; + return qobject_cast<QQuickTableViewDelegate *>(tableView->itemAtCell({column, row})); +} + +QTEST_MAIN(tst_qquicktableviewdelegate) + +#include "tst_qquicktableviewdelegate.moc" diff --git a/tests/benchmarks/quickcontrols/creationtime/tst_creationtime.cpp b/tests/benchmarks/quickcontrols/creationtime/tst_creationtime.cpp index 8f525ea766..44edd815e5 100644 --- a/tests/benchmarks/quickcontrols/creationtime/tst_creationtime.cpp +++ b/tests/benchmarks/quickcontrols/creationtime/tst_creationtime.cpp @@ -83,8 +83,9 @@ void tst_CreationTime::basic_data() QTest::addColumn<QUrl>("url"); // Calendar is excluded because it's a singleton and can't be created. // TreeViewDelegate is excluded since it's a delegate that can only be created by TreeView. + // TableViewDelegate is excluded since it's a delegate that can only be created by TableView. addTestRowForEachControl(styleHelper.engine.data(), QQC2_IMPORT_PATH, "basic", "QtQuick/Controls/Basic", - QStringList() << "ApplicationWindow" << "Calendar" << "TreeViewDelegate"); + QStringList() << "ApplicationWindow" << "Calendar" << "TreeViewDelegate" << "TableViewDelegate"); } void tst_CreationTime::fusion() diff --git a/tests/benchmarks/quickcontrols/objectcount/tst_objectcount.cpp b/tests/benchmarks/quickcontrols/objectcount/tst_objectcount.cpp index f4119f0737..1a06cdf629 100644 --- a/tests/benchmarks/quickcontrols/objectcount/tst_objectcount.cpp +++ b/tests/benchmarks/quickcontrols/objectcount/tst_objectcount.cpp @@ -64,8 +64,9 @@ static void initTestRows(QQmlEngine *engine) { // Calendar is excluded because it's a singleton and can't be created. // TreeViewDelegate is excluded since it's a delegate that can only be created by TreeView. + // TableViewDelegate is excluded since it's a delegate that can only be created by TableView. addTestRowForEachControl(engine, QQC2_IMPORT_PATH, "basic", "QtQuick/Controls/Basic", - QStringList() << "Calendar" << "TreeViewDelegate"); + QStringList() << "Calendar" << "TreeViewDelegate" << "TableViewDelegate"); addTestRowForEachControl(engine, QQC2_IMPORT_PATH, "fusion", "QtQuick/Controls/Fusion", QStringList() << "ButtonPanel" << "CheckIndicator" << "RadioIndicator" << "SliderGroove" << "SliderHandle" << "SwitchIndicator" << "TreeViewDelegate"); |