1// Copyright (C) 2022 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 "qquicksplitview_p.h"
5#include "qquicksplitview_p_p.h"
6#include "qquickcontentitem_p.h"
7
8#include <QtCore/qdebug.h>
9#include <QtCore/qloggingcategory.h>
10#include <QtCore/qcborarray.h>
11#include <QtCore/qcbormap.h>
12#include <QtCore/qcborvalue.h>
13#include <QtQml/QQmlInfo>
14
15QT_BEGIN_NAMESPACE
16
17/*!
18 \qmltype SplitView
19 \inherits Container
20//! \instantiates QQuickSplitView
21 \inqmlmodule QtQuick.Controls
22 \since 5.13
23 \ingroup qtquickcontrols-containers
24 \ingroup qtquickcontrols-focusscopes
25 \brief Lays out items with a draggable splitter between each item.
26
27 SplitView is a control that lays out items horizontally or vertically with
28 a draggable splitter between each item.
29
30 SplitView supports the following attached properties on items it manages:
31
32 \list
33 \li \l{minimumWidth}{SplitView.minimumWidth}
34 \li \l{minimumHeight}{SplitView.minimumHeight}
35 \li \l{preferredWidth}{SplitView.preferredWidth}
36 \li \l{preferredHeight}{SplitView.preferredHeight}
37 \li \l{maximumWidth}{SplitView.maximumWidth}
38 \li \l{maximumHeight}{SplitView.maximumHeight}
39 \li \l{fillWidth}{SplitView.fillWidth} (true for only one child)
40 \li \l{fillHeight}{SplitView.fillHeight} (true for only one child)
41 \endlist
42
43 In addition, each handle has the following read-only attached properties:
44
45 \list
46 \li \l{SplitHandle::hovered}{SplitHandle.hovered}
47 \li \l{SplitHandle::pressed}{SplitHandle.pressed}
48 \endlist
49
50 \note Handles should be purely visual and not handle events, as it can
51 interfere with their hovered and pressed states.
52
53 The preferred size of items in a SplitView can be specified via
54 \l{Item::}{implicitWidth} and \l{Item::}{implicitHeight} or
55 \c SplitView.preferredWidth and \c SplitView.preferredHeight:
56
57 \code
58 SplitView {
59 anchors.fill: parent
60
61 Item {
62 SplitView.preferredWidth: 50
63 }
64
65 // ...
66 }
67 \endcode
68
69 For a horizontal SplitView, it's not necessary to specify the preferred
70 height of each item, as they will be resized to the height of the view.
71 This applies in reverse for vertical views.
72
73 When a split handle is dragged, the \c SplitView.preferredWidth or
74 \c SplitView.preferredHeight property is overwritten, depending on the
75 \l orientation of the view.
76
77 To limit the size of items in a horizontal view, use the following
78 properties:
79
80 \code
81 SplitView {
82 anchors.fill: parent
83
84 Item {
85 SplitView.minimumWidth: 25
86 SplitView.preferredWidth: 50
87 SplitView.maximumWidth: 100
88 }
89
90 // ...
91 }
92 \endcode
93
94 To limit the size of items in a vertical view, use the following
95 properties:
96
97 \code
98 SplitView {
99 anchors.fill: parent
100 orientation: Qt.Vertical
101
102 Item {
103 SplitView.minimumHeight: 25
104 SplitView.preferredHeight: 50
105 SplitView.maximumHeight: 100
106 }
107
108 // ...
109 }
110 \endcode
111
112 There will always be one item (the fill item) in the SplitView that has
113 \c SplitView.fillWidth set to \c true (or \c SplitView.fillHeight, if
114 \l orientation is \c Qt.Vertical). This means that the item will get all
115 leftover space when other items have been laid out. By default, the last
116 visible child of the SplitView will have this set, but it can be changed by
117 explicitly setting \c fillWidth to \c true on another item.
118
119 A handle can belong to the item either on the left or top side, or on the
120 right or bottom side:
121
122 \list
123 \li If the fill item is to the right: the handle belongs to the left
124 item.
125 \li If the fill item is on the left: the handle belongs to the right
126 item.
127 \endlist
128
129 To create a SplitView with three items, and let the center item get
130 superfluous space, one could do the following:
131
132 \code
133 SplitView {
134 anchors.fill: parent
135 orientation: Qt.Horizontal
136
137 Rectangle {
138 implicitWidth: 200
139 SplitView.maximumWidth: 400
140 color: "lightblue"
141 Label {
142 text: "View 1"
143 anchors.centerIn: parent
144 }
145 }
146 Rectangle {
147 id: centerItem
148 SplitView.minimumWidth: 50
149 SplitView.fillWidth: true
150 color: "lightgray"
151 Label {
152 text: "View 2"
153 anchors.centerIn: parent
154 }
155 }
156 Rectangle {
157 implicitWidth: 200
158 color: "lightgreen"
159 Label {
160 text: "View 3"
161 anchors.centerIn: parent
162 }
163 }
164 }
165 \endcode
166
167 \section1 Serializing SplitView's State
168
169 The main purpose of SplitView is to allow users to easily configure the
170 size of various UI elements. In addition, the user's preferred sizes should
171 be remembered across sessions. To achieve this, the values of the \c
172 SplitView.preferredWidth and \c SplitView.preferredHeight properties can be
173 serialized using the \l saveState() and \l restoreState() functions:
174
175 \qml
176 import QtCore
177 import QtQuick.Controls
178
179 ApplicationWindow {
180 // ...
181
182 Component.onCompleted: splitView.restoreState(settings.splitView)
183 Component.onDestruction: settings.splitView = splitView.saveState()
184
185 Settings {
186 id: settings
187 property var splitView
188 }
189
190 SplitView {
191 id: splitView
192 // ...
193 }
194 }
195 \endqml
196
197 Alternatively, the \l {Settings::}{value()} and \l {Settings::}{setValue()}
198 functions of \l Settings can be used:
199
200 \qml
201 import QtCore
202 import QtQuick.Controls
203
204 ApplicationWindow {
205 // ...
206
207 Component.onCompleted: splitView.restoreState(settings.value("ui/splitview"))
208 Component.onDestruction: settings.setValue("ui/splitview", splitView.saveState())
209
210 Settings {
211 id: settings
212 }
213
214 SplitView {
215 id: splitView
216 // ...
217 }
218 }
219 \endqml
220
221 \sa SplitHandle, {Customizing SplitView}, {Container Controls}
222*/
223
224Q_LOGGING_CATEGORY(qlcQQuickSplitView, "qt.quick.controls.splitview")
225Q_LOGGING_CATEGORY(qlcQQuickSplitViewPointer, "qt.quick.controls.splitview.pointer")
226Q_LOGGING_CATEGORY(qlcQQuickSplitViewState, "qt.quick.controls.splitview.state")
227
228/*
229 Updates m_fillIndex to be between 0 .. (item count - 1).
230*/
231void QQuickSplitViewPrivate::updateFillIndex()
232{
233 const int count = contentModel->count();
234 const bool horizontal = isHorizontal();
235
236 qCDebug(qlcQQuickSplitView) << "looking for fillWidth/Height item amongst" << count << "items";
237
238 int fillIndex = -1;
239 int lastVisibleIndex = -1;
240 for (int i = 0; i < count; ++i) {
241 QQuickItem *item = qobject_cast<QQuickItem*>(o: contentModel->object(index: i));
242 if (!item->isVisible())
243 continue;
244
245 lastVisibleIndex = i;
246
247 const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
248 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: item, create: false));
249 if (!attached)
250 continue;
251
252 if ((horizontal && attached->fillWidth()) || (!horizontal && attached->fillHeight())) {
253 fillIndex = i;
254 qCDebug(qlcQQuickSplitView) << "found fillWidth/Height item at index" << fillIndex;
255 break;
256 }
257 }
258
259 if (fillIndex == -1) {
260 // If there was no item with fillWidth/fillHeight set, fillIndex will be -1,
261 // and we'll set m_fillIndex to the last visible item.
262 // If there was an item with fillWidth/fillHeight set, we were already done and this will be skipped.
263 fillIndex = lastVisibleIndex != -1 ? lastVisibleIndex : count - 1;
264 qCDebug(qlcQQuickSplitView) << "found no fillWidth/Height item; using last item at index" << fillIndex;
265 }
266 // Take new fillIndex into use.
267 m_fillIndex = fillIndex;
268}
269
270/*
271 Resizes split items according to their preferred size and any constraints.
272
273 If a split item is being resized due to a split handle being dragged,
274 it will be resized accordingly.
275
276 Items that aren't visible are skipped.
277*/
278void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &usedHeight, int &indexBeingResizedDueToDrag)
279{
280 const int count = contentModel->count();
281 const bool horizontal = isHorizontal();
282 for (int index = 0; index < count; ++index) {
283 QQuickItem *item = qobject_cast<QQuickItem*>(o: contentModel->object(index));
284 if (!item->isVisible()) {
285 // The item is not visible, so skip it.
286 qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": split item " << item
287 << " at index " << index << " is not visible; skipping it and its handles (if any)";
288 continue;
289 }
290
291 const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
292 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
293 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: item, create: false));
294 const auto sizeData = effectiveSizeData(itemPrivate, attached);
295
296 const bool resizeLeftItem = m_fillIndex > m_pressedHandleIndex;
297 // True if any handle is pressed.
298 const bool isAHandlePressed = m_pressedHandleIndex != -1;
299 // True if this particular item is being resized as a result of a handle being dragged.
300 const bool isBeingResized = isAHandlePressed && ((resizeLeftItem && index == m_pressedHandleIndex)
301 || (!resizeLeftItem && index == m_nextVisibleIndexAfterPressedHandle));
302 if (isBeingResized) {
303 indexBeingResizedDueToDrag = index;
304 qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": dragging handle for item";
305 }
306
307 const qreal size = horizontal ? width : height;
308 qreal requestedSize = 0;
309 if (isBeingResized) {
310 // Don't let the mouse go past either edge of the SplitView.
311 const qreal clampedMousePos = horizontal
312 ? qBound(min: qreal(0.0), val: m_mousePos.x(), max: qreal(width))
313 : qBound(min: qreal(0.0), val: m_mousePos.y(), max: qreal(height));
314
315 // We also need to ensure that the item's edge doesn't go too far
316 // out and hence give the item more space than is available.
317 const int firstIndex = resizeLeftItem ? m_nextVisibleIndexAfterPressedHandle : 0;
318 const int lastIndex = resizeLeftItem ? contentModel->count() - 1 : m_pressedHandleIndex;
319 const qreal accumulated = accumulatedSize(firstIndex, lastIndex);
320
321 const qreal mousePosRelativeToLeftHandleEdge = horizontal
322 ? m_pressPos.x() - m_handlePosBeforePress.x()
323 : m_pressPos.y() - m_handlePosBeforePress.y();
324
325 const QQuickItem *pressedHandleItem = m_handleItems.at(i: m_pressedHandleIndex);
326 const qreal pressedHandleSize = horizontal ? pressedHandleItem->width() : pressedHandleItem->height();
327
328 if (resizeLeftItem) {
329 // The handle shouldn't cross other handles, so use the right edge of
330 // the first handle to the left as the left edge.
331 qreal leftEdge = 0;
332 if (m_pressedHandleIndex - 1 >= 0) {
333 const QQuickItem *leftHandle = m_handleItems.at(i: m_pressedHandleIndex - 1);
334 leftEdge = horizontal
335 ? leftHandle->x() + leftHandle->width()
336 : leftHandle->y() + leftHandle->height();
337 }
338
339 // The mouse can be clicked anywhere in the handle, and if we don't account for
340 // its position within the handle, the handle will jump when dragged.
341 const qreal pressedHandlePos = clampedMousePos - mousePosRelativeToLeftHandleEdge;
342
343 const qreal rightStop = size - accumulated - pressedHandleSize;
344 qreal leftStop = qMax(a: leftEdge, b: pressedHandlePos);
345 // qBound() doesn't care if min is greater than max, but we do.
346 if (leftStop > rightStop)
347 leftStop = rightStop;
348 const qreal newHandlePos = qBound(min: leftStop, val: pressedHandlePos, max: rightStop);
349 const qreal newItemSize = newHandlePos - leftEdge;
350
351 // We still need to use requestedSize in the width/height call below,
352 // because sizeData has already been calculated and now contains an old
353 // effectivePreferredWidth/Height value.
354 requestedSize = newItemSize;
355
356 qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized (dragged) " << item
357 << " (clampedMousePos=" << clampedMousePos
358 << " pressedHandlePos=" << pressedHandlePos
359 << " accumulated=" << accumulated
360 << " leftEdge=" << leftEdge
361 << " leftStop=" << leftStop
362 << " rightStop=" << rightStop
363 << " newHandlePos=" << newHandlePos
364 << " newItemSize=" << newItemSize << ")";
365 } else { // Resizing the item on the right.
366 // The handle shouldn't cross other handles, so use the left edge of
367 // the first handle to the right as the right edge.
368 qreal rightEdge = size;
369 if (m_nextVisibleIndexAfterPressedHandle < m_handleItems.size()) {
370 const QQuickItem *rightHandle = m_handleItems.at(i: m_nextVisibleIndexAfterPressedHandle);
371 rightEdge = horizontal ? rightHandle->x() : rightHandle->y();
372 }
373
374 // The mouse can be clicked anywhere in the handle, and if we don't account for
375 // its position within the handle, the handle will jump when dragged.
376 const qreal pressedHandlePos = clampedMousePos - mousePosRelativeToLeftHandleEdge;
377
378 const qreal leftStop = accumulated - pressedHandleSize;
379 qreal rightStop = qMin(a: rightEdge - pressedHandleSize, b: pressedHandlePos);
380 // qBound() doesn't care if min is greater than max, but we do.
381 if (rightStop < leftStop)
382 rightStop = leftStop;
383 const qreal newHandlePos = qBound(min: leftStop, val: pressedHandlePos, max: rightStop);
384 const qreal newItemSize = rightEdge - (newHandlePos + pressedHandleSize);
385
386 // We still need to use requestedSize in the width/height call below,
387 // because sizeData has already been calculated and now contains an old
388 // effectivePreferredWidth/Height value.
389 requestedSize = newItemSize;
390
391 qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized (dragged) " << item
392 << " (clampedMousePos=" << clampedMousePos
393 << " pressedHandlePos=" << pressedHandlePos
394 << " accumulated=" << accumulated
395 << " leftEdge=" << rightEdge
396 << " leftStop=" << leftStop
397 << " rightStop=" << rightStop
398 << " newHandlePos=" << newHandlePos
399 << " newItemSize=" << newItemSize << ")";
400 }
401 } else if (index != m_fillIndex) {
402 // No handle is being dragged and we're not the fill item,
403 // so set our preferred size as we normally would.
404 requestedSize = horizontal
405 ? sizeData.effectivePreferredWidth : sizeData.effectivePreferredHeight;
406 }
407
408 if (index != m_fillIndex) {
409 LayoutData layoutData;
410 if (horizontal) {
411 layoutData.width = qBound(
412 min: sizeData.effectiveMinimumWidth,
413 val: requestedSize,
414 max: sizeData.effectiveMaximumWidth);
415 layoutData.height = height;
416 } else {
417 layoutData.width = width;
418 layoutData.height = qBound(
419 min: sizeData.effectiveMinimumHeight,
420 val: requestedSize,
421 max: sizeData.effectiveMaximumHeight);
422 }
423
424 // Mark that this item has been manually resized. After this
425 // we can override the preferredWidth & preferredHeight
426 if (isBeingResized)
427 layoutData.wasResizedByHandle = true;
428
429 m_layoutData.insert(key: item, value: layoutData);
430
431 qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized split item " << item
432 << " (effective"
433 << " minW=" << sizeData.effectiveMinimumWidth
434 << ", minH=" << sizeData.effectiveMinimumHeight
435 << ", prfW=" << sizeData.effectivePreferredWidth
436 << ", prfH=" << sizeData.effectivePreferredHeight
437 << ", maxW=" << sizeData.effectiveMaximumWidth
438 << ", maxH=" << sizeData.effectiveMaximumHeight << ")";
439
440 // Keep track of how much space has been used so far.
441 if (horizontal)
442 usedWidth += layoutData.width;
443 else
444 usedHeight += layoutData.height;
445 } else if (indexBeingResizedDueToDrag != m_fillIndex) {
446 // The fill item is resized afterwards, outside of the loop.
447 qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": skipping fill item as we resize it last";
448 }
449
450 // Also account for the size of the handle for this item (if any).
451 // We do this for the fill item too, which is why it's outside of the check above.
452 if (index < count - 1 && m_handle) {
453 QQuickItem *handleItem = m_handleItems.at(i: index);
454 // The handle for an item that's not visible will usually already be skipped
455 // with the item visibility check higher up, but if the view looks like this
456 // [ visible ] | [ visible (fill) ] | [ hidden ]
457 // ^
458 // hidden
459 // and we're iterating over the second item (which is visible but has no handle),
460 // we need to add an extra check for it to avoid it still taking up space.
461 if (handleItem->isVisible()) {
462 if (horizontal) {
463 qCDebug(qlcQQuickSplitView).nospace() << " - " << index
464 << ": handle takes up " << handleItem->width() << " width";
465 usedWidth += handleItem->width();
466 } else {
467 qCDebug(qlcQQuickSplitView).nospace() << " - " << index
468 << ": handle takes up " << handleItem->height() << " height";
469 usedHeight += handleItem->height();
470 }
471 } else {
472 qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": handle is not visible; skipping it";
473 }
474 }
475 }
476}
477
478/*
479 Resizes the fill item by giving it the remaining space
480 after all other items have been resized.
481
482 Items that aren't visible are skipped.
483*/
484void QQuickSplitViewPrivate::layoutResizeFillItem(QQuickItem *fillItem,
485 qreal &usedWidth, qreal &usedHeight, int indexBeingResizedDueToDrag)
486{
487 // Only bother resizing if it it's visible. Also, if it's being resized due to a drag,
488 // then we've already set its size in layoutResizeSplitItems(), so no need to do it here.
489 if (!fillItem->isVisible() || indexBeingResizedDueToDrag == m_fillIndex) {
490 qCDebug(qlcQQuickSplitView).nospace() << m_fillIndex << ": - fill item " << fillItem
491 << " is not visible or was already resized due to a drag;"
492 << " skipping it and its handles (if any)";
493 return;
494 }
495
496 const QQuickItemPrivate *fillItemPrivate = QQuickItemPrivate::get(item: fillItem);
497 const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
498 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: fillItem, create: false));
499 const auto fillSizeData = effectiveSizeData(itemPrivate: fillItemPrivate, attached);
500
501 LayoutData layoutData;
502 if (isHorizontal()) {
503 layoutData.width = qBound(
504 min: fillSizeData.effectiveMinimumWidth,
505 val: width - usedWidth,
506 max: fillSizeData.effectiveMaximumWidth);
507 layoutData.height = height;
508 usedWidth += layoutData.width;
509 } else {
510 layoutData.width = width;
511 layoutData.height = qBound(
512 min: fillSizeData.effectiveMinimumHeight,
513 val: height - usedHeight,
514 max: fillSizeData.effectiveMaximumHeight);
515 usedHeight += layoutData.height;
516 }
517
518 m_layoutData.insert(key: fillItem, value: layoutData);
519
520 qCDebug(qlcQQuickSplitView).nospace() << " - " << m_fillIndex
521 << ": resized split fill item " << fillItem << " (effective"
522 << " minW=" << fillSizeData.effectiveMinimumWidth
523 << ", minH=" << fillSizeData.effectiveMinimumHeight
524 << ", maxW=" << fillSizeData.effectiveMaximumWidth
525 << ", maxH=" << fillSizeData.effectiveMaximumHeight << ")";
526}
527
528/*
529 Limit the sizes if needed and apply them into items.
530*/
531void QQuickSplitViewPrivate::limitAndApplySizes(qreal usedWidth, qreal usedHeight)
532{
533 const int count = contentModel->count();
534 const bool horizontal = isHorizontal();
535
536 const qreal maxSize = horizontal ? width : height;
537 const qreal usedSize = horizontal ? usedWidth : usedHeight;
538 if (usedSize > maxSize) {
539 // If items don't fit, reduce the size of non-filled items from
540 // right to left / bottom to top. At this point filled item is
541 // already at its minimum size or usedSize wouldn't be > maxSize.
542 qreal delta = usedSize - maxSize;
543 for (int index = count - 1; index >= 0; --index) {
544 if (index == m_fillIndex)
545 continue;
546 QQuickItem *item = qobject_cast<QQuickItem*>(o: contentModel->object(index));
547 if (!item->isVisible())
548 continue;
549
550 const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
551 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
552 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: item, create: false));
553 const auto sizeData = effectiveSizeData(itemPrivate, attached);
554 const qreal maxReduce = horizontal ?
555 m_layoutData[item].width - sizeData.effectiveMinimumWidth :
556 m_layoutData[item].height - sizeData.effectiveMinimumHeight;
557
558 const qreal reduce = std::min(a: maxReduce, b: delta);
559 if (horizontal)
560 m_layoutData[item].width -= reduce;
561 else
562 m_layoutData[item].height -= reduce;
563
564 delta -= reduce;
565 if (delta <= 0) {
566 // Now all the items fit, so continue
567 break;
568 }
569 }
570 }
571
572 // Apply the new sizes into items
573 for (int index = 0; index < count; ++index) {
574 QQuickItem *item = qobject_cast<QQuickItem*>(o: contentModel->object(index));
575 if (!item->isVisible())
576 continue;
577
578 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
579 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: item, create: false));
580 LayoutData layoutData = m_layoutData.value(key: item);
581 if (layoutData.wasResizedByHandle) {
582 // Modify the preferredWidth/Height, otherwise the original implicit/preferred size
583 // will be used on the next layout (when it's no longer being resized).
584 if (!attached) {
585 // Force the attached object to be created since we rely on it.
586 attached = qobject_cast<QQuickSplitViewAttached*>(
587 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: item, create: true));
588 }
589 /*
590 Users could conceivably respond to size changes in items by setting attached
591 SplitView properties:
592
593 onWidthChanged: if (width < 10) secondItem.SplitView.preferredWidth = 100
594
595 We handle this by doing another layout after the current layout if the
596 attached/implicit size properties are set during this layout. However, we also
597 need to set preferredWidth/Height here, otherwise the original implicit/preferred sizes
598 will be used on the next layout (when it's no longer being resized).
599 But we don't want this to count as a request for a delayed layout, so we guard against it.
600 */
601 m_ignoreNextLayoutRequest = true;
602 if (horizontal)
603 attached->setPreferredWidth(layoutData.width);
604 else
605 attached->setPreferredHeight(layoutData.height);
606 }
607
608 item->setWidth(layoutData.width);
609 item->setHeight(layoutData.height);
610 }
611}
612
613/*
614 Positions items by laying them out in a row or column.
615
616 Items that aren't visible are skipped.
617*/
618void QQuickSplitViewPrivate::layoutPositionItems(const QQuickItem *fillItem)
619{
620 const bool horizontal = isHorizontal();
621 const int count = contentModel->count();
622 qreal usedWidth = 0;
623 qreal usedHeight = 0;
624
625 for (int i = 0; i < count; ++i) {
626 QQuickItem *item = qobject_cast<QQuickItem*>(o: contentModel->object(index: i));
627 if (!item->isVisible()) {
628 qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": split item " << item
629 << " is not visible; skipping it and its handles (if any)";
630 continue;
631 }
632
633 // Position the item.
634 if (horizontal) {
635 item->setX(usedWidth);
636 item->setY(0);
637 } else {
638 item->setX(0);
639 item->setY(usedHeight);
640 }
641
642 // Keep track of how much space has been used so far.
643 if (horizontal)
644 usedWidth += item->width();
645 else
646 usedHeight += item->height();
647
648 if (Q_UNLIKELY(qlcQQuickSplitView().isDebugEnabled())) {
649 const QQuickItemPrivate *fillItemPrivate = QQuickItemPrivate::get(item: fillItem);
650 const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
651 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: fillItem, create: false));
652 const auto sizeData = effectiveSizeData(itemPrivate: fillItemPrivate, attached);
653 qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": positioned "
654 << (i == m_fillIndex ? "fill item " : "item ") << item << " (effective"
655 << " minW=" << sizeData.effectiveMinimumWidth
656 << ", minH=" << sizeData.effectiveMinimumHeight
657 << ", prfW=" << sizeData.effectivePreferredWidth
658 << ", prfH=" << sizeData.effectivePreferredHeight
659 << ", maxW=" << sizeData.effectiveMaximumWidth
660 << ", maxH=" << sizeData.effectiveMaximumHeight << ")";
661 }
662
663 // Position the handle for this item (if any).
664 if (i < count - 1 && m_handle) {
665 // Position the handle.
666 QQuickItem *handleItem = m_handleItems.at(i);
667 handleItem->setX(horizontal ? usedWidth : 0);
668 handleItem->setY(horizontal ? 0 : usedHeight);
669
670 if (horizontal)
671 usedWidth += handleItem->width();
672 else
673 usedHeight += handleItem->height();
674
675 qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": positioned handle " << handleItem;
676 }
677 }
678}
679
680void QQuickSplitViewPrivate::requestLayout()
681{
682 Q_Q(QQuickSplitView);
683 q->polish();
684}
685
686/*
687 Layout steps are (horizontal SplitView as an example):
688 1) layoutResizeSplitItems: Gives each non-filled item its preferredWidth
689 or if not set, implicitWidth. Sizes are kept between effectiveMinimumWidth
690 and effectiveMaximumWidth and stored into layoutData for now.
691 2) layoutResizeFillItem: Gives filled item all the remaining space. Size is
692 kept between effectiveMinimumWidth and effectiveMaximumWidth and stored
693 into layoutData for now.
694 3) limitAndApplySizes: If we have used more space than SplitView item has,
695 start reducing non-filled item sizes from right-to-left. Reduce them up
696 to minimumWidth or until SplitView item width is reached. Finally set the
697 new item sizes from layoutData.
698*/
699void QQuickSplitViewPrivate::layout()
700{
701 if (!componentComplete)
702 return;
703
704 if (m_layingOut)
705 return;
706
707 const int count = contentModel->count();
708 if (count <= 0)
709 return;
710
711 Q_ASSERT_X(m_fillIndex < count, Q_FUNC_INFO, qPrintable(
712 QString::fromLatin1("m_fillIndex is %1 but our count is %2").arg(m_fillIndex).arg(count)));
713
714 Q_ASSERT_X(!m_handle || m_handleItems.size() == count - 1, Q_FUNC_INFO, qPrintable(QString::fromLatin1(
715 "Expected %1 handle items, but there are %2").arg(count - 1).arg(m_handleItems.size())));
716
717 // We allow mouse events to instantly trigger layouts, whereas with e.g.
718 // attached properties being set, we require a delayed layout.
719 // To prevent recursive calls during mouse events, we need this guard.
720 QBoolBlocker guard(m_layingOut, true);
721
722 const bool horizontal = isHorizontal();
723 qCDebug(qlcQQuickSplitView) << "laying out" << count << "split items"
724 << (horizontal ? "horizontally" : "vertically") << "in SplitView" << q_func();
725
726 // Total sizes of items used during the layout operation.
727 qreal usedWidth = 0;
728 qreal usedHeight = 0;
729 int indexBeingResizedDueToDrag = -1;
730 m_layoutData.clear();
731
732 qCDebug(qlcQQuickSplitView) << " resizing:";
733
734 // First, resize the non-filled items. We need to do this first because otherwise fill
735 // items would take up all of the remaining space as soon as they are encountered.
736 layoutResizeSplitItems(usedWidth, usedHeight, indexBeingResizedDueToDrag);
737
738 qCDebug(qlcQQuickSplitView).nospace()
739 << " - (remaining width=" << width - usedWidth
740 << " remaining height=" << height - usedHeight << ")";
741
742 // Give the fill item the remaining space.
743 QQuickItem *fillItem = qobject_cast<QQuickItem*>(o: contentModel->object(index: m_fillIndex));
744 layoutResizeFillItem(fillItem, usedWidth, usedHeight, indexBeingResizedDueToDrag);
745
746 // Reduce the sizes still if needed and apply them into items.
747 limitAndApplySizes(usedWidth, usedHeight);
748
749 qCDebug(qlcQQuickSplitView) << " positioning:";
750
751 // Position the items.
752 layoutPositionItems(fillItem);
753
754 qCDebug(qlcQQuickSplitView).nospace() << "finished layouting";
755}
756
757void QQuickSplitViewPrivate::createHandles()
758{
759 Q_ASSERT(m_handle);
760 // A handle only makes sense if there are two items on either side.
761 if (contentModel->count() <= 1)
762 return;
763
764 // Create new handle items if there aren't enough.
765 const int count = contentModel->count() - 1;
766 qCDebug(qlcQQuickSplitView) << "creating" << count << "handles";
767 m_handleItems.reserve(size: count);
768 for (int i = 0; i < count; ++i)
769 createHandleItem(index: i);
770}
771
772void QQuickSplitViewPrivate::createHandleItem(int index)
773{
774 Q_Q(QQuickSplitView);
775 if (contentModel->count() <= 1)
776 return;
777
778 qCDebug(qlcQQuickSplitView) << "- creating handle for split item at index" << index
779 << "from handle component" << m_handle;
780
781 // If we don't use the correct context, it won't be possible to refer to
782 // the control's id from within the delegate.
783 QQmlContext *context = m_handle->creationContext();
784 // The component might not have been created in QML, in which case
785 // the creation context will be null and we have to create it ourselves.
786 if (!context)
787 context = qmlContext(q);
788 QQuickItem *handleItem = qobject_cast<QQuickItem*>(o: m_handle->beginCreate(context));
789 if (handleItem) {
790 handleItem->setParent(q);
791 qCDebug(qlcQQuickSplitView) << "- successfully created handle item" << handleItem << "for split item at index" << index;
792
793 // Insert the item to our list of items *before* its parent is set to us,
794 // so that we can avoid it being added as a content item by checking
795 // if it is in the list in isContent().
796 m_handleItems.insert(i: index, t: handleItem);
797
798 handleItem->setParentItem(q);
799 // Handles must have priority for press events, so we need to set this.
800 handleItem->setAcceptedMouseButtons(Qt::LeftButton);
801 handleItem->setKeepMouseGrab(true);
802#if QT_CONFIG(cursor)
803 updateCursorHandle(handleItem);
804#endif
805 m_handle->completeCreate();
806 resizeHandle(handleItem);
807 }
808}
809
810void QQuickSplitViewPrivate::removeExcessHandles()
811{
812 int excess = m_handleItems.size() - qMax(a: 0, b: contentModel->count() - 1);
813 qCDebug(qlcQQuickSplitView) << "removing" << excess << "excess handles from the end of our list";
814 for (; excess > 0; --excess) {
815 QQuickItem *handleItem = m_handleItems.takeLast();
816 delete handleItem;
817 }
818}
819
820qreal QQuickSplitViewPrivate::accumulatedSize(int firstIndex, int lastIndex) const
821{
822 qreal size = 0.0;
823 const bool horizontal = isHorizontal();
824 for (int i = firstIndex; i <= lastIndex; ++i) {
825 QQuickItem *item = qobject_cast<QQuickItem*>(o: contentModel->object(index: i));
826 if (item->isVisible()) {
827 if (i != m_fillIndex) {
828 size += horizontal ? item->width() : item->height();
829 } else {
830 // If the fill item has a minimum size specified, we must respect it.
831 const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
832 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: item, create: false));
833 if (attached) {
834 const QQuickSplitViewAttachedPrivate *attachedPrivate
835 = QQuickSplitViewAttachedPrivate::get(attached);
836 if (horizontal && attachedPrivate->m_isMinimumWidthSet)
837 size += attachedPrivate->m_minimumWidth;
838 else if (!horizontal && attachedPrivate->m_isMinimumHeightSet)
839 size += attachedPrivate->m_minimumHeight;
840 }
841 }
842 }
843
844 // Only add the handle's width if there's actually a handle for this split item index.
845 if (i < lastIndex || lastIndex < contentModel->count() - 1) {
846 const QQuickItem *handleItem = m_handleItems.at(i);
847 if (handleItem->isVisible())
848 size += horizontal ? handleItem->width() : handleItem->height();
849 }
850 }
851 return size;
852}
853
854qreal effectiveMinimumWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate)
855{
856 return attachedPrivate && attachedPrivate->m_isMinimumWidthSet ? attachedPrivate->m_minimumWidth : 0;
857}
858
859qreal effectiveMinimumHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate)
860{
861 return attachedPrivate && attachedPrivate->m_isMinimumHeightSet ? attachedPrivate->m_minimumHeight : 0;
862}
863
864qreal effectivePreferredWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate,
865 const QQuickItemPrivate *itemPrivate)
866{
867 return attachedPrivate && attachedPrivate->m_isPreferredWidthSet
868 ? attachedPrivate->m_preferredWidth : itemPrivate->implicitWidth;
869}
870
871qreal effectivePreferredHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate,
872 const QQuickItemPrivate *itemPrivate)
873{
874 return attachedPrivate && attachedPrivate->m_isPreferredHeightSet
875 ? attachedPrivate->m_preferredHeight : itemPrivate->implicitHeight;
876}
877
878qreal effectiveMaximumWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate)
879{
880 return attachedPrivate && attachedPrivate->m_isMaximumWidthSet
881 ? attachedPrivate->m_maximumWidth : std::numeric_limits<qreal>::infinity();
882}
883
884qreal effectiveMaximumHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate)
885{
886 return attachedPrivate && attachedPrivate->m_isMaximumHeightSet
887 ? attachedPrivate->m_maximumHeight : std::numeric_limits<qreal>::infinity();
888}
889
890// We don't just take an index, because the item and attached properties object
891// will both be used outside of this function by calling code, so save some
892// time by not accessing them twice.
893QQuickSplitViewPrivate::EffectiveSizeData QQuickSplitViewPrivate::effectiveSizeData(
894 const QQuickItemPrivate *itemPrivate, const QQuickSplitViewAttached *attached) const
895{
896 EffectiveSizeData data;
897 const QQuickSplitViewAttachedPrivate *attachedPrivate = attached ? QQuickSplitViewAttachedPrivate::get(attached) : nullptr;
898 data.effectiveMinimumWidth = effectiveMinimumWidth(attachedPrivate);
899 data.effectiveMinimumHeight = effectiveMinimumHeight(attachedPrivate);
900 data.effectivePreferredWidth = effectivePreferredWidth(attachedPrivate, itemPrivate);
901 data.effectivePreferredHeight = effectivePreferredHeight(attachedPrivate, itemPrivate);
902 data.effectiveMaximumWidth = effectiveMaximumWidth(attachedPrivate);
903 data.effectiveMaximumHeight = effectiveMaximumHeight(attachedPrivate);
904 return data;
905}
906
907int QQuickSplitViewPrivate::handleIndexForSplitIndex(int splitIndex) const
908{
909 // If it's the first and only item in the view, it doesn't have a handle,
910 // so return -1: splitIndex (0) - 1.
911 // If it's the last item in the view, it doesn't have a handle, so use
912 // the handle for the previous item.
913 return splitIndex == contentModel->count() - 1 ? splitIndex - 1 : splitIndex;
914}
915
916void QQuickSplitViewPrivate::destroyHandles()
917{
918 qCDebug(qlcQQuickSplitView) << "destroying" << m_handleItems.size() << "handles";
919 qDeleteAll(c: m_handleItems);
920 m_handleItems.clear();
921}
922
923void QQuickSplitViewPrivate::resizeHandle(QQuickItem *handleItem)
924{
925 const bool horizontal = isHorizontal();
926 handleItem->setWidth(horizontal ? handleItem->implicitWidth() : width);
927 handleItem->setHeight(horizontal ? height : handleItem->implicitHeight());
928}
929
930void QQuickSplitViewPrivate::resizeHandles()
931{
932 for (QQuickItem *handleItem : m_handleItems)
933 resizeHandle(handleItem);
934}
935
936#if QT_CONFIG(cursor)
937void QQuickSplitViewPrivate::updateCursorHandle(QQuickItem *handleItem)
938{
939 handleItem->setCursor(isHorizontal() ? Qt::SplitHCursor : Qt::SplitVCursor);
940}
941#endif
942
943void QQuickSplitViewPrivate::updateHandleVisibilities()
944{
945 // If this is the first item that is visible, we won't have any
946 // handles yet, because we don't create a handle if we only have one item.
947 if (m_handleItems.isEmpty())
948 return;
949
950 // If the visibility/children change makes any item the last (right/bottom-most)
951 // visible item, we don't want to display a handle for it either:
952 // [ visible (fill) ] | [ hidden ] | [ hidden ]
953 // ^ ^
954 // hidden hidden
955 const int count = contentModel->count();
956 int lastVisibleItemIndex = -1;
957 for (int i = count - 1; i >= 0; --i) {
958 const QQuickItem *item = qobject_cast<QQuickItem*>(o: contentModel->object(index: i));
959 if (item->isVisible()) {
960 lastVisibleItemIndex = i;
961 break;
962 }
963 }
964
965 for (int i = 0; i < count - 1; ++i) {
966 const QQuickItem *item = qobject_cast<QQuickItem*>(o: contentModel->object(index: i));
967 QQuickItem *handleItem = m_handleItems.at(i);
968 if (i != lastVisibleItemIndex)
969 handleItem->setVisible(item->isVisible());
970 else
971 handleItem->setVisible(false);
972 qCDebug(qlcQQuickSplitView) << "set visible property of handle" << handleItem << "at index"
973 << i << "to" << handleItem->isVisible();
974 }
975}
976
977void QQuickSplitViewPrivate::updateHoveredHandle(QQuickItem *hoveredItem)
978{
979 qCDebug(qlcQQuickSplitViewPointer) << "updating hovered handle after" << hoveredItem << "was hovered";
980
981 const int oldHoveredHandleIndex = m_hoveredHandleIndex;
982 m_hoveredHandleIndex = m_handleItems.indexOf(t: hoveredItem);
983 if (m_hoveredHandleIndex == oldHoveredHandleIndex)
984 return;
985
986 // First, clear the hovered flag of any previously-hovered handle.
987 if (oldHoveredHandleIndex != -1) {
988 QQuickItem *oldHoveredHandle = m_handleItems.at(i: oldHoveredHandleIndex);
989 QQuickSplitHandleAttached *oldHoveredHandleAttached = qobject_cast<QQuickSplitHandleAttached*>(
990 object: qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(obj: oldHoveredHandle, create: true));
991 QQuickSplitHandleAttachedPrivate::get(attached: oldHoveredHandleAttached)->setHovered(false);
992 qCDebug(qlcQQuickSplitViewPointer) << "handle item at index" << oldHoveredHandleIndex << "is no longer hovered";
993 }
994
995 if (m_hoveredHandleIndex != -1) {
996 QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>(
997 object: qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(obj: hoveredItem, create: true));
998 QQuickSplitHandleAttachedPrivate::get(attached: handleAttached)->setHovered(true);
999 qCDebug(qlcQQuickSplitViewPointer) << "handle item at index" << m_hoveredHandleIndex << "is now hovered";
1000 } else {
1001 qCDebug(qlcQQuickSplitViewPointer) << "either there is no hovered item or" << hoveredItem << "is not a handle";
1002 }
1003}
1004
1005void QQuickSplitViewPrivate::setResizing(bool resizing)
1006{
1007 Q_Q(QQuickSplitView);
1008 if (resizing == m_resizing)
1009 return;
1010
1011 m_resizing = resizing;
1012 emit q->resizingChanged();
1013}
1014
1015bool QQuickSplitViewPrivate::isHorizontal() const
1016{
1017 return m_orientation == Qt::Horizontal;
1018}
1019
1020QQuickItem *QQuickSplitViewPrivate::getContentItem()
1021{
1022 Q_Q(QQuickSplitView);
1023 if (QQuickItem *item = QQuickContainerPrivate::getContentItem())
1024 return item;
1025
1026 return new QQuickContentItem(q);
1027}
1028
1029bool QQuickSplitViewPrivate::handlePress(const QPointF &point, ulong timestamp)
1030{
1031 Q_Q(QQuickSplitView);
1032 QQuickContainerPrivate::handlePress(point, timestamp);
1033
1034 QQuickItem *pressedItem = q->childAt(x: point.x(), y: point.y());
1035 const int pressedHandleIndex = m_handleItems.indexOf(t: pressedItem);
1036 if (pressedHandleIndex != -1) {
1037 m_pressedHandleIndex = pressedHandleIndex;
1038 m_pressPos = point;
1039 m_mousePos = point;
1040
1041 const QQuickItem *leftOrTopItem = qobject_cast<QQuickItem*>(o: contentModel->object(index: m_pressedHandleIndex));
1042 // Find the first item to the right/bottom of this one that is visible.
1043 QQuickItem *rightOrBottomItem = nullptr;
1044 m_nextVisibleIndexAfterPressedHandle = -1;
1045 for (int i = m_pressedHandleIndex + 1; i < contentModel->count(); ++i) {
1046 auto nextItem = qobject_cast<QQuickItem*>(o: contentModel->object(index: i));
1047 if (nextItem->isVisible()) {
1048 rightOrBottomItem = nextItem;
1049 m_nextVisibleIndexAfterPressedHandle = i;
1050 break;
1051 }
1052 }
1053 Q_ASSERT_X(rightOrBottomItem, Q_FUNC_INFO, qPrintable(QString::fromLatin1(
1054 "Failed to find a visible item to the right/bottom of the one that was pressed at index %1; this shouldn't happen")
1055 .arg(m_pressedHandleIndex)));
1056
1057 const bool isHorizontal = m_orientation == Qt::Horizontal;
1058 m_leftOrTopItemSizeBeforePress = isHorizontal ? leftOrTopItem->width() : leftOrTopItem->height();
1059 m_rightOrBottomItemSizeBeforePress = isHorizontal ? rightOrBottomItem->width() : rightOrBottomItem->height();
1060 m_handlePosBeforePress = pressedItem->position();
1061
1062
1063 // Force the attached object to be created since we rely on it.
1064 QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>(
1065 object: qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(obj: pressedItem, create: true));
1066 QQuickSplitHandleAttachedPrivate::get(attached: handleAttached)->setPressed(true);
1067
1068 setResizing(true);
1069
1070 qCDebug(qlcQQuickSplitViewPointer).nospace() << "handled press -"
1071 << " left/top index=" << m_pressedHandleIndex << ","
1072 << " size before press=" << m_leftOrTopItemSizeBeforePress << ","
1073 << " item=" << leftOrTopItem
1074 << " right/bottom index=" << m_nextVisibleIndexAfterPressedHandle << ","
1075 << " size before press=" << m_rightOrBottomItemSizeBeforePress
1076 << " item=" << rightOrBottomItem;
1077 }
1078 return true;
1079}
1080
1081bool QQuickSplitViewPrivate::handleMove(const QPointF &point, ulong timestamp)
1082{
1083 QQuickContainerPrivate::handleMove(point, timestamp);
1084
1085 if (m_pressedHandleIndex != -1) {
1086 m_mousePos = point;
1087 // Don't request layouts for input events because we want
1088 // resizing to be as responsive and smooth as possible.
1089 updatePolish();
1090 }
1091 return true;
1092}
1093
1094bool QQuickSplitViewPrivate::handleRelease(const QPointF &point, ulong timestamp)
1095{
1096 QQuickContainerPrivate::handleRelease(point, timestamp);
1097
1098 if (m_pressedHandleIndex != -1) {
1099 QQuickItem *pressedHandle = m_handleItems.at(i: m_pressedHandleIndex);
1100 QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>(
1101 object: qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(obj: pressedHandle, create: true));
1102 QQuickSplitHandleAttachedPrivate::get(attached: handleAttached)->setPressed(false);
1103 }
1104
1105 setResizing(false);
1106
1107 m_pressedHandleIndex = -1;
1108 m_pressPos = QPointF();
1109 m_mousePos = QPointF();
1110 m_handlePosBeforePress = QPointF();
1111 m_leftOrTopItemSizeBeforePress = 0.0;
1112 m_rightOrBottomItemSizeBeforePress = 0.0;
1113 return true;
1114}
1115
1116void QQuickSplitViewPrivate::itemVisibilityChanged(QQuickItem *item)
1117{
1118 const int itemIndex = contentModel->indexOf(object: item, objectContext: nullptr);
1119 Q_ASSERT(itemIndex != -1);
1120
1121 qCDebug(qlcQQuickSplitView) << "visible property of split item"
1122 << item << "at index" << itemIndex << "changed to" << item->isVisible();
1123
1124 // The visibility of an item just changed, so we need to update the visibility
1125 // of the corresponding handle (if one exists).
1126
1127 const int handleIndex = handleIndexForSplitIndex(splitIndex: itemIndex);
1128 if (handleIndex != -1) {
1129 QQuickItem *handleItem = m_handleItems.at(i: handleIndex);
1130 handleItem->setVisible(item->isVisible());
1131
1132 qCDebug(qlcQQuickSplitView) << "set visible property of handle item"
1133 << handleItem << "at index" << handleIndex << "to" << item->isVisible();
1134 }
1135
1136 updateHandleVisibilities();
1137 updateFillIndex();
1138 requestLayout();
1139}
1140
1141void QQuickSplitViewPrivate::itemImplicitWidthChanged(QQuickItem *)
1142{
1143 requestLayout();
1144}
1145
1146void QQuickSplitViewPrivate::itemImplicitHeightChanged(QQuickItem *)
1147{
1148 requestLayout();
1149}
1150
1151void QQuickSplitViewPrivate::updatePolish()
1152{
1153 layout();
1154}
1155
1156QQuickSplitViewPrivate *QQuickSplitViewPrivate::get(QQuickSplitView *splitView)
1157{
1158 return splitView->d_func();
1159}
1160
1161QQuickSplitView::QQuickSplitView(QQuickItem *parent)
1162 : QQuickContainer(*(new QQuickSplitViewPrivate), parent)
1163{
1164 Q_D(QQuickSplitView);
1165 d->changeTypes |= QQuickItemPrivate::Visibility;
1166
1167 setFiltersChildMouseEvents(true);
1168}
1169
1170QQuickSplitView::QQuickSplitView(QQuickSplitViewPrivate &dd, QQuickItem *parent)
1171 : QQuickContainer(dd, parent)
1172{
1173 Q_D(QQuickSplitView);
1174 d->changeTypes |= QQuickItemPrivate::Visibility;
1175
1176 setFiltersChildMouseEvents(true);
1177}
1178
1179QQuickSplitView::~QQuickSplitView()
1180{
1181 Q_D(QQuickSplitView);
1182 for (int i = 0; i < d->contentModel->count(); ++i) {
1183 QQuickItem *item = qobject_cast<QQuickItem*>(o: d->contentModel->object(index: i));
1184 d->removeImplicitSizeListener(item);
1185 }
1186}
1187
1188/*!
1189 \qmlproperty enumeration QtQuick.Controls::SplitView::orientation
1190
1191 This property holds the orientation of the SplitView.
1192
1193 The orientation determines how the split items are laid out:
1194
1195 Possible values:
1196 \value Qt.Horizontal The items are laid out horizontally (default).
1197 \value Qt.Vertical The items are laid out vertically.
1198*/
1199Qt::Orientation QQuickSplitView::orientation() const
1200{
1201 Q_D(const QQuickSplitView);
1202 return d->m_orientation;
1203}
1204
1205void QQuickSplitView::setOrientation(Qt::Orientation orientation)
1206{
1207 Q_D(QQuickSplitView);
1208 if (orientation == d->m_orientation)
1209 return;
1210
1211 d->m_orientation = orientation;
1212
1213#if QT_CONFIG(cursor)
1214 for (QQuickItem *handleItem : d->m_handleItems)
1215 d->updateCursorHandle(handleItem);
1216#endif
1217 emit orientationChanged();
1218
1219 // Do this after emitting orientationChanged so that the bindings in QML
1220 // update the implicit size in time.
1221 d->resizeHandles();
1222 // This is queued (via polish) anyway, but to make our intentions clear,
1223 // do it afterwards too.
1224 d->requestLayout();
1225}
1226
1227/*!
1228 \qmlproperty bool QtQuick.Controls::SplitView::resizing
1229 \readonly
1230
1231 This property is \c true when the user is resizing
1232 split items by dragging on the splitter handles.
1233*/
1234bool QQuickSplitView::isResizing() const
1235{
1236 Q_D(const QQuickSplitView);
1237 return d->m_resizing;
1238}
1239
1240/*!
1241 \qmlproperty Component QtQuick.Controls::SplitView::handle
1242
1243 This property holds the handle component.
1244
1245 An instance of this component will be instantiated \c {count - 1}
1246 times, as long as \c count is greater than than \c {1}.
1247
1248 The following table explains how each handle will be resized
1249 depending on the orientation of the split view:
1250
1251 \table
1252 \header
1253 \li Orientation
1254 \li Handle Width
1255 \li Handle Height
1256 \row
1257 \li \c Qt.Horizontal
1258 \li \c implicitWidth
1259 \li The \c height of the SplitView.
1260 \row
1261 \li \c Qt.Vertical
1262 \li The \c width of the SplitView.
1263 \li \c implicitHeight
1264 \endtable
1265
1266 To change the size of the handle for mouse and touch events without
1267 changing its visual size, use a \l {Item::}{containmentMask}:
1268
1269 \snippet qtquickcontrols-splitview-handle-containmentmask.qml 1
1270
1271 \sa {Customizing SplitView}
1272*/
1273QQmlComponent *QQuickSplitView::handle()
1274{
1275 Q_D(const QQuickSplitView);
1276 return d->m_handle;
1277}
1278
1279void QQuickSplitView::setHandle(QQmlComponent *handle)
1280{
1281 Q_D(QQuickSplitView);
1282 if (handle == d->m_handle)
1283 return;
1284
1285 qCDebug(qlcQQuickSplitView) << "setting handle" << handle;
1286
1287 if (d->m_handle)
1288 d->destroyHandles();
1289
1290 d->m_handle = handle;
1291
1292 if (d->m_handle) {
1293 d->createHandles();
1294 d->updateHandleVisibilities();
1295 }
1296
1297 d->requestLayout();
1298
1299 emit handleChanged();
1300}
1301
1302bool QQuickSplitView::isContent(QQuickItem *item) const
1303{
1304 Q_D(const QQuickSplitView);
1305 if (!qmlContext(item))
1306 return false;
1307
1308 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
1309 return false;
1310
1311 return !d->m_handleItems.contains(t: item);
1312}
1313
1314QQuickSplitViewAttached *QQuickSplitView::qmlAttachedProperties(QObject *object)
1315{
1316 return new QQuickSplitViewAttached(object);
1317}
1318
1319/*!
1320 \qmlmethod var QtQuick.Controls::SplitView::saveState()
1321
1322 Saves the preferred sizes of split items into a byte array and returns it.
1323
1324 \sa {Serializing SplitView's State}, restoreState()
1325*/
1326QVariant QQuickSplitView::saveState()
1327{
1328#if QT_CONFIG(cborstreamwriter)
1329 Q_D(QQuickSplitView);
1330 qCDebug(qlcQQuickSplitViewState) << "saving state for split items in" << this;
1331
1332 // Save the preferred sizes of each split item.
1333 QCborArray cborArray;
1334 for (int i = 0; i < d->contentModel->count(); ++i) {
1335 const QQuickItem *item = qobject_cast<QQuickItem*>(o: d->contentModel->object(index: i));
1336 const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
1337 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: item, create: false));
1338 // Don't serialise stuff if we don't need to. If a split item was given a preferred
1339 // size in QML or it was dragged, it will have an attached object and either
1340 // m_isPreferredWidthSet or m_isPreferredHeightSet (or both) will be true,
1341 // so items without these can be skipped. We write the index of each item
1342 // that has data so that we know which item to set it on when restoring.
1343 if (!attached)
1344 continue;
1345
1346 const QQuickSplitViewAttachedPrivate *attachedPrivate = QQuickSplitViewAttachedPrivate::get(attached);
1347 if (!attachedPrivate->m_isPreferredWidthSet && !attachedPrivate->m_isPreferredHeightSet)
1348 continue;
1349
1350 QCborMap cborMap;
1351 cborMap[QLatin1String("index")] = i;
1352 if (attachedPrivate->m_isPreferredWidthSet) {
1353 cborMap[QLatin1String("preferredWidth")] = static_cast<double>(attachedPrivate->m_preferredWidth);
1354
1355 qCDebug(qlcQQuickSplitViewState).nospace() << "- wrote preferredWidth of "
1356 << attachedPrivate->m_preferredWidth << " for split item " << item << " at index " << i;
1357 }
1358 if (attachedPrivate->m_isPreferredHeightSet) {
1359 cborMap[QLatin1String("preferredHeight")] = static_cast<double>(attachedPrivate->m_preferredHeight);
1360
1361 qCDebug(qlcQQuickSplitViewState).nospace() << "- wrote preferredHeight of "
1362 << attachedPrivate->m_preferredHeight << " for split item " << item << " at index " << i;
1363 }
1364
1365 cborArray.append(value: cborMap);
1366 }
1367
1368 const QByteArray byteArray = cborArray.toCborValue().toCbor();
1369 qCDebug(qlcQQuickSplitViewState) << "the resulting byte array is:" << byteArray;
1370 return QVariant(byteArray);
1371#else
1372 return QVariant();
1373#endif
1374}
1375
1376/*!
1377 \qmlmethod bool QtQuick.Controls::SplitView::restoreState(state)
1378
1379 Reads the preferred sizes from \a state and applies them to the split items.
1380
1381 Returns \c true if the state was successfully restored, otherwise \c false.
1382
1383 \sa {Serializing SplitView's State}, saveState()
1384*/
1385bool QQuickSplitView::restoreState(const QVariant &state)
1386{
1387 const QByteArray cborByteArray = state.toByteArray();
1388 Q_D(QQuickSplitView);
1389 if (cborByteArray.isEmpty())
1390 return false;
1391
1392 QCborParserError parserError;
1393 const QCborValue cborValue(QCborValue::fromCbor(ba: cborByteArray, error: &parserError));
1394 if (parserError.error != QCborError::NoError) {
1395 qmlWarning(me: this) << "Error reading SplitView state:" << parserError.errorString();
1396 return false;
1397 }
1398
1399 qCDebug(qlcQQuickSplitViewState) << "restoring state for split items of" << this
1400 << "from the following string:" << state;
1401
1402 const QCborArray cborArray(cborValue.toArray());
1403 const int ourCount = d->contentModel->count();
1404 // This could conceivably happen if items were removed from the SplitView since the state was last saved.
1405 if (cborArray.size() > ourCount) {
1406 qmlWarning(me: this) << "Error reading SplitView state: expected "
1407 << ourCount << " or less split items but got " << cborArray.size();
1408 return false;
1409 }
1410
1411 for (auto it = cborArray.constBegin(); it != cborArray.constEnd(); ++it) {
1412 QCborMap cborMap(it->toMap());
1413 const int splitItemIndex = cborMap.value(key: QLatin1String("index")).toInteger();
1414 const bool isPreferredWidthSet = cborMap.contains(key: QLatin1String("preferredWidth"));
1415 const bool isPreferredHeightSet = cborMap.contains(key: QLatin1String("preferredHeight"));
1416
1417 QQuickItem *item = qobject_cast<QQuickItem*>(o: d->contentModel->object(index: splitItemIndex));
1418 // If the split item does not have a preferred size specified in QML, it could still have
1419 // been resized via dragging before it was saved. In this case, it won't have an
1420 // attached object upon application startup, so we create it.
1421 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
1422 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: item, create: true));
1423 if (isPreferredWidthSet) {
1424 const qreal preferredWidth = cborMap.value(key: QLatin1String("preferredWidth")).toDouble();
1425 attached->setPreferredWidth(preferredWidth);
1426 }
1427 if (isPreferredHeightSet) {
1428 const qreal preferredHeight = cborMap.value(key: QLatin1String("preferredHeight")).toDouble();
1429 attached->setPreferredHeight(preferredHeight);
1430 }
1431
1432 const QQuickSplitViewAttachedPrivate *attachedPrivate = QQuickSplitViewAttachedPrivate::get(attached);
1433 qCDebug(qlcQQuickSplitViewState).nospace()
1434 << "- restored the following state for split item " << item << " at index " << splitItemIndex
1435 << ": preferredWidthSet=" << attachedPrivate->m_isPreferredWidthSet
1436 << " preferredWidth=" << attachedPrivate->m_preferredWidth
1437 << " preferredHeightSet=" << attachedPrivate->m_isPreferredHeightSet
1438 << " preferredHeight=" << attachedPrivate->m_preferredHeight;
1439 }
1440
1441 return true;
1442}
1443
1444void QQuickSplitView::componentComplete()
1445{
1446 Q_D(QQuickSplitView);
1447 QQuickControl::componentComplete();
1448 d->updateFillIndex();
1449 d->updatePolish();
1450}
1451
1452void QQuickSplitView::hoverMoveEvent(QHoverEvent *event)
1453{
1454 Q_D(QQuickSplitView);
1455 QQuickContainer::hoverMoveEvent(event);
1456
1457 QQuickItem *hoveredItem = childAt(x: event->position().toPoint().x(), y: event->position().toPoint().y());
1458 d->updateHoveredHandle(hoveredItem);
1459}
1460
1461void QQuickSplitView::hoverLeaveEvent(QHoverEvent *event)
1462{
1463 Q_UNUSED(event);
1464 Q_D(QQuickSplitView);
1465 // If SplitView is no longer hovered (e.g. visible set to false), clear handle hovered value
1466 d->updateHoveredHandle(hoveredItem: nullptr);
1467}
1468
1469bool QQuickSplitView::childMouseEventFilter(QQuickItem *item, QEvent *event)
1470{
1471 Q_D(QQuickSplitView);
1472 qCDebug(qlcQQuickSplitViewPointer) << "childMouseEventFilter called with" << item << event;
1473
1474 if (Q_LIKELY(event->isPointerEvent())) {
1475 auto *pointerEvent = static_cast<QPointerEvent *>(event);
1476 const auto &eventPoint = pointerEvent->points().first();
1477 const QPointF point = mapFromItem(item, point: eventPoint.position());
1478 const auto timestamp = pointerEvent->timestamp();
1479
1480 switch (event->type()) {
1481 case QEvent::MouseButtonPress:
1482 d->handlePress(point, timestamp);
1483 // Keep the mouse grab if this item belongs to the handle,
1484 // otherwise this event can be stolen e.g. Flickable if we're inside it.
1485 if (d->m_pressedHandleIndex != -1)
1486 item->setKeepMouseGrab(true);
1487 break;
1488 case QEvent::MouseButtonRelease:
1489 d->handleRelease(point, timestamp);
1490 break;
1491 case QEvent::MouseMove:
1492 d->handleMove(point, timestamp);
1493 break;
1494 case QEvent::TouchBegin:
1495 if (pointerEvent->pointCount() == 1) {
1496 d->handlePress(point, timestamp);
1497 // We filter the event on behalf of item, but we want the item
1498 // to be the exclusive grabber so that we can continue to filter
1499 // touch events for it.
1500 if (d->m_pressedHandleIndex != -1) {
1501 item->setKeepTouchGrab(true);
1502 pointerEvent->setExclusiveGrabber(point: eventPoint, exclusiveGrabber: item);
1503 }
1504 }
1505 break;
1506 case QEvent::TouchEnd:
1507 if (pointerEvent->pointCount() == 1)
1508 d->handleRelease(point, timestamp);
1509 break;
1510 case QEvent::TouchUpdate:
1511 if (pointerEvent->pointCount() == 1)
1512 d->handleMove(point, timestamp);
1513 break;
1514 default:
1515 break;
1516 }
1517 }
1518
1519 // If this event belongs to the handle, filter it. (d->m_pressedHandleIndex != -1) means that
1520 // we press or move the handle, so we don't need to propagate it further.
1521 if (d->m_pressedHandleIndex != -1)
1522 return true;
1523
1524 return QQuickContainer::childMouseEventFilter(item, event);
1525}
1526
1527void QQuickSplitView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
1528{
1529 Q_D(QQuickSplitView);
1530 QQuickControl::geometryChange(newGeometry, oldGeometry);
1531 d->resizeHandles();
1532 d->requestLayout();
1533}
1534
1535void QQuickSplitView::itemAdded(int index, QQuickItem *item)
1536{
1537 Q_D(QQuickSplitView);
1538 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
1539 return;
1540
1541 const int count = d->contentModel->count();
1542 qCDebug(qlcQQuickSplitView).nospace() << "split item " << item << " added at index " << index
1543 << "; there are now " << count << " items";
1544
1545 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
1546 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: item, create: false));
1547 if (attached)
1548 QQuickSplitViewAttachedPrivate::get(attached)->setView(this);
1549
1550 // Only need to add handles if we have more than one split item.
1551 if (count > 1) {
1552 // If the item was added at the end, it shouldn't get a handle;
1553 // the handle always goes to the split item on the left.
1554 d->createHandleItem(index: index < count - 1 ? index : index - 1);
1555 }
1556
1557 d->addImplicitSizeListener(item);
1558
1559 d->updateHandleVisibilities();
1560 d->updateFillIndex();
1561 d->requestLayout();
1562}
1563
1564void QQuickSplitView::itemMoved(int index, QQuickItem *item)
1565{
1566 Q_D(QQuickSplitView);
1567 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
1568 return;
1569
1570 qCDebug(qlcQQuickSplitView) << "split item" << item << "moved to index" << index;
1571
1572 d->updateHandleVisibilities();
1573 d->updateFillIndex();
1574 d->requestLayout();
1575}
1576
1577void QQuickSplitView::itemRemoved(int index, QQuickItem *item)
1578{
1579 Q_D(QQuickSplitView);
1580 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
1581 return;
1582
1583 qCDebug(qlcQQuickSplitView).nospace() << "split item " << item << " removed from index " << index
1584 << "; there are now " << d->contentModel->count() << " items";
1585
1586 // Clear hovered/pressed handle if there are any.
1587 if (d->m_hoveredHandleIndex != -1 || d->m_pressedHandleIndex != -1) {
1588 const int handleIndex = d->m_hoveredHandleIndex != -1 ? d->m_hoveredHandleIndex : d->m_pressedHandleIndex;
1589 QQuickItem *itemHandle = d->m_handleItems.at(i: handleIndex);
1590 QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>(
1591 object: qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(obj: itemHandle, create: false));
1592 if (handleAttached) {
1593 auto handleAttachedPrivate = QQuickSplitHandleAttachedPrivate::get(attached: handleAttached);
1594 handleAttachedPrivate->setHovered(false);
1595 handleAttachedPrivate->setPressed(false);
1596 }
1597
1598 d->m_hoveredHandleIndex = -1;
1599 d->m_pressedHandleIndex = -1;
1600 }
1601
1602 // Unset any attached properties since the item is no longer owned by us.
1603 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
1604 object: qmlAttachedPropertiesObject<QQuickSplitView>(obj: item, create: false));
1605 if (attached)
1606 QQuickSplitViewAttachedPrivate::get(attached)->setView(this);
1607
1608 d->removeImplicitSizeListener(item);
1609
1610 d->removeExcessHandles();
1611 d->updateHandleVisibilities();
1612 d->updateFillIndex();
1613 d->requestLayout();
1614}
1615
1616#if QT_CONFIG(accessibility)
1617QAccessible::Role QQuickSplitView::accessibleRole() const
1618{
1619 return QAccessible::Pane;
1620}
1621#endif
1622
1623QQuickSplitViewAttached::QQuickSplitViewAttached(QObject *parent)
1624 : QObject(*(new QQuickSplitViewAttachedPrivate), parent)
1625{
1626 Q_D(QQuickSplitViewAttached);
1627 QQuickItem *item = qobject_cast<QQuickItem *>(o: parent);
1628 if (!item) {
1629 qmlWarning(me: parent) << "SplitView: attached properties can only be used on Items";
1630 return;
1631 }
1632
1633 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
1634 return;
1635
1636 d->m_splitItem = item;
1637
1638 // Child items get added to SplitView's contentItem, so we have to ensure
1639 // that exists first before trying to set m_splitView.
1640 // Apparently, in some cases it's normal for the parent item
1641 // to not exist until shortly after this constructor has run.
1642 if (!item->parentItem())
1643 return;
1644
1645 // This will get hit when attached SplitView properties are imperatively set
1646 // on an item that previously had none set, for example.
1647 QQuickSplitView *splitView = qobject_cast<QQuickSplitView*>(object: item->parentItem()->parentItem());
1648 if (!splitView) {
1649 qmlWarning(me: parent) << "SplitView: attached properties must be accessed through a direct child of SplitView";
1650 return;
1651 }
1652
1653 d->setView(splitView);
1654}
1655
1656/*!
1657 \qmlattachedproperty SplitView QtQuick.Controls::SplitView::view
1658
1659 This attached property holds the split view of the item it is
1660 attached to, or \c null if the item is not in a split view.
1661*/
1662QQuickSplitView *QQuickSplitViewAttached::view() const
1663{
1664 Q_D(const QQuickSplitViewAttached);
1665 return d->m_splitView;
1666}
1667
1668/*!
1669 \qmlattachedproperty real QtQuick.Controls::SplitView::minimumWidth
1670
1671 This attached property controls the minimum width of the split item.
1672 The \l preferredWidth is bound within the \l minimumWidth and
1673 \l maximumWidth. A split item cannot be dragged to be smaller than
1674 its \c minimumWidth.
1675
1676 The default value is \c 0. To reset this property to its default value,
1677 set it to \c undefined.
1678
1679 \sa maximumWidth, preferredWidth, fillWidth, minimumHeight
1680*/
1681qreal QQuickSplitViewAttached::minimumWidth() const
1682{
1683 Q_D(const QQuickSplitViewAttached);
1684 return d->m_minimumWidth;
1685}
1686
1687void QQuickSplitViewAttached::setMinimumWidth(qreal width)
1688{
1689 Q_D(QQuickSplitViewAttached);
1690 d->m_isMinimumWidthSet = true;
1691 if (qFuzzyCompare(p1: width, p2: d->m_minimumWidth))
1692 return;
1693
1694 d->m_minimumWidth = width;
1695 d->requestLayoutView();
1696 emit minimumWidthChanged();
1697}
1698
1699void QQuickSplitViewAttached::resetMinimumWidth()
1700{
1701 Q_D(QQuickSplitViewAttached);
1702 const qreal oldEffectiveMinimumWidth = effectiveMinimumWidth(attachedPrivate: d);
1703
1704 d->m_isMinimumWidthSet = false;
1705 d->m_minimumWidth = -1;
1706
1707 const qreal newEffectiveMinimumWidth = effectiveMinimumWidth(attachedPrivate: d);
1708 if (qFuzzyCompare(p1: newEffectiveMinimumWidth, p2: oldEffectiveMinimumWidth))
1709 return;
1710
1711 d->requestLayoutView();
1712 emit minimumWidthChanged();
1713}
1714
1715/*!
1716 \qmlattachedproperty real QtQuick.Controls::SplitView::minimumHeight
1717
1718 This attached property controls the minimum height of the split item.
1719 The \l preferredHeight is bound within the \l minimumHeight and
1720 \l maximumHeight. A split item cannot be dragged to be smaller than
1721 its \c minimumHeight.
1722
1723 The default value is \c 0. To reset this property to its default value,
1724 set it to \c undefined.
1725
1726 \sa maximumHeight, preferredHeight, fillHeight, minimumWidth
1727*/
1728qreal QQuickSplitViewAttached::minimumHeight() const
1729{
1730 Q_D(const QQuickSplitViewAttached);
1731 return d->m_minimumHeight;
1732}
1733
1734void QQuickSplitViewAttached::setMinimumHeight(qreal height)
1735{
1736 Q_D(QQuickSplitViewAttached);
1737 d->m_isMinimumHeightSet = true;
1738 if (qFuzzyCompare(p1: height, p2: d->m_minimumHeight))
1739 return;
1740
1741 d->m_minimumHeight = height;
1742 d->requestLayoutView();
1743 emit minimumHeightChanged();
1744}
1745
1746void QQuickSplitViewAttached::resetMinimumHeight()
1747{
1748 Q_D(QQuickSplitViewAttached);
1749 const qreal oldEffectiveMinimumHeight = effectiveMinimumHeight(attachedPrivate: d);
1750
1751 d->m_isMinimumHeightSet = false;
1752 d->m_minimumHeight = -1;
1753
1754 const qreal newEffectiveMinimumHeight = effectiveMinimumHeight(attachedPrivate: d);
1755 if (qFuzzyCompare(p1: newEffectiveMinimumHeight, p2: oldEffectiveMinimumHeight))
1756 return;
1757
1758 d->requestLayoutView();
1759 emit minimumHeightChanged();
1760}
1761
1762/*!
1763 \qmlattachedproperty real QtQuick.Controls::SplitView::preferredWidth
1764
1765 This attached property controls the preferred width of the split item. The
1766 preferred width will be used as the size of the item, and will be bound
1767 within the \l minimumWidth and \l maximumWidth. If the preferred width
1768 is not set, the item's \l {Item::}{implicitWidth} will be used.
1769
1770 When a split item is resized, the preferredWidth will be set in order
1771 to keep track of the new size.
1772
1773 By default, this property is not set, and therefore
1774 \l {Item::}{implicitWidth} will be used instead. To reset this property to
1775 its default value, set it to \c undefined.
1776
1777 \note Do not set the \l{Item::}{width} property of a split item, as it will be
1778 overwritten upon each layout of the SplitView.
1779
1780 \sa minimumWidth, maximumWidth, fillWidth, preferredHeight
1781*/
1782qreal QQuickSplitViewAttached::preferredWidth() const
1783{
1784 Q_D(const QQuickSplitViewAttached);
1785 return d->m_preferredWidth;
1786}
1787
1788void QQuickSplitViewAttached::setPreferredWidth(qreal width)
1789{
1790 Q_D(QQuickSplitViewAttached);
1791 d->m_isPreferredWidthSet = true;
1792 // Make sure that we clear this flag now, before we emit the change signals
1793 // which could cause another setter to be called.
1794 auto splitViewPrivate = d->m_splitView ? QQuickSplitViewPrivate::get(splitView: d->m_splitView) : nullptr;
1795 const bool ignoreNextLayoutRequest = splitViewPrivate && splitViewPrivate->m_ignoreNextLayoutRequest;
1796 if (splitViewPrivate)
1797 splitViewPrivate->m_ignoreNextLayoutRequest = false;
1798
1799 if (qFuzzyCompare(p1: width, p2: d->m_preferredWidth))
1800 return;
1801
1802 d->m_preferredWidth = width;
1803
1804 if (!ignoreNextLayoutRequest) {
1805 // We are currently in the middle of performing a layout, and the user (not our internal code)
1806 // changed the preferred width of one of the split items, so request another layout.
1807 d->requestLayoutView();
1808 }
1809
1810 emit preferredWidthChanged();
1811}
1812
1813void QQuickSplitViewAttached::resetPreferredWidth()
1814{
1815 Q_D(QQuickSplitViewAttached);
1816 const qreal oldEffectivePreferredWidth = effectivePreferredWidth(
1817 attachedPrivate: d, itemPrivate: QQuickItemPrivate::get(item: d->m_splitItem));
1818
1819 d->m_isPreferredWidthSet = false;
1820 d->m_preferredWidth = -1;
1821
1822 const qreal newEffectivePreferredWidth = effectivePreferredWidth(
1823 attachedPrivate: d, itemPrivate: QQuickItemPrivate::get(item: d->m_splitItem));
1824 if (qFuzzyCompare(p1: newEffectivePreferredWidth, p2: oldEffectivePreferredWidth))
1825 return;
1826
1827 d->requestLayoutView();
1828 emit preferredWidthChanged();
1829}
1830
1831/*!
1832 \qmlattachedproperty real QtQuick.Controls::SplitView::preferredHeight
1833
1834 This attached property controls the preferred height of the split item. The
1835 preferred height will be used as the size of the item, and will be bound
1836 within the \l minimumHeight and \l maximumHeight. If the preferred height
1837 is not set, the item's \l{Item::}{implicitHeight} will be used.
1838
1839 When a split item is resized, the preferredHeight will be set in order
1840 to keep track of the new size.
1841
1842 By default, this property is not set, and therefore
1843 \l{Item::}{implicitHeight} will be used instead. To reset this property to
1844 its default value, set it to \c undefined.
1845
1846 \note Do not set the \l{Item::}{height} property of a split item, as it will be
1847 overwritten upon each layout of the SplitView.
1848
1849 \sa minimumHeight, maximumHeight, fillHeight, preferredWidth
1850*/
1851qreal QQuickSplitViewAttached::preferredHeight() const
1852{
1853 Q_D(const QQuickSplitViewAttached);
1854 return d->m_preferredHeight;
1855}
1856
1857void QQuickSplitViewAttached::setPreferredHeight(qreal height)
1858{
1859 Q_D(QQuickSplitViewAttached);
1860 d->m_isPreferredHeightSet = true;
1861 // Make sure that we clear this flag now, before we emit the change signals
1862 // which could cause another setter to be called.
1863 auto splitViewPrivate = d->m_splitView ? QQuickSplitViewPrivate::get(splitView: d->m_splitView) : nullptr;
1864 const bool ignoreNextLayoutRequest = splitViewPrivate && splitViewPrivate->m_ignoreNextLayoutRequest;
1865 if (splitViewPrivate)
1866 splitViewPrivate->m_ignoreNextLayoutRequest = false;
1867
1868 if (qFuzzyCompare(p1: height, p2: d->m_preferredHeight))
1869 return;
1870
1871 d->m_preferredHeight = height;
1872
1873 if (!ignoreNextLayoutRequest) {
1874 // We are currently in the middle of performing a layout, and the user (not our internal code)
1875 // changed the preferred height of one of the split items, so request another layout.
1876 d->requestLayoutView();
1877 }
1878
1879 emit preferredHeightChanged();
1880}
1881
1882void QQuickSplitViewAttached::resetPreferredHeight()
1883{
1884 Q_D(QQuickSplitViewAttached);
1885 const qreal oldEffectivePreferredHeight = effectivePreferredHeight(
1886 attachedPrivate: d, itemPrivate: QQuickItemPrivate::get(item: d->m_splitItem));
1887
1888 d->m_isPreferredHeightSet = false;
1889 d->m_preferredHeight = -1;
1890
1891 const qreal newEffectivePreferredHeight = effectivePreferredHeight(
1892 attachedPrivate: d, itemPrivate: QQuickItemPrivate::get(item: d->m_splitItem));
1893 if (qFuzzyCompare(p1: newEffectivePreferredHeight, p2: oldEffectivePreferredHeight))
1894 return;
1895
1896 d->requestLayoutView();
1897 emit preferredHeightChanged();
1898}
1899
1900/*!
1901 \qmlattachedproperty real QtQuick.Controls::SplitView::maximumWidth
1902
1903 This attached property controls the maximum width of the split item.
1904 The \l preferredWidth is bound within the \l minimumWidth and
1905 \l maximumWidth. A split item cannot be dragged to be larger than
1906 its \c maximumWidth.
1907
1908 The default value is \c Infinity. To reset this property to its default
1909 value, set it to \c undefined.
1910
1911 \sa minimumWidth, preferredWidth, fillWidth, maximumHeight
1912*/
1913qreal QQuickSplitViewAttached::maximumWidth() const
1914{
1915 Q_D(const QQuickSplitViewAttached);
1916 return d->m_maximumWidth;
1917}
1918
1919void QQuickSplitViewAttached::setMaximumWidth(qreal width)
1920{
1921 Q_D(QQuickSplitViewAttached);
1922 d->m_isMaximumWidthSet = true;
1923 if (qFuzzyCompare(p1: width, p2: d->m_maximumWidth))
1924 return;
1925
1926 d->m_maximumWidth = width;
1927 d->requestLayoutView();
1928 emit maximumWidthChanged();
1929}
1930
1931void QQuickSplitViewAttached::resetMaximumWidth()
1932{
1933 Q_D(QQuickSplitViewAttached);
1934 const qreal oldEffectiveMaximumWidth = effectiveMaximumWidth(attachedPrivate: d);
1935
1936 d->m_isMaximumWidthSet = false;
1937 d->m_maximumWidth = -1;
1938
1939 const qreal newEffectiveMaximumWidth = effectiveMaximumWidth(attachedPrivate: d);
1940 if (qFuzzyCompare(p1: newEffectiveMaximumWidth, p2: oldEffectiveMaximumWidth))
1941 return;
1942
1943 d->requestLayoutView();
1944 emit maximumWidthChanged();
1945}
1946
1947/*!
1948 \qmlattachedproperty real QtQuick.Controls::SplitView::maximumHeight
1949
1950 This attached property controls the maximum height of the split item.
1951 The \l preferredHeight is bound within the \l minimumHeight and
1952 \l maximumHeight. A split item cannot be dragged to be larger than
1953 its \c maximumHeight.
1954
1955 The default value is \c Infinity. To reset this property to its default
1956 value, set it to \c undefined.
1957
1958 \sa minimumHeight, preferredHeight, fillHeight, maximumWidth
1959*/
1960qreal QQuickSplitViewAttached::maximumHeight() const
1961{
1962 Q_D(const QQuickSplitViewAttached);
1963 return d->m_maximumHeight;
1964}
1965
1966void QQuickSplitViewAttached::setMaximumHeight(qreal height)
1967{
1968 Q_D(QQuickSplitViewAttached);
1969 d->m_isMaximumHeightSet = true;
1970 if (qFuzzyCompare(p1: height, p2: d->m_maximumHeight))
1971 return;
1972
1973 d->m_maximumHeight = height;
1974 d->requestLayoutView();
1975 emit maximumHeightChanged();
1976}
1977
1978void QQuickSplitViewAttached::resetMaximumHeight()
1979{
1980 Q_D(QQuickSplitViewAttached);
1981 const qreal oldEffectiveMaximumHeight = effectiveMaximumHeight(attachedPrivate: d);
1982
1983 d->m_isMaximumHeightSet = false;
1984 d->m_maximumHeight = -1;
1985
1986 const qreal newEffectiveMaximumHeight = effectiveMaximumHeight(attachedPrivate: d);
1987 if (qFuzzyCompare(p1: newEffectiveMaximumHeight, p2: oldEffectiveMaximumHeight))
1988 return;
1989
1990 d->requestLayoutView();
1991 emit maximumHeightChanged();
1992}
1993
1994/*!
1995 \qmlattachedproperty bool QtQuick.Controls::SplitView::fillWidth
1996
1997 This attached property controls whether the item takes the remaining space
1998 in the split view after all other items have been laid out.
1999
2000 By default, the last visible child of the split view will fill the view,
2001 but it can be changed by explicitly setting \c fillWidth to \c true on
2002 another item. If multiple items have \c fillWidth set to \c true, the
2003 left-most item will fill the view.
2004
2005 The width of a split item with \c fillWidth set to \c true is still
2006 restricted within its \l minimumWidth and \l maximumWidth.
2007
2008 \sa minimumWidth, preferredWidth, maximumWidth, fillHeight
2009*/
2010bool QQuickSplitViewAttached::fillWidth() const
2011{
2012 Q_D(const QQuickSplitViewAttached);
2013 return d->m_fillWidth;
2014}
2015
2016void QQuickSplitViewAttached::setFillWidth(bool fill)
2017{
2018 Q_D(QQuickSplitViewAttached);
2019 d->m_isFillWidthSet = true;
2020 if (fill == d->m_fillWidth)
2021 return;
2022
2023 d->m_fillWidth = fill;
2024 if (d->m_splitView && d->m_splitView->orientation() == Qt::Horizontal)
2025 QQuickSplitViewPrivate::get(splitView: d->m_splitView)->updateFillIndex();
2026 d->requestLayoutView();
2027 emit fillWidthChanged();
2028}
2029
2030/*!
2031 \qmlattachedproperty bool QtQuick.Controls::SplitView::fillHeight
2032
2033 This attached property controls whether the item takes the remaining space
2034 in the split view after all other items have been laid out.
2035
2036 By default, the last visible child of the split view will fill the view,
2037 but it can be changed by explicitly setting \c fillHeight to \c true on
2038 another item. If multiple items have \c fillHeight set to \c true, the
2039 top-most item will fill the view.
2040
2041 The height of a split item with \c fillHeight set to \c true is still
2042 restricted within its \l minimumHeight and \l maximumHeight.
2043
2044 \sa minimumHeight, preferredHeight, maximumHeight, fillWidth
2045*/
2046bool QQuickSplitViewAttached::fillHeight() const
2047{
2048 Q_D(const QQuickSplitViewAttached);
2049 return d->m_fillHeight;
2050}
2051
2052void QQuickSplitViewAttached::setFillHeight(bool fill)
2053{
2054 Q_D(QQuickSplitViewAttached);
2055 d->m_isFillHeightSet = true;
2056 if (fill == d->m_fillHeight)
2057 return;
2058
2059 d->m_fillHeight = fill;
2060 if (d->m_splitView && d->m_splitView->orientation() == Qt::Vertical)
2061 QQuickSplitViewPrivate::get(splitView: d->m_splitView)->updateFillIndex();
2062 d->requestLayoutView();
2063 emit fillHeightChanged();
2064}
2065
2066QQuickSplitViewAttachedPrivate::QQuickSplitViewAttachedPrivate()
2067 : m_fillWidth(false)
2068 , m_fillHeight(false)
2069 , m_isFillWidthSet(false)
2070 , m_isFillHeightSet(false)
2071 , m_isMinimumWidthSet(false)
2072 , m_isMinimumHeightSet(false)
2073 , m_isPreferredWidthSet(false)
2074 , m_isPreferredHeightSet(false)
2075 , m_isMaximumWidthSet(false)
2076 , m_isMaximumHeightSet(false)
2077 , m_minimumWidth(0)
2078 , m_minimumHeight(0)
2079 , m_preferredWidth(-1)
2080 , m_preferredHeight(-1)
2081 , m_maximumWidth(std::numeric_limits<qreal>::infinity())
2082 , m_maximumHeight(std::numeric_limits<qreal>::infinity())
2083{
2084}
2085
2086void QQuickSplitViewAttachedPrivate::setView(QQuickSplitView *newView)
2087{
2088 Q_Q(QQuickSplitViewAttached);
2089 if (newView == m_splitView)
2090 return;
2091
2092 m_splitView = newView;
2093 qCDebug(qlcQQuickSplitView) << "set SplitView" << newView << "on attached object" << this;
2094 emit q->viewChanged();
2095}
2096
2097void QQuickSplitViewAttachedPrivate::requestLayoutView()
2098{
2099 if (m_splitView)
2100 QQuickSplitViewPrivate::get(splitView: m_splitView)->requestLayout();
2101}
2102
2103QQuickSplitViewAttachedPrivate *QQuickSplitViewAttachedPrivate::get(QQuickSplitViewAttached *attached)
2104{
2105 return attached->d_func();
2106}
2107
2108const QQuickSplitViewAttachedPrivate *QQuickSplitViewAttachedPrivate::get(const QQuickSplitViewAttached *attached)
2109{
2110 return attached->d_func();
2111}
2112
2113QQuickSplitHandleAttachedPrivate::QQuickSplitHandleAttachedPrivate()
2114 : m_hovered(false)
2115 , m_pressed(false)
2116{
2117}
2118
2119void QQuickSplitHandleAttachedPrivate::setHovered(bool hovered)
2120{
2121 Q_Q(QQuickSplitHandleAttached);
2122 if (hovered == m_hovered)
2123 return;
2124
2125 m_hovered = hovered;
2126 emit q->hoveredChanged();
2127}
2128
2129void QQuickSplitHandleAttachedPrivate::setPressed(bool pressed)
2130{
2131 Q_Q(QQuickSplitHandleAttached);
2132 if (pressed == m_pressed)
2133 return;
2134
2135 m_pressed = pressed;
2136 emit q->pressedChanged();
2137}
2138
2139QQuickSplitHandleAttachedPrivate *QQuickSplitHandleAttachedPrivate::get(QQuickSplitHandleAttached *attached)
2140{
2141 return attached->d_func();
2142}
2143
2144const QQuickSplitHandleAttachedPrivate *QQuickSplitHandleAttachedPrivate::get(const QQuickSplitHandleAttached *attached)
2145{
2146 return attached->d_func();
2147}
2148
2149QQuickSplitHandleAttached::QQuickSplitHandleAttached(QObject *parent)
2150 : QObject(*(new QQuickSplitHandleAttachedPrivate), parent)
2151{
2152}
2153
2154/*!
2155 \qmltype SplitHandle
2156 \inherits QtObject
2157//! \instantiates QQuickSplitHandleAttached
2158 \inqmlmodule QtQuick.Controls
2159 \since 5.13
2160 \brief Provides attached properties for SplitView handles.
2161
2162 SplitHandle provides attached properties for \l SplitView handles.
2163
2164 For split items themselves, use the attached \l SplitView properties.
2165
2166 \sa SplitView
2167*/
2168
2169/*!
2170 \qmlattachedproperty bool QtQuick.Controls::SplitHandle::hovered
2171
2172 This attached property holds whether the split handle is hovered.
2173
2174 \sa pressed
2175*/
2176bool QQuickSplitHandleAttached::isHovered() const
2177{
2178 Q_D(const QQuickSplitHandleAttached);
2179 return d->m_hovered;
2180}
2181
2182/*!
2183 \qmlattachedproperty bool QtQuick.Controls::SplitHandle::pressed
2184
2185 This attached property holds whether the split handle is pressed.
2186
2187 \sa hovered
2188*/
2189bool QQuickSplitHandleAttached::isPressed() const
2190{
2191 Q_D(const QQuickSplitHandleAttached);
2192 return d->m_pressed;
2193}
2194
2195QQuickSplitHandleAttached *QQuickSplitHandleAttached::qmlAttachedProperties(QObject *object)
2196{
2197 return new QQuickSplitHandleAttached(object);
2198}
2199
2200QT_END_NAMESPACE
2201
2202#include "moc_qquicksplitview_p.cpp"
2203

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