Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquicksplitview.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquicksplitview_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
16
21
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*>(contentModel->object(i));
242 if (!item->isVisible())
243 continue;
244
245 lastVisibleIndex = i;
246
247 const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
248 qmlAttachedPropertiesObject<QQuickSplitView>(item, 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) {
284 if (!item->isVisible()) {
285 // The item is not visible, so skip it.
286 qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": split item " << item
287 << " at index " << index << " is not visible; skipping it and its handles (if any)";
288 continue;
289 }
290
291 const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
292 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
293 qmlAttachedPropertiesObject<QQuickSplitView>(item, 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(qreal(0.0), m_mousePos.x(), qreal(width))
313 : qBound(qreal(0.0), m_mousePos.y(), 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
324
325 const QQuickItem *pressedHandleItem = m_handleItems.at(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(leftEdge, 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(leftStop, pressedHandlePos, 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;
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(rightEdge - pressedHandleSize, 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(leftStop, pressedHandlePos, 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 sizeData.effectiveMinimumWidth,
416 requestedSize,
417 sizeData.effectiveMaximumWidth);
418 layoutData.height = height;
419 } else {
420 layoutData.width = width;
421 layoutData.height = qBound(
422 sizeData.effectiveMinimumHeight,
423 requestedSize,
424 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(item, 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(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*/
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->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(fillItem);
501 const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
502 qmlAttachedPropertiesObject<QQuickSplitView>(fillItem, false));
503 const auto fillSizeData = effectiveSizeData(fillItemPrivate, attached);
504
505 LayoutData layoutData;
506 if (isHorizontal()) {
507 layoutData.width = qBound(
508 fillSizeData.effectiveMinimumWidth,
509 width - usedWidth,
510 fillSizeData.effectiveMaximumWidth);
511 layoutData.height = height;
512 usedWidth += layoutData.width;
513 } else {
514 layoutData.width = width;
515 layoutData.height = qBound(
516 fillSizeData.effectiveMinimumHeight,
517 height - usedHeight,
518 fillSizeData.effectiveMaximumHeight);
519 usedHeight += layoutData.height;
520 }
521
522 m_layoutData.insert(fillItem, 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*/
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;
554 if (!item->isVisible())
555 continue;
556
557 const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
558 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
559 qmlAttachedPropertiesObject<QQuickSplitView>(item, 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(maxReduce, 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) {
584 if (!item->isVisible())
585 continue;
586
587 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
588 qmlAttachedPropertiesObject<QQuickSplitView>(item, false));
589 LayoutData layoutData = m_layoutData.value(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 qmlAttachedPropertiesObject<QQuickSplitView>(item, 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 */
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*/
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) {
640 if (!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())) {
662 const QQuickItemPrivate *fillItemPrivate = QQuickItemPrivate::get(fillItem);
663 const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
664 qmlAttachedPropertiesObject<QQuickSplitView>(fillItem, false));
665 const auto sizeData = effectiveSizeData(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
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*/
713{
715 return;
716
717 if (m_layingOut)
718 return;
719
720 const int count = contentModel->count();
721 if (count <= 0)
722 return;
723
725 QString::fromLatin1("m_fillIndex is %1 but our count is %2").arg(m_fillIndex).arg(count)));
726
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.
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
771{
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";
781 for (int i = 0; i < count; ++i)
783}
784
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.
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)
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(index, 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
819 resizeHandle(handleItem);
820 }
821}
822
824{
825 int excess = m_handleItems.size() - qMax(0, 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) {
839 if (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 qmlAttachedPropertiesObject<QQuickSplitView>(item, false));
846 if (attached) {
847 const QQuickSplitViewAttachedPrivate *attachedPrivate
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
868{
869 return attachedPrivate && attachedPrivate->m_isMinimumWidthSet ? attachedPrivate->m_minimumWidth : 0;
870}
871
873{
874 return attachedPrivate && attachedPrivate->m_isMinimumHeightSet ? attachedPrivate->m_minimumHeight : 0;
875}
876
878 const QQuickItemPrivate *itemPrivate)
879{
880 return attachedPrivate && attachedPrivate->m_isPreferredWidthSet
881 ? attachedPrivate->m_preferredWidth : itemPrivate->implicitWidth;
882}
883
885 const QQuickItemPrivate *itemPrivate)
886{
887 return attachedPrivate && attachedPrivate->m_isPreferredHeightSet
888 ? attachedPrivate->m_preferredHeight : itemPrivate->implicitHeight;
889}
890
892{
893 return attachedPrivate && attachedPrivate->m_isMaximumWidthSet
894 ? attachedPrivate->m_maximumWidth : std::numeric_limits<qreal>::infinity();
895}
896
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.
907 const QQuickItemPrivate *itemPrivate, const QQuickSplitViewAttached *attached) const
908{
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
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
930{
931 qCDebug(qlcQQuickSplitView) << "destroying" << m_handleItems.size() << "handles";
934}
935
937{
938 const bool horizontal = isHorizontal();
939 handleItem->setWidth(horizontal ? handleItem->implicitWidth() : width);
940 handleItem->setHeight(horizontal ? height : handleItem->implicitHeight());
941}
942
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
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.
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) {
972 if (item->isVisible()) {
973 lastVisibleItemIndex = i;
974 break;
975 }
976 }
977
978 for (int i = 0; i < count - 1; ++i) {
980 QQuickItem *handleItem = m_handleItems.at(i);
981 if (i != lastVisibleItemIndex)
982 handleItem->setVisible(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
991{
992 qCDebug(qlcQQuickSplitViewPointer) << "updating hovered handle after" << hoveredItem << "was hovered";
993
994 const int oldHoveredHandleIndex = m_hoveredHandleIndex;
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(oldHoveredHandleIndex);
1002 QQuickSplitHandleAttached *oldHoveredHandleAttached = qobject_cast<QQuickSplitHandleAttached*>(
1003 qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(oldHoveredHandle, true));
1004 QQuickSplitHandleAttachedPrivate::get(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 qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(hoveredItem, true));
1011 QQuickSplitHandleAttachedPrivate::get(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
1019{
1020 Q_Q(QQuickSplitView);
1021 if (resizing == m_resizing)
1022 return;
1023
1024 m_resizing = resizing;
1025 emit q->resizingChanged();
1026}
1027
1032
1041
1043{
1044 Q_Q(QQuickSplitView);
1045 QQuickContainerPrivate::handlePress(point, timestamp);
1046
1047 QQuickItem *pressedItem = q->childAt(point.x(), point.y());
1048 const int pressedHandleIndex = m_handleItems.indexOf(pressedItem);
1049 if (pressedHandleIndex != -1) {
1050 m_pressedHandleIndex = pressedHandleIndex;
1051 m_pressPos = point;
1052 m_mousePos = point;
1053
1055 // Find the first item to the right/bottom of this one that is visible.
1056 QQuickItem *rightOrBottomItem = nullptr;
1058 for (int i = m_pressedHandleIndex + 1; i < contentModel->count(); ++i) {
1060 if (nextItem->isVisible()) {
1061 rightOrBottomItem = nextItem;
1063 break;
1064 }
1065 }
1067 "Failed to find a visible item to the right/bottom of the one that was pressed at index %1; this shouldn't happen")
1069
1071 m_leftOrTopItemSizeBeforePress = isHorizontal ? leftOrTopItem->width() : leftOrTopItem->height();
1072 m_rightOrBottomItemSizeBeforePress = isHorizontal ? rightOrBottomItem->width() : rightOrBottomItem->height();
1073 m_handlePosBeforePress = pressedItem->position();
1074
1075
1076 // Force the attached object to be created since we rely on it.
1077 QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>(
1078 qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(pressedItem, true));
1079 QQuickSplitHandleAttachedPrivate::get(handleAttached)->setPressed(true);
1080
1081 setResizing(true);
1082
1083 qCDebug(qlcQQuickSplitViewPointer).nospace() << "handled press -"
1084 << " left/top index=" << m_pressedHandleIndex << ","
1085 << " size before press=" << m_leftOrTopItemSizeBeforePress << ","
1086 << " item=" << leftOrTopItem
1087 << " right/bottom index=" << m_nextVisibleIndexAfterPressedHandle << ","
1088 << " size before press=" << m_rightOrBottomItemSizeBeforePress
1089 << " item=" << rightOrBottomItem;
1090 }
1091 return true;
1092}
1093
1095{
1096 QQuickContainerPrivate::handleMove(point, timestamp);
1097
1098 if (m_pressedHandleIndex != -1) {
1099 m_mousePos = point;
1100 // Don't request layouts for input events because we want
1101 // resizing to be as responsive and smooth as possible.
1102 updatePolish();
1103 }
1104 return true;
1105}
1106
1108{
1109 QQuickContainerPrivate::handleRelease(point, timestamp);
1110
1111 if (m_pressedHandleIndex != -1) {
1113 QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>(
1114 qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(pressedHandle, true));
1115 QQuickSplitHandleAttachedPrivate::get(handleAttached)->setPressed(false);
1116 }
1117
1118 setResizing(false);
1119
1121 m_pressPos = QPointF();
1122 m_mousePos = QPointF();
1126 return true;
1127}
1128
1130{
1131 const int itemIndex = contentModel->indexOf(item, nullptr);
1132 Q_ASSERT(itemIndex != -1);
1133
1134 qCDebug(qlcQQuickSplitView) << "visible property of split item"
1135 << item << "at index" << itemIndex << "changed to" << item->isVisible();
1136
1137 // The visibility of an item just changed, so we need to update the visibility
1138 // of the corresponding handle (if one exists).
1139
1140 const int handleIndex = handleIndexForSplitIndex(itemIndex);
1141 if (handleIndex != -1) {
1142 QQuickItem *handleItem = m_handleItems.at(handleIndex);
1143 handleItem->setVisible(item->isVisible());
1144
1145 qCDebug(qlcQQuickSplitView) << "set visible property of handle item"
1146 << handleItem << "at index" << handleIndex << "to" << item->isVisible();
1147 }
1148
1151 requestLayout();
1152}
1153
1158
1163
1168
1170{
1171 return splitView->d_func();
1172}
1173
1182
1191
1193{
1194 Q_D(QQuickSplitView);
1195 for (int i = 0; i < d->contentModel->count(); ++i) {
1196 QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(i));
1197 d->removeImplicitSizeListener(item);
1198 }
1199}
1200
1213{
1214 Q_D(const QQuickSplitView);
1215 return d->m_orientation;
1216}
1217
1219{
1220 Q_D(QQuickSplitView);
1221 if (orientation == d->m_orientation)
1222 return;
1223
1224 d->m_orientation = orientation;
1225
1226#if QT_CONFIG(cursor)
1227 for (QQuickItem *handleItem : d->m_handleItems)
1228 d->updateCursorHandle(handleItem);
1229#endif
1231
1232 // Do this after emitting orientationChanged so that the bindings in QML
1233 // update the implicit size in time.
1234 d->resizeHandles();
1235 // This is queued (via polish) anyway, but to make our intentions clear,
1236 // do it afterwards too.
1237 d->requestLayout();
1238}
1239
1248{
1249 Q_D(const QQuickSplitView);
1250 return d->m_resizing;
1251}
1252
1287{
1288 Q_D(const QQuickSplitView);
1289 return d->m_handle;
1290}
1291
1293{
1294 Q_D(QQuickSplitView);
1295 if (handle == d->m_handle)
1296 return;
1297
1298 qCDebug(qlcQQuickSplitView) << "setting handle" << handle;
1299
1300 if (d->m_handle)
1301 d->destroyHandles();
1302
1303 d->m_handle = handle;
1304
1305 if (d->m_handle) {
1306 d->createHandles();
1307 d->updateHandleVisibilities();
1308 }
1309
1310 d->requestLayout();
1311
1313}
1314
1316{
1317 Q_D(const QQuickSplitView);
1318 if (!qmlContext(item))
1319 return false;
1320
1321 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
1322 return false;
1323
1324 return !d->m_handleItems.contains(item);
1325}
1326
1331
1340{
1341#if QT_CONFIG(cborstreamwriter)
1342 Q_D(QQuickSplitView);
1343 qCDebug(qlcQQuickSplitViewState) << "saving state for split items in" << this;
1344
1345 // Save the preferred sizes of each split item.
1346 QCborArray cborArray;
1347 for (int i = 0; i < d->contentModel->count(); ++i) {
1348 const QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(i));
1349 const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
1350 qmlAttachedPropertiesObject<QQuickSplitView>(item, false));
1351 // Don't serialise stuff if we don't need to. If a split item was given a preferred
1352 // size in QML or it was dragged, it will have an attached object and either
1353 // m_isPreferredWidthSet or m_isPreferredHeightSet (or both) will be true,
1354 // so items without these can be skipped. We write the index of each item
1355 // that has data so that we know which item to set it on when restoring.
1356 if (!attached)
1357 continue;
1358
1360 if (!attachedPrivate->m_isPreferredWidthSet && !attachedPrivate->m_isPreferredHeightSet)
1361 continue;
1362
1363 QCborMap cborMap;
1364 cborMap[QLatin1String("index")] = i;
1365 if (attachedPrivate->m_isPreferredWidthSet) {
1366 cborMap[QLatin1String("preferredWidth")] = static_cast<double>(attachedPrivate->m_preferredWidth);
1367
1368 qCDebug(qlcQQuickSplitViewState).nospace() << "- wrote preferredWidth of "
1369 << attachedPrivate->m_preferredWidth << " for split item " << item << " at index " << i;
1370 }
1371 if (attachedPrivate->m_isPreferredHeightSet) {
1372 cborMap[QLatin1String("preferredHeight")] = static_cast<double>(attachedPrivate->m_preferredHeight);
1373
1374 qCDebug(qlcQQuickSplitViewState).nospace() << "- wrote preferredHeight of "
1375 << attachedPrivate->m_preferredHeight << " for split item " << item << " at index " << i;
1376 }
1377
1378 cborArray.append(cborMap);
1379 }
1380
1381 const QByteArray byteArray = cborArray.toCborValue().toCbor();
1382 qCDebug(qlcQQuickSplitViewState) << "the resulting byte array is:" << byteArray;
1383 return QVariant(byteArray);
1384#else
1385 return QVariant();
1386#endif
1387}
1388
1399{
1400 const QByteArray cborByteArray = state.toByteArray();
1401 Q_D(QQuickSplitView);
1402 if (cborByteArray.isEmpty())
1403 return false;
1404
1405 QCborParserError parserError;
1406 const QCborValue cborValue(QCborValue::fromCbor(cborByteArray, &parserError));
1407 if (parserError.error != QCborError::NoError) {
1408 qmlWarning(this) << "Error reading SplitView state:" << parserError.errorString();
1409 return false;
1410 }
1411
1412 qCDebug(qlcQQuickSplitViewState) << "restoring state for split items of" << this
1413 << "from the following string:" << state;
1414
1415 const QCborArray cborArray(cborValue.toArray());
1416 const int ourCount = d->contentModel->count();
1417 // This could conceivably happen if items were removed from the SplitView since the state was last saved.
1418 if (cborArray.size() > ourCount) {
1419 qmlWarning(this) << "Error reading SplitView state: expected "
1420 << ourCount << " or less split items but got " << cborArray.size();
1421 return false;
1422 }
1423
1424 for (auto it = cborArray.constBegin(); it != cborArray.constEnd(); ++it) {
1425 QCborMap cborMap(it->toMap());
1426 const int splitItemIndex = cborMap.value(QLatin1String("index")).toInteger();
1427 const bool isPreferredWidthSet = cborMap.contains(QLatin1String("preferredWidth"));
1428 const bool isPreferredHeightSet = cborMap.contains(QLatin1String("preferredHeight"));
1429
1430 QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(splitItemIndex));
1431 // If the split item does not have a preferred size specified in QML, it could still have
1432 // been resized via dragging before it was saved. In this case, it won't have an
1433 // attached object upon application startup, so we create it.
1434 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
1435 qmlAttachedPropertiesObject<QQuickSplitView>(item, true));
1436 if (isPreferredWidthSet) {
1437 const qreal preferredWidth = cborMap.value(QLatin1String("preferredWidth")).toDouble();
1438 attached->setPreferredWidth(preferredWidth);
1439 }
1440 if (isPreferredHeightSet) {
1441 const qreal preferredHeight = cborMap.value(QLatin1String("preferredHeight")).toDouble();
1442 attached->setPreferredHeight(preferredHeight);
1443 }
1444
1446 qCDebug(qlcQQuickSplitViewState).nospace()
1447 << "- restored the following state for split item " << item << " at index " << splitItemIndex
1448 << ": preferredWidthSet=" << attachedPrivate->m_isPreferredWidthSet
1449 << " preferredWidth=" << attachedPrivate->m_preferredWidth
1450 << " preferredHeightSet=" << attachedPrivate->m_isPreferredHeightSet
1451 << " preferredHeight=" << attachedPrivate->m_preferredHeight;
1452 }
1453
1454 return true;
1455}
1456
1458{
1459 Q_D(QQuickSplitView);
1461 d->updateFillIndex();
1462 d->updatePolish();
1463}
1464
1466{
1467 Q_D(QQuickSplitView);
1469
1470 QQuickItem *hoveredItem = childAt(event->position().toPoint().x(), event->position().toPoint().y());
1471 d->updateHoveredHandle(hoveredItem);
1472}
1473
1475{
1476 Q_UNUSED(event);
1477 Q_D(QQuickSplitView);
1478 // If SplitView is no longer hovered (e.g. visible set to false), clear handle hovered value
1479 d->updateHoveredHandle(nullptr);
1480}
1481
1483{
1484 Q_D(QQuickSplitView);
1485 qCDebug(qlcQQuickSplitViewPointer) << "childMouseEventFilter called with" << item << event;
1486
1487 if (Q_LIKELY(event->isPointerEvent())) {
1488 auto *pointerEvent = static_cast<QPointerEvent *>(event);
1489 const auto &eventPoint = pointerEvent->points().first();
1490 const QPointF point = mapFromItem(item, eventPoint.position());
1491 const auto timestamp = pointerEvent->timestamp();
1492
1493 switch (event->type()) {
1495 d->handlePress(point, timestamp);
1496 // Keep the mouse grab if this item belongs to the handle,
1497 // otherwise this event can be stolen e.g. Flickable if we're inside it.
1498 if (d->m_pressedHandleIndex != -1)
1499 item->setKeepMouseGrab(true);
1500 break;
1502 d->handleRelease(point, timestamp);
1503 break;
1504 case QEvent::MouseMove:
1505 d->handleMove(point, timestamp);
1506 break;
1507 case QEvent::TouchBegin:
1508 if (pointerEvent->pointCount() == 1) {
1509 d->handlePress(point, timestamp);
1510 // We filter the event on behalf of item, but we want the item
1511 // to be the exclusive grabber so that we can continue to filter
1512 // touch events for it.
1513 if (d->m_pressedHandleIndex != -1) {
1514 item->setKeepTouchGrab(true);
1515 pointerEvent->setExclusiveGrabber(eventPoint, item);
1516 }
1517 }
1518 break;
1519 case QEvent::TouchEnd:
1520 if (pointerEvent->pointCount() == 1)
1521 d->handleRelease(point, timestamp);
1522 break;
1524 if (pointerEvent->pointCount() == 1)
1525 d->handleMove(point, timestamp);
1526 break;
1527 default:
1528 break;
1529 }
1530 }
1531
1532 // If this event belongs to the handle, filter it. (d->m_pressedHandleIndex != -1) means that
1533 // we press or move the handle, so we don't need to propagate it further.
1534 if (d->m_pressedHandleIndex != -1)
1535 return true;
1536
1538}
1539
1540void QQuickSplitView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
1541{
1542 Q_D(QQuickSplitView);
1543 QQuickControl::geometryChange(newGeometry, oldGeometry);
1544 d->resizeHandles();
1545 d->requestLayout();
1546}
1547
1549{
1550 Q_D(QQuickSplitView);
1551 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
1552 return;
1553
1554 const int count = d->contentModel->count();
1555 qCDebug(qlcQQuickSplitView).nospace() << "split item " << item << " added at index " << index
1556 << "; there are now " << count << " items";
1557
1558 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
1559 qmlAttachedPropertiesObject<QQuickSplitView>(item, false));
1560 if (attached)
1561 QQuickSplitViewAttachedPrivate::get(attached)->setView(this);
1562
1563 // Only need to add handles if we have more than one split item.
1564 if (count > 1) {
1565 // If the item was added at the end, it shouldn't get a handle;
1566 // the handle always goes to the split item on the left.
1567 d->createHandleItem(index < count - 1 ? index : index - 1);
1568 }
1569
1570 d->addImplicitSizeListener(item);
1571
1572 d->updateHandleVisibilities();
1573 d->updateFillIndex();
1574 d->requestLayout();
1575}
1576
1578{
1579 Q_D(QQuickSplitView);
1580 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
1581 return;
1582
1583 qCDebug(qlcQQuickSplitView) << "split item" << item << "moved to index" << index;
1584
1585 d->updateHandleVisibilities();
1586 d->updateFillIndex();
1587 d->requestLayout();
1588}
1589
1591{
1592 Q_D(QQuickSplitView);
1593 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
1594 return;
1595
1596 qCDebug(qlcQQuickSplitView).nospace() << "split item " << item << " removed from index " << index
1597 << "; there are now " << d->contentModel->count() << " items";
1598
1599 // Clear hovered/pressed handle if there are any.
1600 if (d->m_hoveredHandleIndex != -1 || d->m_pressedHandleIndex != -1) {
1601 const int handleIndex = d->m_hoveredHandleIndex != -1 ? d->m_hoveredHandleIndex : d->m_pressedHandleIndex;
1602 QQuickItem *itemHandle = d->m_handleItems.at(handleIndex);
1603 QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>(
1604 qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(itemHandle, false));
1605 if (handleAttached) {
1606 auto handleAttachedPrivate = QQuickSplitHandleAttachedPrivate::get(handleAttached);
1607 handleAttachedPrivate->setHovered(false);
1608 handleAttachedPrivate->setPressed(false);
1609 }
1610
1611 d->m_hoveredHandleIndex = -1;
1612 d->m_pressedHandleIndex = -1;
1613 }
1614
1615 // Unset any attached properties since the item is no longer owned by us.
1616 QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>(
1617 qmlAttachedPropertiesObject<QQuickSplitView>(item, false));
1618 if (attached)
1619 QQuickSplitViewAttachedPrivate::get(attached)->setView(this);
1620
1621 d->removeImplicitSizeListener(item);
1622
1623 d->removeExcessHandles();
1624 d->updateHandleVisibilities();
1625 d->updateFillIndex();
1626 d->requestLayout();
1627}
1628
1629#if QT_CONFIG(accessibility)
1630QAccessible::Role QQuickSplitView::accessibleRole() const
1631{
1632 return QAccessible::Pane;
1633}
1634#endif
1635
1637 : QObject(*(new QQuickSplitViewAttachedPrivate), parent)
1638{
1641 if (!item) {
1642 qmlWarning(parent) << "SplitView: attached properties can only be used on Items";
1643 return;
1644 }
1645
1646 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
1647 return;
1648
1649 d->m_splitItem = item;
1650
1651 // Child items get added to SplitView's contentItem, so we have to ensure
1652 // that exists first before trying to set m_splitView.
1653 // Apparently, in some cases it's normal for the parent item
1654 // to not exist until shortly after this constructor has run.
1655 if (!item->parentItem())
1656 return;
1657
1658 // This will get hit when attached SplitView properties are imperatively set
1659 // on an item that previously had none set, for example.
1660 QQuickSplitView *splitView = qobject_cast<QQuickSplitView*>(item->parentItem()->parentItem());
1661 if (!splitView) {
1662 qmlWarning(parent) << "SplitView: attached properties must be accessed through a direct child of SplitView";
1663 return;
1664 }
1665
1666 d->setView(splitView);
1667}
1668
1676{
1677 Q_D(const QQuickSplitViewAttached);
1678 return d->m_splitView;
1679}
1680
1695{
1696 Q_D(const QQuickSplitViewAttached);
1697 return d->m_minimumWidth;
1698}
1699
1701{
1703 d->m_isMinimumWidthSet = true;
1704 if (qFuzzyCompare(width, d->m_minimumWidth))
1705 return;
1706
1707 d->m_minimumWidth = width;
1708 d->requestLayoutView();
1710}
1711
1713{
1715 const qreal oldEffectiveMinimumWidth = effectiveMinimumWidth(d);
1716
1717 d->m_isMinimumWidthSet = false;
1718 d->m_minimumWidth = -1;
1719
1720 const qreal newEffectiveMinimumWidth = effectiveMinimumWidth(d);
1721 if (qFuzzyCompare(newEffectiveMinimumWidth, oldEffectiveMinimumWidth))
1722 return;
1723
1724 d->requestLayoutView();
1726}
1727
1742{
1743 Q_D(const QQuickSplitViewAttached);
1744 return d->m_minimumHeight;
1745}
1746
1748{
1750 d->m_isMinimumHeightSet = true;
1751 if (qFuzzyCompare(height, d->m_minimumHeight))
1752 return;
1753
1754 d->m_minimumHeight = height;
1755 d->requestLayoutView();
1757}
1758
1760{
1762 const qreal oldEffectiveMinimumHeight = effectiveMinimumHeight(d);
1763
1764 d->m_isMinimumHeightSet = false;
1765 d->m_minimumHeight = -1;
1766
1767 const qreal newEffectiveMinimumHeight = effectiveMinimumHeight(d);
1768 if (qFuzzyCompare(newEffectiveMinimumHeight, oldEffectiveMinimumHeight))
1769 return;
1770
1771 d->requestLayoutView();
1773}
1774
1796{
1797 Q_D(const QQuickSplitViewAttached);
1798 return d->m_preferredWidth;
1799}
1800
1802{
1804 d->m_isPreferredWidthSet = true;
1805 // Make sure that we clear this flag now, before we emit the change signals
1806 // which could cause another setter to be called.
1807 auto splitViewPrivate = d->m_splitView ? QQuickSplitViewPrivate::get(d->m_splitView) : nullptr;
1808 const bool ignoreNextLayoutRequest = splitViewPrivate && splitViewPrivate->m_ignoreNextLayoutRequest;
1809 if (splitViewPrivate)
1810 splitViewPrivate->m_ignoreNextLayoutRequest = false;
1811
1812 if (qFuzzyCompare(width, d->m_preferredWidth))
1813 return;
1814
1815 d->m_preferredWidth = width;
1816
1817 if (!ignoreNextLayoutRequest) {
1818 // We are currently in the middle of performing a layout, and the user (not our internal code)
1819 // changed the preferred width of one of the split items, so request another layout.
1820 d->requestLayoutView();
1821 }
1822
1824}
1825
1827{
1829 const qreal oldEffectivePreferredWidth = effectivePreferredWidth(
1830 d, QQuickItemPrivate::get(d->m_splitItem));
1831
1832 d->m_isPreferredWidthSet = false;
1833 d->m_preferredWidth = -1;
1834
1835 const qreal newEffectivePreferredWidth = effectivePreferredWidth(
1836 d, QQuickItemPrivate::get(d->m_splitItem));
1837 if (qFuzzyCompare(newEffectivePreferredWidth, oldEffectivePreferredWidth))
1838 return;
1839
1840 d->requestLayoutView();
1842}
1843
1865{
1866 Q_D(const QQuickSplitViewAttached);
1867 return d->m_preferredHeight;
1868}
1869
1871{
1873 d->m_isPreferredHeightSet = true;
1874 // Make sure that we clear this flag now, before we emit the change signals
1875 // which could cause another setter to be called.
1876 auto splitViewPrivate = d->m_splitView ? QQuickSplitViewPrivate::get(d->m_splitView) : nullptr;
1877 const bool ignoreNextLayoutRequest = splitViewPrivate && splitViewPrivate->m_ignoreNextLayoutRequest;
1878 if (splitViewPrivate)
1879 splitViewPrivate->m_ignoreNextLayoutRequest = false;
1880
1881 if (qFuzzyCompare(height, d->m_preferredHeight))
1882 return;
1883
1884 d->m_preferredHeight = height;
1885
1886 if (!ignoreNextLayoutRequest) {
1887 // We are currently in the middle of performing a layout, and the user (not our internal code)
1888 // changed the preferred height of one of the split items, so request another layout.
1889 d->requestLayoutView();
1890 }
1891
1893}
1894
1896{
1898 const qreal oldEffectivePreferredHeight = effectivePreferredHeight(
1899 d, QQuickItemPrivate::get(d->m_splitItem));
1900
1901 d->m_isPreferredHeightSet = false;
1902 d->m_preferredHeight = -1;
1903
1904 const qreal newEffectivePreferredHeight = effectivePreferredHeight(
1905 d, QQuickItemPrivate::get(d->m_splitItem));
1906 if (qFuzzyCompare(newEffectivePreferredHeight, oldEffectivePreferredHeight))
1907 return;
1908
1909 d->requestLayoutView();
1911}
1912
1927{
1928 Q_D(const QQuickSplitViewAttached);
1929 return d->m_maximumWidth;
1930}
1931
1933{
1935 d->m_isMaximumWidthSet = true;
1936 if (qFuzzyCompare(width, d->m_maximumWidth))
1937 return;
1938
1939 d->m_maximumWidth = width;
1940 d->requestLayoutView();
1942}
1943
1945{
1947 const qreal oldEffectiveMaximumWidth = effectiveMaximumWidth(d);
1948
1949 d->m_isMaximumWidthSet = false;
1950 d->m_maximumWidth = -1;
1951
1952 const qreal newEffectiveMaximumWidth = effectiveMaximumWidth(d);
1953 if (qFuzzyCompare(newEffectiveMaximumWidth, oldEffectiveMaximumWidth))
1954 return;
1955
1956 d->requestLayoutView();
1958}
1959
1974{
1975 Q_D(const QQuickSplitViewAttached);
1976 return d->m_maximumHeight;
1977}
1978
1980{
1982 d->m_isMaximumHeightSet = true;
1983 if (qFuzzyCompare(height, d->m_maximumHeight))
1984 return;
1985
1986 d->m_maximumHeight = height;
1987 d->requestLayoutView();
1989}
1990
1992{
1994 const qreal oldEffectiveMaximumHeight = effectiveMaximumHeight(d);
1995
1996 d->m_isMaximumHeightSet = false;
1997 d->m_maximumHeight = -1;
1998
1999 const qreal newEffectiveMaximumHeight = effectiveMaximumHeight(d);
2000 if (qFuzzyCompare(newEffectiveMaximumHeight, oldEffectiveMaximumHeight))
2001 return;
2002
2003 d->requestLayoutView();
2005}
2006
2024{
2025 Q_D(const QQuickSplitViewAttached);
2026 return d->m_fillWidth;
2027}
2028
2030{
2032 d->m_isFillWidthSet = true;
2033 if (fill == d->m_fillWidth)
2034 return;
2035
2036 d->m_fillWidth = fill;
2037 if (d->m_splitView && d->m_splitView->orientation() == Qt::Horizontal)
2038 QQuickSplitViewPrivate::get(d->m_splitView)->updateFillIndex();
2039 d->requestLayoutView();
2041}
2042
2060{
2061 Q_D(const QQuickSplitViewAttached);
2062 return d->m_fillHeight;
2063}
2064
2066{
2068 d->m_isFillHeightSet = true;
2069 if (fill == d->m_fillHeight)
2070 return;
2071
2072 d->m_fillHeight = fill;
2073 if (d->m_splitView && d->m_splitView->orientation() == Qt::Vertical)
2074 QQuickSplitViewPrivate::get(d->m_splitView)->updateFillIndex();
2075 d->requestLayoutView();
2077}
2078
2080 : m_fillWidth(false)
2081 , m_fillHeight(false)
2082 , m_isFillWidthSet(false)
2083 , m_isFillHeightSet(false)
2084 , m_isMinimumWidthSet(false)
2085 , m_isMinimumHeightSet(false)
2086 , m_isPreferredWidthSet(false)
2087 , m_isPreferredHeightSet(false)
2088 , m_isMaximumWidthSet(false)
2089 , m_isMaximumHeightSet(false)
2090 , m_minimumWidth(0)
2091 , m_minimumHeight(0)
2092 , m_preferredWidth(-1)
2093 , m_preferredHeight(-1)
2094 , m_maximumWidth(std::numeric_limits<qreal>::infinity())
2095 , m_maximumHeight(std::numeric_limits<qreal>::infinity())
2096{
2097}
2098
2100{
2102 if (newView == m_splitView)
2103 return;
2104
2105 m_splitView = newView;
2106 qCDebug(qlcQQuickSplitView) << "set SplitView" << newView << "on attached object" << this;
2107 emit q->viewChanged();
2108}
2109
2115
2120
2122{
2123 return attached->d_func();
2124}
2125
2131
2133{
2135 if (hovered == m_hovered)
2136 return;
2137
2138 m_hovered = hovered;
2139 emit q->hoveredChanged();
2140}
2141
2143{
2145 if (pressed == m_pressed)
2146 return;
2147
2148 m_pressed = pressed;
2149 emit q->pressedChanged();
2150}
2151
2156
2158{
2159 return attached->d_func();
2160}
2161
2166
2171
2190{
2191 Q_D(const QQuickSplitHandleAttached);
2192 return d->m_hovered;
2193}
2194
2203{
2204 Q_D(const QQuickSplitHandleAttached);
2205 return d->m_pressed;
2206}
2207
2212
2214
2215#include "moc_qquicksplitview_p.cpp"
\inmodule QtCore
Definition qbytearray.h:57
\inmodule QtCore\reentrant
Definition qcborarray.h:20
\inmodule QtCore\reentrant
Definition qcbormap.h:21
\inmodule QtCore\reentrant
Definition qcborvalue.h:47
\inmodule QtCore
Definition qcoreevent.h:45
@ MouseMove
Definition qcoreevent.h:63
@ MouseButtonPress
Definition qcoreevent.h:60
@ TouchUpdate
Definition qcoreevent.h:242
@ TouchBegin
Definition qcoreevent.h:241
@ MouseButtonRelease
Definition qcoreevent.h:61
void setX(qreal x)
QGraphicsItem * parentItem() const
Returns a pointer to this item's parent item.
bool isVisible() const
Returns true if the item is visible; otherwise, false is returned.
void setY(qreal y)
\inmodule QtGui
Definition qevent.h:246
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
T & first()
Definition qlist.h:645
iterator insert(qsizetype i, parameter_type t)
Definition qlist.h:488
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
value_type takeLast()
Definition qlist.h:567
void reserve(qsizetype size)
Definition qlist.h:753
void clear()
Definition qlist.h:434
\inmodule QtCore
Definition qobject.h:103
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
A base class for pointer events.
Definition qevent.h:73
const QList< QEventPoint > & points() const
Returns a list of points in this pointer event.
Definition qevent.h:87
The QQmlComponent class encapsulates a QML component definition.
virtual QObject * beginCreate(QQmlContext *)
Create an object instance from this component, within the specified context.
virtual void completeCreate()
This method provides advanced control over component instance creation.
QQmlContext * creationContext() const
Returns the QQmlContext the component was created in.
The QQmlContext class defines a context within a QML engine.
Definition qqmlcontext.h:25
int count() const override
\qmlproperty int QtQml.Models::ObjectModel::count
int indexOf(QObject *object, QObject *objectContext) const override
QQmlObjectModel * contentModel
virtual bool handlePress(const QPointF &point, ulong timestamp)
virtual QQuickItem * getContentItem()
virtual bool handleRelease(const QPointF &point, ulong timestamp)
virtual bool handleMove(const QPointF &point, ulong timestamp)
void componentComplete() override
Invoked after the root component that caused this instantiation has completed construction.
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override
quint32 componentComplete
QQmlListProperty< QObject > data()
static QQuickItemPrivate * get(QQuickItem *item)
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
void setFiltersChildMouseEvents(bool filter)
Sets whether pointer events intended for this item's children should be filtered through this item.
Q_INVOKABLE QPointF mapFromItem(const QQuickItem *item, const QPointF &point) const
Maps the given point in item's coordinate system to the equivalent point within this item's coordinat...
qreal x
\qmlproperty real QtQuick::Item::x \qmlproperty real QtQuick::Item::y \qmlproperty real QtQuick::Item...
Definition qquickitem.h:72
QString state() const
\qmlproperty string QtQuick::Item::state
virtual void hoverMoveEvent(QHoverEvent *event)
This event handler can be reimplemented in a subclass to receive hover-move events for an item.
Q_INVOKABLE QQuickItem * childAt(qreal x, qreal y) const
\qmlmethod QtQuick::Item::childAt(real x, real y)
qreal width
This property holds the width of this item.
Definition qquickitem.h:75
void setVisible(bool)
QPointF position() const
virtual bool childMouseEventFilter(QQuickItem *, QEvent *)
Reimplement this method to filter the pointer events that are received by this item's children.
void setX(qreal)
static QQuickSplitHandleAttachedPrivate * get(QQuickSplitHandleAttached *attached)
bool isHovered() const
Provides attached properties for SplitView handles.
QQuickSplitHandleAttached(QObject *parent=nullptr)
static QQuickSplitHandleAttached * qmlAttachedProperties(QObject *object)
bool isPressed() const
\qmlattachedproperty bool QtQuick.Controls::SplitHandle::pressed
void setView(QQuickSplitView *newView)
static QQuickSplitViewAttachedPrivate * get(QQuickSplitViewAttached *attached)
void setMinimumHeight(qreal height)
void setMinimumWidth(qreal width)
void setMaximumHeight(qreal height)
QQuickSplitViewAttached(QObject *parent=nullptr)
void setPreferredWidth(qreal width)
void setPreferredHeight(qreal height)
void setMaximumWidth(qreal width)
void resizeHandle(QQuickItem *handleItem)
void setResizing(bool resizing)
bool handlePress(const QPointF &point, ulong timestamp) override
qreal accumulatedSize(int firstIndex, int lastIndex) const
void itemImplicitHeightChanged(QQuickItem *item) override
void itemVisibilityChanged(QQuickItem *item) override
bool handleRelease(const QPointF &point, ulong timestamp) override
void itemImplicitWidthChanged(QQuickItem *item) override
QHash< QQuickItem *, LayoutData > m_layoutData
void layoutResizeFillItem(QQuickItem *fillItem, qreal &usedWidth, qreal &usedHeight, int indexBeingResizedDueToDrag)
int handleIndexForSplitIndex(int splitIndex) const
bool handleMove(const QPointF &point, ulong timestamp) override
void limitAndApplySizes(qreal usedWidth, qreal usedHeight)
void layoutResizeSplitItems(qreal &usedWidth, qreal &usedHeight, int &indexBeingResizedDueToDrag)
EffectiveSizeData effectiveSizeData(const QQuickItemPrivate *itemPrivate, const QQuickSplitViewAttached *attached) const
void layoutPositionItems(const QQuickItem *fillItem)
void updateHoveredHandle(QQuickItem *hoveredItem)
QList< QQuickItem * > m_handleItems
static QQuickSplitViewPrivate * get(QQuickSplitView *splitView)
void createHandleItem(int index)
QQuickItem * getContentItem() override
void updateFillIndex()
Lays out items with a draggable splitter between each item.
static QQuickSplitViewAttached * qmlAttachedProperties(QObject *object)
void handleChanged()
void componentComplete() override
Invoked after the root component that caused this instantiation has completed construction.
void setOrientation(Qt::Orientation orientation)
~QQuickSplitView() override
void setHandle(QQmlComponent *handle)
void itemAdded(int index, QQuickItem *item) override
void hoverLeaveEvent(QHoverEvent *event) override
This event handler can be reimplemented in a subclass to receive hover-leave events for an item.
void hoverMoveEvent(QHoverEvent *event) override
This event handler can be reimplemented in a subclass to receive hover-move events for an item.
Q_INVOKABLE QVariant saveState()
\qmlmethod var QtQuick.Controls::SplitView::saveState()
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override
bool isContent(QQuickItem *item) const override
void orientationChanged()
QQmlComponent * handle
\qmlproperty Component QtQuick.Controls::SplitView::handle
Qt::Orientation orientation
void itemRemoved(int index, QQuickItem *item) override
bool childMouseEventFilter(QQuickItem *item, QEvent *event) override
Reimplement this method to filter the pointer events that are received by this item's children.
Q_INVOKABLE bool restoreState(const QVariant &state)
\qmlmethod bool QtQuick.Controls::SplitView::restoreState(state)
bool isResizing() const
\qmlproperty bool QtQuick.Controls::SplitView::resizing \readonly
QQuickSplitView(QQuickItem *parent=nullptr)
void itemMoved(int index, QQuickItem *item) override
\inmodule QtCore\reentrant
Definition qrect.h:484
const_iterator constBegin() const noexcept
Definition qset.h:139
const_iterator constEnd() const noexcept
Definition qset.h:143
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
\inmodule QtCore
Definition qvariant.h:65
qDeleteAll(list.begin(), list.end())
QSet< QString >::iterator it
else opt state
[0]
Combined button and popup list for selecting options.
Definition qcompare.h:63
@ LeftButton
Definition qnamespace.h:58
Orientation
Definition qnamespace.h:98
@ Horizontal
Definition qnamespace.h:99
@ Vertical
Definition qnamespace.h:100
@ SplitHCursor
static void * context
#define Q_UNLIKELY(x)
#define Q_LIKELY(x)
#define Q_FUNC_INFO
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLuint64 GLenum void * handle
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLenum GLenum GLsizei count
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLint GLsizei width
struct _cl_event * event
GLsizei const void * pointer
Definition qopenglext.h:384
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:75
Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me)
QQuickItem * qobject_cast< QQuickItem * >(QObject *o)
Definition qquickitem.h:492
qreal effectiveMaximumHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate)
qreal effectivePreferredHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate, const QQuickItemPrivate *itemPrivate)
qreal effectivePreferredWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate, const QQuickItemPrivate *itemPrivate)
qreal effectiveMinimumWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate)
qreal effectiveMaximumWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate)
qreal effectiveMinimumHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
SSL_CTX int void * arg
#define qPrintable(string)
Definition qstring.h:1531
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define emit
#define Q_UNUSED(x)
unsigned long ulong
Definition qtypes.h:35
double qreal
Definition qtypes.h:187
ba fill(true)
QGraphicsItem * item
\inmodule QtCore\reentrant
Definition qcborvalue.h:37
QString errorString() const
\variable QCborParserError::offset
Definition qcborvalue.h:41
QCborError error
Definition qcborvalue.h:39
qsizetype indexOf(const AT &t, qsizetype from=0) const noexcept
Definition qlist.h:962