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

source code of qtquickcontrols2/src/quicktemplates2/qquicksplitview.cpp