aboutsummaryrefslogtreecommitdiffstats
path: root/src/quicktemplates/qquickselectionrectangle.cpp
diff options
context:
space:
mode:
authorMitch Curtis <mitch.curtis@qt.io>2022-11-18 15:15:16 +0800
committerMitch Curtis <mitch.curtis@qt.io>2022-12-01 10:26:20 +0800
commit4bd87b903b355b53e3105ba1ae7c154c4e55cdaf (patch)
treecc2edb597f0d5871302eb86e9dda78217384a5aa /src/quicktemplates/qquickselectionrectangle.cpp
parent786e1748d4469c135a922a221024f3f9c421c0de (diff)
Remove "2" from Qt Quick Controls directories
Qt Quick Controls 2 was named that way because it was a follow-up to Qt Quick Controls 1.x. Now that Qt Quick Controls 1 is no longer supported, we don't need to have "2" in the name. Work on this was already started for the documentation in 1abdfe5d5a052f2298b7bf657513dfa7e0c66a56. By doing this renaming a few weeks before feature freeze, it won't affect the release but still results in as little time possible spent manually fixing conflicts in cherry-picks from non-LTS releases as a result of the renaming. This patch does the following: - Renames directories. - Adapts CMakeLists.txt and other files to account for the new paths. A follow-up patch will handle documentation. It does not touch library names or other user-facing stuff, as that will have to be done in Qt 7. Task-number: QTBUG-95413 Change-Id: I170d8db19033ee71e495ff0c5c1a517a41ed7634 Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
Diffstat (limited to 'src/quicktemplates/qquickselectionrectangle.cpp')
-rw-r--r--src/quicktemplates/qquickselectionrectangle.cpp577
1 files changed, 577 insertions, 0 deletions
diff --git a/src/quicktemplates/qquickselectionrectangle.cpp b/src/quicktemplates/qquickselectionrectangle.cpp
new file mode 100644
index 0000000000..612b53dca2
--- /dev/null
+++ b/src/quicktemplates/qquickselectionrectangle.cpp
@@ -0,0 +1,577 @@
+// Copyright (C) 2021 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 "qquickselectionrectangle_p.h"
+#include "qquickselectionrectangle_p_p.h"
+
+#include <QtQml/qqmlinfo.h>
+#include <QtQuick/private/qquickdraghandler_p.h>
+#include <QtQuick/private/qquickhoverhandler_p.h>
+
+#include <QtQuick/private/qquicktableview_p_p.h>
+
+#include "qquickscrollview_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype SelectionRectangle
+ \inherits Control
+//! \instantiates QQuickSelectionRectangle
+ \inqmlmodule QtQuick.Controls
+ \since 6.2
+ \ingroup utilities
+ \brief Used to select table cells inside a TableView.
+
+ \image qtquickcontrols2-selectionrectangle.png
+
+ SelectionRectangle is used for selecting table cells in a TableView. It lets
+ the user start a selection by doing a pointer drag inside the viewport, or by
+ doing a long press on top of a cell.
+
+ For a SelectionRectangle to be able to select cells, TableView must have
+ an ItemSelectionModel assigned. The ItemSelectionModel will store any
+ selections done on the model, and can be used for querying
+ which cells that the user has selected.
+
+ The following example shows how you can make a SelectionRectangle target
+ a TableView:
+
+ \snippet qtquickcontrols2-selectionrectangle.qml 0
+
+ \note A SelectionRectangle itself is not shown as part of a selection. Only the
+ delegates (like topLeftHandle and bottomRightHandle) are used.
+ You should also consider \l {Selecting items}{rendering the TableView delegate as selected}.
+
+ \sa TableView, TableView::selectionModel, ItemSelectionModel
+*/
+
+/*!
+ \qmlproperty Item QtQuick.Controls::SelectionRectangle::target
+
+ This property holds the TableView on which the
+ SelectionRectangle should act.
+*/
+
+/*!
+ \qmlproperty bool QtQuick.Controls::SelectionRectangle::active
+ \readonly
+
+ This property is \c true while the user is performing a
+ selection. The selection will be active from the time the
+ the user starts to select, and until the selection is
+ removed again, for example from tapping inside the viewport.
+*/
+
+/*!
+ \qmlproperty bool QtQuick.Controls::SelectionRectangle::dragging
+ \readonly
+
+ This property is \c true whenever the user is doing a pointer drag or
+ a handle drag to adjust the selection rectangle.
+*/
+
+/*!
+ \qmlproperty Component QtQuick.Controls::SelectionRectangle::topLeftHandle
+
+ This property holds the delegate that will be shown on the center of the
+ top-left corner of the selection rectangle. When a handle is
+ provided, the user can drag it to adjust the selection.
+
+ The handle is not hidden by default when a selection is removed.
+ Instead, this is the responsibility of the delegate, to open up for
+ custom fade-out animations. The easiest way to ensure that the handle
+ ends up hidden, is to simply bind \l visible to the the \l active
+ state of the SelectionRectangle:
+
+ \qml
+ SelectionRectangle {
+ topLeftHandle: Rectangle {
+ width: 20
+ height: 20
+ visible: SelectionRectangle.control.active
+ }
+ }
+ \endqml
+
+ Set this property to \c null if you don't want a selection handle on the top-left.
+
+ \sa bottomRightHandle
+*/
+
+/*!
+ \qmlproperty Component QtQuick.Controls::SelectionRectangle::bottomRightHandle
+
+ This property holds the delegate that will be shown on the center of the
+ top-left corner of the selection rectangle. When a handle is
+ provided, the user can drag it to adjust the selection.
+
+ The handle is not hidden by default when a selection is removed.
+ Instead, this is the responsibility of the delegate, to open up for
+ custom fade-out animations. The easiest way to ensure that the handle
+ ends up hidden, is to simply bind \l visible to the the \l active
+ state of the SelectionRectangle:
+
+ \qml
+ SelectionRectangle {
+ bottomRightHandle: Rectangle {
+ width: 20
+ height: 20
+ visible: SelectionRectangle.control.active
+ }
+ }
+ \endqml
+
+ Set this property to \c null if you don't want a selection handle on the bottom-right.
+
+ \sa topLeftHandle
+*/
+
+/*!
+ \qmlproperty enumeration QtQuick.Controls::SelectionRectangle::selectionMode
+
+ This property holds when a selection should start.
+
+ \value SelectionRectangle.Drag A selection will start by doing a pointer drag inside the viewport
+ \value SelectionRectangle.PressAndHold A selection will start by doing a press and hold on top a cell
+ \value SelectionRectangle.Auto SelectionRectangle will choose which mode to use based on the target
+ and the platform. This normally means \c PressAndHold on touch based platforms, and \c Drag on desktop.
+ However, \c Drag will only be used if it doesn't conflict with flicking. This means that
+ TableView will need to be configured with \c interactive set to \c false, or placed
+ inside a ScrollView (where flicking, by default, is off for mouse events), for \c Drag to be chosen.
+
+ The default value is \c Auto.
+*/
+
+/*!
+ \qmlattachedproperty SelectionRectangle QtQuick::SelectionRectangle::control
+
+ This attached property holds the SelectionRectangle that manages the delegate instance.
+ It is attached to each handle instance.
+*/
+
+/*!
+ \qmlattachedproperty bool QtQuick::SelectionRectangle::dragging
+
+ This attached property will be \c true if the user is dragging on the handle.
+ It is attached to each handle instance.
+*/
+
+QQuickSelectionRectanglePrivate::QQuickSelectionRectanglePrivate()
+ : QQuickControlPrivate()
+{
+ m_tapHandler = new QQuickTapHandler();
+ m_dragHandler = new QQuickDragHandler();
+ m_dragHandler->setTarget(nullptr);
+
+ QObject::connect(&m_scrollTimer, &QTimer::timeout, [&]{
+ if (m_topLeftHandle && m_draggedHandle == m_topLeftHandle.data())
+ m_selectable->setSelectionStartPos(m_scrollToPoint);
+ else
+ m_selectable->setSelectionEndPos(m_scrollToPoint);
+ updateHandles();
+ const QSizeF dist = m_selectable->scrollTowardsSelectionPoint(m_scrollToPoint, m_scrollSpeed);
+ m_scrollToPoint.rx() += dist.width() > 0 ? m_scrollSpeed.width() : -m_scrollSpeed.width();
+ m_scrollToPoint.ry() += dist.height() > 0 ? m_scrollSpeed.height() : -m_scrollSpeed.height();
+ m_scrollSpeed = QSizeF(qAbs(dist.width() * 0.007), qAbs(dist.height() * 0.007));
+ });
+
+ QObject::connect(m_tapHandler, &QQuickTapHandler::tapped, [this] {
+ updateActiveState(false);
+ });
+
+ QObject::connect(m_tapHandler, &QQuickTapHandler::longPressed, [this]() {
+ const QPointF pos = m_tapHandler->point().pressPosition();
+ if (!m_selectable->canStartSelection(pos))
+ return;
+ if (handleUnderPos(pos) != nullptr) {
+ // Don't allow press'n'hold to start a new
+ // selection if it started on top of a handle.
+ return;
+ }
+ if (!m_alwaysAcceptPressAndHold) {
+ if (m_selectionMode == QQuickSelectionRectangle::Auto) {
+ // In Auto mode, we only accept press and hold from touch
+ if (m_tapHandler->point().device()->pointerType() != QPointingDevice::PointerType::Finger)
+ return;
+ } else if (m_selectionMode != QQuickSelectionRectangle::PressAndHold) {
+ return;
+ }
+ }
+
+ m_selectable->clearSelection();
+ m_selectable->setSelectionStartPos(pos);
+ m_selectable->setSelectionEndPos(pos);
+ updateHandles();
+ updateActiveState(true);
+ });
+
+ QObject::connect(m_dragHandler, &QQuickDragHandler::activeChanged, [this]() {
+ const QPointF startPos = m_dragHandler->centroid().pressPosition();
+ const QPointF dragPos = m_dragHandler->centroid().position();
+
+ if (m_dragHandler->active()) {
+ if (!m_selectable->canStartSelection(startPos))
+ return;
+ m_selectable->clearSelection();
+ m_selectable->setSelectionStartPos(startPos);
+ m_selectable->setSelectionEndPos(dragPos);
+ m_draggedHandle = nullptr;
+ updateHandles();
+ updateActiveState(true);
+ updateDraggingState(true);
+ } else {
+ m_scrollTimer.stop();
+ m_selectable->normalizeSelection();
+ updateDraggingState(false);
+ }
+ });
+
+ QObject::connect(m_dragHandler, &QQuickDragHandler::centroidChanged, [this]() {
+ if (!m_dragging)
+ return;
+ const QPointF pos = m_dragHandler->centroid().position();
+ m_selectable->setSelectionEndPos(pos);
+ updateHandles();
+ scrollTowardsPos(pos);
+ });
+}
+
+void QQuickSelectionRectanglePrivate::scrollTowardsPos(const QPointF &pos)
+{
+ m_scrollToPoint = pos;
+ if (m_scrollTimer.isActive())
+ return;
+
+ const QSizeF dist = m_selectable->scrollTowardsSelectionPoint(m_scrollToPoint, m_scrollSpeed);
+ if (!dist.isNull())
+ m_scrollTimer.start(1);
+}
+
+QQuickItem *QQuickSelectionRectanglePrivate::handleUnderPos(const QPointF &pos)
+{
+ const auto handlerTarget = m_selectable->selectionPointerHandlerTarget();
+ if (m_topLeftHandle) {
+ const QPointF localPos = m_topLeftHandle->mapFromItem(handlerTarget, pos);
+ if (m_topLeftHandle->contains(localPos))
+ return m_topLeftHandle.data();
+ }
+
+ if (m_bottomRightHandle) {
+ const QPointF localPos = m_bottomRightHandle->mapFromItem(handlerTarget, pos);
+ if (m_bottomRightHandle->contains(localPos))
+ return m_bottomRightHandle.data();
+ }
+
+ return nullptr;
+}
+
+void QQuickSelectionRectanglePrivate::updateDraggingState(bool dragging)
+{
+ if (dragging != m_dragging) {
+ m_dragging = dragging;
+ emit q_func()->draggingChanged();
+ }
+
+ if (auto attached = getAttachedObject(m_draggedHandle))
+ attached->setDragging(dragging);
+}
+
+void QQuickSelectionRectanglePrivate::updateActiveState(bool active)
+{
+ if (active == m_active)
+ return;
+
+ m_active = active;
+ emit q_func()->activeChanged();
+}
+
+QQuickItem *QQuickSelectionRectanglePrivate::createHandle(QQmlComponent *delegate, Qt::Corner corner)
+{
+ Q_Q(QQuickSelectionRectangle);
+
+ // Incubate the handle
+ QObject *obj = delegate->beginCreate(QQmlEngine::contextForObject(q));
+ QQuickItem *handleItem = qobject_cast<QQuickItem*>(obj);
+ const auto handlerTarget = m_selectable->selectionPointerHandlerTarget();
+ handleItem->setParentItem(handlerTarget);
+ if (auto attached = getAttachedObject(handleItem))
+ attached->setControl(q);
+ delegate->completeCreate();
+ if (handleItem->z() == 0)
+ handleItem->setZ(100);
+
+ // Add pointer handlers to it
+ QQuickDragHandler *dragHandler = new QQuickDragHandler();
+ dragHandler->setTarget(nullptr);
+ dragHandler->setParentItem(handleItem);
+ dragHandler->setGrabPermissions(QQuickPointerHandler::CanTakeOverFromAnything);
+
+ QQuickHoverHandler *hoverHandler = new QQuickHoverHandler();
+ hoverHandler->setTarget(nullptr);
+ hoverHandler->setParentItem(handleItem);
+ hoverHandler->setCursorShape(Qt::SizeFDiagCursor);
+ hoverHandler->setBlocking(true);
+
+ QObject::connect(dragHandler, &QQuickDragHandler::activeChanged, [this, corner, handleItem, dragHandler]() {
+ if (dragHandler->active()) {
+ const QPointF localPos = dragHandler->centroid().position();
+ const QPointF pos = handleItem->mapToItem(handleItem->parentItem(), localPos);
+ if (corner == Qt::TopLeftCorner)
+ m_selectable->setSelectionStartPos(pos);
+ else
+ m_selectable->setSelectionEndPos(pos);
+
+ m_draggedHandle = handleItem;
+ updateHandles();
+ updateDraggingState(true);
+ QGuiApplication::setOverrideCursor(Qt::SizeFDiagCursor);
+ } else {
+ m_scrollTimer.stop();
+ m_selectable->normalizeSelection();
+ updateDraggingState(false);
+ QGuiApplication::restoreOverrideCursor();
+ }
+ });
+
+ QObject::connect(dragHandler, &QQuickDragHandler::centroidChanged, [this, corner, handleItem, dragHandler]() {
+ if (!m_dragging)
+ return;
+
+ const QPointF localPos = dragHandler->centroid().position();
+ const QPointF pos = handleItem->mapToItem(handleItem->parentItem(), localPos);
+ if (corner == Qt::TopLeftCorner)
+ m_selectable->setSelectionStartPos(pos);
+ else
+ m_selectable->setSelectionEndPos(pos);
+
+ updateHandles();
+ scrollTowardsPos(pos);
+ });
+
+ return handleItem;
+}
+
+void QQuickSelectionRectanglePrivate::updateHandles()
+{
+ if (!m_selectable)
+ return;
+
+ const QRectF rect = m_selectable->selectionRectangle().normalized();
+
+ if (!m_topLeftHandle && m_topLeftHandleDelegate)
+ m_topLeftHandle.reset(createHandle(m_topLeftHandleDelegate, Qt::TopLeftCorner));
+
+ if (!m_bottomRightHandle && m_bottomRightHandleDelegate)
+ m_bottomRightHandle.reset(createHandle(m_bottomRightHandleDelegate, Qt::BottomRightCorner));
+
+ if (m_topLeftHandle) {
+ m_topLeftHandle->setX(rect.x() - (m_topLeftHandle->width() / 2));
+ m_topLeftHandle->setY(rect.y() - (m_topLeftHandle->height() / 2));
+ }
+
+ if (m_bottomRightHandle) {
+ m_bottomRightHandle->setX(rect.x() + rect.width() - (m_bottomRightHandle->width() / 2));
+ m_bottomRightHandle->setY(rect.y() + rect.height() - (m_bottomRightHandle->height() / 2));
+ }
+}
+
+void QQuickSelectionRectanglePrivate::connectToTarget()
+{
+ // To support QuickSelectionRectangle::Auto, we need to listen for changes to the target
+ if (const auto flickable = qobject_cast<QQuickFlickable *>(m_target)) {
+ connect(flickable, &QQuickFlickable::interactiveChanged, this, &QQuickSelectionRectanglePrivate::updateSelectionMode);
+ }
+}
+
+void QQuickSelectionRectanglePrivate::updateSelectionMode()
+{
+ Q_Q(QQuickSelectionRectangle);
+
+ const bool enabled = q->isEnabled();
+ m_tapHandler->setEnabled(enabled);
+
+ if (m_selectionMode == QQuickSelectionRectangle::Auto) {
+ if (qobject_cast<QQuickScrollView *>(m_target->parentItem())) {
+ // ScrollView allows flicking with touch, but not with mouse. So we do
+ // the same here: you can drag to select with a mouse, but not with touch.
+ m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse);
+ m_dragHandler->setEnabled(enabled);
+ } else if (const auto flickable = qobject_cast<QQuickFlickable *>(m_target)) {
+ m_dragHandler->setEnabled(enabled && !flickable->isInteractive());
+ } else {
+ m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse);
+ m_dragHandler->setEnabled(enabled);
+ }
+ } else if (m_selectionMode == QQuickSelectionRectangle::Drag) {
+ m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::AllDevices);
+ m_dragHandler->setEnabled(enabled);
+ } else {
+ m_dragHandler->setEnabled(false);
+ }
+
+ // If you can't select using a drag, we always accept a PressAndHold
+ m_alwaysAcceptPressAndHold = !m_dragHandler->enabled();
+}
+
+QQuickSelectionRectangleAttached *QQuickSelectionRectanglePrivate::getAttachedObject(const QObject *object) const
+{
+ QObject *attachedObject = qmlAttachedPropertiesObject<QQuickSelectionRectangle>(object);
+ return static_cast<QQuickSelectionRectangleAttached *>(attachedObject);
+}
+
+// --------------------------------------------------------
+
+QQuickSelectionRectangle::QQuickSelectionRectangle(QQuickItem *parent)
+ : QQuickControl(*(new QQuickSelectionRectanglePrivate), parent)
+{
+ Q_D(QQuickSelectionRectangle);
+ d->m_tapHandler->setParent(this);
+ d->m_dragHandler->setParent(this);
+
+ QObject::connect(this, &QQuickItem::enabledChanged, [=]() {
+ d->m_scrollTimer.stop();
+ d->updateSelectionMode();
+ d->updateDraggingState(false);
+ d->updateActiveState(false);
+ });
+}
+
+QQuickItem *QQuickSelectionRectangle::target() const
+{
+ return d_func()->m_target;
+}
+
+void QQuickSelectionRectangle::setTarget(QQuickItem *target)
+{
+ Q_D(QQuickSelectionRectangle);
+ if (d->m_target == target)
+ return;
+
+ if (d->m_selectable) {
+ d->m_scrollTimer.stop();
+ d->m_tapHandler->setParent(this);
+ d->m_dragHandler->setParent(this);
+ d->m_target->disconnect(this);
+ }
+
+ d->m_target = target;
+ d->m_selectable = nullptr;
+
+ if (d->m_target) {
+ d->m_selectable = dynamic_cast<QQuickSelectable *>(QObjectPrivate::get(d->m_target.data()));
+ if (!d->m_selectable)
+ qmlWarning(this) << "the assigned target is not supported by the control";
+ }
+
+ if (d->m_selectable) {
+ const auto handlerTarget = d->m_selectable->selectionPointerHandlerTarget();
+ d->m_dragHandler->setParentItem(handlerTarget);
+ d->m_tapHandler->setParentItem(handlerTarget);
+ d->connectToTarget();
+ d->updateSelectionMode();
+ }
+
+ emit targetChanged();
+}
+
+bool QQuickSelectionRectangle::active()
+{
+ return d_func()->m_active;
+}
+
+bool QQuickSelectionRectangle::dragging()
+{
+ return d_func()->m_dragging;
+}
+
+QQuickSelectionRectangle::SelectionMode QQuickSelectionRectangle::selectionMode() const
+{
+ return d_func()->m_selectionMode;
+}
+
+void QQuickSelectionRectangle::setSelectionMode(QQuickSelectionRectangle::SelectionMode selectionMode)
+{
+ Q_D(QQuickSelectionRectangle);
+ if (d->m_selectionMode == selectionMode)
+ return;
+
+ d->m_selectionMode = selectionMode;
+
+ if (d->m_target)
+ d->updateSelectionMode();
+
+ emit selectionModeChanged();
+}
+
+QQmlComponent *QQuickSelectionRectangle::topLeftHandle() const
+{
+ return d_func()->m_topLeftHandleDelegate;
+}
+
+void QQuickSelectionRectangle::setTopLeftHandle(QQmlComponent *topLeftHandle)
+{
+ Q_D(QQuickSelectionRectangle);
+ if (d->m_topLeftHandleDelegate == topLeftHandle)
+ return;
+
+ d->m_topLeftHandleDelegate = topLeftHandle;
+ emit topLeftHandleChanged();
+}
+
+QQmlComponent *QQuickSelectionRectangle::bottomRightHandle() const
+{
+ return d_func()->m_bottomRightHandleDelegate;
+}
+
+void QQuickSelectionRectangle::setBottomRightHandle(QQmlComponent *bottomRightHandle)
+{
+ Q_D(QQuickSelectionRectangle);
+ if (d->m_bottomRightHandleDelegate == bottomRightHandle)
+ return;
+
+ d->m_bottomRightHandleDelegate = bottomRightHandle;
+ emit bottomRightHandleChanged();
+}
+
+QQuickSelectionRectangleAttached *QQuickSelectionRectangle::qmlAttachedProperties(QObject *obj)
+{
+ return new QQuickSelectionRectangleAttached(obj);
+}
+
+QQuickSelectionRectangleAttached::QQuickSelectionRectangleAttached(QObject *parent)
+ : QObject(parent)
+{
+}
+
+QQuickSelectionRectangle *QQuickSelectionRectangleAttached::control() const
+{
+ return m_control;
+}
+
+void QQuickSelectionRectangleAttached::setControl(QQuickSelectionRectangle *control)
+{
+ if (m_control == control)
+ return;
+
+ m_control = control;
+ emit controlChanged();
+}
+
+bool QQuickSelectionRectangleAttached::dragging() const
+{
+ return m_dragging;
+}
+
+void QQuickSelectionRectangleAttached::setDragging(bool dragging)
+{
+ if (m_dragging == dragging)
+ return;
+
+ m_dragging = dragging;
+ emit draggingChanged();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qquickselectionrectangle_p.cpp"