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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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