1// Copyright (C) 2016 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 "qsplitter.h"
5
6#include "qapplication.h"
7#include "qcursor.h"
8#include "qdrawutil.h"
9#include "qevent.h"
10#include "qlayout.h"
11#include "qlist.h"
12#include "qpainter.h"
13#if QT_CONFIG(rubberband)
14#include "qrubberband.h"
15#endif
16#include "qstyle.h"
17#include "qstyleoption.h"
18#include "qtextstream.h"
19#include "qvarlengtharray.h"
20#include "private/qlayoutengine_p.h"
21#include "private/qsplitter_p.h"
22#include "qdebug.h"
23
24#include <ctype.h>
25
26QT_BEGIN_NAMESPACE
27
28using namespace Qt::StringLiterals;
29
30//#define QSPLITTER_DEBUG
31
32QSplitterPrivate::~QSplitterPrivate()
33{
34}
35
36/*!
37 \class QSplitterHandle
38 \brief The QSplitterHandle class provides handle functionality for the splitter.
39
40 \ingroup organizers
41 \inmodule QtWidgets
42
43 QSplitterHandle is typically what people think about when they think about
44 a splitter. It is the handle that is used to resize the widgets.
45
46 A typical developer using QSplitter will never have to worry about
47 QSplitterHandle. It is provided for developers who want splitter handles
48 that provide extra features, such as popup menus.
49
50 The typical way one would create splitter handles is to subclass QSplitter and then
51 reimplement QSplitter::createHandle() to instantiate the custom splitter
52 handle. For example, a minimum QSplitter subclass might look like this:
53
54 \snippet splitterhandle/splitter.h 0
55
56 The \l{QSplitter::}{createHandle()} implementation simply constructs a
57 custom splitter handle, called \c Splitter in this example:
58
59 \snippet splitterhandle/splitter.cpp 1
60
61 Information about a given handle can be obtained using functions like
62 orientation() and opaqueResize(), and is retrieved from its parent splitter.
63 Details like these can be used to give custom handles different appearances
64 depending on the splitter's orientation.
65
66 The complexity of a custom handle subclass depends on the tasks that it
67 needs to perform. A simple subclass might only provide a paintEvent()
68 implementation:
69
70 \snippet splitterhandle/splitter.cpp 0
71
72 In this example, a predefined gradient is set up differently depending on
73 the orientation of the handle. QSplitterHandle provides a reasonable
74 size hint for the handle, so the subclass does not need to provide a
75 reimplementation of sizeHint() unless the handle has special size
76 requirements.
77
78 \sa QSplitter
79*/
80
81/*!
82 Creates a QSplitter handle with the given \a orientation and
83 \a parent.
84*/
85QSplitterHandle::QSplitterHandle(Qt::Orientation orientation, QSplitter *parent)
86 : QWidget(*new QSplitterHandlePrivate, parent, { })
87{
88 Q_D(QSplitterHandle);
89 d->s = parent;
90 setOrientation(orientation);
91}
92
93/*!
94 Destructor.
95*/
96QSplitterHandle::~QSplitterHandle()
97{
98}
99
100/*!
101 Sets the orientation of the splitter handle to \a orientation.
102 This is usually propagated from the QSplitter.
103
104 \sa QSplitter::setOrientation()
105*/
106void QSplitterHandle::setOrientation(Qt::Orientation orientation)
107{
108 Q_D(QSplitterHandle);
109 d->orient = orientation;
110#ifndef QT_NO_CURSOR
111 setCursor(orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor);
112#endif
113}
114
115/*!
116 Returns the handle's orientation. This is usually propagated from the QSplitter.
117
118 \sa QSplitter::orientation()
119*/
120Qt::Orientation QSplitterHandle::orientation() const
121{
122 Q_D(const QSplitterHandle);
123 return d->orient;
124}
125
126
127/*!
128 Returns \c true if widgets are resized dynamically (opaquely) while interactively moving the
129 splitter. Otherwise returns \c false. This value is controlled by the QSplitter.
130
131 \sa QSplitter::opaqueResize()
132*/
133bool QSplitterHandle::opaqueResize() const
134{
135 Q_D(const QSplitterHandle);
136 return d->s->opaqueResize();
137}
138
139
140/*!
141 Returns the splitter associated with this splitter handle.
142
143 \sa QSplitter::handle()
144*/
145QSplitter *QSplitterHandle::splitter() const
146{
147 return d_func()->s;
148}
149
150/*!
151 Tells the splitter to move this handle to position \a pos, which is
152 the distance from the left or top edge of the widget.
153
154 Note that \a pos is also measured from the left (or top) for
155 right-to-left languages. This function will map \a pos to the
156 appropriate position before calling QSplitter::moveSplitter().
157
158 \sa QSplitter::moveSplitter(), closestLegalPosition()
159*/
160void QSplitterHandle::moveSplitter(int pos)
161{
162 Q_D(QSplitterHandle);
163 if (d->s->isRightToLeft() && d->orient == Qt::Horizontal)
164 pos = d->s->contentsRect().width() - pos;
165 d->s->moveSplitter(pos, index: d->s->indexOf(w: this));
166}
167
168/*!
169 Returns the closest legal position to \a pos of the splitter
170 handle. The positions are measured from the left or top edge of
171 the splitter, even for right-to-left languages.
172
173 \sa QSplitter::closestLegalPosition(), moveSplitter()
174*/
175
176int QSplitterHandle::closestLegalPosition(int pos)
177{
178 Q_D(QSplitterHandle);
179 QSplitter *s = d->s;
180 if (s->isRightToLeft() && d->orient == Qt::Horizontal) {
181 int w = s->contentsRect().width();
182 return w - s->closestLegalPosition(w - pos, s->indexOf(w: this));
183 }
184 return s->closestLegalPosition(pos, s->indexOf(w: this));
185}
186
187/*!
188 \reimp
189*/
190QSize QSplitterHandle::sizeHint() const
191{
192 Q_D(const QSplitterHandle);
193 int hw = d->s->handleWidth();
194 QStyleOption opt(0);
195 opt.initFrom(w: d->s);
196 opt.state = QStyle::State_None;
197 return parentWidget()->style()->sizeFromContents(ct: QStyle::CT_Splitter, opt: &opt, contentsSize: QSize(hw, hw), w: d->s);
198}
199
200/*!
201 \reimp
202*/
203void QSplitterHandle::resizeEvent(QResizeEvent *event)
204{
205 Q_D(const QSplitterHandle);
206
207 // Ensure the actual grab area is at least 4 or 5 pixels
208 const int handleMargin = (5 - d->s->handleWidth()) / 2;
209
210 // Note that QSplitter uses contentsRect for layouting
211 // and ensures that handles are drawn on top of widgets
212 // We simply use the contents margins for draggin and only
213 // paint the mask area
214 const bool useTinyMode = handleMargin > 0;
215 setAttribute(Qt::WA_MouseNoMask, on: useTinyMode);
216 if (useTinyMode) {
217 if (orientation() == Qt::Horizontal)
218 setContentsMargins(left: handleMargin, top: 0, right: handleMargin, bottom: 0);
219 else
220 setContentsMargins(left: 0, top: handleMargin, right: 0, bottom: handleMargin);
221 setMask(QRegion(contentsRect()));
222 } else {
223 setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
224 clearMask();
225 }
226
227 QWidget::resizeEvent(event);
228}
229
230/*!
231 \reimp
232*/
233bool QSplitterHandle::event(QEvent *event)
234{
235 Q_D(QSplitterHandle);
236 switch(event->type()) {
237 case QEvent::HoverEnter:
238 d->hover = true;
239 update();
240 break;
241 case QEvent::HoverLeave:
242 d->hover = false;
243 update();
244 break;
245 default:
246 break;
247 }
248 return QWidget::event(event);
249}
250
251/*!
252 \reimp
253*/
254void QSplitterHandle::mouseMoveEvent(QMouseEvent *e)
255{
256 Q_D(QSplitterHandle);
257 if (!d->pressed)
258 return;
259
260 const int pos = d->pick(pos: parentWidget()->mapFromGlobal(e->globalPosition().toPoint()))
261 - d->mouseOffset;
262 if (opaqueResize()) {
263 moveSplitter(pos);
264 } else {
265 d->s->setRubberBand(closestLegalPosition(pos));
266 }
267}
268
269/*!
270 \reimp
271*/
272void QSplitterHandle::mousePressEvent(QMouseEvent *e)
273{
274 Q_D(QSplitterHandle);
275 if (e->button() == Qt::LeftButton) {
276 d->mouseOffset = d->pick(pos: e->position().toPoint());
277 d->pressed = true;
278 update();
279 }
280}
281
282/*!
283 \reimp
284*/
285void QSplitterHandle::mouseReleaseEvent(QMouseEvent *e)
286{
287 Q_D(QSplitterHandle);
288 if (!d->pressed)
289 return;
290
291 if (!opaqueResize()) {
292 const int pos = d->pick(pos: parentWidget()->mapFromGlobal(e->globalPosition().toPoint()))
293 - d->mouseOffset;
294 d->s->setRubberBand(-1);
295 moveSplitter(pos);
296 }
297
298 d->pressed = false;
299 update();
300}
301
302/*!
303 \reimp
304*/
305void QSplitterHandle::paintEvent(QPaintEvent *)
306{
307 Q_D(QSplitterHandle);
308 QPainter p(this);
309 QStyleOption opt(0);
310 opt.rect = contentsRect();
311 opt.palette = palette();
312 if (orientation() == Qt::Horizontal)
313 opt.state = QStyle::State_Horizontal;
314 else
315 opt.state = QStyle::State_None;
316 if (d->hover)
317 opt.state |= QStyle::State_MouseOver;
318 if (d->pressed)
319 opt.state |= QStyle::State_Sunken;
320 if (isEnabled())
321 opt.state |= QStyle::State_Enabled;
322 parentWidget()->style()->drawControl(element: QStyle::CE_Splitter, opt: &opt, p: &p, w: d->s);
323}
324
325
326int QSplitterLayoutStruct::getWidgetSize(Qt::Orientation orient)
327{
328 if (sizer == -1) {
329 QSize s = widget->sizeHint();
330 const int presizer = pick(size: s, orient);
331 const int realsize = pick(size: widget->size(), orient);
332 if (!s.isValid() || (widget->testAttribute(attribute: Qt::WA_Resized) && (realsize > presizer))) {
333 sizer = pick(size: widget->size(), orient);
334 } else {
335 sizer = presizer;
336 }
337 QSizePolicy p = widget->sizePolicy();
338 int sf = (orient == Qt::Horizontal) ? p.horizontalStretch() : p.verticalStretch();
339 if (sf > 1)
340 sizer *= sf;
341 }
342 return sizer;
343}
344
345int QSplitterLayoutStruct::getHandleSize(Qt::Orientation orient)
346{
347 return pick(size: handle->sizeHint(), orient);
348}
349
350void QSplitterPrivate::init()
351{
352 Q_Q(QSplitter);
353 QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Preferred);
354 if (orient == Qt::Vertical)
355 sp.transpose();
356 q->setSizePolicy(sp);
357 q->setAttribute(Qt::WA_WState_OwnSizePolicy, on: false);
358}
359
360void QSplitterPrivate::recalc(bool update)
361{
362 Q_Q(QSplitter);
363 int n = list.size();
364 /*
365 Splitter handles before the first visible widget or right
366 before a hidden widget must be hidden.
367 */
368 bool first = true;
369 bool allInvisible = n != 0;
370 for (int i = 0; i < n ; ++i) {
371 QSplitterLayoutStruct *s = list.at(i);
372 bool widgetHidden = s->widget->isHidden();
373 if (allInvisible && !widgetHidden && !s->collapsed)
374 allInvisible = false;
375 s->handle->setHidden(first || widgetHidden);
376 if (!widgetHidden)
377 first = false;
378 }
379
380 if (allInvisible)
381 for (int i = 0; i < n ; ++i) {
382 QSplitterLayoutStruct *s = list.at(i);
383 if (!s->widget->isHidden()) {
384 s->collapsed = false;
385 break;
386 }
387 }
388
389 int fi = 2 * q->frameWidth();
390 int maxl = fi;
391 int minl = fi;
392 int maxt = QWIDGETSIZE_MAX;
393 int mint = fi;
394 /*
395 calculate min/max sizes for the whole splitter
396 */
397 bool empty = true;
398 for (int j = 0; j < n; j++) {
399 QSplitterLayoutStruct *s = list.at(i: j);
400
401 if (!s->widget->isHidden()) {
402 empty = false;
403 if (!s->handle->isHidden()) {
404 minl += s->getHandleSize(orient);
405 maxl += s->getHandleSize(orient);
406 }
407
408 QSize minS = qSmartMinSize(w: s->widget);
409 minl += pick(s: minS);
410 maxl += pick(s: qSmartMaxSize(w: s->widget));
411 mint = qMax(a: mint, b: trans(s: minS));
412 int tm = trans(s: qSmartMaxSize(w: s->widget));
413 if (tm > 0)
414 maxt = qMin(a: maxt, b: tm);
415 }
416 }
417
418 if (empty) {
419 if (qobject_cast<QSplitter *>(object: parent)) {
420 // nested splitters; be nice
421 maxl = maxt = 0;
422 } else {
423 // QSplitter with no children yet
424 maxl = QWIDGETSIZE_MAX;
425 }
426 } else {
427 maxl = qMin<int>(a: maxl, QWIDGETSIZE_MAX);
428 }
429 if (maxt < mint)
430 maxt = mint;
431
432 if (update) {
433 if (orient == Qt::Horizontal) {
434 q->setMaximumSize(maxw: maxl, maxh: maxt);
435 if (q->isWindow())
436 q->setMinimumSize(minw: minl,minh: mint);
437 } else {
438 q->setMaximumSize(maxw: maxt, maxh: maxl);
439 if (q->isWindow())
440 q->setMinimumSize(minw: mint,minh: minl);
441 }
442 doResize();
443 q->updateGeometry();
444 } else {
445 firstShow = true;
446 }
447}
448
449void QSplitterPrivate::doResize()
450{
451 Q_Q(QSplitter);
452 QRect r = q->contentsRect();
453 int n = list.size();
454 QList<QLayoutStruct> a(n * 2);
455 int i;
456
457 bool noStretchFactorsSet = true;
458 for (i = 0; i < n; ++i) {
459 QSizePolicy p = list.at(i)->widget->sizePolicy();
460 int sf = orient == Qt::Horizontal ? p.horizontalStretch() : p.verticalStretch();
461 if (sf != 0) {
462 noStretchFactorsSet = false;
463 break;
464 }
465 }
466
467 int j=0;
468 for (i = 0; i < n; ++i) {
469 QSplitterLayoutStruct *s = list.at(i);
470#ifdef QSPLITTER_DEBUG
471 qDebug("widget %d hidden: %d collapsed: %d handle hidden: %d", i, s->widget->isHidden(),
472 s->collapsed, s->handle->isHidden());
473#endif
474
475 a[j].init();
476 if (s->handle->isHidden()) {
477 a[j].maximumSize = 0;
478 } else {
479 a[j].sizeHint = a[j].minimumSize = a[j].maximumSize = s->getHandleSize(orient);
480 a[j].empty = false;
481 }
482 ++j;
483
484 a[j].init();
485 if (s->widget->isHidden() || s->collapsed) {
486 a[j].maximumSize = 0;
487 } else {
488 a[j].minimumSize = pick(s: qSmartMinSize(w: s->widget));
489 a[j].maximumSize = pick(s: qSmartMaxSize(w: s->widget));
490 a[j].empty = false;
491
492 bool stretch = noStretchFactorsSet;
493 if (!stretch) {
494 QSizePolicy p = s->widget->sizePolicy();
495 int sf = orient == Qt::Horizontal ? p.horizontalStretch() : p.verticalStretch();
496 stretch = (sf != 0);
497 }
498 if (stretch) {
499 a[j].stretch = s->getWidgetSize(orient);
500 a[j].sizeHint = a[j].minimumSize;
501 a[j].expansive = true;
502 } else {
503 a[j].sizeHint = qMax(a: s->getWidgetSize(orient), b: a[j].minimumSize);
504 }
505 }
506 ++j;
507 }
508
509 qGeomCalc(chain&: a, start: 0, count: n*2, pos: pick(pos: r.topLeft()), space: pick(s: r.size()), spacer: 0);
510
511#ifdef QSPLITTER_DEBUG
512 for (i = 0; i < n*2; ++i) {
513 qDebug("%*s%d: stretch %d, sh %d, minS %d, maxS %d, exp %d, emp %d -> %d, %d",
514 i, "", i,
515 a[i].stretch,
516 a[i].sizeHint,
517 a[i].minimumSize,
518 a[i].maximumSize,
519 a[i].expansive,
520 a[i].empty,
521 a[i].pos,
522 a[i].size);
523 }
524#endif
525
526 for (i = 0; i < n; ++i) {
527 QSplitterLayoutStruct *s = list.at(i);
528 setGeo(s, pos: a[i*2+1].pos, size: a[i*2+1].size, allowCollapse: false);
529 }
530}
531
532void QSplitterPrivate::storeSizes()
533{
534 for (int i = 0; i < list.size(); ++i) {
535 QSplitterLayoutStruct *sls = list.at(i);
536 sls->sizer = pick(s: sls->rect.size());
537 }
538}
539
540void QSplitterPrivate::addContribution(int index, int *min, int *max, bool mayCollapse) const
541{
542 QSplitterLayoutStruct *s = list.at(i: index);
543 if (!s->widget->isHidden()) {
544 if (!s->handle->isHidden()) {
545 *min += s->getHandleSize(orient);
546 *max += s->getHandleSize(orient);
547 }
548 if (mayCollapse || !s->collapsed)
549 *min += pick(s: qSmartMinSize(w: s->widget));
550
551 *max += pick(s: qSmartMaxSize(w: s->widget));
552 }
553}
554
555int QSplitterPrivate::findWidgetJustBeforeOrJustAfter(int index, int delta, int &collapsibleSize) const
556{
557 if (delta < 0)
558 index += delta;
559 do {
560 QWidget *w = list.at(i: index)->widget;
561 if (!w->isHidden()) {
562 if (collapsible(list.at(i: index)))
563 collapsibleSize = pick(s: qSmartMinSize(w));
564 return index;
565 }
566 index += delta;
567 } while (index >= 0 && index < list.size());
568
569 return -1;
570}
571
572/*
573 For the splitter handle with index \a index, \a min and \a max give the range without collapsing any widgets,
574 and \a farMin and farMax give the range with collapsing included.
575*/
576void QSplitterPrivate::getRange(int index, int *farMin, int *min, int *max, int *farMax) const
577{
578 Q_Q(const QSplitter);
579 int n = list.size();
580 if (index <= 0 || index >= n)
581 return;
582
583 int collapsibleSizeBefore = 0;
584 int idJustBefore = findWidgetJustBeforeOrJustAfter(index, delta: -1, collapsibleSize&: collapsibleSizeBefore);
585
586 int collapsibleSizeAfter = 0;
587 int idJustAfter = findWidgetJustBeforeOrJustAfter(index, delta: +1, collapsibleSize&: collapsibleSizeAfter);
588
589 int minBefore = 0;
590 int minAfter = 0;
591 int maxBefore = 0;
592 int maxAfter = 0;
593 int i;
594
595 for (i = 0; i < index; ++i)
596 addContribution(index: i, min: &minBefore, max: &maxBefore, mayCollapse: i == idJustBefore);
597 for (i = index; i < n; ++i)
598 addContribution(index: i, min: &minAfter, max: &maxAfter, mayCollapse: i == idJustAfter);
599
600 QRect r = q->contentsRect();
601 int farMinVal;
602 int minVal;
603 int maxVal;
604 int farMaxVal;
605
606 int smartMinBefore = qMax(a: minBefore, b: pick(s: r.size()) - maxAfter);
607 int smartMaxBefore = qMin(a: maxBefore, b: pick(s: r.size()) - minAfter);
608
609 minVal = pick(pos: r.topLeft()) + smartMinBefore;
610 maxVal = pick(pos: r.topLeft()) + smartMaxBefore;
611
612 farMinVal = minVal;
613 if (minBefore - collapsibleSizeBefore >= pick(s: r.size()) - maxAfter)
614 farMinVal -= collapsibleSizeBefore;
615 farMaxVal = maxVal;
616 if (pick(s: r.size()) - (minAfter - collapsibleSizeAfter) <= maxBefore)
617 farMaxVal += collapsibleSizeAfter;
618
619 if (farMin)
620 *farMin = farMinVal;
621 if (min)
622 *min = minVal;
623 if (max)
624 *max = maxVal;
625 if (farMax)
626 *farMax = farMaxVal;
627}
628
629int QSplitterPrivate::adjustPos(int pos, int index, int *farMin, int *min, int *max, int *farMax) const
630{
631 const int Threshold = 40;
632
633 getRange(index, farMin, min, max, farMax);
634
635 if (pos >= *min) {
636 if (pos <= *max) {
637 return pos;
638 } else {
639 int delta = pos - *max;
640 int width = *farMax - *max;
641
642 if (delta > width / 2 && delta >= qMin(a: Threshold, b: width)) {
643 return *farMax;
644 } else {
645 return *max;
646 }
647 }
648 } else {
649 int delta = *min - pos;
650 int width = *min - *farMin;
651
652 if (delta > width / 2 && delta >= qMin(a: Threshold, b: width)) {
653 return *farMin;
654 } else {
655 return *min;
656 }
657 }
658}
659
660bool QSplitterPrivate::collapsible(QSplitterLayoutStruct *s) const
661{
662 if (s->collapsible != Default) {
663 return (bool)s->collapsible;
664 } else {
665 return childrenCollapsible;
666 }
667}
668
669void QSplitterPrivate::updateHandles()
670{
671 Q_Q(QSplitter);
672 recalc(update: q->isVisible());
673}
674
675void QSplitterPrivate::setSizes_helper(const QList<int> &sizes, bool clampNegativeSize)
676{
677 int j = 0;
678
679 for (int i = 0; i < list.size(); ++i) {
680 QSplitterLayoutStruct *s = list.at(i);
681
682 s->collapsed = false;
683 s->sizer = sizes.value(i: j++);
684 if (clampNegativeSize && s->sizer < 0)
685 s->sizer = 0;
686 int smartMinSize = pick(s: qSmartMinSize(w: s->widget));
687
688 // Make sure that we reset the collapsed state.
689 if (s->sizer == 0) {
690 if (collapsible(s) && smartMinSize > 0) {
691 s->collapsed = true;
692 } else {
693 s->sizer = smartMinSize;
694 }
695 } else {
696 if (s->sizer < smartMinSize)
697 s->sizer = smartMinSize;
698 }
699 }
700 doResize();
701}
702
703/*
704 Used by various methods inserting a widget to find out if we need to show the widget
705 explicitly, which we have to if the splitter is already visible, and if the widget hasn't
706 been explicitly hidden before inserting it.
707*/
708bool QSplitterPrivate::shouldShowWidget(const QWidget *w) const
709{
710 Q_Q(const QSplitter);
711 return q->isVisible() && !QWidgetPrivate::get(w)->isExplicitlyHidden();
712}
713
714void QSplitterPrivate::setGeo(QSplitterLayoutStruct *sls, int p, int s, bool allowCollapse)
715{
716 Q_Q(QSplitter);
717 QWidget *w = sls->widget;
718 QRect r;
719 QRect contents = q->contentsRect();
720 if (orient == Qt::Horizontal) {
721 r.setRect(ax: p, ay: contents.y(), aw: s, ah: contents.height());
722 } else {
723 r.setRect(ax: contents.x(), ay: p, aw: contents.width(), ah: s);
724 }
725 sls->rect = r;
726
727 int minSize = pick(s: qSmartMinSize(w));
728
729 if (orient == Qt::Horizontal && q->isRightToLeft())
730 r.moveRight(pos: contents.width() - r.left());
731
732 if (allowCollapse)
733 sls->collapsed = s <= 0 && minSize > 0 && !w->isHidden();
734
735 // Hide the child widget, but without calling hide() so that
736 // the splitter handle is still shown.
737 if (sls->collapsed)
738 r.moveTopLeft(p: QPoint(-r.width()-1, -r.height()-1));
739
740 w->setGeometry(r);
741
742 if (!sls->handle->isHidden()) {
743 QSplitterHandle *h = sls->handle;
744 QSize hs = h->sizeHint();
745 const QMargins m = h->contentsMargins();
746 if (orient==Qt::Horizontal) {
747 if (q->isRightToLeft())
748 p = contents.width() - p + hs.width();
749 h->setGeometry(ax: p-hs.width() - m.left(), ay: contents.y(), aw: hs.width() + m.left() + m.right(), ah: contents.height());
750 } else {
751 h->setGeometry(ax: contents.x(), ay: p-hs.height() - m.top(), aw: contents.width(), ah: hs.height() + m.top() + m.bottom());
752 }
753 }
754}
755
756void QSplitterPrivate::doMove(bool backwards, int hPos, int index, int delta, bool mayCollapse,
757 int *positions, int *widths)
758{
759 if (index < 0 || index >= list.size())
760 return;
761
762#ifdef QSPLITTER_DEBUG
763 qDebug() << "QSplitterPrivate::doMove" << backwards << hPos << index << delta << mayCollapse;
764#endif
765
766 QSplitterLayoutStruct *s = list.at(i: index);
767 QWidget *w = s->widget;
768
769 int nextId = backwards ? index - delta : index + delta;
770
771 if (w->isHidden()) {
772 doMove(backwards, hPos, index: nextId, delta, mayCollapse: collapsible(index: nextId), positions, widths);
773 } else {
774 int hs =s->handle->isHidden() ? 0 : s->getHandleSize(orient);
775
776 int ws = backwards ? hPos - pick(pos: s->rect.topLeft())
777 : pick(pos: s->rect.bottomRight()) - hPos -hs + 1;
778 if (ws > 0 || (!s->collapsed && !mayCollapse)) {
779 ws = qMin(a: ws, b: pick(s: qSmartMaxSize(w)));
780 ws = qMax(a: ws, b: pick(s: qSmartMinSize(w)));
781 } else {
782 ws = 0;
783 }
784 positions[index] = backwards ? hPos - ws : hPos + hs;
785 widths[index] = ws;
786 doMove(backwards, hPos: backwards ? hPos - ws - hs : hPos + hs + ws, index: nextId, delta,
787 mayCollapse: collapsible(index: nextId), positions, widths);
788 }
789
790}
791
792QSplitterLayoutStruct *QSplitterPrivate::findWidget(QWidget *w) const
793{
794 for (int i = 0; i < list.size(); ++i) {
795 if (list.at(i)->widget == w)
796 return list.at(i);
797 }
798 return nullptr;
799}
800
801
802/*!
803 \internal
804*/
805void QSplitterPrivate::insertWidget_helper(int index, QWidget *widget, bool show)
806{
807 Q_Q(QSplitter);
808 QScopedValueRollback b(blockChildAdd, true);
809 const bool needShow = show && shouldShowWidget(w: widget);
810 if (widget->parentWidget() != q)
811 widget->setParent(q);
812 if (needShow)
813 widget->show();
814 insertWidget(index, widget);
815 recalc(update: q->isVisible());
816}
817
818/*
819 Inserts the widget \a w at position \a index in the splitter's list of widgets.
820
821 If \a w is already in the splitter, it will be moved to the new position.
822*/
823
824QSplitterLayoutStruct *QSplitterPrivate::insertWidget(int index, QWidget *w)
825{
826 Q_Q(QSplitter);
827 QSplitterLayoutStruct *sls = nullptr;
828 int i;
829 int last = list.size();
830 for (i = 0; i < list.size(); ++i) {
831 QSplitterLayoutStruct *s = list.at(i);
832 if (s->widget == w) {
833 sls = s;
834 --last;
835 break;
836 }
837 }
838 if (index < 0 || index > last)
839 index = last;
840
841 if (sls) {
842 list.move(from: i,to: index);
843 } else {
844 sls = new QSplitterLayoutStruct;
845 QSplitterHandle *newHandle = q->createHandle();
846 newHandle->setObjectName("qt_splithandle_"_L1 + w->objectName());
847 sls->handle = newHandle;
848 sls->widget = w;
849 w->lower();
850 list.insert(i: index,t: sls);
851
852 if (newHandle && q->isVisible())
853 newHandle->show(); // will trigger sending of post events
854
855 }
856 return sls;
857}
858
859/*!
860 \class QSplitter
861 \brief The QSplitter class implements a splitter widget.
862
863 \ingroup organizers
864 \inmodule QtWidgets
865
866
867 A splitter lets the user control the size of child widgets by dragging the
868 boundary between them. Any number of widgets may be controlled by a
869 single splitter. The typical use of a QSplitter is to create several
870 widgets and add them using insertWidget() or addWidget().
871
872 The following example will show a QListView, QTreeView, and
873 QTextEdit side by side, with two splitter handles:
874
875 \snippet splitter/splitter.cpp 0
876
877 If a widget is already inside a QSplitter when insertWidget() or
878 addWidget() is called, it will move to the new position. This can be used
879 to reorder widgets in the splitter later. You can use indexOf(),
880 widget(), and count() to get access to the widgets inside the splitter.
881
882 A default QSplitter lays out its children horizontally (side by side); you
883 can use setOrientation(Qt::Vertical) to lay its
884 children out vertically.
885
886 By default, all widgets can be as large or as small as the user
887 wishes, between the \l minimumSizeHint() (or \l minimumSize())
888 and \l maximumSize() of the widgets.
889
890 QSplitter resizes its children dynamically by default. If you
891 would rather have QSplitter resize the children only at the end of
892 a resize operation, call setOpaqueResize(false).
893
894 The initial distribution of size between the widgets is determined by
895 multiplying the initial size with the stretch factor.
896 You can also use setSizes() to set the sizes
897 of all the widgets. The function sizes() returns the sizes set by the user.
898 Alternatively, you can save and restore the sizes of the widgets from a
899 QByteArray using saveState() and restoreState() respectively.
900
901 When you hide() a child, its space will be distributed among the
902 other children. It will be reinstated when you show() it again.
903
904 \note Adding a QLayout to a QSplitter is not supported (either through
905 setLayout() or making the QSplitter a parent of the QLayout); use addWidget()
906 instead (see example above).
907
908 \sa QSplitterHandle, QHBoxLayout, QVBoxLayout, QTabWidget
909*/
910
911
912/*!
913 Constructs a horizontal splitter with the \a parent
914 argument passed on to the QFrame constructor.
915
916 \sa setOrientation()
917*/
918QSplitter::QSplitter(QWidget *parent)
919 : QSplitter(Qt::Horizontal, parent)
920{
921}
922
923
924/*!
925 Constructs a splitter with the given \a orientation and \a parent.
926
927 \sa setOrientation()
928*/
929QSplitter::QSplitter(Qt::Orientation orientation, QWidget *parent)
930 : QFrame(*new QSplitterPrivate, parent)
931{
932 Q_D(QSplitter);
933 d->orient = orientation;
934 d->init();
935}
936
937
938/*!
939 Destroys the splitter. All children are deleted.
940*/
941
942QSplitter::~QSplitter()
943{
944 Q_D(QSplitter);
945#if QT_CONFIG(rubberband)
946 delete d->rubberBand;
947#endif
948 while (!d->list.isEmpty())
949 delete d->list.takeFirst();
950}
951
952/*!
953 Updates the splitter's state. You should not need to call this
954 function.
955*/
956void QSplitter::refresh()
957{
958 Q_D(QSplitter);
959 d->recalc(update: true);
960}
961
962/*!
963 \property QSplitter::orientation
964 \brief the orientation of the splitter
965
966 By default, the orientation is horizontal (i.e., the widgets are
967 laid out side by side). The possible orientations are
968 Qt::Horizontal and Qt::Vertical.
969
970 \sa QSplitterHandle::orientation()
971*/
972
973void QSplitter::setOrientation(Qt::Orientation orientation)
974{
975 Q_D(QSplitter);
976 if (d->orient == orientation)
977 return;
978
979 if (!testAttribute(attribute: Qt::WA_WState_OwnSizePolicy)) {
980 setSizePolicy(sizePolicy().transposed());
981 setAttribute(Qt::WA_WState_OwnSizePolicy, on: false);
982 }
983
984 d->orient = orientation;
985
986 for (int i = 0; i < d->list.size(); ++i) {
987 QSplitterLayoutStruct *s = d->list.at(i);
988 s->handle->setOrientation(orientation);
989 }
990 d->recalc(update: isVisible());
991}
992
993Qt::Orientation QSplitter::orientation() const
994{
995 Q_D(const QSplitter);
996 return d->orient;
997}
998
999/*!
1000 \property QSplitter::childrenCollapsible
1001 \brief whether child widgets can be resized down to size 0 by the user
1002
1003 By default, children are collapsible. It is possible to enable
1004 and disable the collapsing of individual children using
1005 setCollapsible().
1006
1007 \sa setCollapsible()
1008*/
1009
1010void QSplitter::setChildrenCollapsible(bool collapse)
1011{
1012 Q_D(QSplitter);
1013 d->childrenCollapsible = collapse;
1014}
1015
1016bool QSplitter::childrenCollapsible() const
1017{
1018 Q_D(const QSplitter);
1019 return d->childrenCollapsible;
1020}
1021
1022/*!
1023 Sets whether the child widget at \a index is collapsible to \a collapse.
1024
1025 By default, children are collapsible, meaning that the user can
1026 resize them down to size 0, even if they have a non-zero
1027 minimumSize() or minimumSizeHint(). This behavior can be changed
1028 on a per-widget basis by calling this function, or globally for
1029 all the widgets in the splitter by setting the \l
1030 childrenCollapsible property.
1031
1032 \sa childrenCollapsible
1033*/
1034
1035void QSplitter::setCollapsible(int index, bool collapse)
1036{
1037 Q_D(QSplitter);
1038
1039 if (Q_UNLIKELY(index < 0 || index >= d->list.size())) {
1040 qWarning(msg: "QSplitter::setCollapsible: Index %d out of range", index);
1041 return;
1042 }
1043 d->list.at(i: index)->collapsible = collapse ? 1 : 0;
1044}
1045
1046/*!
1047 Returns \c true if the widget at \a index is collapsible, otherwise returns \c false.
1048*/
1049bool QSplitter::isCollapsible(int index) const
1050{
1051 Q_D(const QSplitter);
1052 if (Q_UNLIKELY(index < 0 || index >= d->list.size())) {
1053 qWarning(msg: "QSplitter::isCollapsible: Index %d out of range", index);
1054 return false;
1055 }
1056 return d->list.at(i: index)->collapsible;
1057}
1058
1059/*!
1060 \reimp
1061*/
1062void QSplitter::resizeEvent(QResizeEvent *)
1063{
1064 Q_D(QSplitter);
1065 d->doResize();
1066}
1067
1068/*!
1069 Adds the given \a widget to the splitter's layout after all the other
1070 items.
1071
1072 If \a widget is already in the splitter, it will be moved to the new position.
1073
1074 \note The splitter takes ownership of the widget.
1075
1076 \sa insertWidget(), widget(), indexOf()
1077*/
1078void QSplitter::addWidget(QWidget *widget)
1079{
1080 Q_D(QSplitter);
1081 insertWidget(index: d->list.size(), widget);
1082}
1083
1084/*!
1085 Inserts the \a widget specified into the splitter's layout at the
1086 given \a index.
1087
1088 If \a widget is already in the splitter, it will be moved to the new position.
1089
1090 If \a index is an invalid index, then the widget will be inserted at the end.
1091
1092 \note The splitter takes ownership of the widget.
1093
1094 \sa addWidget(), indexOf(), widget()
1095*/
1096void QSplitter::insertWidget(int index, QWidget *widget)
1097{
1098 Q_D(QSplitter);
1099 d->insertWidget_helper(index, widget, show: true);
1100}
1101
1102/*!
1103 \since 5.9
1104
1105 Replaces the widget in the splitter's layout at the given \a index by \a widget.
1106
1107 Returns the widget that has just been replaced if \a index is valid and \a widget
1108 is not already a child of the splitter. Otherwise, it returns null and no replacement
1109 or addition is made.
1110
1111 The geometry of the newly inserted widget will be the same as the widget it replaces.
1112 Its visible and collapsed states are also inherited.
1113
1114 \note The splitter takes ownership of \a widget and sets the parent of the
1115 replaced widget to null.
1116
1117 \note Because \a widget gets \l{QWidget::setParent()}{reparented} into the splitter,
1118 its \l{QWidget::}{geometry} may not be set right away, but only after \a widget will
1119 receive the appropriate events.
1120
1121 \sa insertWidget(), indexOf()
1122*/
1123QWidget *QSplitter::replaceWidget(int index, QWidget *widget)
1124{
1125 Q_D(QSplitter);
1126 if (!widget) {
1127 qWarning(msg: "QSplitter::replaceWidget: Widget can't be null");
1128 return nullptr;
1129 }
1130
1131 if (index < 0 || index >= d->list.size()) {
1132 qWarning(msg: "QSplitter::replaceWidget: Index %d out of range", index);
1133 return nullptr;
1134 }
1135
1136 QSplitterLayoutStruct *s = d->list.at(i: index);
1137 QWidget *current = s->widget;
1138 if (current == widget) {
1139 qWarning(msg: "QSplitter::replaceWidget: Trying to replace a widget with itself");
1140 return nullptr;
1141 }
1142
1143 if (widget->parentWidget() == this) {
1144 qWarning(msg: "QSplitter::replaceWidget: Trying to replace a widget with one of its siblings");
1145 return nullptr;
1146 }
1147
1148 QScopedValueRollback b(d->blockChildAdd, true);
1149
1150 const QRect geom = current->geometry();
1151 const bool wasHidden = current->isHidden();
1152
1153 s->widget = widget;
1154 current->setParent(nullptr);
1155 widget->setParent(this);
1156
1157 // The splitter layout struct's geometry is already set and
1158 // should not change. Only set the geometry on the new widget
1159 widget->setGeometry(geom);
1160 widget->lower();
1161 if (wasHidden)
1162 widget->hide();
1163 else if (d->shouldShowWidget(w: widget))
1164 widget->show();
1165
1166 return current;
1167}
1168
1169/*!
1170 Returns the index in the splitter's layout of the specified \a widget,
1171 or -1 if \a widget is not found. This also works for handles.
1172
1173 Handles are numbered from 0. There are as many handles as there
1174 are child widgets, but the handle at position 0 is always hidden.
1175
1176
1177 \sa count(), widget()
1178*/
1179int QSplitter::indexOf(QWidget *widget) const
1180{
1181 Q_D(const QSplitter);
1182 for (int i = 0; i < d->list.size(); ++i) {
1183 QSplitterLayoutStruct *s = d->list.at(i);
1184 if (s->widget == widget || s->handle == widget)
1185 return i;
1186 }
1187 return -1;
1188}
1189
1190/*!
1191 Returns a new splitter handle as a child widget of this splitter.
1192 This function can be reimplemented in subclasses to provide support
1193 for custom handles.
1194
1195 \sa handle(), indexOf()
1196*/
1197QSplitterHandle *QSplitter::createHandle()
1198{
1199 Q_D(QSplitter);
1200 return new QSplitterHandle(d->orient, this);
1201}
1202
1203/*!
1204 Returns the handle to the left of (or above) the item in the
1205 splitter's layout at the given \a index, or \nullptr if there is no such item.
1206 The handle at index 0 is always hidden.
1207
1208 For right-to-left languages such as Arabic and Hebrew, the layout
1209 of horizontal splitters is reversed. The handle will be to the
1210 right of the widget at \a index.
1211
1212 \sa count(), widget(), indexOf(), createHandle(), setHandleWidth()
1213*/
1214QSplitterHandle *QSplitter::handle(int index) const
1215{
1216 Q_D(const QSplitter);
1217 if (index < 0 || index >= d->list.size())
1218 return nullptr;
1219 return d->list.at(i: index)->handle;
1220}
1221
1222/*!
1223 Returns the widget at the given \a index in the splitter's layout,
1224 or \nullptr if there is no such widget.
1225
1226 \sa count(), handle(), indexOf(), insertWidget()
1227*/
1228QWidget *QSplitter::widget(int index) const
1229{
1230 Q_D(const QSplitter);
1231 if (index < 0 || index >= d->list.size())
1232 return nullptr;
1233 return d->list.at(i: index)->widget;
1234}
1235
1236/*!
1237 Returns the number of widgets contained in the splitter's layout.
1238
1239 \sa widget(), handle()
1240*/
1241int QSplitter::count() const
1242{
1243 Q_D(const QSplitter);
1244 return d->list.size();
1245}
1246
1247/*!
1248 \reimp
1249
1250 Tells the splitter that the child widget described by \a c has been
1251 inserted or removed.
1252
1253 This method is also used to handle the situation where a widget is created
1254 with the splitter as a parent but not explicitly added with insertWidget()
1255 or addWidget(). This is for compatibility and not the recommended way of
1256 putting widgets into a splitter in new code. Please use insertWidget() or
1257 addWidget() in new code.
1258
1259 \sa addWidget(), insertWidget()
1260*/
1261
1262void QSplitter::childEvent(QChildEvent *c)
1263{
1264 Q_D(QSplitter);
1265 if (c->added()) {
1266 if (!c->child()->isWidgetType()) {
1267 if (Q_UNLIKELY(qobject_cast<QLayout *>(c->child())))
1268 qWarning(msg: "Adding a QLayout to a QSplitter is not supported.");
1269 return;
1270 }
1271 QWidget *w = static_cast<QWidget*>(c->child());
1272 if (!d->blockChildAdd && !w->isWindow() && !d->findWidget(w))
1273 d->insertWidget_helper(index: d->list.size(), widget: w, show: false);
1274 } else if (c->polished()) {
1275 if (!c->child()->isWidgetType())
1276 return;
1277 QWidget *w = static_cast<QWidget*>(c->child());
1278 if (!d->blockChildAdd && !w->isWindow() && d->shouldShowWidget(w))
1279 w->show();
1280 } else if (c->removed()) {
1281 QObject *child = c->child();
1282 for (int i = 0; i < d->list.size(); ++i) {
1283 QSplitterLayoutStruct *s = d->list.at(i);
1284 if (s->widget == child) {
1285 d->list.removeAt(i);
1286 delete s;
1287 d->recalc(update: isVisible());
1288 return;
1289 }
1290 }
1291 }
1292}
1293
1294
1295/*!
1296 Displays a rubber band at position \a pos. If \a pos is negative, the
1297 rubber band is removed.
1298*/
1299
1300void QSplitter::setRubberBand(int pos)
1301{
1302#if QT_CONFIG(rubberband)
1303 Q_D(QSplitter);
1304 if (pos < 0) {
1305 if (d->rubberBand)
1306 d->rubberBand->deleteLater();
1307 return;
1308 }
1309 QRect r = contentsRect();
1310 const int rBord = 3; // customizable?
1311 int hw = handleWidth();
1312 if (!d->rubberBand) {
1313 QScopedValueRollback b(d->blockChildAdd, true);
1314 d->rubberBand = new QRubberBand(QRubberBand::Line, this);
1315 // For accessibility to identify this special widget.
1316 d->rubberBand->setObjectName("qt_rubberband"_L1);
1317 }
1318
1319 const QRect newGeom = d->orient == Qt::Horizontal ? QRect(QPoint(pos + hw / 2 - rBord, r.y()), QSize(2 * rBord, r.height()))
1320 : QRect(QPoint(r.x(), pos + hw / 2 - rBord), QSize(r.width(), 2 * rBord));
1321 d->rubberBand->setGeometry(newGeom);
1322 d->rubberBand->show();
1323#else
1324 Q_UNUSED(pos);
1325#endif
1326}
1327
1328/*!
1329 \reimp
1330*/
1331
1332bool QSplitter::event(QEvent *e)
1333{
1334 Q_D(QSplitter);
1335 switch (e->type()) {
1336 case QEvent::Hide:
1337 // Reset firstShow to false here since things can be done to the splitter in between
1338 if (!d->firstShow)
1339 d->firstShow = true;
1340 break;
1341 case QEvent::Show:
1342 if (!d->firstShow)
1343 break;
1344 d->firstShow = false;
1345 Q_FALLTHROUGH();
1346 case QEvent::HideToParent:
1347 case QEvent::ShowToParent:
1348 case QEvent::LayoutRequest:
1349 d->recalc(update: isVisible());
1350 break;
1351 default:
1352 ;
1353 }
1354 return QFrame::event(e);
1355}
1356
1357/*!
1358 \fn void QSplitter::splitterMoved(int pos, int index)
1359
1360 This signal is emitted when the splitter handle at a particular \a
1361 index has been moved to position \a pos.
1362
1363 For right-to-left languages such as Arabic and Hebrew, the layout
1364 of horizontal splitters is reversed. \a pos is then the
1365 distance from the right edge of the widget.
1366
1367 \sa moveSplitter()
1368*/
1369
1370/*!
1371 Moves the left or top edge of the splitter handle at \a index as
1372 close as possible to position \a pos, which is the distance from the
1373 left or top edge of the widget.
1374
1375 For right-to-left languages such as Arabic and Hebrew, the layout
1376 of horizontal splitters is reversed. \a pos is then the distance
1377 from the right edge of the widget.
1378
1379 \sa splitterMoved(), closestLegalPosition(), getRange()
1380*/
1381void QSplitter::moveSplitter(int pos, int index)
1382{
1383 Q_D(QSplitter);
1384 QSplitterLayoutStruct *s = d->list.at(i: index);
1385 int farMin = 0;
1386 int min = 0;
1387 int max = 0;
1388 int farMax = 0;
1389
1390#ifdef QSPLITTER_DEBUG
1391 int debugp = pos;
1392#endif
1393
1394 pos = d->adjustPos(pos, index, farMin: &farMin, min: &min, max: &max, farMax: &farMax);
1395 int oldP = d->pick(pos: s->rect.topLeft());
1396#ifdef QSPLITTER_DEBUG
1397 qDebug() << "QSplitter::moveSplitter" << debugp << index << "adjusted" << pos << "oldP" << oldP;
1398#endif
1399
1400 QVarLengthArray<int, 32> poss(d->list.size());
1401 QVarLengthArray<int, 32> ws(d->list.size());
1402 bool upLeft;
1403
1404 d->doMove(backwards: false, hPos: pos, index, delta: +1, mayCollapse: (d->collapsible(s) && (pos > max)), positions: poss.data(), widths: ws.data());
1405 d->doMove(backwards: true, hPos: pos, index: index - 1, delta: +1, mayCollapse: (d->collapsible(index: index - 1) && (pos < min)), positions: poss.data(), widths: ws.data());
1406 upLeft = (pos < oldP);
1407
1408 int wid, delta, count = d->list.size();
1409 if (upLeft) {
1410 wid = 0;
1411 delta = 1;
1412 } else {
1413 wid = count - 1;
1414 delta = -1;
1415 }
1416 for (; wid >= 0 && wid < count; wid += delta) {
1417 QSplitterLayoutStruct *sls = d->list.at( i: wid );
1418 if (!sls->widget->isHidden())
1419 d->setGeo(sls, p: poss[wid], s: ws[wid], allowCollapse: true);
1420 }
1421 d->storeSizes();
1422
1423 emit splitterMoved(pos, index);
1424}
1425
1426
1427/*!
1428 Returns the valid range of the splitter at \a index in
1429 *\a{min} and *\a{max} if \a min and \a max are not 0.
1430*/
1431
1432void QSplitter::getRange(int index, int *min, int *max) const
1433{
1434 Q_D(const QSplitter);
1435 d->getRange(index, farMin: min, min: nullptr, max: nullptr, farMax: max);
1436}
1437
1438
1439/*!
1440 Returns the closest legal position to \a pos of the widget at \a index.
1441
1442 For right-to-left languages such as Arabic and Hebrew, the layout
1443 of horizontal splitters is reversed. Positions are then measured
1444 from the right edge of the widget.
1445
1446 \sa getRange()
1447*/
1448
1449int QSplitter::closestLegalPosition(int pos, int index)
1450{
1451 Q_D(QSplitter);
1452 int x = 0;
1453 int i = 0;
1454 int n = 0;
1455 int u = 0;
1456 return d->adjustPos(pos, index, farMin: &u, min: &n, max: &i, farMax: &x);
1457}
1458
1459/*!
1460 \property QSplitter::opaqueResize
1461 Returns \c true if widgets are resized dynamically (opaquely) while interactively moving the
1462 splitter. Otherwise returns \c false.
1463
1464 The default resize behavior is style dependent (determined by the
1465 SH_Splitter_OpaqueResize style hint). However, you can override it
1466 by calling setOpaqueResize()
1467
1468 \sa QStyle::StyleHint
1469*/
1470
1471bool QSplitter::opaqueResize() const
1472{
1473 Q_D(const QSplitter);
1474 return d->opaqueResizeSet ? d->opaque : style()->styleHint(stylehint: QStyle::SH_Splitter_OpaqueResize, opt: nullptr, widget: this);
1475}
1476
1477
1478void QSplitter::setOpaqueResize(bool on)
1479{
1480 Q_D(QSplitter);
1481 d->opaqueResizeSet = true;
1482 d->opaque = on;
1483}
1484
1485
1486/*!
1487 \reimp
1488*/
1489QSize QSplitter::sizeHint() const
1490{
1491 Q_D(const QSplitter);
1492 ensurePolished();
1493 int l = 0;
1494 int t = 0;
1495 for (int i = 0; i < d->list.size(); ++i) {
1496 QWidget *w = d->list.at(i)->widget;
1497 if (w->isHidden())
1498 continue;
1499 QSize s = w->sizeHint();
1500 if (s.isValid()) {
1501 l += d->pick(s);
1502 t = qMax(a: t, b: d->trans(s));
1503 }
1504 }
1505 return orientation() == Qt::Horizontal ? QSize(l, t) : QSize(t, l);
1506}
1507
1508
1509/*!
1510 \reimp
1511*/
1512
1513QSize QSplitter::minimumSizeHint() const
1514{
1515 Q_D(const QSplitter);
1516 ensurePolished();
1517 int l = 0;
1518 int t = 0;
1519
1520 for (int i = 0; i < d->list.size(); ++i) {
1521 QSplitterLayoutStruct *s = d->list.at(i);
1522 if (!s || !s->widget)
1523 continue;
1524 if (s->widget->isHidden())
1525 continue;
1526 QSize widgetSize = qSmartMinSize(w: s->widget);
1527 if (widgetSize.isValid()) {
1528 l += d->pick(s: widgetSize);
1529 t = qMax(a: t, b: d->trans(s: widgetSize));
1530 }
1531 if (!s->handle || s->handle->isHidden())
1532 continue;
1533 QSize splitterSize = s->handle->sizeHint();
1534 if (splitterSize.isValid()) {
1535 l += d->pick(s: splitterSize);
1536 t = qMax(a: t, b: d->trans(s: splitterSize));
1537 }
1538 }
1539 return orientation() == Qt::Horizontal ? QSize(l, t) : QSize(t, l);
1540}
1541
1542
1543/*!
1544 Returns a list of the size parameters of all the widgets in this splitter.
1545
1546 If the splitter's orientation is horizontal, the list contains the
1547 widgets width in pixels, from left to right; if the orientation is
1548 vertical, the list contains the widgets' heights in pixels,
1549 from top to bottom.
1550
1551 Giving the values to another splitter's setSizes() function will
1552 produce a splitter with the same layout as this one.
1553
1554 Note that invisible widgets have a size of 0.
1555
1556 \sa setSizes()
1557*/
1558
1559QList<int> QSplitter::sizes() const
1560{
1561 Q_D(const QSplitter);
1562 ensurePolished();
1563
1564 const int numSizes = d->list.size();
1565 QList<int> list;
1566 list.reserve(size: numSizes);
1567
1568 for (int i = 0; i < numSizes; ++i) {
1569 QSplitterLayoutStruct *s = d->list.at(i);
1570 list.append(t: d->pick(s: s->rect.size()));
1571 }
1572 return list;
1573}
1574
1575/*!
1576 Sets the child widgets' respective sizes to the values given in the \a list.
1577
1578 If the splitter is horizontal, the values set the width of each
1579 widget in pixels, from left to right. If the splitter is vertical, the
1580 height of each widget is set, from top to bottom.
1581
1582 Extra values in the \a list are ignored. If \a list contains too few
1583 values, the result is undefined, but the program will still be well-behaved.
1584
1585 The overall size of the splitter widget is not affected.
1586 Instead, any additional/missing space is distributed amongst the
1587 widgets according to the relative weight of the sizes.
1588
1589 If you specify a size of 0, the widget will be invisible. The size policies
1590 of the widgets are preserved. That is, a value smaller than the minimal size
1591 hint of the respective widget will be replaced by the value of the hint.
1592
1593 \sa sizes()
1594*/
1595
1596void QSplitter::setSizes(const QList<int> &list)
1597{
1598 Q_D(QSplitter);
1599 d->setSizes_helper(sizes: list, clampNegativeSize: true);
1600}
1601
1602/*!
1603 \property QSplitter::handleWidth
1604 \brief the width of the splitter handles
1605
1606 By default, this property contains a value that depends on the user's platform
1607 and style preferences.
1608
1609 If you set handleWidth to 1 or 0, the actual grab area will grow to overlap a
1610 few pixels of its respective widgets.
1611*/
1612
1613int QSplitter::handleWidth() const
1614{
1615 Q_D(const QSplitter);
1616 if (d->handleWidth >= 0) {
1617 return d->handleWidth;
1618 } else {
1619 return style()->pixelMetric(metric: QStyle::PM_SplitterWidth, option: nullptr, widget: this);
1620 }
1621}
1622
1623void QSplitter::setHandleWidth(int width)
1624{
1625 Q_D(QSplitter);
1626 d->handleWidth = width;
1627 d->updateHandles();
1628}
1629
1630/*!
1631 \reimp
1632*/
1633void QSplitter::changeEvent(QEvent *ev)
1634{
1635 Q_D(QSplitter);
1636 if (ev->type() == QEvent::StyleChange)
1637 d->updateHandles();
1638 QFrame::changeEvent(ev);
1639}
1640
1641static const qint32 SplitterMagic = 0xff;
1642
1643/*!
1644 Saves the state of the splitter's layout.
1645
1646 Typically this is used in conjunction with QSettings to remember the size
1647 for a future session. A version number is stored as part of the data.
1648 Here is an example:
1649
1650 \snippet splitter/splitter.cpp 1
1651
1652 \sa restoreState()
1653*/
1654QByteArray QSplitter::saveState() const
1655{
1656 Q_D(const QSplitter);
1657 int version = 1;
1658 QByteArray data;
1659 QDataStream stream(&data, QIODevice::WriteOnly);
1660 stream.setVersion(QDataStream::Qt_5_0);
1661
1662 stream << qint32(SplitterMagic);
1663 stream << qint32(version);
1664 const int numSizes = d->list.size();
1665 QList<int> list;
1666 list.reserve(size: numSizes);
1667 for (int i = 0; i < numSizes; ++i) {
1668 QSplitterLayoutStruct *s = d->list.at(i);
1669 list.append(t: s->sizer);
1670 }
1671 stream << list;
1672 stream << childrenCollapsible();
1673 stream << qint32(d->handleWidth);
1674 stream << opaqueResize();
1675 stream << qint32(orientation());
1676 stream << d->opaqueResizeSet;
1677 return data;
1678}
1679
1680/*!
1681 Restores the splitter's layout to the \a state specified.
1682 Returns \c true if the state is restored; otherwise returns \c false.
1683
1684 Typically this is used in conjunction with QSettings to restore the size
1685 from a past session. Here is an example:
1686
1687 Restore the splitter's state:
1688
1689 \snippet splitter/splitter.cpp 2
1690
1691 A failure to restore the splitter's layout may result from either
1692 invalid or out-of-date data in the supplied byte array.
1693
1694 \sa saveState()
1695*/
1696bool QSplitter::restoreState(const QByteArray &state)
1697{
1698 Q_D(QSplitter);
1699 int version = 1;
1700 QByteArray sd = state;
1701 QDataStream stream(&sd, QIODevice::ReadOnly);
1702 stream.setVersion(QDataStream::Qt_5_0);
1703 QList<int> list;
1704 bool b;
1705 qint32 i;
1706 qint32 marker;
1707 qint32 v;
1708
1709 stream >> marker;
1710 stream >> v;
1711 if (marker != SplitterMagic || v > version)
1712 return false;
1713
1714 stream >> list;
1715 d->setSizes_helper(sizes: list, clampNegativeSize: false);
1716
1717 stream >> b;
1718 setChildrenCollapsible(b);
1719
1720 stream >> i;
1721 setHandleWidth(i);
1722
1723 stream >> b;
1724 setOpaqueResize(b);
1725
1726 stream >> i;
1727 setOrientation(Qt::Orientation(i));
1728 d->doResize();
1729
1730 if (v >= 1)
1731 stream >> d->opaqueResizeSet;
1732
1733 return true;
1734}
1735
1736/*!
1737 Updates the size policy of the widget at position \a index to
1738 have a stretch factor of \a stretch.
1739
1740 \a stretch is not the effective stretch factor; the effective
1741 stretch factor is calculated by taking the initial size of the
1742 widget and multiplying it with \a stretch.
1743
1744 This function is provided for convenience. It is equivalent to
1745
1746 \snippet code/src_gui_widgets_qsplitter.cpp 0
1747
1748 \sa setSizes(), widget()
1749*/
1750void QSplitter::setStretchFactor(int index, int stretch)
1751{
1752 Q_D(QSplitter);
1753 if (index <= -1 || index >= d->list.size())
1754 return;
1755
1756 QWidget *widget = d->list.at(i: index)->widget;
1757 QSizePolicy sp = widget->sizePolicy();
1758 sp.setHorizontalStretch(stretch);
1759 sp.setVerticalStretch(stretch);
1760 widget->setSizePolicy(sp);
1761}
1762
1763QT_END_NAMESPACE
1764
1765#include "moc_qsplitter.cpp"
1766

source code of qtbase/src/widgets/widgets/qsplitter.cpp