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

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