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

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