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