1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickselectionrectangle_p.h"
5#include "qquickselectionrectangle_p_p.h"
6
7#include <QtQml/qqmlinfo.h>
8#include <QtQml/qqmlcomponent.h>
9#include <QtQuick/private/qquickdraghandler_p.h>
10#include <QtQuick/private/qquickhoverhandler_p.h>
11
12#include <QtQuick/private/qquicktableview_p_p.h>
13
14#include "qquickscrollview_p.h"
15
16QT_BEGIN_NAMESPACE
17
18/*!
19 \qmltype SelectionRectangle
20 \inherits Control
21//! \nativetype QQuickSelectionRectangle
22 \inqmlmodule QtQuick.Controls
23 \since 6.2
24 \ingroup utilities
25 \brief Used to select table cells inside a TableView.
26
27 \image qtquickcontrols-selectionrectangle.png
28
29 SelectionRectangle is used for selecting table cells in a TableView. It lets
30 the user start a selection by doing a pointer drag inside the viewport, or by
31 doing a long press on top of a cell.
32
33 For a SelectionRectangle to be able to select cells, TableView must have
34 an ItemSelectionModel assigned. The ItemSelectionModel will store any
35 selections done on the model, and can be used for querying
36 which cells that the user has selected.
37
38 The following example shows how you can make a SelectionRectangle target
39 a TableView:
40
41 \snippet qtquickcontrols-selectionrectangle.qml 0
42
43 \note A SelectionRectangle itself is not shown as part of a selection. Only the
44 delegates (like topLeftHandle and bottomRightHandle) are used.
45 You should also consider \l {Selecting items}{rendering the TableView delegate as selected}.
46
47 \sa TableView, TableView::selectionModel, ItemSelectionModel
48*/
49
50/*!
51 \qmlproperty Item QtQuick.Controls::SelectionRectangle::target
52
53 This property holds the TableView on which the
54 SelectionRectangle should act.
55*/
56
57/*!
58 \qmlproperty bool QtQuick.Controls::SelectionRectangle::active
59 \readonly
60
61 This property is \c true while the user is performing a
62 selection. The selection will be active from the time the
63 the user starts to select, and until the selection is
64 removed again, for example from tapping inside the viewport.
65*/
66
67/*!
68 \qmlproperty bool QtQuick.Controls::SelectionRectangle::dragging
69 \readonly
70
71 This property is \c true whenever the user is doing a pointer drag or
72 a handle drag to adjust the selection rectangle.
73*/
74
75/*!
76 \qmlproperty Component QtQuick.Controls::SelectionRectangle::topLeftHandle
77
78 This property holds the delegate that will be shown on the center of the
79 top-left corner of the selection rectangle. When a handle is
80 provided, the user can drag it to adjust the selection.
81
82 The handle is not hidden by default when a selection is removed.
83 Instead, this is the responsibility of the delegate, to open up for
84 custom fade-out animations. The easiest way to ensure that the handle
85 ends up hidden, is to simply bind \l {Item::}{visible} to the \l active
86 state of the SelectionRectangle:
87
88 \qml
89 SelectionRectangle {
90 topLeftHandle: Rectangle {
91 width: 20
92 height: 20
93 visible: SelectionRectangle.control.active
94 }
95 }
96 \endqml
97
98 Set this property to \c null if you don't want a selection handle on the top-left.
99
100 \sa bottomRightHandle
101*/
102
103/*!
104 \qmlproperty Component QtQuick.Controls::SelectionRectangle::bottomRightHandle
105
106 This property holds the delegate that will be shown on the center of the
107 top-left corner of the selection rectangle. When a handle is
108 provided, the user can drag it to adjust the selection.
109
110 The handle is not hidden by default when a selection is removed.
111 Instead, this is the responsibility of the delegate, to open up for
112 custom fade-out animations. The easiest way to ensure that the handle
113 ends up hidden, is to simply bind \l {Item::}{visible} to the \l active
114 state of the SelectionRectangle:
115
116 \qml
117 SelectionRectangle {
118 bottomRightHandle: Rectangle {
119 width: 20
120 height: 20
121 visible: SelectionRectangle.control.active
122 }
123 }
124 \endqml
125
126 Set this property to \c null if you don't want a selection handle on the bottom-right.
127
128 \sa topLeftHandle
129*/
130
131/*!
132 \qmlproperty enumeration QtQuick.Controls::SelectionRectangle::selectionMode
133
134 This property holds when a selection should start.
135
136 \value SelectionRectangle.Drag A selection will start by doing a pointer drag inside the viewport
137 \value SelectionRectangle.PressAndHold A selection will start by doing a press and hold on top of a cell
138 \value SelectionRectangle.Auto SelectionRectangle will choose which mode to
139 use based on the \l target and the input device in use. This normally
140 means \c Drag when using a mouse, and \c PressAndHold on a touchscreen.
141 However, \c Drag will only be used if it doesn't conflict with flicking.
142 One way to avoid conflict is to disable mouse-drag flicking by setting
143 \l {Flickable::}{acceptedButtons} to \c Qt.NoButton. In that case, the
144 mouse wheel or touchpad scrolling gesture continues to work, touchscreen
145 flicking continues to work, and touchscreen selection can be started by
146 press-and-hold. Another way is to set \l {Flickable::}{interactive} to
147 \c false, which disables flicking and scrolling altogether (perhaps
148 this should be done only temporarily, in some mode in your UI).
149 Yet another way is to place the TableView inside a ScrollView (where
150 flicking, by default, is off for mouse events).
151
152 The default value is \c Auto.
153*/
154
155/*!
156 \qmlattachedproperty SelectionRectangle QtQuick.Controls::SelectionRectangle::control
157
158 This attached property holds the SelectionRectangle that manages the delegate instance.
159 It is attached to each handle instance.
160*/
161
162/*!
163 \qmlattachedproperty bool QtQuick.Controls::SelectionRectangle::dragging
164
165 This attached property will be \c true if the user is dragging on the handle.
166 It is attached to each handle instance.
167*/
168
169QQuickSelectionRectanglePrivate::QQuickSelectionRectanglePrivate()
170 : QQuickControlPrivate()
171{
172 m_tapHandler = new QQuickTapHandler();
173 m_dragHandler = new QQuickDragHandler();
174 m_dragHandler->setTarget(nullptr);
175
176 QObject::connect(sender: &m_scrollTimer, signal: &QTimer::timeout, slot: [&]{
177 if (m_topLeftHandle && m_draggedHandle == m_topLeftHandle.data())
178 m_selectable->setSelectionStartPos(m_scrollToPoint);
179 else
180 m_selectable->setSelectionEndPos(m_scrollToPoint);
181 updateHandles();
182 const QSizeF dist = m_selectable->scrollTowardsPoint(pos: m_scrollToPoint, step: m_scrollSpeed);
183 m_scrollToPoint.rx() += dist.width() > 0 ? m_scrollSpeed.width() : -m_scrollSpeed.width();
184 m_scrollToPoint.ry() += dist.height() > 0 ? m_scrollSpeed.height() : -m_scrollSpeed.height();
185 m_scrollSpeed = QSizeF(qAbs(t: dist.width() * 0.007), qAbs(t: dist.height() * 0.007));
186 });
187
188 QObject::connect(sender: m_tapHandler, signal: &QQuickTapHandler::pressedChanged, slot: [this]() {
189 Q_Q(QQuickSelectionRectangle);
190
191 if (!m_tapHandler->isPressed()) {
192 // Deactivate the selection rectangle when the tap handler
193 // is released and there's no selection
194 if (q->active() && !m_selectable->hasSelection())
195 updateActiveState(isActive: false);
196 return;
197 }
198 if (m_effectiveSelectionMode != QQuickSelectionRectangle::Drag)
199 return;
200
201 const QPointF pos = m_tapHandler->point().pressPosition();
202 const auto modifiers = m_tapHandler->point().modifiers();
203 if (modifiers & ~(Qt::ControlModifier | Qt::ShiftModifier))
204 return;
205
206 // A selection rectangle is not valid when its width or height is 0
207 const auto isSelectionRectValid = [](const QRectF &selectionRect) -> bool {
208 return ((selectionRect.width() != 0) || (selectionRect.height() != 0));
209 };
210
211 if (modifiers & Qt::ShiftModifier) {
212 // Extend the selection towards the pressed cell. If there is no
213 // existing selection, start a new selection from the current item
214 // to the pressed item.
215 if (!m_active) {
216 if (!m_selectable->startSelection(pos, modifiers))
217 return;
218 m_selectable->setSelectionStartPos(QPoint{-1, -1});
219 }
220 m_selectable->setSelectionEndPos(pos);
221 // Don't update and activate the selection handlers when
222 // the size of m_selectable's selection rectangle is 0.
223 if (!isSelectionRectValid(m_selectable->selectionRectangle()))
224 return;
225 updateHandles();
226 updateActiveState(isActive: true);
227 } else if (modifiers & Qt::ControlModifier) {
228 // Select a single cell, but keep the old selection (unless
229 // m_selectable->startSelection(pos. modifiers) returns false, which
230 // it will if selectionMode only allows a single selection).
231 if (handleUnderPos(pos) != nullptr) {
232 // Don't allow press'n'hold to start a new
233 // selection if it started on top of a handle.
234 return;
235 }
236
237 if (!m_selectable->startSelection(pos, modifiers))
238 return;
239 m_selectable->setSelectionStartPos(pos);
240 m_selectable->setSelectionEndPos(pos);
241 // Don't update and activate the selection handlers when
242 // the size of m_selectable's selection rectangle is 0.
243 if (!isSelectionRectValid(m_selectable->selectionRectangle()))
244 return;
245 updateHandles();
246 updateActiveState(isActive: true);
247 }
248 });
249
250 QObject::connect(sender: m_tapHandler, signal: &QQuickTapHandler::longPressed, slot: [this]() {
251 if (m_tapHandler->point().device()->type() == QInputDevice::DeviceType::TouchScreen &&
252 m_selectionMode == QQuickSelectionRectangle::Auto) {
253 const QQuickTableView *tableview = qobject_cast<QQuickTableView *>(object: m_target);
254 if (tableview && !tableview->isInteractive())
255 return; // you can select by touch drag, so don't allow touch long-press
256 // otherwise, touch long-press is allowed
257 } else if (m_effectiveSelectionMode != QQuickSelectionRectangle::PressAndHold) {
258 return;
259 }
260
261 const QPointF pos = m_tapHandler->point().pressPosition();
262 const auto modifiers = m_tapHandler->point().modifiers();
263 if (handleUnderPos(pos) != nullptr) {
264 // Don't allow press'n'hold to start a new
265 // selection if it started on top of a handle.
266 return;
267 }
268
269 // A selection rectangle is not valid when its width or height is 0
270 const auto isSelectionRectValid = [](const QRectF &selectionRect) -> bool {
271 return ((selectionRect.width() != 0) || (selectionRect.height() != 0));
272 };
273
274 if (modifiers == Qt::ShiftModifier) {
275 // Extend the selection towards the pressed cell. If there is no
276 // existing selection, start a new selection from the current item
277 // to the pressed item.
278 if (!m_active) {
279 if (!m_selectable->startSelection(pos, modifiers))
280 return;
281 m_selectable->setSelectionStartPos(QPoint{-1, -1});
282 }
283 m_selectable->setSelectionEndPos(pos);
284 if (!isSelectionRectValid(m_selectable->selectionRectangle()))
285 return;
286 updateHandles();
287 updateActiveState(isActive: true);
288 } else {
289 // Select a single cell. m_selectable->startSelection() will decide
290 // if the existing selection should also be cleared.
291 if (!m_selectable->startSelection(pos, modifiers))
292 return;
293 m_selectable->setSelectionStartPos(pos);
294 m_selectable->setSelectionEndPos(pos);
295 if (!isSelectionRectValid(m_selectable->selectionRectangle()))
296 return;
297 updateHandles();
298 updateActiveState(isActive: true);
299 }
300 });
301
302 QObject::connect(sender: m_dragHandler, signal: &QQuickDragHandler::activeChanged, slot: [this]() {
303 Q_ASSERT(m_effectiveSelectionMode == QQuickSelectionRectangle::Drag);
304 const QPointF startPos = m_dragHandler->centroid().pressPosition();
305 const QPointF dragPos = m_dragHandler->centroid().position();
306 const auto modifiers = m_dragHandler->centroid().modifiers();
307 if (modifiers & ~(Qt::ControlModifier | Qt::ShiftModifier))
308 return;
309
310 // A selection rectangle is not valid when its width or height is 0
311 const auto isSelectionRectValid = [](const QRectF &selectionRect) -> bool {
312 return ((selectionRect.width() != 0) || (selectionRect.height() != 0));
313 };
314
315 if (m_dragHandler->active()) {
316 // Start a new selection unless there is an active selection
317 // already, and one of the relevant modifiers are being held.
318 // In that case we continue to extend the active selection instead.
319 const bool modifiersHeld = modifiers & (Qt::ControlModifier | Qt::ShiftModifier);
320 if (!m_active || !modifiersHeld) {
321 if (!m_selectable->startSelection(pos: startPos, modifiers))
322 return;
323 m_selectable->setSelectionStartPos(startPos);
324 }
325 m_selectable->setSelectionEndPos(dragPos);
326 m_draggedHandle = nullptr;
327 if (!isSelectionRectValid(m_selectable->selectionRectangle()))
328 return;
329 updateHandles();
330 updateActiveState(isActive: true);
331 updateDraggingState(isDragging: true);
332 } else {
333 m_scrollTimer.stop();
334 m_selectable->normalizeSelection();
335 updateDraggingState(isDragging: false);
336 }
337 });
338
339 QObject::connect(sender: m_dragHandler, signal: &QQuickDragHandler::centroidChanged, slot: [this]() {
340 if (!m_dragging)
341 return;
342 const QPointF pos = m_dragHandler->centroid().position();
343 m_selectable->setSelectionEndPos(pos);
344 updateHandles();
345 scrollTowardsPos(pos);
346 });
347}
348
349void QQuickSelectionRectanglePrivate::scrollTowardsPos(const QPointF &pos)
350{
351 m_scrollToPoint = pos;
352 if (m_scrollTimer.isActive())
353 return;
354
355 const QSizeF dist = m_selectable->scrollTowardsPoint(pos: m_scrollToPoint, step: m_scrollSpeed);
356 if (!dist.isNull())
357 m_scrollTimer.start(msec: 1);
358}
359
360QQuickItem *QQuickSelectionRectanglePrivate::handleUnderPos(const QPointF &pos)
361{
362 const auto handlerTarget = m_selectable->selectionPointerHandlerTarget();
363 if (m_topLeftHandle) {
364 const QPointF localPos = m_topLeftHandle->mapFromItem(item: handlerTarget, point: pos);
365 if (m_topLeftHandle->contains(point: localPos))
366 return m_topLeftHandle.data();
367 }
368
369 if (m_bottomRightHandle) {
370 const QPointF localPos = m_bottomRightHandle->mapFromItem(item: handlerTarget, point: pos);
371 if (m_bottomRightHandle->contains(point: localPos))
372 return m_bottomRightHandle.data();
373 }
374
375 return nullptr;
376}
377
378void QQuickSelectionRectanglePrivate::updateDraggingState(bool dragging)
379{
380 if (dragging != m_dragging) {
381 m_dragging = dragging;
382 emit q_func()->draggingChanged();
383 }
384
385 if (auto attached = getAttachedObject(object: m_draggedHandle))
386 attached->setDragging(dragging);
387}
388
389void QQuickSelectionRectanglePrivate::updateActiveState(bool active)
390{
391 if (active == m_active)
392 return;
393
394 m_active = active;
395
396 if (const auto tableview = qobject_cast<QQuickTableView *>(object: m_target)) {
397 if (active) {
398 // If the position of rows and columns changes, we'll need to reposition the handles
399 connect(sender: tableview, signal: &QQuickTableView::layoutChanged, receiverPrivate: this, slot: &QQuickSelectionRectanglePrivate::updateHandles);
400 } else {
401 disconnect(sender: tableview, signal: &QQuickTableView::layoutChanged, receiverPrivate: this, slot: &QQuickSelectionRectanglePrivate::updateHandles);
402 }
403 }
404
405 emit q_func()->activeChanged();
406}
407
408QQuickItem *QQuickSelectionRectanglePrivate::createHandle(QQmlComponent *delegate, Qt::Corner corner)
409{
410 Q_Q(QQuickSelectionRectangle);
411
412 // Incubate the handle
413 QObject *obj = delegate->beginCreate(QQmlEngine::contextForObject(q));
414 QQuickItem *handleItem = qobject_cast<QQuickItem*>(o: obj);
415 const auto handlerTarget = m_selectable->selectionPointerHandlerTarget();
416 handleItem->setParentItem(handlerTarget);
417 if (auto attached = getAttachedObject(object: handleItem))
418 attached->setControl(q);
419 delegate->completeCreate();
420 if (handleItem->z() == 0)
421 handleItem->setZ(100);
422
423 // Add pointer handlers to it
424 QQuickDragHandler *dragHandler = new QQuickDragHandler();
425 dragHandler->setTarget(nullptr);
426 dragHandler->setParentItem(handleItem);
427 dragHandler->setGrabPermissions(QQuickPointerHandler::CanTakeOverFromAnything);
428
429 QQuickHoverHandler *hoverHandler = new QQuickHoverHandler();
430 hoverHandler->setTarget(nullptr);
431 hoverHandler->setParentItem(handleItem);
432#if QT_CONFIG(cursor)
433 hoverHandler->setCursorShape(Qt::SizeFDiagCursor);
434#endif
435 hoverHandler->setBlocking(true);
436
437 // Add a dummy TapHandler that blocks the user from being
438 // able to tap on a tap handler underneath the handle.
439 QQuickTapHandler *tapHandler = new QQuickTapHandler();
440 tapHandler->setTarget(nullptr);
441 tapHandler->setParentItem(handleItem);
442 // Set a dummy gesture policy so that the tap handler
443 // will get an exclusive grab already on press
444 tapHandler->setGesturePolicy(QQuickTapHandler::DragWithinBounds);
445
446 QObject::connect(sender: dragHandler, signal: &QQuickDragHandler::activeChanged, slot: [this, corner, handleItem, dragHandler]() {
447 if (dragHandler->active()) {
448 const QPointF localPos = dragHandler->centroid().position();
449 const QPointF pos = handleItem->mapToItem(item: handleItem->parentItem(), point: localPos);
450 if (corner == Qt::TopLeftCorner)
451 m_selectable->setSelectionStartPos(pos);
452 else
453 m_selectable->setSelectionEndPos(pos);
454
455 m_draggedHandle = handleItem;
456 updateHandles();
457 updateDraggingState(dragging: true);
458#if QT_CONFIG(cursor)
459 QGuiApplication::setOverrideCursor(Qt::SizeFDiagCursor);
460#endif
461 } else {
462 m_scrollTimer.stop();
463 m_selectable->normalizeSelection();
464 updateDraggingState(dragging: false);
465#if QT_CONFIG(cursor)
466 QGuiApplication::restoreOverrideCursor();
467#endif
468 }
469 });
470
471 QObject::connect(sender: dragHandler, signal: &QQuickDragHandler::centroidChanged, slot: [this, corner, handleItem, dragHandler]() {
472 if (!m_dragging)
473 return;
474
475 const QPointF localPos = dragHandler->centroid().position();
476 const QPointF pos = handleItem->mapToItem(item: handleItem->parentItem(), point: localPos);
477 if (corner == Qt::TopLeftCorner)
478 m_selectable->setSelectionStartPos(pos);
479 else
480 m_selectable->setSelectionEndPos(pos);
481
482 updateHandles();
483 scrollTowardsPos(pos);
484 });
485
486 return handleItem;
487}
488
489void QQuickSelectionRectanglePrivate::updateHandles()
490{
491 const QRectF rect = m_selectable->selectionRectangle().normalized();
492
493 if (!m_topLeftHandle && m_topLeftHandleDelegate)
494 m_topLeftHandle.reset(other: createHandle(delegate: m_topLeftHandleDelegate, corner: Qt::TopLeftCorner));
495
496 if (!m_bottomRightHandle && m_bottomRightHandleDelegate)
497 m_bottomRightHandle.reset(other: createHandle(delegate: m_bottomRightHandleDelegate, corner: Qt::BottomRightCorner));
498
499 if (m_topLeftHandle) {
500 m_topLeftHandle->setX(rect.x() - (m_topLeftHandle->width() / 2));
501 m_topLeftHandle->setY(rect.y() - (m_topLeftHandle->height() / 2));
502 }
503
504 if (m_bottomRightHandle) {
505 m_bottomRightHandle->setX(rect.x() + rect.width() - (m_bottomRightHandle->width() / 2));
506 m_bottomRightHandle->setY(rect.y() + rect.height() - (m_bottomRightHandle->height() / 2));
507 }
508}
509
510void QQuickSelectionRectanglePrivate::connectToTarget()
511{
512 // To support QuickSelectionRectangle::Auto, we need to listen for changes to the target
513 if (const auto flickable = qobject_cast<QQuickFlickable *>(object: m_target)) {
514 connect(sender: flickable, signal: &QQuickFlickable::interactiveChanged, receiverPrivate: this, slot: &QQuickSelectionRectanglePrivate::updateSelectionMode);
515 connect(sender: flickable, signal: &QQuickFlickable::acceptedButtonsChanged, receiverPrivate: this, slot: &QQuickSelectionRectanglePrivate::updateSelectionMode);
516 }
517
518 // Add a callback function that tells if the selection was
519 // modified outside of the actions taken by SelectionRectangle.
520 m_selectable->setCallback([this](QQuickSelectable::CallBackFlag flag){
521 switch (flag) {
522 case QQuickSelectable::CallBackFlag::CancelSelection:
523 // The selection is either cleared, or can no longer be
524 // represented as a rectangle with two selection handles.
525 updateActiveState(active: false);
526 break;
527 case QQuickSelectable::CallBackFlag::SelectionRectangleChanged:
528 // The selection has changed, but the selection is still
529 // rectangular and without holes.
530 updateHandles();
531 break;
532 default:
533 Q_UNREACHABLE();
534 }
535 });
536}
537
538void QQuickSelectionRectanglePrivate::updateSelectionMode()
539{
540 Q_Q(QQuickSelectionRectangle);
541
542 const bool enabled = q->isEnabled();
543 m_tapHandler->setEnabled(enabled);
544
545 if (m_selectionMode == QQuickSelectionRectangle::Auto) {
546 if (m_target && qobject_cast<QQuickScrollView *>(object: m_target->parentItem())) {
547 // ScrollView allows flicking with touch, but not with mouse. So we do
548 // the opposite here: you can drag to select with a mouse, but not with touch.
549 m_effectiveSelectionMode = QQuickSelectionRectangle::Drag;
550 m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse);
551 m_dragHandler->setEnabled(enabled);
552 } else if (const auto flickable = qobject_cast<QQuickFlickable *>(object: m_target)) {
553 // Flickable allows flicking with mouse by default, but it can be disabled by
554 // changing acceptedMouseButtons(). Setting interactive to false disables flicking
555 // altogether. So we allow Drag with devices that don't conflict with flicking.
556 if (enabled && (!flickable->isInteractive() ||
557 !flickable->acceptedMouseButtons().testFlag(flag: Qt::LeftButton))) {
558 m_effectiveSelectionMode = QQuickSelectionRectangle::Drag;
559 m_dragHandler->setAcceptedDevices(flickable->isInteractive()
560 ? QInputDevice::DeviceType::Mouse
561 : QInputDevice::DeviceType::AllDevices);
562 m_dragHandler->setEnabled(true);
563 } else {
564 m_effectiveSelectionMode = QQuickSelectionRectangle::PressAndHold;
565 m_dragHandler->setEnabled(false);
566 }
567 } else {
568 m_effectiveSelectionMode = QQuickSelectionRectangle::Drag;
569 m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse);
570 m_dragHandler->setEnabled(enabled);
571 }
572 } else if (m_selectionMode == QQuickSelectionRectangle::Drag) {
573 m_effectiveSelectionMode = QQuickSelectionRectangle::Drag;
574 m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::AllDevices);
575 m_dragHandler->setEnabled(enabled);
576 } else {
577 m_effectiveSelectionMode = QQuickSelectionRectangle::PressAndHold;
578 m_dragHandler->setEnabled(false);
579 }
580}
581
582QQuickSelectionRectangleAttached *QQuickSelectionRectanglePrivate::getAttachedObject(const QObject *object) const
583{
584 QObject *attachedObject = qmlAttachedPropertiesObject<QQuickSelectionRectangle>(obj: object);
585 return static_cast<QQuickSelectionRectangleAttached *>(attachedObject);
586}
587
588// --------------------------------------------------------
589
590QQuickSelectionRectangle::QQuickSelectionRectangle(QQuickItem *parent)
591 : QQuickControl(*(new QQuickSelectionRectanglePrivate), parent)
592{
593 Q_D(QQuickSelectionRectangle);
594 d->m_tapHandler->setParent(this);
595 d->m_dragHandler->setParent(this);
596
597 QObject::connect(sender: this, signal: &QQuickItem::enabledChanged, slot: [=]() {
598 d->m_scrollTimer.stop();
599 d->updateSelectionMode();
600 d->updateDraggingState(dragging: false);
601 d->updateActiveState(active: false);
602 });
603}
604
605QQuickItem *QQuickSelectionRectangle::target() const
606{
607 return d_func()->m_target;
608}
609
610void QQuickSelectionRectangle::setTarget(QQuickItem *target)
611{
612 Q_D(QQuickSelectionRectangle);
613 if (d->m_target == target)
614 return;
615
616 if (d->m_selectable) {
617 d->m_scrollTimer.stop();
618 d->m_tapHandler->setParent(this);
619 d->m_dragHandler->setParent(this);
620 d->m_target->disconnect(receiver: this);
621 d->m_selectable->setCallback(nullptr);
622 }
623
624 d->m_target = target;
625 d->m_selectable = nullptr;
626
627 if (d->m_target) {
628 d->m_selectable = dynamic_cast<QQuickSelectable *>(QObjectPrivate::get(o: d->m_target.data()));
629 if (!d->m_selectable)
630 qmlWarning(me: this) << "the assigned target is not supported by the control";
631 }
632
633 if (d->m_selectable) {
634 const auto handlerTarget = d->m_selectable->selectionPointerHandlerTarget();
635 d->m_dragHandler->setParentItem(handlerTarget);
636 d->m_tapHandler->setParentItem(handlerTarget);
637 d->connectToTarget();
638 d->updateSelectionMode();
639 }
640
641 emit targetChanged();
642}
643
644bool QQuickSelectionRectangle::active()
645{
646 return d_func()->m_active;
647}
648
649bool QQuickSelectionRectangle::dragging()
650{
651 return d_func()->m_dragging;
652}
653
654QQuickSelectionRectangle::SelectionMode QQuickSelectionRectangle::selectionMode() const
655{
656 return d_func()->m_selectionMode;
657}
658
659void QQuickSelectionRectangle::setSelectionMode(QQuickSelectionRectangle::SelectionMode selectionMode)
660{
661 Q_D(QQuickSelectionRectangle);
662 if (d->m_selectionMode == selectionMode)
663 return;
664
665 d->m_selectionMode = selectionMode;
666
667 if (d->m_target)
668 d->updateSelectionMode();
669
670 emit selectionModeChanged();
671}
672
673QQmlComponent *QQuickSelectionRectangle::topLeftHandle() const
674{
675 return d_func()->m_topLeftHandleDelegate;
676}
677
678void QQuickSelectionRectangle::setTopLeftHandle(QQmlComponent *topLeftHandle)
679{
680 Q_D(QQuickSelectionRectangle);
681 if (d->m_topLeftHandleDelegate == topLeftHandle)
682 return;
683
684 d->m_topLeftHandleDelegate = topLeftHandle;
685 emit topLeftHandleChanged();
686}
687
688QQmlComponent *QQuickSelectionRectangle::bottomRightHandle() const
689{
690 return d_func()->m_bottomRightHandleDelegate;
691}
692
693void QQuickSelectionRectangle::setBottomRightHandle(QQmlComponent *bottomRightHandle)
694{
695 Q_D(QQuickSelectionRectangle);
696 if (d->m_bottomRightHandleDelegate == bottomRightHandle)
697 return;
698
699 d->m_bottomRightHandleDelegate = bottomRightHandle;
700 emit bottomRightHandleChanged();
701}
702
703QQuickSelectionRectangleAttached *QQuickSelectionRectangle::qmlAttachedProperties(QObject *obj)
704{
705 return new QQuickSelectionRectangleAttached(obj);
706}
707
708QQuickSelectionRectangleAttached::QQuickSelectionRectangleAttached(QObject *parent)
709 : QObject(parent)
710{
711}
712
713QQuickSelectionRectangle *QQuickSelectionRectangleAttached::control() const
714{
715 return m_control;
716}
717
718void QQuickSelectionRectangleAttached::setControl(QQuickSelectionRectangle *control)
719{
720 if (m_control == control)
721 return;
722
723 m_control = control;
724 emit controlChanged();
725}
726
727bool QQuickSelectionRectangleAttached::dragging() const
728{
729 return m_dragging;
730}
731
732void QQuickSelectionRectangleAttached::setDragging(bool dragging)
733{
734 if (m_dragging == dragging)
735 return;
736
737 m_dragging = dragging;
738 emit draggingChanged();
739}
740
741QT_END_NAMESPACE
742
743#include "moc_qquickselectionrectangle_p.cpp"
744

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