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/*!
5 \class QMdiArea
6 \brief The QMdiArea widget provides an area in which MDI windows are displayed.
7 \since 4.3
8 \ingroup mainwindow-classes
9 \inmodule QtWidgets
10
11 QMdiArea functions, essentially, like a window manager for MDI
12 windows. For instance, it draws the windows it manages on itself
13 and arranges them in a cascading or tile pattern. QMdiArea is
14 commonly used as the center widget in a QMainWindow to create MDI
15 applications, but can also be placed in any layout. The following
16 code adds an area to a main window:
17
18 \snippet mdiarea/mdiareasnippets.cpp 0
19
20 Unlike the window managers for top-level windows, all window flags
21 (Qt::WindowFlags) are supported by QMdiArea as long as the flags
22 are supported by the current widget style.
23
24 Subwindows in QMdiArea are instances of QMdiSubWindow. They
25 are added to an MDI area with addSubWindow(). It is common to pass
26 a QWidget, which is set as the internal widget, to this function,
27 but it is also possible to pass a QMdiSubWindow directly. The class
28 inherits QWidget, and you can use the same API as with a normal
29 top-level window when programming. QMdiSubWindow also has behavior
30 that is specific to MDI windows. See the QMdiSubWindow class
31 description for more details.
32
33 A subwindow becomes active when it gets the keyboard focus, or
34 when setFocus() is called. The user activates a window by moving
35 focus in the usual ways. The MDI area emits the
36 subWindowActivated() signal when the active window changes, and
37 the activeSubWindow() function returns the active subwindow.
38
39 The convenience function subWindowList() returns a list of all
40 subwindows. This information could be used in a popup menu
41 containing a list of windows, for example.
42
43 The subwindows are sorted by the current
44 \l{QMdiArea::}{WindowOrder}. This is used for the subWindowList()
45 and for activateNextSubWindow() and activatePreviousSubWindow().
46 Also, it is used when cascading or tiling the windows with
47 cascadeSubWindows() and tileSubWindows().
48
49 QMdiArea provides two built-in layout strategies for
50 subwindows: cascadeSubWindows() and tileSubWindows(). Both are
51 slots and are easily connected to menu entries.
52
53 \table
54 \row \li \inlineimage mdi-cascade.png
55 \li \inlineimage mdi-tile.png
56 \endtable
57
58 \note The default scroll bar property for QMdiArea is Qt::ScrollBarAlwaysOff.
59
60 \sa QMdiSubWindow
61*/
62
63/*!
64 \fn void QMdiArea::subWindowActivated(QMdiSubWindow *window)
65
66 QMdiArea emits this signal after \a window has been activated. When \a
67 window is \nullptr, QMdiArea has just deactivated its last active window,
68 and there are no active windows on the workspace.
69
70 \sa QMdiArea::activeSubWindow()
71*/
72
73/*!
74 \enum QMdiArea::AreaOption
75
76 This enum describes options that customize the behavior of the
77 QMdiArea.
78
79 \value DontMaximizeSubWindowOnActivation When the active subwindow
80 is maximized, the default behavior is to maximize the next
81 subwindow that is activated. Set this option if you do not want
82 this behavior.
83*/
84
85/*!
86 \enum QMdiArea::WindowOrder
87
88 Specifies the criteria to use for ordering the list of child windows
89 returned by subWindowList(). The functions cascadeSubWindows() and
90 tileSubWindows() follow this order when arranging the windows.
91
92 \value CreationOrder The windows are returned in the order of
93 their creation.
94
95 \value StackingOrder The windows are returned in the order in
96 which they are stacked, with the top-most window being last in
97 the list.
98
99 \value ActivationHistoryOrder The windows are returned in the order in
100 which they were activated.
101
102 \sa subWindowList()
103*/
104
105/*!
106 \enum QMdiArea::ViewMode
107 \since 4.4
108
109 This enum describes the view mode of the area; i.e. how sub-windows
110 will be displayed.
111
112 \value SubWindowView Display sub-windows with window frames (default).
113 \value TabbedView Display sub-windows with tabs in a tab bar.
114
115 \sa setViewMode()
116*/
117
118#include "qmdiarea_p.h"
119
120#include <QApplication>
121#include <QStyle>
122#include <QChildEvent>
123#include <QResizeEvent>
124#include <QScrollBar>
125#include <QtAlgorithms>
126#include <QPainter>
127#include <QFontMetrics>
128#include <QStyleOption>
129#include <QDebug>
130#include <qmath.h>
131#if QT_CONFIG(menu)
132#include <qmenu.h>
133#endif
134#include <private/qlayoutengine_p.h>
135
136#include <algorithm>
137
138QT_BEGIN_NAMESPACE
139
140using namespace Qt::StringLiterals;
141using namespace QMdi;
142
143// Asserts in debug mode, gives warning otherwise.
144static bool sanityCheck(const QMdiSubWindow * const child, const char *where)
145{
146 if (Q_UNLIKELY(!child)) {
147 const char error[] = "null pointer";
148 Q_ASSERT_X(false, where, error);
149 qWarning(msg: "%s:%s", where, error);
150 return false;
151 }
152 return true;
153}
154
155static bool sanityCheck(const QList<QWidget *> &widgets, const int index, const char *where)
156{
157 if (Q_UNLIKELY(index < 0 || index >= widgets.size())) {
158 const char error[] = "index out of range";
159 Q_ASSERT_X(false, where, error);
160 qWarning(msg: "%s:%s", where, error);
161 return false;
162 }
163 if (Q_UNLIKELY(!widgets.at(index))) {
164 const char error[] = "null pointer";
165 Q_ASSERT_X(false, where, error);
166 qWarning(msg: "%s:%s", where, error);
167 return false;
168 }
169 return true;
170}
171
172static void setIndex(int *index, int candidate, int min, int max, bool isIncreasing)
173{
174 if (!index)
175 return;
176
177 if (isIncreasing) {
178 if (candidate > max)
179 *index = min;
180 else
181 *index = qMax(a: candidate, b: min);
182 } else {
183 if (candidate < min)
184 *index = max;
185 else
186 *index = qMin(a: candidate, b: max);
187 }
188 Q_ASSERT(*index >= min && *index <= max);
189}
190
191static inline bool useScrollBar(const QRect &childrenRect, const QSize &maxViewportSize,
192 Qt::Orientation orientation)
193{
194 if (orientation == Qt::Horizontal)
195 return childrenRect.width() > maxViewportSize.width()
196 || childrenRect.left() < 0
197 || childrenRect.right() >= maxViewportSize.width();
198 else
199 return childrenRect.height() > maxViewportSize.height()
200 || childrenRect.top() < 0
201 || childrenRect.bottom() >= maxViewportSize.height();
202}
203
204// Returns the closest mdi area containing the widget (if any).
205static inline QMdiArea *mdiAreaParent(QWidget *widget)
206{
207 if (!widget)
208 return nullptr;
209
210 QWidget *parent = widget->parentWidget();
211 while (parent) {
212 if (QMdiArea *area = qobject_cast<QMdiArea *>(object: parent))
213 return area;
214 parent = parent->parentWidget();
215 }
216 return nullptr;
217}
218
219#if QT_CONFIG(tabwidget)
220QTabBar::Shape _q_tb_tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position);
221#endif // QT_CONFIG(tabwidget)
222
223static inline QString tabTextFor(QMdiSubWindow *subWindow)
224{
225 if (!subWindow)
226 return QString();
227
228 QString title = subWindow->windowTitle();
229 if (subWindow->isWindowModified()) {
230 title.replace(before: "[*]"_L1, after: "*"_L1);
231 } else {
232 extern QString qt_setWindowTitle_helperHelper(const QString&, const QWidget*);
233 title = qt_setWindowTitle_helperHelper(title, subWindow);
234 }
235
236 return title.isEmpty() ? QMdiArea::tr(s: "(Untitled)") : title;
237}
238
239/*!
240 \internal
241*/
242void RegularTiler::rearrange(QList<QWidget *> &widgets, const QRect &domain) const
243{
244 if (widgets.isEmpty())
245 return;
246
247 const int n = widgets.size();
248 const int ncols = qMax(a: qCeil(v: qSqrt(v: qreal(n))), b: 1);
249 const int nrows = qMax(a: (n % ncols) ? (n / ncols + 1) : (n / ncols), b: 1);
250 const int nspecial = (n % ncols) ? (ncols - n % ncols) : 0;
251 const int dx = domain.width() / ncols;
252 const int dy = domain.height() / nrows;
253
254 int i = 0;
255 for (int row = 0; row < nrows; ++row) {
256 const int y1 = int(row * (dy + 1));
257 for (int col = 0; col < ncols; ++col) {
258 if (row == 1 && col < nspecial)
259 continue;
260 const int x1 = int(col * (dx + 1));
261 int x2 = int(x1 + dx);
262 int y2 = int(y1 + dy);
263 if (row == 0 && col < nspecial) {
264 y2 *= 2;
265 if (nrows != 2)
266 y2 += 1;
267 else
268 y2 = domain.bottom();
269 }
270 if (col == ncols - 1 && x2 != domain.right())
271 x2 = domain.right();
272 if (row == nrows - 1 && y2 != domain.bottom())
273 y2 = domain.bottom();
274 if (!sanityCheck(widgets, index: i, where: "RegularTiler"))
275 continue;
276 QWidget *widget = widgets.at(i: i++);
277 QRect newGeometry = QRect(QPoint(x1, y1), QPoint(x2, y2));
278 widget->setGeometry(QStyle::visualRect(direction: widget->layoutDirection(), boundingRect: domain, logicalRect: newGeometry));
279 }
280 }
281}
282
283/*!
284 \internal
285*/
286void SimpleCascader::rearrange(QList<QWidget *> &widgets, const QRect &domain) const
287{
288 if (widgets.isEmpty())
289 return;
290
291 // Tunables:
292 const int topOffset = 0;
293 const int bottomOffset = 50;
294 const int leftOffset = 0;
295 const int rightOffset = 100;
296 const int dx = 10;
297
298 QStyleOptionTitleBar options;
299 options.initFrom(w: widgets.at(i: 0));
300 int titleBarHeight = widgets.at(i: 0)->style()->pixelMetric(metric: QStyle::PM_TitleBarHeight, option: &options, widget: widgets.at(i: 0));
301 const QFontMetrics fontMetrics = QFontMetrics(QApplication::font(className: "QMdiSubWindowTitleBar"));
302 const int dy = qMax(a: titleBarHeight - (titleBarHeight - fontMetrics.height()) / 2, b: 1)
303 + widgets.at(i: 0)->style()->pixelMetric(metric: QStyle::PM_FocusFrameVMargin, option: nullptr, widget: widgets.at(i: 0));
304
305 const int n = widgets.size();
306 const int nrows = qMax(a: (domain.height() - (topOffset + bottomOffset)) / dy, b: 1);
307 const int ncols = qMax(a: n / nrows + ((n % nrows) ? 1 : 0), b: 1);
308 const int dcol = (domain.width() - (leftOffset + rightOffset)) / ncols;
309
310 int i = 0;
311 for (int row = 0; row < nrows; ++row) {
312 for (int col = 0; col < ncols; ++col) {
313 const int x = leftOffset + row * dx + col * dcol;
314 const int y = topOffset + row * dy;
315 if (!sanityCheck(widgets, index: i, where: "SimpleCascader"))
316 continue;
317 QWidget *widget = widgets.at(i: i++);
318 QRect newGeometry = QRect(QPoint(x, y), widget->sizeHint());
319 widget->setGeometry(QStyle::visualRect(direction: widget->layoutDirection(), boundingRect: domain, logicalRect: newGeometry));
320 if (i == n)
321 return;
322 }
323 }
324}
325
326/*!
327 \internal
328*/
329void IconTiler::rearrange(QList<QWidget *> &widgets, const QRect &domain) const
330{
331 if (widgets.isEmpty() || !sanityCheck(widgets, index: 0, where: "IconTiler"))
332 return;
333
334 const int n = widgets.size();
335 const int width = qMax(a: widgets.at(i: 0)->width(), b: 1);
336 const int height = widgets.at(i: 0)->height();
337 const int ncols = qMax(a: domain.width() / width, b: 1);
338 const int nrows = n / ncols + ((n % ncols) ? 1 : 0);
339
340 int i = 0;
341 for (int row = 0; row < nrows; ++row) {
342 for (int col = 0; col < ncols; ++col) {
343 const int x = col * width;
344 const int y = domain.height() - height - row * height;
345 if (!sanityCheck(widgets, index: i, where: "IconTiler"))
346 continue;
347 QWidget *widget = widgets.at(i: i++);
348 QPoint newPos(x, y);
349 QRect newGeometry = QRect(newPos.x(), newPos.y(), widget->width(), widget->height());
350 widget->setGeometry(QStyle::visualRect(direction: widget->layoutDirection(), boundingRect: domain, logicalRect: newGeometry));
351 if (i == n)
352 return;
353 }
354 }
355}
356
357/*!
358 \internal
359 Calculates the accumulated overlap (intersection area) between 'source' and 'rects'.
360*/
361int MinOverlapPlacer::accumulatedOverlap(const QRect &source, const QList<QRect> &rects)
362{
363 int accOverlap = 0;
364 for (const QRect &rect : rects) {
365 QRect intersection = source.intersected(other: rect);
366 accOverlap += intersection.width() * intersection.height();
367 }
368 return accOverlap;
369}
370
371
372/*!
373 \internal
374 Finds among 'source' the rectangle with the minimum accumulated overlap with the
375 rectangles in 'rects'.
376*/
377QRect MinOverlapPlacer::findMinOverlapRect(const QList<QRect> &source, const QList<QRect> &rects)
378{
379 int minAccOverlap = -1;
380 QRect minAccOverlapRect;
381 for (const QRect &srcRect : source) {
382 const int accOverlap = accumulatedOverlap(source: srcRect, rects);
383 if (accOverlap < minAccOverlap || minAccOverlap == -1) {
384 minAccOverlap = accOverlap;
385 minAccOverlapRect = srcRect;
386 }
387 }
388 return minAccOverlapRect;
389}
390
391/*!
392 \internal
393 Gets candidates for the final placement.
394*/
395QList<QRect> MinOverlapPlacer::getCandidatePlacements(const QSize &size, const QList<QRect> &rects,
396 const QRect &domain)
397{
398 QList<QRect> result;
399
400 QList<int> xlist;
401 xlist.reserve(size: 2 + rects.size());
402 xlist << domain.left() << domain.right() - size.width() + 1;
403
404 QList<int> ylist;
405 ylist.reserve(size: 2 + rects.size());
406 ylist << domain.top();
407 if (domain.bottom() - size.height() + 1 >= 0)
408 ylist << domain.bottom() - size.height() + 1;
409
410 for (const QRect &rect : rects) {
411 xlist << rect.right() + 1;
412 ylist << rect.bottom() + 1;
413 }
414
415 std::sort(first: xlist.begin(), last: xlist.end());
416 xlist.erase(begin: std::unique(first: xlist.begin(), last: xlist.end()), end: xlist.end());
417
418 std::sort(first: ylist.begin(), last: ylist.end());
419 ylist.erase(begin: std::unique(first: ylist.begin(), last: ylist.end()), end: ylist.end());
420
421 result.reserve(size: ylist.size() * xlist.size());
422 for (int y : std::as_const(t&: ylist))
423 for (int x : std::as_const(t&: xlist))
424 result << QRect(QPoint(x, y), size);
425 return result;
426}
427
428/*!
429 \internal
430 Finds all rectangles in 'source' not completely inside 'domain'. The result is stored
431 in 'result' and also removed from 'source'.
432*/
433QList<QRect> MinOverlapPlacer::findNonInsiders(const QRect &domain, QList<QRect> &source)
434{
435 const auto containedInDomain =
436 [domain](const QRect &srcRect) { return domain.contains(r: srcRect); };
437
438 const auto firstOut = std::stable_partition(first: source.begin(), last: source.end(), pred: containedInDomain);
439
440 QList<QRect> result;
441 result.reserve(size: source.end() - firstOut);
442 std::copy(firstOut, source.end(), std::back_inserter(x&: result));
443
444 source.erase(begin: firstOut, end: source.end());
445
446 return result;
447}
448
449/*!
450 \internal
451 Finds all rectangles in 'source' that overlaps 'domain' by the maximum overlap area
452 between 'domain' and any rectangle in 'source'. The result is stored in 'result'.
453*/
454QList<QRect> MinOverlapPlacer::findMaxOverlappers(const QRect &domain, const QList<QRect> &source)
455{
456 QList<QRect> result;
457 result.reserve(size: source.size());
458
459 int maxOverlap = -1;
460 for (const QRect &srcRect : source) {
461 QRect intersection = domain.intersected(other: srcRect);
462 const int overlap = intersection.width() * intersection.height();
463 if (overlap >= maxOverlap || maxOverlap == -1) {
464 if (overlap > maxOverlap) {
465 maxOverlap = overlap;
466 result.clear();
467 }
468 result << srcRect;
469 }
470 }
471
472 return result;
473}
474
475/*!
476 \internal
477 Finds among the rectangles in 'source' the best placement. Here, 'best' means the
478 placement that overlaps the rectangles in 'rects' as little as possible while at the
479 same time being as much as possible inside 'domain'.
480*/
481QPoint MinOverlapPlacer::findBestPlacement(const QRect &domain, const QList<QRect> &rects,
482 QList<QRect> &source)
483{
484 const QList<QRect> nonInsiders = findNonInsiders(domain, source);
485
486 if (!source.empty())
487 return findMinOverlapRect(source, rects).topLeft();
488
489 QList<QRect> maxOverlappers = findMaxOverlappers(domain, source: nonInsiders);
490 return findMinOverlapRect(source: maxOverlappers, rects).topLeft();
491}
492
493
494/*!
495 \internal
496 Places the rectangle defined by 'size' relative to 'rects' and 'domain' so that it
497 overlaps 'rects' as little as possible and 'domain' as much as possible.
498 Returns the position of the resulting rectangle.
499*/
500QPoint MinOverlapPlacer::place(const QSize &size, const QList<QRect> &rects,
501 const QRect &domain) const
502{
503 if (size.isEmpty() || !domain.isValid())
504 return QPoint();
505 for (const QRect &rect : rects) {
506 if (!rect.isValid())
507 return QPoint();
508 }
509
510 QList<QRect> candidates = getCandidatePlacements(size, rects, domain);
511 return findBestPlacement(domain, rects, source&: candidates);
512}
513
514#if QT_CONFIG(tabbar)
515class QMdiAreaTabBar : public QTabBar
516{
517public:
518 QMdiAreaTabBar(QWidget *parent) : QTabBar(parent) {}
519
520protected:
521 void mousePressEvent(QMouseEvent *event) override;
522#ifndef QT_NO_CONTEXTMENU
523 void contextMenuEvent(QContextMenuEvent *event) override;
524#endif
525
526private:
527 QMdiSubWindow *subWindowFromIndex(int index) const;
528};
529
530/*!
531 \internal
532*/
533void QMdiAreaTabBar::mousePressEvent(QMouseEvent *event)
534{
535 if (event->button() != Qt::MiddleButton) {
536 QTabBar::mousePressEvent(event);
537 return;
538 }
539
540 QMdiSubWindow *subWindow = subWindowFromIndex(index: tabAt(pos: event->position().toPoint()));
541 if (!subWindow) {
542 event->ignore();
543 return;
544 }
545
546 subWindow->close();
547}
548
549#ifndef QT_NO_CONTEXTMENU
550/*!
551 \internal
552*/
553void QMdiAreaTabBar::contextMenuEvent(QContextMenuEvent *event)
554{
555 QPointer<QMdiSubWindow> subWindow = subWindowFromIndex(index: tabAt(pos: event->pos()));
556 if (!subWindow || subWindow->isHidden()) {
557 event->ignore();
558 return;
559 }
560
561#if QT_CONFIG(menu)
562 QMdiSubWindowPrivate *subWindowPrivate = subWindow->d_func();
563 if (!subWindowPrivate->systemMenu) {
564 event->ignore();
565 return;
566 }
567
568 QMdiSubWindow *currentSubWindow = subWindowFromIndex(index: currentIndex());
569 Q_ASSERT(currentSubWindow);
570
571 // We don't want these actions to show up in the system menu when the
572 // current sub-window is maximized, i.e. covers the entire viewport.
573 if (currentSubWindow->isMaximized()) {
574 subWindowPrivate->setVisible(QMdiSubWindowPrivate::MoveAction, visible: false);
575 subWindowPrivate->setVisible(QMdiSubWindowPrivate::ResizeAction, visible: false);
576 subWindowPrivate->setVisible(QMdiSubWindowPrivate::MinimizeAction, visible: false);
577 subWindowPrivate->setVisible(QMdiSubWindowPrivate::MaximizeAction, visible: false);
578 subWindowPrivate->setVisible(QMdiSubWindowPrivate::RestoreAction, visible: false);
579 subWindowPrivate->setVisible(QMdiSubWindowPrivate::StayOnTopAction, visible: false);
580 }
581
582 // Show system menu.
583 subWindowPrivate->systemMenu->exec(pos: event->globalPos());
584 if (!subWindow)
585 return;
586
587 // Restore action visibility.
588 subWindowPrivate->updateActions();
589#endif // QT_CONFIG(menu)
590}
591#endif // QT_NO_CONTEXTMENU
592
593/*!
594 \internal
595*/
596QMdiSubWindow *QMdiAreaTabBar::subWindowFromIndex(int index) const
597{
598 if (index < 0 || index >= count())
599 return nullptr;
600
601 QMdiArea *mdiArea = qobject_cast<QMdiArea *>(object: parentWidget());
602 Q_ASSERT(mdiArea);
603
604 const QList<QMdiSubWindow *> subWindows = mdiArea->subWindowList();
605 Q_ASSERT(index < subWindows.size());
606
607 QMdiSubWindow *subWindow = mdiArea->subWindowList().at(i: index);
608 Q_ASSERT(subWindow);
609
610 return subWindow;
611}
612#endif // QT_CONFIG(tabbar)
613
614/*!
615 \internal
616*/
617QMdiAreaPrivate::QMdiAreaPrivate()
618 : cascader(nullptr),
619 regularTiler(nullptr),
620 iconTiler(nullptr),
621 placer(nullptr),
622#if QT_CONFIG(rubberband)
623 rubberBand(nullptr),
624#endif
625#if QT_CONFIG(tabbar)
626 tabBar(nullptr),
627#endif
628 activationOrder(QMdiArea::CreationOrder),
629 viewMode(QMdiArea::SubWindowView),
630#if QT_CONFIG(tabbar)
631 documentMode(false),
632 tabsClosable(false),
633 tabsMovable(false),
634#endif
635#if QT_CONFIG(tabwidget)
636 tabShape(QTabWidget::Rounded),
637 tabPosition(QTabWidget::North),
638#endif
639 ignoreGeometryChange(false),
640 ignoreWindowStateChange(false),
641 isActivated(false),
642 isSubWindowsTiled(false),
643 showActiveWindowMaximized(false),
644 tileCalledFromResizeEvent(false),
645 updatesDisabledByUs(false),
646 inViewModeChange(false),
647 indexToNextWindow(-1),
648 indexToPreviousWindow(-1),
649 indexToHighlighted(-1),
650 indexToLastActiveTab(-1)
651{
652}
653
654/*!
655 \internal
656*/
657void QMdiAreaPrivate::_q_deactivateAllWindows(QMdiSubWindow *aboutToActivate)
658{
659 if (ignoreWindowStateChange)
660 return;
661
662 Q_Q(QMdiArea);
663 if (!aboutToActivate)
664 aboutToBecomeActive = qobject_cast<QMdiSubWindow *>(object: q->sender());
665 else
666 aboutToBecomeActive = aboutToActivate;
667 Q_ASSERT(aboutToBecomeActive);
668
669 // Take a copy because child->showNormal() could indirectly call
670 // QCoreApplication::sendEvent(), which could call unknown code that e.g.
671 // recurses into the class modifying childWindows.
672 const auto subWindows = childWindows;
673 for (QMdiSubWindow *child : subWindows) {
674 if (!sanityCheck(child, where: "QMdiArea::deactivateAllWindows") || aboutToBecomeActive == child)
675 continue;
676 // We don't want to handle signals caused by child->showNormal().
677 ignoreWindowStateChange = true;
678 if (!(options & QMdiArea::DontMaximizeSubWindowOnActivation) && !showActiveWindowMaximized)
679 showActiveWindowMaximized = child->isMaximized() && child->isVisible();
680 if (showActiveWindowMaximized && child->isMaximized()) {
681 if (q->updatesEnabled()) {
682 updatesDisabledByUs = true;
683 q->setUpdatesEnabled(false);
684 }
685 child->showNormal();
686 }
687 if (child->isMinimized() && !child->isShaded() && !windowStaysOnTop(subWindow: child))
688 child->lower();
689 ignoreWindowStateChange = false;
690 child->d_func()->setActive(activate: false);
691 }
692}
693
694/*!
695 \internal
696*/
697void QMdiAreaPrivate::_q_processWindowStateChanged(Qt::WindowStates oldState,
698 Qt::WindowStates newState)
699{
700 if (ignoreWindowStateChange)
701 return;
702
703 Q_Q(QMdiArea);
704 QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(object: q->sender());
705 if (!child)
706 return;
707
708 // windowActivated
709 if (!(oldState & Qt::WindowActive) && (newState & Qt::WindowActive))
710 emitWindowActivated(child);
711 // windowDeactivated
712 else if ((oldState & Qt::WindowActive) && !(newState & Qt::WindowActive))
713 resetActiveWindow(child);
714
715 // windowMinimized
716 if (!(oldState & Qt::WindowMinimized) && (newState & Qt::WindowMinimized)) {
717 isSubWindowsTiled = false;
718 arrangeMinimizedSubWindows();
719 // windowMaximized
720 } else if (!(oldState & Qt::WindowMaximized) && (newState & Qt::WindowMaximized)) {
721 internalRaise(child);
722 // windowRestored
723 } else if (!(newState & (Qt::WindowMaximized | Qt::WindowMinimized))) {
724 internalRaise(child);
725 if (oldState & Qt::WindowMinimized)
726 arrangeMinimizedSubWindows();
727 }
728}
729
730void QMdiAreaPrivate::_q_currentTabChanged(int index)
731{
732#if !QT_CONFIG(tabbar)
733 Q_UNUSED(index);
734#else
735 if (!tabBar || index < 0)
736 return;
737
738 // If the previous active sub-window was hidden, disable the tab.
739 if (indexToLastActiveTab >= 0 && indexToLastActiveTab < tabBar->count()
740 && indexToLastActiveTab < childWindows.size()) {
741 QMdiSubWindow *lastActive = childWindows.at(i: indexToLastActiveTab);
742 if (lastActive && lastActive->isHidden())
743 tabBar->setTabEnabled(index: indexToLastActiveTab, enabled: false);
744 }
745
746 indexToLastActiveTab = index;
747 Q_ASSERT(childWindows.size() > index);
748 QMdiSubWindow *subWindow = childWindows.at(i: index);
749 Q_ASSERT(subWindow);
750 activateWindow(child: subWindow);
751#endif // QT_CONFIG(tabbar)
752}
753
754void QMdiAreaPrivate::_q_closeTab(int index)
755{
756#if !QT_CONFIG(tabbar)
757 Q_UNUSED(index);
758#else
759 QMdiSubWindow *subWindow = childWindows.at(i: index);
760 Q_ASSERT(subWindow);
761 subWindow->close();
762#endif // QT_CONFIG(tabbar)
763}
764
765void QMdiAreaPrivate::_q_moveTab(int from, int to)
766{
767#if !QT_CONFIG(tabbar)
768 Q_UNUSED(from);
769 Q_UNUSED(to);
770#else
771 childWindows.move(from, to);
772
773 // Put the active window in front to update activation order.
774 const int indexToActiveWindow = childWindows.indexOf(t: active);
775 if (indexToActiveWindow != -1) {
776 const int index = indicesToActivatedChildren.indexOf(t: indexToActiveWindow);
777 Q_ASSERT(index != -1);
778 if (index != 0) { // if it's not in front
779 indicesToActivatedChildren.move(from: index, to: 0);
780 internalRaise(child: active);
781 }
782 }
783#endif // QT_CONFIG(tabbar)
784}
785
786/*!
787 \internal
788*/
789void QMdiAreaPrivate::appendChild(QMdiSubWindow *child)
790{
791 Q_Q(QMdiArea);
792 Q_ASSERT(child && childWindows.indexOf(child) == -1);
793
794 if (child->parent() != viewport)
795 child->setParent(parent: viewport, f: child->windowFlags());
796 childWindows.append(t: QPointer<QMdiSubWindow>(child));
797
798 if (!child->testAttribute(attribute: Qt::WA_Resized) && q->isVisible()) {
799 QSize newSize(child->sizeHint().boundedTo(otherSize: viewport->size()));
800 child->resize(newSize.expandedTo(otherSize: qSmartMinSize(w: child)));
801 }
802
803 if (!placer)
804 placer = new MinOverlapPlacer;
805 place(placer, child);
806
807 if (hbarpolicy != Qt::ScrollBarAlwaysOff)
808 child->setOption(option: QMdiSubWindow::AllowOutsideAreaHorizontally, on: true);
809 else
810 child->setOption(option: QMdiSubWindow::AllowOutsideAreaHorizontally, on: false);
811
812 if (vbarpolicy != Qt::ScrollBarAlwaysOff)
813 child->setOption(option: QMdiSubWindow::AllowOutsideAreaVertically, on: true);
814 else
815 child->setOption(option: QMdiSubWindow::AllowOutsideAreaVertically, on: false);
816
817 internalRaise(child);
818 indicesToActivatedChildren.prepend(t: childWindows.size() - 1);
819 Q_ASSERT(indicesToActivatedChildren.size() == childWindows.size());
820
821#if QT_CONFIG(tabbar)
822 if (tabBar) {
823 tabBar->addTab(icon: child->windowIcon(), text: tabTextFor(subWindow: child));
824 updateTabBarGeometry();
825 if (childWindows.size() == 1 && !(options & QMdiArea::DontMaximizeSubWindowOnActivation))
826 showActiveWindowMaximized = true;
827 }
828#endif
829
830 if (!(child->windowFlags() & Qt::SubWindow))
831 child->setWindowFlags(Qt::SubWindow);
832 child->installEventFilter(filterObj: q);
833
834 QObject::connect(sender: child, SIGNAL(aboutToActivate()), receiver: q, SLOT(_q_deactivateAllWindows()));
835 QObject::connect(sender: child, SIGNAL(windowStateChanged(Qt::WindowStates,Qt::WindowStates)),
836 receiver: q, SLOT(_q_processWindowStateChanged(Qt::WindowStates,Qt::WindowStates)));
837}
838
839/*!
840 \internal
841*/
842void QMdiAreaPrivate::place(Placer *placer, QMdiSubWindow *child)
843{
844 if (!placer || !child)
845 return;
846
847 Q_Q(QMdiArea);
848 if (!q->isVisible()) {
849 // The window is only laid out when it's added to QMdiArea,
850 // so there's no need to check that we don't have it in the
851 // list already. appendChild() ensures that.
852 pendingPlacements.append(t: child);
853 return;
854 }
855
856 QList<QRect> rects;
857 rects.reserve(size: childWindows.size());
858 QRect parentRect = q->rect();
859 for (QMdiSubWindow *window : std::as_const(t&: childWindows)) {
860 if (!sanityCheck(child: window, where: "QMdiArea::place") || window == child || !window->isVisibleTo(q)
861 || !window->testAttribute(attribute: Qt::WA_Moved)) {
862 continue;
863 }
864 QRect occupiedGeometry;
865 if (window->isMaximized()) {
866 occupiedGeometry = QRect(window->d_func()->oldGeometry.topLeft(),
867 window->d_func()->restoreSize);
868 } else {
869 occupiedGeometry = window->geometry();
870 }
871 rects.append(t: QStyle::visualRect(direction: child->layoutDirection(), boundingRect: parentRect, logicalRect: occupiedGeometry));
872 }
873 QPoint newPos = placer->place(size: child->size(), rects, domain: parentRect);
874 QRect newGeometry = QRect(newPos.x(), newPos.y(), child->width(), child->height());
875 child->setGeometry(QStyle::visualRect(direction: child->layoutDirection(), boundingRect: parentRect, logicalRect: newGeometry));
876}
877
878/*!
879 \internal
880*/
881void QMdiAreaPrivate::rearrange(Rearranger *rearranger)
882{
883 if (!rearranger)
884 return;
885
886 Q_Q(QMdiArea);
887 if (!q->isVisible()) {
888 // Compress if we already have the rearranger in the list.
889 int index = pendingRearrangements.indexOf(t: rearranger);
890 if (index != -1)
891 pendingRearrangements.move(from: index, to: pendingRearrangements.size() - 1);
892 else
893 pendingRearrangements.append(t: rearranger);
894 return;
895 }
896
897 QList<QWidget *> widgets;
898 const bool reverseList = rearranger->type() == Rearranger::RegularTiler;
899 const QList<QMdiSubWindow *> subWindows = subWindowList(activationOrder, reversed: reverseList);
900 QSize minSubWindowSize;
901 for (QMdiSubWindow *child : subWindows) {
902 if (!sanityCheck(child, where: "QMdiArea::rearrange") || !child->isVisible())
903 continue;
904 if (rearranger->type() == Rearranger::IconTiler) {
905 if (child->isMinimized() && !child->isShaded())
906 widgets.append(t: child);
907 } else {
908 if (child->isMinimized() && !child->isShaded())
909 continue;
910 if (child->isMaximized() || child->isShaded())
911 child->showNormal();
912 minSubWindowSize = minSubWindowSize.expandedTo(otherSize: child->minimumSize())
913 .expandedTo(otherSize: child->d_func()->internalMinimumSize);
914 widgets.append(t: child);
915 }
916 }
917
918 QRect domain = viewport->rect();
919 if (rearranger->type() == Rearranger::RegularTiler && !widgets.isEmpty())
920 domain = resizeToMinimumTileSize(minSubWindowSize, subWindowCount: widgets.size());
921
922 rearranger->rearrange(widgets, domain);
923
924 if (rearranger->type() == Rearranger::RegularTiler && !widgets.isEmpty()) {
925 isSubWindowsTiled = true;
926 updateScrollBars();
927 } else if (rearranger->type() == Rearranger::SimpleCascader) {
928 isSubWindowsTiled = false;
929 }
930}
931
932/*!
933 \internal
934
935 Arranges all minimized windows at the bottom of the workspace.
936*/
937void QMdiAreaPrivate::arrangeMinimizedSubWindows()
938{
939 if (!iconTiler)
940 iconTiler = new IconTiler;
941 rearrange(rearranger: iconTiler);
942}
943
944/*!
945 \internal
946*/
947void QMdiAreaPrivate::activateWindow(QMdiSubWindow *child)
948{
949 if (childWindows.isEmpty()) {
950 Q_ASSERT(!child);
951 Q_ASSERT(!active);
952 return;
953 }
954
955 if (!child) {
956 if (active) {
957 Q_ASSERT(active->d_func()->isActive);
958 active->d_func()->setActive(activate: false);
959 resetActiveWindow();
960 }
961 return;
962 }
963
964 if (child->isHidden() || child == active)
965 return;
966
967 if (child->d_func()->isActive && active == nullptr)
968 child->d_func()->isActive = false;
969
970 child->d_func()->setActive(activate: true);
971}
972
973/*!
974 \internal
975*/
976void QMdiAreaPrivate::activateCurrentWindow()
977{
978 QMdiSubWindow *current = q_func()->currentSubWindow();
979 if (current && !isExplicitlyDeactivated(subWindow: current)) {
980 current->d_func()->activationEnabled = true;
981 current->d_func()->setActive(activate: true, /*changeFocus=*/false);
982 }
983}
984
985void QMdiAreaPrivate::activateHighlightedWindow()
986{
987 if (indexToHighlighted < 0)
988 return;
989
990 Q_ASSERT(indexToHighlighted < childWindows.size());
991 if (tabToPreviousTimer.isActive())
992 activateWindow(child: nextVisibleSubWindow(increaseFactor: -1, QMdiArea::ActivationHistoryOrder));
993 else
994 activateWindow(child: childWindows.at(i: indexToHighlighted));
995#if QT_CONFIG(rubberband)
996 hideRubberBand();
997#endif
998}
999
1000/*!
1001 \internal
1002*/
1003void QMdiAreaPrivate::emitWindowActivated(QMdiSubWindow *activeWindow)
1004{
1005 Q_Q(QMdiArea);
1006 Q_ASSERT(activeWindow);
1007 if (activeWindow == active)
1008 return;
1009 Q_ASSERT(activeWindow->d_func()->isActive);
1010
1011 if (!aboutToBecomeActive)
1012 _q_deactivateAllWindows(aboutToActivate: activeWindow);
1013 Q_ASSERT(aboutToBecomeActive);
1014
1015 // This is true only if 'DontMaximizeSubWindowOnActivation' is disabled
1016 // and the previous active window was maximized.
1017 if (showActiveWindowMaximized) {
1018 if (!activeWindow->isMaximized())
1019 activeWindow->showMaximized();
1020 showActiveWindowMaximized = false;
1021 }
1022
1023 // Put in front to update activation order.
1024 const int indexToActiveWindow = childWindows.indexOf(t: activeWindow);
1025 Q_ASSERT(indexToActiveWindow != -1);
1026 const int index = indicesToActivatedChildren.indexOf(t: indexToActiveWindow);
1027 Q_ASSERT(index != -1);
1028 indicesToActivatedChildren.move(from: index, to: 0);
1029 internalRaise(child: activeWindow);
1030
1031 if (updatesDisabledByUs) {
1032 q->setUpdatesEnabled(true);
1033 updatesDisabledByUs = false;
1034 }
1035
1036 Q_ASSERT(aboutToBecomeActive == activeWindow);
1037 active = activeWindow;
1038 aboutToBecomeActive = nullptr;
1039 Q_ASSERT(active->d_func()->isActive);
1040
1041#if QT_CONFIG(tabbar)
1042 if (tabBar && tabBar->currentIndex() != indexToActiveWindow)
1043 tabBar->setCurrentIndex(indexToActiveWindow);
1044#endif
1045
1046 if (active->isMaximized() && scrollBarsEnabled())
1047 updateScrollBars();
1048
1049 emit q->subWindowActivated(active);
1050}
1051
1052/*!
1053 \internal
1054*/
1055void QMdiAreaPrivate::resetActiveWindow(QMdiSubWindow *deactivatedWindow)
1056{
1057 Q_Q(QMdiArea);
1058 if (deactivatedWindow) {
1059 if (deactivatedWindow != active)
1060 return;
1061 active = nullptr;
1062 if ((aboutToBecomeActive || isActivated || lastWindowAboutToBeDestroyed())
1063 && !isExplicitlyDeactivated(subWindow: deactivatedWindow) && !q->window()->isMinimized()) {
1064 return;
1065 }
1066 emit q->subWindowActivated(nullptr);
1067 return;
1068 }
1069
1070 if (aboutToBecomeActive)
1071 return;
1072
1073 active = nullptr;
1074 emit q->subWindowActivated(nullptr);
1075}
1076
1077/*!
1078 \internal
1079*/
1080void QMdiAreaPrivate::updateActiveWindow(int removedIndex, bool activeRemoved)
1081{
1082 Q_ASSERT(indicesToActivatedChildren.size() == childWindows.size());
1083
1084 // Update indices list first so that we don't rely
1085 for (int i = 0; i < indicesToActivatedChildren.size(); ++i) {
1086 int &index = indicesToActivatedChildren[i];
1087 if (index > removedIndex)
1088 --index;
1089 }
1090
1091#if QT_CONFIG(tabbar)
1092 if (tabBar && removedIndex >= 0) {
1093 const QSignalBlocker blocker(tabBar);
1094 tabBar->removeTab(index: removedIndex);
1095 updateTabBarGeometry();
1096 }
1097#endif
1098
1099 if (childWindows.isEmpty()) {
1100 showActiveWindowMaximized = false;
1101 resetActiveWindow();
1102 return;
1103 }
1104
1105 if (indexToHighlighted >= 0) {
1106#if QT_CONFIG(rubberband)
1107 // Hide rubber band if highlighted window is removed.
1108 if (indexToHighlighted == removedIndex)
1109 hideRubberBand();
1110 else
1111#endif
1112 // or update index if necessary.
1113 if (indexToHighlighted > removedIndex)
1114 --indexToHighlighted;
1115 }
1116
1117 if (!activeRemoved)
1118 return;
1119
1120 // Activate next window.
1121 QMdiSubWindow *next = nextVisibleSubWindow(increaseFactor: 0, activationOrder, removed: removedIndex);
1122 if (next)
1123 activateWindow(child: next);
1124}
1125
1126/*!
1127 \internal
1128*/
1129void QMdiAreaPrivate::updateScrollBars()
1130{
1131 if (ignoreGeometryChange || !scrollBarsEnabled())
1132 return;
1133
1134 Q_Q(QMdiArea);
1135 QSize maxSize = q->maximumViewportSize();
1136 QSize hbarExtent = hbar->sizeHint();
1137 QSize vbarExtent = vbar->sizeHint();
1138
1139 if (q->style()->styleHint(stylehint: QStyle::SH_ScrollView_FrameOnlyAroundContents, opt: nullptr, widget: q)) {
1140 const int doubleFrameWidth = frameWidth * 2;
1141 if (hbarpolicy == Qt::ScrollBarAlwaysOn)
1142 maxSize.rheight() -= doubleFrameWidth;
1143 if (vbarpolicy == Qt::ScrollBarAlwaysOn)
1144 maxSize.rwidth() -= doubleFrameWidth;
1145 hbarExtent.rheight() += doubleFrameWidth;
1146 vbarExtent.rwidth() += doubleFrameWidth;
1147 }
1148
1149 const QRect childrenRect = active && active->isMaximized()
1150 ? active->geometry() : viewport->childrenRect();
1151 bool useHorizontalScrollBar = useScrollBar(childrenRect, maxViewportSize: maxSize, orientation: Qt::Horizontal);
1152 bool useVerticalScrollBar = useScrollBar(childrenRect, maxViewportSize: maxSize, orientation: Qt::Vertical);
1153
1154 if (useHorizontalScrollBar && !useVerticalScrollBar) {
1155 const QSize max = maxSize - QSize(0, hbarExtent.height());
1156 useVerticalScrollBar = useScrollBar(childrenRect, maxViewportSize: max, orientation: Qt::Vertical);
1157 }
1158
1159 if (useVerticalScrollBar && !useHorizontalScrollBar) {
1160 const QSize max = maxSize - QSize(vbarExtent.width(), 0);
1161 useHorizontalScrollBar = useScrollBar(childrenRect, maxViewportSize: max, orientation: Qt::Horizontal);
1162 }
1163
1164 if (useHorizontalScrollBar && hbarpolicy != Qt::ScrollBarAlwaysOn)
1165 maxSize.rheight() -= hbarExtent.height();
1166 if (useVerticalScrollBar && vbarpolicy != Qt::ScrollBarAlwaysOn)
1167 maxSize.rwidth() -= vbarExtent.width();
1168
1169 QRect viewportRect(QPoint(0, 0), maxSize);
1170 const int startX = q->isLeftToRight() ? childrenRect.left() : viewportRect.right()
1171 - childrenRect.right();
1172
1173 const auto singleStep = defaultSingleStep();
1174
1175 // Horizontal scroll bar.
1176 if (isSubWindowsTiled && hbar->value() != 0)
1177 hbar->setValue(0);
1178 const int xOffset = startX + hbar->value();
1179 hbar->setRange(min: qMin(a: 0, b: xOffset),
1180 max: qMax(a: 0, b: xOffset + childrenRect.width() - viewportRect.width()));
1181 hbar->setPageStep(childrenRect.width());
1182 hbar->setSingleStep(childrenRect.width() / singleStep);
1183
1184 // Vertical scroll bar.
1185 if (isSubWindowsTiled && vbar->value() != 0)
1186 vbar->setValue(0);
1187 const int yOffset = childrenRect.top() + vbar->value();
1188 vbar->setRange(min: qMin(a: 0, b: yOffset),
1189 max: qMax(a: 0, b: yOffset + childrenRect.height() - viewportRect.height()));
1190 vbar->setPageStep(childrenRect.height());
1191 vbar->setSingleStep(childrenRect.height() / singleStep);
1192}
1193
1194/*!
1195 \internal
1196*/
1197void QMdiAreaPrivate::internalRaise(QMdiSubWindow *mdiChild) const
1198{
1199 if (!sanityCheck(child: mdiChild, where: "QMdiArea::internalRaise") || childWindows.size() < 2)
1200 return;
1201
1202 QMdiSubWindow *stackUnderChild = nullptr;
1203 if (!windowStaysOnTop(subWindow: mdiChild)) {
1204 const auto children = viewport->children(); // take a copy, as raising/stacking under changes the order
1205 for (QObject *object : children) {
1206 QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(object);
1207 if (!child || !childWindows.contains(t: child))
1208 continue;
1209 if (!child->isHidden() && windowStaysOnTop(subWindow: child)) {
1210 if (stackUnderChild)
1211 child->stackUnder(stackUnderChild);
1212 else
1213 child->raise();
1214 stackUnderChild = child;
1215 }
1216 }
1217 }
1218
1219 if (stackUnderChild)
1220 mdiChild->stackUnder(stackUnderChild);
1221 else
1222 mdiChild->raise();
1223}
1224
1225QRect QMdiAreaPrivate::resizeToMinimumTileSize(const QSize &minSubWindowSize, int subWindowCount)
1226{
1227 Q_Q(QMdiArea);
1228 if (!minSubWindowSize.isValid() || subWindowCount <= 0)
1229 return viewport->rect();
1230
1231 // Calculate minimum size.
1232 const int columns = qMax(a: qCeil(v: qSqrt(v: qreal(subWindowCount))), b: 1);
1233 const int rows = qMax(a: (subWindowCount % columns) ? (subWindowCount / columns + 1)
1234 : (subWindowCount / columns), b: 1);
1235 const int minWidth = minSubWindowSize.width() * columns;
1236 const int minHeight = minSubWindowSize.height() * rows;
1237
1238 // Increase area size if necessary. Scroll bars are provided if we're not able
1239 // to resize to the minimum size.
1240 if (!tileCalledFromResizeEvent) {
1241 QWidget *topLevel = q;
1242 // Find the topLevel for this area, either a real top-level or a sub-window.
1243 while (topLevel && !topLevel->isWindow() && topLevel->windowType() != Qt::SubWindow)
1244 topLevel = topLevel->parentWidget();
1245 // We don't want sub-subwindows to be placed at the edge, thus add 2 pixels.
1246 int minAreaWidth = minWidth + left + right + 2;
1247 int minAreaHeight = minHeight + top + bottom + 2;
1248 if (hbar->isVisible())
1249 minAreaHeight += hbar->height();
1250 if (vbar->isVisible())
1251 minAreaWidth += vbar->width();
1252 if (q->style()->styleHint(stylehint: QStyle::SH_ScrollView_FrameOnlyAroundContents, opt: nullptr, widget: q)) {
1253 const int frame = q->style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth, option: nullptr, widget: q);
1254 minAreaWidth += 2 * frame;
1255 minAreaHeight += 2 * frame;
1256 }
1257 const QSize diff = QSize(minAreaWidth, minAreaHeight).expandedTo(otherSize: q->size()) - q->size();
1258 // Only resize topLevel widget if scroll bars are disabled.
1259 if (hbarpolicy == Qt::ScrollBarAlwaysOff)
1260 topLevel->resize(w: topLevel->size().width() + diff.width(), h: topLevel->size().height());
1261 if (vbarpolicy == Qt::ScrollBarAlwaysOff)
1262 topLevel->resize(w: topLevel->size().width(), h: topLevel->size().height() + diff.height());
1263 }
1264
1265 QRect domain = viewport->rect();
1266
1267 // Adjust domain width and provide horizontal scroll bar.
1268 if (domain.width() < minWidth) {
1269 domain.setWidth(minWidth);
1270 if (hbarpolicy == Qt::ScrollBarAlwaysOff)
1271 q->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
1272 else
1273 hbar->setValue(0);
1274 }
1275 // Adjust domain height and provide vertical scroll bar.
1276 if (domain.height() < minHeight) {
1277 domain.setHeight(minHeight);
1278 if (vbarpolicy == Qt::ScrollBarAlwaysOff)
1279 q->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
1280 else
1281 vbar->setValue(0);
1282 }
1283 return domain;
1284}
1285
1286/*!
1287 \internal
1288*/
1289bool QMdiAreaPrivate::scrollBarsEnabled() const
1290{
1291 return hbarpolicy != Qt::ScrollBarAlwaysOff || vbarpolicy != Qt::ScrollBarAlwaysOff;
1292}
1293
1294/*!
1295 \internal
1296*/
1297bool QMdiAreaPrivate::lastWindowAboutToBeDestroyed() const
1298{
1299 if (childWindows.size() != 1)
1300 return false;
1301
1302 QMdiSubWindow *last = childWindows.at(i: 0);
1303 if (!last)
1304 return true;
1305
1306 if (!last->testAttribute(attribute: Qt::WA_DeleteOnClose))
1307 return false;
1308
1309 return last->d_func()->data.is_closing;
1310}
1311
1312/*!
1313 \internal
1314*/
1315void QMdiAreaPrivate::setChildActivationEnabled(bool enable, bool onlyNextActivationEvent) const
1316{
1317 for (QMdiSubWindow *subWindow : childWindows) {
1318 if (!subWindow || !subWindow->isVisible())
1319 continue;
1320 if (onlyNextActivationEvent)
1321 subWindow->d_func()->ignoreNextActivationEvent = !enable;
1322 else
1323 subWindow->d_func()->activationEnabled = enable;
1324 }
1325}
1326
1327/*!
1328 \internal
1329 \reimp
1330*/
1331void QMdiAreaPrivate::scrollBarPolicyChanged(Qt::Orientation orientation, Qt::ScrollBarPolicy policy)
1332{
1333 if (childWindows.isEmpty())
1334 return;
1335
1336 const QMdiSubWindow::SubWindowOption option = orientation == Qt::Horizontal ?
1337 QMdiSubWindow::AllowOutsideAreaHorizontally : QMdiSubWindow::AllowOutsideAreaVertically;
1338 const bool enable = policy != Qt::ScrollBarAlwaysOff;
1339 // Take a copy because child->setOption() may indirectly call QCoreApplication::sendEvent(),
1340 // the latter could call unknown code that could e.g. recurse into the class
1341 // modifying childWindows.
1342 const auto subWindows = childWindows;
1343 for (QMdiSubWindow *child : subWindows) {
1344 if (!sanityCheck(child, where: "QMdiArea::scrollBarPolicyChanged"))
1345 continue;
1346 child->setOption(option, on: enable);
1347 }
1348 updateScrollBars();
1349}
1350
1351QList<QMdiSubWindow*>
1352QMdiAreaPrivate::subWindowList(QMdiArea::WindowOrder order, bool reversed) const
1353{
1354 QList<QMdiSubWindow *> list;
1355 if (childWindows.isEmpty())
1356 return list;
1357
1358 if (order == QMdiArea::CreationOrder) {
1359 for (QMdiSubWindow *child : childWindows) {
1360 if (!child)
1361 continue;
1362 if (!reversed)
1363 list.append(t: child);
1364 else
1365 list.prepend(t: child);
1366 }
1367 } else if (order == QMdiArea::StackingOrder) {
1368 for (QObject *object : viewport->children()) {
1369 QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(object);
1370 if (!child || !childWindows.contains(t: child))
1371 continue;
1372 if (!reversed)
1373 list.append(t: child);
1374 else
1375 list.prepend(t: child);
1376 }
1377 } else { // ActivationHistoryOrder
1378 Q_ASSERT(indicesToActivatedChildren.size() == childWindows.size());
1379 for (int i = indicesToActivatedChildren.size() - 1; i >= 0; --i) {
1380 QMdiSubWindow *child = childWindows.at(i: indicesToActivatedChildren.at(i));
1381 if (!child)
1382 continue;
1383 if (!reversed)
1384 list.append(t: child);
1385 else
1386 list.prepend(t: child);
1387 }
1388 }
1389 return list;
1390}
1391
1392/*!
1393 \internal
1394*/
1395void QMdiAreaPrivate::disconnectSubWindow(QObject *subWindow)
1396{
1397 if (!subWindow)
1398 return;
1399
1400 Q_Q(QMdiArea);
1401 QObject::disconnect(sender: subWindow, signal: nullptr, receiver: q, member: nullptr);
1402 subWindow->removeEventFilter(obj: q);
1403}
1404
1405/*!
1406 \internal
1407*/
1408QMdiSubWindow *QMdiAreaPrivate::nextVisibleSubWindow(int increaseFactor, QMdiArea::WindowOrder order,
1409 int removedIndex, int fromIndex) const
1410{
1411 if (childWindows.isEmpty())
1412 return nullptr;
1413
1414 Q_Q(const QMdiArea);
1415 const QList<QMdiSubWindow *> subWindows = q->subWindowList(order);
1416 QMdiSubWindow *current = nullptr;
1417
1418 if (removedIndex < 0) {
1419 if (fromIndex >= 0 && fromIndex < subWindows.size())
1420 current = childWindows.at(i: fromIndex);
1421 else
1422 current = q->currentSubWindow();
1423 }
1424
1425 // There's no current sub-window (removed or deactivated),
1426 // so we have to pick the last active or the next in creation order.
1427 if (!current) {
1428 if (removedIndex >= 0 && order == QMdiArea::CreationOrder) {
1429 int candidateIndex = -1;
1430 setIndex(index: &candidateIndex, candidate: removedIndex, min: 0, max: subWindows.size() - 1, isIncreasing: true);
1431 current = childWindows.at(i: candidateIndex);
1432 } else {
1433 current = subWindows.back();
1434 }
1435 }
1436 Q_ASSERT(current);
1437
1438 // Find the index for the current sub-window in the given activation order
1439 const int indexToCurrent = subWindows.indexOf(t: current);
1440 const bool increasing = increaseFactor > 0;
1441
1442 // and use that index + increseFactor as a candidate.
1443 int index = -1;
1444 setIndex(index: &index, candidate: indexToCurrent + increaseFactor, min: 0, max: subWindows.size() - 1, isIncreasing: increasing);
1445 Q_ASSERT(index != -1);
1446
1447 // Try to find another window if the candidate is hidden.
1448 while (subWindows.at(i: index)->isHidden()) {
1449 setIndex(index: &index, candidate: index + increaseFactor, min: 0, max: subWindows.size() - 1, isIncreasing: increasing);
1450 if (index == indexToCurrent)
1451 break;
1452 }
1453
1454 if (!subWindows.at(i: index)->isHidden())
1455 return subWindows.at(i: index);
1456 return nullptr;
1457}
1458
1459/*!
1460 \internal
1461*/
1462void QMdiAreaPrivate::highlightNextSubWindow(int increaseFactor)
1463{
1464 if (childWindows.size() == 1)
1465 return;
1466
1467 Q_Q(QMdiArea);
1468 // There's no highlighted sub-window atm, use current.
1469 if (indexToHighlighted < 0) {
1470 QMdiSubWindow *current = q->currentSubWindow();
1471 if (!current)
1472 return;
1473 indexToHighlighted = childWindows.indexOf(t: current);
1474 }
1475
1476 Q_ASSERT(indexToHighlighted >= 0);
1477 Q_ASSERT(indexToHighlighted < childWindows.size());
1478
1479 QMdiSubWindow *highlight = nextVisibleSubWindow(increaseFactor, order: activationOrder, removedIndex: -1, fromIndex: indexToHighlighted);
1480 if (!highlight)
1481 return;
1482
1483#if QT_CONFIG(rubberband)
1484 if (!rubberBand) {
1485 rubberBand = new QRubberBand(QRubberBand::Rectangle, q);
1486 // For accessibility to identify this special widget.
1487 rubberBand->setObjectName("qt_rubberband"_L1);
1488 rubberBand->setWindowFlags(rubberBand->windowFlags() | Qt::WindowStaysOnTopHint);
1489 }
1490#endif
1491
1492 // Only highlight if we're not switching back to the previously active window (Ctrl-Tab once).
1493#if QT_CONFIG(rubberband)
1494 if (!tabToPreviousTimer.isActive())
1495 showRubberBandFor(subWindow: highlight);
1496#endif
1497
1498 indexToHighlighted = childWindows.indexOf(t: highlight);
1499 Q_ASSERT(indexToHighlighted >= 0);
1500}
1501
1502#if QT_CONFIG(rubberband)
1503void QMdiAreaPrivate::showRubberBandFor(QMdiSubWindow *subWindow)
1504{
1505 if (!subWindow || !rubberBand)
1506 return;
1507
1508#if QT_CONFIG(tabbar)
1509 if (viewMode == QMdiArea::TabbedView)
1510 rubberBand->setGeometry(tabBar->tabRect(index: childWindows.indexOf(t: subWindow)));
1511 else
1512#endif
1513 rubberBand->setGeometry(subWindow->geometry());
1514
1515 rubberBand->raise();
1516 rubberBand->show();
1517}
1518#endif // QT_CONFIG(rubberBand)
1519/*!
1520 \internal
1521 \since 4.4
1522*/
1523void QMdiAreaPrivate::setViewMode(QMdiArea::ViewMode mode)
1524{
1525 Q_Q(QMdiArea);
1526 if (viewMode == mode || inViewModeChange)
1527 return;
1528
1529 // Just a guard since we cannot set viewMode = mode here.
1530 inViewModeChange = true;
1531
1532#if QT_CONFIG(tabbar)
1533 if (mode == QMdiArea::TabbedView) {
1534 Q_ASSERT(!tabBar);
1535 tabBar = new QMdiAreaTabBar(q);
1536 tabBar->setDocumentMode(documentMode);
1537 tabBar->setTabsClosable(tabsClosable);
1538 tabBar->setMovable(tabsMovable);
1539#if QT_CONFIG(tabwidget)
1540 tabBar->setShape(_q_tb_tabBarShapeFrom(shape: tabShape, position: tabPosition));
1541#endif
1542
1543 isSubWindowsTiled = false;
1544
1545 // Take a copy as tabBar->addTab() will (indirectly) create a connection between
1546 // the tab close button clicked() signal and the _q_closeTab() slot, which may
1547 // indirectly call QCoreApplication::sendEvent(), the latter could result in
1548 // invoking unknown code that could e.g. recurse into the class modifying childWindows.
1549 const auto subWindows = childWindows;
1550 for (QMdiSubWindow *subWindow : subWindows)
1551 tabBar->addTab(icon: subWindow->windowIcon(), text: tabTextFor(subWindow));
1552
1553 QMdiSubWindow *current = q->currentSubWindow();
1554 if (current) {
1555 tabBar->setCurrentIndex(childWindows.indexOf(t: current));
1556 // Restore sub-window (i.e. cleanup buttons in menu bar and window title).
1557 if (current->isMaximized())
1558 current->showNormal();
1559
1560 viewMode = mode;
1561
1562 // Now, maximize it.
1563 if (!q->testOption(opton: QMdiArea::DontMaximizeSubWindowOnActivation)) {
1564 current->showMaximized();
1565 }
1566 } else {
1567 viewMode = mode;
1568 }
1569
1570 if (q->isVisible())
1571 tabBar->show();
1572 updateTabBarGeometry();
1573
1574 QObject::connect(sender: tabBar, SIGNAL(currentChanged(int)), receiver: q, SLOT(_q_currentTabChanged(int)));
1575 QObject::connect(sender: tabBar, SIGNAL(tabCloseRequested(int)), receiver: q, SLOT(_q_closeTab(int)));
1576 QObject::connect(sender: tabBar, SIGNAL(tabMoved(int,int)), receiver: q, SLOT(_q_moveTab(int,int)));
1577 } else
1578#endif // QT_CONFIG(tabbar)
1579 { // SubWindowView
1580#if QT_CONFIG(tabbar)
1581 delete tabBar;
1582 tabBar = nullptr;
1583#endif // QT_CONFIG(tabbar)
1584
1585 viewMode = mode;
1586 q->setViewportMargins(left: 0, top: 0, right: 0, bottom: 0);
1587 indexToLastActiveTab = -1;
1588
1589 QMdiSubWindow *current = q->currentSubWindow();
1590 if (current && current->isMaximized())
1591 current->showNormal();
1592 }
1593
1594 Q_ASSERT(viewMode == mode);
1595 inViewModeChange = false;
1596}
1597
1598#if QT_CONFIG(tabbar)
1599/*!
1600 \internal
1601*/
1602void QMdiAreaPrivate::updateTabBarGeometry()
1603{
1604 if (!tabBar)
1605 return;
1606
1607 Q_Q(QMdiArea);
1608#if QT_CONFIG(tabwidget)
1609 Q_ASSERT(_q_tb_tabBarShapeFrom(tabShape, tabPosition) == tabBar->shape());
1610#endif
1611 const QSize tabBarSizeHint = tabBar->sizeHint();
1612
1613 int areaHeight = q->height();
1614 if (hbar && hbar->isVisible())
1615 areaHeight -= hbar->height();
1616
1617 int areaWidth = q->width();
1618 if (vbar && vbar->isVisible())
1619 areaWidth -= vbar->width();
1620
1621 QRect tabBarRect;
1622#if QT_CONFIG(tabwidget)
1623 switch (tabPosition) {
1624 case QTabWidget::North:
1625 q->setViewportMargins(left: 0, top: tabBarSizeHint.height(), right: 0, bottom: 0);
1626 tabBarRect = QRect(0, 0, areaWidth, tabBarSizeHint.height());
1627 break;
1628 case QTabWidget::South:
1629 q->setViewportMargins(left: 0, top: 0, right: 0, bottom: tabBarSizeHint.height());
1630 tabBarRect = QRect(0, areaHeight - tabBarSizeHint.height(), areaWidth, tabBarSizeHint.height());
1631 break;
1632 case QTabWidget::East:
1633 if (q->layoutDirection() == Qt::LeftToRight)
1634 q->setViewportMargins(left: 0, top: 0, right: tabBarSizeHint.width(), bottom: 0);
1635 else
1636 q->setViewportMargins(left: tabBarSizeHint.width(), top: 0, right: 0, bottom: 0);
1637 tabBarRect = QRect(areaWidth - tabBarSizeHint.width(), 0, tabBarSizeHint.width(), areaHeight);
1638 break;
1639 case QTabWidget::West:
1640 if (q->layoutDirection() == Qt::LeftToRight)
1641 q->setViewportMargins(left: tabBarSizeHint.width(), top: 0, right: 0, bottom: 0);
1642 else
1643 q->setViewportMargins(left: 0, top: 0, right: tabBarSizeHint.width(), bottom: 0);
1644 tabBarRect = QRect(0, 0, tabBarSizeHint.width(), areaHeight);
1645 break;
1646 default:
1647 break;
1648 }
1649#endif // QT_CONFIG(tabwidget)
1650
1651 tabBar->setGeometry(QStyle::visualRect(direction: q->layoutDirection(), boundingRect: q->contentsRect(), logicalRect: tabBarRect));
1652}
1653
1654/*!
1655 \internal
1656*/
1657void QMdiAreaPrivate::refreshTabBar()
1658{
1659 if (!tabBar)
1660 return;
1661
1662 tabBar->setDocumentMode(documentMode);
1663 tabBar->setTabsClosable(tabsClosable);
1664 tabBar->setMovable(tabsMovable);
1665#if QT_CONFIG(tabwidget)
1666 tabBar->setShape(_q_tb_tabBarShapeFrom(shape: tabShape, position: tabPosition));
1667#endif
1668 updateTabBarGeometry();
1669}
1670#endif // QT_CONFIG(tabbar)
1671
1672/*!
1673 Constructs an empty mdi area. \a parent is passed to QWidget's
1674 constructor.
1675*/
1676QMdiArea::QMdiArea(QWidget *parent)
1677 : QAbstractScrollArea(*new QMdiAreaPrivate, parent)
1678{
1679 setBackground(palette().brush(cr: QPalette::Dark));
1680 setFrameStyle(QFrame::NoFrame);
1681 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1682 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1683 setViewport(nullptr);
1684 setFocusPolicy(Qt::NoFocus);
1685 QApplication::instance()->installEventFilter(filterObj: this);
1686}
1687
1688/*!
1689 Destroys the MDI area.
1690*/
1691QMdiArea::~QMdiArea()
1692{
1693 Q_D(QMdiArea);
1694 delete d->cascader;
1695 d->cascader = nullptr;
1696
1697 delete d->regularTiler;
1698 d->regularTiler = nullptr;
1699
1700 delete d->iconTiler;
1701 d->iconTiler = nullptr;
1702
1703 delete d->placer;
1704 d->placer = nullptr;
1705}
1706
1707/*!
1708 \reimp
1709*/
1710QSize QMdiArea::sizeHint() const
1711{
1712 // Calculate a proper scale factor for the desktop's size.
1713 // This also takes into account that we can have nested workspaces.
1714 int nestedCount = 0;
1715 QWidget *widget = this->parentWidget();
1716 while (widget) {
1717 if (qobject_cast<QMdiArea *>(object: widget))
1718 ++nestedCount;
1719 widget = widget->parentWidget();
1720 }
1721 const int scaleFactor = 3 * (nestedCount + 1);
1722
1723 QSize desktopSize = QGuiApplication::primaryScreen()->virtualSize();
1724 QSize size(desktopSize.width() * 2 / scaleFactor, desktopSize.height() * 2 / scaleFactor);
1725 for (QMdiSubWindow *child : d_func()->childWindows) {
1726 if (!sanityCheck(child, where: "QMdiArea::sizeHint"))
1727 continue;
1728 size = size.expandedTo(otherSize: child->sizeHint());
1729 }
1730 return size;
1731}
1732
1733/*!
1734 \reimp
1735*/
1736QSize QMdiArea::minimumSizeHint() const
1737{
1738 Q_D(const QMdiArea);
1739 QSize size(style()->pixelMetric(metric: QStyle::PM_MdiSubWindowMinimizedWidth, option: nullptr, widget: this),
1740 style()->pixelMetric(metric: QStyle::PM_TitleBarHeight, option: nullptr, widget: this));
1741 size = size.expandedTo(otherSize: QAbstractScrollArea::minimumSizeHint());
1742 if (!d->scrollBarsEnabled()) {
1743 for (QMdiSubWindow *child : d->childWindows) {
1744 if (!sanityCheck(child, where: "QMdiArea::sizeHint"))
1745 continue;
1746 size = size.expandedTo(otherSize: child->minimumSizeHint());
1747 }
1748 }
1749 return size;
1750}
1751
1752/*!
1753 Returns a pointer to the current subwindow, or \nullptr if there is
1754 no current subwindow.
1755
1756 This function will return the same as activeSubWindow() if
1757 the QApplication containing QMdiArea is active.
1758
1759 \sa activeSubWindow(), QApplication::activeWindow()
1760*/
1761QMdiSubWindow *QMdiArea::currentSubWindow() const
1762{
1763 Q_D(const QMdiArea);
1764 if (d->childWindows.isEmpty())
1765 return nullptr;
1766
1767 if (d->active)
1768 return d->active;
1769
1770 if (d->isActivated && !window()->isMinimized())
1771 return nullptr;
1772
1773 Q_ASSERT(d->indicesToActivatedChildren.size() > 0);
1774 int index = d->indicesToActivatedChildren.at(i: 0);
1775 Q_ASSERT(index >= 0 && index < d->childWindows.size());
1776 QMdiSubWindow *current = d->childWindows.at(i: index);
1777 Q_ASSERT(current);
1778 return current;
1779}
1780
1781/*!
1782 Returns a pointer to the current active subwindow. If no
1783 window is currently active, \nullptr is returned.
1784
1785 Subwindows are treated as top-level windows with respect to
1786 window state, i.e., if a widget outside the MDI area is the active
1787 window, no subwindow will be active. Note that if a widget in the
1788 window in which the MDI area lives gains focus, the window will be
1789 activated.
1790
1791 \sa setActiveSubWindow(), Qt::WindowState
1792*/
1793QMdiSubWindow *QMdiArea::activeSubWindow() const
1794{
1795 Q_D(const QMdiArea);
1796 return d->active;
1797}
1798
1799/*!
1800 Activates the subwindow \a window. If \a window is \nullptr, any
1801 current active window is deactivated.
1802
1803 \sa activeSubWindow()
1804*/
1805void QMdiArea::setActiveSubWindow(QMdiSubWindow *window)
1806{
1807 Q_D(QMdiArea);
1808 if (!window) {
1809 d->activateWindow(child: nullptr);
1810 return;
1811 }
1812
1813 if (Q_UNLIKELY(d->childWindows.isEmpty())) {
1814 qWarning(msg: "QMdiArea::setActiveSubWindow: workspace is empty");
1815 return;
1816 }
1817
1818 if (Q_UNLIKELY(d->childWindows.indexOf(window) == -1)) {
1819 qWarning(msg: "QMdiArea::setActiveSubWindow: window is not inside workspace");
1820 return;
1821 }
1822
1823 d->activateWindow(child: window);
1824}
1825
1826/*!
1827 Closes the active subwindow.
1828
1829 \sa closeAllSubWindows()
1830*/
1831void QMdiArea::closeActiveSubWindow()
1832{
1833 Q_D(QMdiArea);
1834 if (d->active)
1835 d->active->close();
1836}
1837
1838/*!
1839 Returns a list of all subwindows in the MDI area. If \a order is
1840 CreationOrder (the default), the windows are sorted in the order
1841 in which they were inserted into the workspace. If \a order is
1842 StackingOrder, the windows are listed in their stacking order,
1843 with the topmost window as the last item in the list. If \a order
1844 is ActivationHistoryOrder, the windows are listed according to
1845 their recent activation history.
1846
1847 \sa WindowOrder
1848*/
1849QList<QMdiSubWindow *> QMdiArea::subWindowList(WindowOrder order) const
1850{
1851 Q_D(const QMdiArea);
1852 return d->subWindowList(order, reversed: false);
1853}
1854
1855/*!
1856 Closes all subwindows by sending a QCloseEvent to each window.
1857 You may receive subWindowActivated() signals from subwindows
1858 before they are closed (if the MDI area activates the subwindow
1859 when another is closing).
1860
1861 Subwindows that ignore the close event will remain open.
1862
1863 \sa closeActiveSubWindow()
1864*/
1865void QMdiArea::closeAllSubWindows()
1866{
1867 Q_D(QMdiArea);
1868 if (d->childWindows.isEmpty())
1869 return;
1870
1871 d->isSubWindowsTiled = false;
1872 // Take a copy because the child->close() call below may end up indirectly calling
1873 // QCoreApplication::send{Spontaneous}Event(), which may call unknown code that
1874 // could e.g. recurse into the class modifying d->childWindows.
1875 const auto subWindows = d->childWindows;
1876 for (QMdiSubWindow *child : subWindows) {
1877 if (!sanityCheck(child, where: "QMdiArea::closeAllSubWindows"))
1878 continue;
1879 child->close();
1880 }
1881
1882 d->updateScrollBars();
1883}
1884
1885/*!
1886 Gives the keyboard focus to another window in the list of child
1887 windows. The window activated will be the next one determined
1888 by the current \l{QMdiArea::WindowOrder} {activation order}.
1889
1890 \sa activatePreviousSubWindow(), QMdiArea::WindowOrder
1891*/
1892void QMdiArea::activateNextSubWindow()
1893{
1894 Q_D(QMdiArea);
1895 if (d->childWindows.isEmpty())
1896 return;
1897
1898 QMdiSubWindow *next = d->nextVisibleSubWindow(increaseFactor: 1, order: d->activationOrder);
1899 if (next)
1900 d->activateWindow(child: next);
1901}
1902
1903/*!
1904 Gives the keyboard focus to another window in the list of child
1905 windows. The window activated will be the previous one determined
1906 by the current \l{QMdiArea::WindowOrder} {activation order}.
1907
1908 \sa activateNextSubWindow(), QMdiArea::WindowOrder
1909*/
1910void QMdiArea::activatePreviousSubWindow()
1911{
1912 Q_D(QMdiArea);
1913 if (d->childWindows.isEmpty())
1914 return;
1915
1916 QMdiSubWindow *previous = d->nextVisibleSubWindow(increaseFactor: -1, order: d->activationOrder);
1917 if (previous)
1918 d->activateWindow(child: previous);
1919}
1920
1921/*!
1922 Adds \a widget as a new subwindow to the MDI area. If \a
1923 windowFlags are non-zero, they will override the flags set on the
1924 widget.
1925
1926 The \a widget can be either a QMdiSubWindow or another QWidget
1927 (in which case the MDI area will create a subwindow and set the \a
1928 widget as the internal widget).
1929
1930 \note Once the subwindow has been added, its parent will be the
1931 \e{viewport widget} of the QMdiArea.
1932
1933 \snippet mdiarea/mdiareasnippets.cpp 1
1934
1935 When you create your own subwindow, you must set the
1936 Qt::WA_DeleteOnClose widget attribute if you want the window to be
1937 deleted when closed in the MDI area. If not, the window will be
1938 hidden and the MDI area will not activate the next subwindow.
1939
1940 Returns the QMdiSubWindow that is added to the MDI area.
1941
1942 \sa removeSubWindow()
1943*/
1944QMdiSubWindow *QMdiArea::addSubWindow(QWidget *widget, Qt::WindowFlags windowFlags)
1945{
1946 if (Q_UNLIKELY(!widget)) {
1947 qWarning(msg: "QMdiArea::addSubWindow: null pointer to widget");
1948 return nullptr;
1949 }
1950
1951 Q_D(QMdiArea);
1952 // QWidget::setParent clears focusWidget so store it
1953 QWidget *childFocus = widget->focusWidget();
1954 QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(object: widget);
1955
1956 // Widget is already a QMdiSubWindow
1957 if (child) {
1958 if (Q_UNLIKELY(d->childWindows.indexOf(child) != -1)) {
1959 qWarning(msg: "QMdiArea::addSubWindow: window is already added");
1960 return child;
1961 }
1962 child->setParent(parent: viewport(), f: windowFlags ? windowFlags : child->windowFlags());
1963 // Create a QMdiSubWindow
1964 } else {
1965 child = new QMdiSubWindow(viewport(), windowFlags);
1966 child->setAttribute(Qt::WA_DeleteOnClose);
1967 child->setWidget(widget);
1968 Q_ASSERT(child->testAttribute(Qt::WA_DeleteOnClose));
1969 }
1970
1971 d->appendChild(child);
1972
1973 if (childFocus)
1974 childFocus->setFocus();
1975
1976 return child;
1977}
1978
1979/*!
1980 Removes \a widget from the MDI area. The \a widget must be
1981 either a QMdiSubWindow or a widget that is the internal widget of
1982 a subwindow. Note \a widget is never actually deleted by QMdiArea.
1983 If a QMdiSubWindow is passed in, its parent is set to \nullptr and it is
1984 removed; but if an internal widget is passed in, the child widget
1985 is set to \nullptr and the QMdiSubWindow is \e not removed.
1986
1987 \sa addSubWindow()
1988*/
1989void QMdiArea::removeSubWindow(QWidget *widget)
1990{
1991 if (Q_UNLIKELY(!widget)) {
1992 qWarning(msg: "QMdiArea::removeSubWindow: null pointer to widget");
1993 return;
1994 }
1995
1996 Q_D(QMdiArea);
1997 if (d->childWindows.isEmpty())
1998 return;
1999
2000 if (QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(object: widget)) {
2001 int index = d->childWindows.indexOf(t: child);
2002 if (Q_UNLIKELY(index == -1)) {
2003 qWarning(msg: "QMdiArea::removeSubWindow: window is not inside workspace");
2004 return;
2005 }
2006 d->disconnectSubWindow(subWindow: child);
2007 d->childWindows.removeAll(t: child);
2008 d->indicesToActivatedChildren.removeAll(t: index);
2009 d->updateActiveWindow(removedIndex: index, activeRemoved: d->active == child);
2010 child->setParent(nullptr);
2011 return;
2012 }
2013
2014 bool found = false;
2015 // Take a copy because child->setWidget(nullptr) will indirectly
2016 // QCoreApplication::sendEvent(); the latter could call unknown code that could
2017 // e.g. recurse into the class modifying d->childWindows.
2018 const auto subWindows = d->childWindows;
2019 for (QMdiSubWindow *child : subWindows) {
2020 if (!sanityCheck(child, where: "QMdiArea::removeSubWindow"))
2021 continue;
2022 if (child->widget() == widget) {
2023 child->setWidget(nullptr);
2024 Q_ASSERT(!child->widget());
2025 found = true;
2026 break;
2027 }
2028 }
2029
2030 if (Q_UNLIKELY(!found))
2031 qWarning(msg: "QMdiArea::removeSubWindow: widget is not child of any window inside QMdiArea");
2032}
2033
2034/*!
2035 \property QMdiArea::background
2036 \brief the background brush for the workspace
2037
2038 This property sets the background brush for the workspace area
2039 itself. By default, it is a gray color, but can be any brush
2040 (e.g., colors, gradients or pixmaps).
2041*/
2042QBrush QMdiArea::background() const
2043{
2044 return d_func()->background;
2045}
2046
2047void QMdiArea::setBackground(const QBrush &brush)
2048{
2049 Q_D(QMdiArea);
2050 if (d->background != brush) {
2051 d->background = brush;
2052 d->viewport->setAttribute(Qt::WA_OpaquePaintEvent, on: brush.isOpaque());
2053 d->viewport->update();
2054 }
2055}
2056
2057
2058/*!
2059 \property QMdiArea::activationOrder
2060 \brief the ordering criteria for subwindow lists
2061 \since 4.4
2062
2063 This property specifies the ordering criteria for the list of
2064 subwindows returned by subWindowList(). By default, it is the window
2065 creation order.
2066
2067 \sa subWindowList()
2068*/
2069QMdiArea::WindowOrder QMdiArea::activationOrder() const
2070{
2071 Q_D(const QMdiArea);
2072 return d->activationOrder;
2073}
2074
2075void QMdiArea::setActivationOrder(WindowOrder order)
2076{
2077 Q_D(QMdiArea);
2078 if (order != d->activationOrder)
2079 d->activationOrder = order;
2080}
2081
2082/*!
2083 If \a on is true, \a option is enabled on the MDI area; otherwise
2084 it is disabled. See AreaOption for the effect of each option.
2085
2086 \sa AreaOption, testOption()
2087*/
2088void QMdiArea::setOption(AreaOption option, bool on)
2089{
2090 Q_D(QMdiArea);
2091 d->options.setFlag(flag: option, on);
2092}
2093
2094/*!
2095 Returns \c true if \a option is enabled; otherwise returns \c false.
2096
2097 \sa AreaOption, setOption()
2098*/
2099bool QMdiArea::testOption(AreaOption option) const
2100{
2101 return d_func()->options & option;
2102}
2103
2104/*!
2105 \property QMdiArea::viewMode
2106 \brief the way sub-windows are displayed in the QMdiArea.
2107 \since 4.4
2108
2109 By default, the SubWindowView is used to display sub-windows.
2110
2111 \sa ViewMode, setTabShape(), setTabPosition()
2112*/
2113QMdiArea::ViewMode QMdiArea::viewMode() const
2114{
2115 Q_D(const QMdiArea);
2116 return d->viewMode;
2117}
2118
2119void QMdiArea::setViewMode(ViewMode mode)
2120{
2121 Q_D(QMdiArea);
2122 d->setViewMode(mode);
2123}
2124
2125#if QT_CONFIG(tabbar)
2126/*!
2127 \property QMdiArea::documentMode
2128 \brief whether the tab bar is set to document mode in tabbed view mode.
2129 \since 4.5
2130
2131 Document mode is disabled by default.
2132
2133 \sa QTabBar::documentMode, setViewMode()
2134*/
2135bool QMdiArea::documentMode() const
2136{
2137 Q_D(const QMdiArea);
2138 return d->documentMode;
2139}
2140
2141void QMdiArea::setDocumentMode(bool enabled)
2142{
2143 Q_D(QMdiArea);
2144 if (d->documentMode == enabled)
2145 return;
2146
2147 d->documentMode = enabled;
2148 d->refreshTabBar();
2149}
2150
2151/*!
2152 \property QMdiArea::tabsClosable
2153 \brief whether the tab bar should place close buttons on each tab in tabbed view mode.
2154 \since 4.8
2155
2156 Tabs are not closable by default.
2157
2158 \sa QTabBar::tabsClosable, setViewMode()
2159*/
2160bool QMdiArea::tabsClosable() const
2161{
2162 Q_D(const QMdiArea);
2163 return d->tabsClosable;
2164}
2165
2166void QMdiArea::setTabsClosable(bool closable)
2167{
2168 Q_D(QMdiArea);
2169 if (d->tabsClosable == closable)
2170 return;
2171
2172 d->tabsClosable = closable;
2173 d->refreshTabBar();
2174}
2175
2176/*!
2177 \property QMdiArea::tabsMovable
2178 \brief whether the user can move the tabs within the tabbar area in tabbed view mode.
2179 \since 4.8
2180
2181 Tabs are not movable by default.
2182
2183 \sa QTabBar::movable, setViewMode()
2184*/
2185bool QMdiArea::tabsMovable() const
2186{
2187 Q_D(const QMdiArea);
2188 return d->tabsMovable;
2189}
2190
2191void QMdiArea::setTabsMovable(bool movable)
2192{
2193 Q_D(QMdiArea);
2194 if (d->tabsMovable == movable)
2195 return;
2196
2197 d->tabsMovable = movable;
2198 d->refreshTabBar();
2199}
2200#endif // QT_CONFIG(tabbar)
2201
2202#if QT_CONFIG(tabwidget)
2203/*!
2204 \property QMdiArea::tabShape
2205 \brief the shape of the tabs in tabbed view mode.
2206 \since 4.4
2207
2208 Possible values for this property are QTabWidget::Rounded
2209 (default) or QTabWidget::Triangular.
2210
2211 \sa QTabWidget::TabShape, setViewMode()
2212*/
2213QTabWidget::TabShape QMdiArea::tabShape() const
2214{
2215 Q_D(const QMdiArea);
2216 return d->tabShape;
2217}
2218
2219void QMdiArea::setTabShape(QTabWidget::TabShape shape)
2220{
2221 Q_D(QMdiArea);
2222 if (d->tabShape == shape)
2223 return;
2224
2225 d->tabShape = shape;
2226 d->refreshTabBar();
2227}
2228
2229/*!
2230 \property QMdiArea::tabPosition
2231 \brief the position of the tabs in tabbed view mode.
2232 \since 4.4
2233
2234 Possible values for this property are described by the
2235 QTabWidget::TabPosition enum.
2236
2237 \sa QTabWidget::TabPosition, setViewMode()
2238*/
2239QTabWidget::TabPosition QMdiArea::tabPosition() const
2240{
2241 Q_D(const QMdiArea);
2242 return d->tabPosition;
2243}
2244
2245void QMdiArea::setTabPosition(QTabWidget::TabPosition position)
2246{
2247 Q_D(QMdiArea);
2248 if (d->tabPosition == position)
2249 return;
2250
2251 d->tabPosition = position;
2252 d->refreshTabBar();
2253}
2254#endif // QT_CONFIG(tabwidget)
2255
2256/*!
2257 \reimp
2258*/
2259void QMdiArea::childEvent(QChildEvent *childEvent)
2260{
2261 Q_D(QMdiArea);
2262 if (childEvent->type() == QEvent::ChildPolished) {
2263 if (QMdiSubWindow *mdiChild = qobject_cast<QMdiSubWindow *>(object: childEvent->child())) {
2264 if (d->childWindows.indexOf(t: mdiChild) == -1)
2265 d->appendChild(child: mdiChild);
2266 }
2267 }
2268}
2269
2270/*!
2271 \reimp
2272*/
2273void QMdiArea::resizeEvent(QResizeEvent *resizeEvent)
2274{
2275 Q_D(QMdiArea);
2276 if (d->childWindows.isEmpty()) {
2277 resizeEvent->ignore();
2278 return;
2279 }
2280
2281#if QT_CONFIG(tabbar)
2282 d->updateTabBarGeometry();
2283#endif
2284
2285 // Re-tile the views if we're in tiled mode. Re-tile means we will change
2286 // the geometry of the children, which in turn means 'isSubWindowsTiled'
2287 // is set to false, so we have to update the state at the end.
2288 if (d->isSubWindowsTiled) {
2289 d->tileCalledFromResizeEvent = true;
2290 tileSubWindows();
2291 d->tileCalledFromResizeEvent = false;
2292 d->isSubWindowsTiled = true;
2293 d->startResizeTimer();
2294 // We don't have scroll bars or any maximized views.
2295 return;
2296 }
2297
2298 // Resize maximized views.
2299 bool hasMaximizedSubWindow = false;
2300 // Take a copy because child->resize() may call QCoreApplication::sendEvent()
2301 // which may invoke unknown code, that could e.g. recurse into the class
2302 // modifying d->childWindows.
2303 const auto subWindows = d->childWindows;
2304 for (QMdiSubWindow *child : subWindows) {
2305 if (sanityCheck(child, where: "QMdiArea::resizeEvent") && child->isMaximized()
2306 && child->size() != resizeEvent->size()) {
2307 auto realSize = resizeEvent->size();
2308 const auto minSizeHint = child->minimumSizeHint();
2309 // QMdiSubWindow is no tlw so minimumSize() is not set by the layout manager
2310 // and therefore we have to take care by ourself that we're not getting smaller
2311 // than allowed
2312 if (minSizeHint.isValid())
2313 realSize = realSize.expandedTo(otherSize: minSizeHint);
2314 child->resize(realSize);
2315 if (!hasMaximizedSubWindow)
2316 hasMaximizedSubWindow = true;
2317 }
2318 }
2319
2320 d->updateScrollBars();
2321
2322 // Minimized views are stacked under maximized views so there's
2323 // no need to re-arrange minimized views on-demand. Start a timer
2324 // just to make things faster with subsequent resize events.
2325 if (hasMaximizedSubWindow)
2326 d->startResizeTimer();
2327 else
2328 d->arrangeMinimizedSubWindows();
2329}
2330
2331/*!
2332 \reimp
2333*/
2334void QMdiArea::timerEvent(QTimerEvent *timerEvent)
2335{
2336 Q_D(QMdiArea);
2337 if (timerEvent->id() == d->resizeTimer.id()) {
2338 d->resizeTimer.stop();
2339 d->arrangeMinimizedSubWindows();
2340 } else if (timerEvent->id() == d->tabToPreviousTimer.id()) {
2341 d->tabToPreviousTimer.stop();
2342 if (d->indexToHighlighted < 0)
2343 return;
2344#if QT_CONFIG(rubberband)
2345 // We're not doing a "quick switch" ... show rubber band.
2346 Q_ASSERT(d->indexToHighlighted < d->childWindows.size());
2347 Q_ASSERT(d->rubberBand);
2348 d->showRubberBandFor(subWindow: d->childWindows.at(i: d->indexToHighlighted));
2349#endif
2350 }
2351}
2352
2353/*!
2354 \reimp
2355*/
2356void QMdiArea::showEvent(QShowEvent *showEvent)
2357{
2358 Q_D(QMdiArea);
2359 if (!d->pendingRearrangements.isEmpty()) {
2360 bool skipPlacement = false;
2361 // Take a copy because d->rearrange() may modify d->pendingRearrangements
2362 const auto pendingRearrange = d->pendingRearrangements;
2363 for (Rearranger *rearranger : pendingRearrange) {
2364 // If this is the case, we don't have to lay out pending child windows
2365 // since the rearranger will find a placement for them.
2366 if (rearranger->type() != Rearranger::IconTiler && !skipPlacement)
2367 skipPlacement = true;
2368 d->rearrange(rearranger);
2369 }
2370 d->pendingRearrangements.clear();
2371
2372 if (skipPlacement && !d->pendingPlacements.isEmpty())
2373 d->pendingPlacements.clear();
2374 }
2375
2376 if (!d->pendingPlacements.isEmpty()) {
2377 // Nothing obvious in the loop body changes the container (in this code path)
2378 // during iteration, this is calling into a non-const method that does change
2379 // the container when called from other places. So take a copy anyway for good
2380 // measure.
2381 const auto copy = d->pendingPlacements;
2382 for (QMdiSubWindow *window : copy) {
2383 if (!window)
2384 continue;
2385 if (d->viewMode == TabbedView && window->d_func()->isActive && !d->active) {
2386 d->showActiveWindowMaximized = true;
2387 d->emitWindowActivated(activeWindow: window); // Also maximizes the window
2388 continue;
2389 }
2390 if (!window->testAttribute(attribute: Qt::WA_Resized)) {
2391 QSize newSize(window->sizeHint().boundedTo(otherSize: viewport()->size()));
2392 window->resize(newSize.expandedTo(otherSize: qSmartMinSize(w: window)));
2393 }
2394 if (!window->testAttribute(attribute: Qt::WA_Moved) && !window->isMinimized()
2395 && !window->isMaximized()) {
2396 d->place(placer: d->placer, child: window);
2397 }
2398 }
2399 d->pendingPlacements.clear();
2400 }
2401
2402 d->setChildActivationEnabled(enable: true);
2403 d->activateCurrentWindow();
2404
2405 QAbstractScrollArea::showEvent(event: showEvent);
2406}
2407
2408/*!
2409 \reimp
2410*/
2411bool QMdiArea::viewportEvent(QEvent *event)
2412{
2413 Q_D(QMdiArea);
2414 switch (event->type()) {
2415 case QEvent::ChildRemoved: {
2416 d->isSubWindowsTiled = false;
2417 QObject *removedChild = static_cast<QChildEvent *>(event)->child();
2418 for (int i = 0; i < d->childWindows.size(); ++i) {
2419 QObject *child = d->childWindows.at(i);
2420 if (!child || child == removedChild || !child->parent()
2421 || child->parent() != viewport()) {
2422 if (!testOption(option: DontMaximizeSubWindowOnActivation)) {
2423 // In this case we can only rely on the child being a QObject
2424 // (or 0), but let's try and see if we can get more information.
2425 QWidget *mdiChild = qobject_cast<QWidget *>(o: removedChild);
2426 if (mdiChild && mdiChild->isMaximized())
2427 d->showActiveWindowMaximized = true;
2428 }
2429 d->disconnectSubWindow(subWindow: child);
2430 const bool activeRemoved = i == d->indicesToActivatedChildren.at(i: 0);
2431 d->childWindows.removeAt(i);
2432 d->indicesToActivatedChildren.removeAll(t: i);
2433 d->updateActiveWindow(removedIndex: i, activeRemoved);
2434 d->arrangeMinimizedSubWindows();
2435 break;
2436 }
2437 }
2438 d->updateScrollBars();
2439 break;
2440 }
2441 case QEvent::Destroy:
2442 d->isSubWindowsTiled = false;
2443 d->resetActiveWindow();
2444 d->childWindows.clear();
2445 qWarning(msg: "QMdiArea: Deleting the view port is undefined, use setViewport instead.");
2446 break;
2447 default:
2448 break;
2449 }
2450 return QAbstractScrollArea::viewportEvent(event);
2451}
2452
2453/*!
2454 \reimp
2455*/
2456void QMdiArea::scrollContentsBy(int dx, int dy)
2457{
2458 Q_D(QMdiArea);
2459 const bool wasSubWindowsTiled = d->isSubWindowsTiled;
2460 d->ignoreGeometryChange = true;
2461 viewport()->scroll(dx: isLeftToRight() ? dx : -dx, dy);
2462 d->arrangeMinimizedSubWindows();
2463 d->ignoreGeometryChange = false;
2464 if (wasSubWindowsTiled)
2465 d->isSubWindowsTiled = true;
2466}
2467
2468/*!
2469 Arranges all child windows in a tile pattern.
2470
2471 \sa cascadeSubWindows()
2472*/
2473void QMdiArea::tileSubWindows()
2474{
2475 Q_D(QMdiArea);
2476 if (!d->regularTiler)
2477 d->regularTiler = new RegularTiler;
2478 d->rearrange(rearranger: d->regularTiler);
2479}
2480
2481/*!
2482 Arranges all the child windows in a cascade pattern.
2483
2484 \sa tileSubWindows()
2485*/
2486void QMdiArea::cascadeSubWindows()
2487{
2488 Q_D(QMdiArea);
2489 if (!d->cascader)
2490 d->cascader = new SimpleCascader;
2491 d->rearrange(rearranger: d->cascader);
2492}
2493
2494/*!
2495 \reimp
2496*/
2497bool QMdiArea::event(QEvent *event)
2498{
2499 Q_D(QMdiArea);
2500 switch (event->type()) {
2501 case QEvent::WindowActivate: {
2502 d->isActivated = true;
2503 if (d->childWindows.isEmpty())
2504 break;
2505 if (!d->active)
2506 d->activateCurrentWindow();
2507 d->setChildActivationEnabled(enable: false, onlyNextActivationEvent: true);
2508 break;
2509 }
2510 case QEvent::WindowDeactivate:
2511 d->isActivated = false;
2512 d->setChildActivationEnabled(enable: false, onlyNextActivationEvent: true);
2513 break;
2514 case QEvent::StyleChange:
2515 // Re-tile the views if we're in tiled mode. Re-tile means we will change
2516 // the geometry of the children, which in turn means 'isSubWindowsTiled'
2517 // is set to false, so we have to update the state at the end.
2518 if (d->isSubWindowsTiled) {
2519 tileSubWindows();
2520 d->isSubWindowsTiled = true;
2521 }
2522 break;
2523 case QEvent::WindowIconChange: {
2524 // Take a copy because QCoreApplication::sendEvent() may call unknown code,
2525 // that may cause recursing into the class
2526 const auto subWindows = d->childWindows;
2527 for (QMdiSubWindow *window : subWindows) {
2528 if (sanityCheck(child: window, where: "QMdiArea::WindowIconChange"))
2529 QCoreApplication::sendEvent(receiver: window, event);
2530 }
2531 break;
2532 }
2533 case QEvent::Hide:
2534 d->setActive(subWindow: d->active, active: false, changeFocus: false);
2535 d->setChildActivationEnabled(enable: false);
2536 break;
2537#if QT_CONFIG(tabbar)
2538 case QEvent::LayoutDirectionChange:
2539 d->updateTabBarGeometry();
2540 break;
2541#endif
2542 default:
2543 break;
2544 }
2545 return QAbstractScrollArea::event(event);
2546}
2547
2548/*!
2549 \reimp
2550*/
2551bool QMdiArea::eventFilter(QObject *object, QEvent *event)
2552{
2553 if (!object)
2554 return QAbstractScrollArea::eventFilter(object, event);
2555
2556 Q_D(QMdiArea);
2557 // Global key events with Ctrl modifier.
2558 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
2559
2560 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
2561 // Ignore key events without a Ctrl modifier (except for press/release on the modifier itself).
2562 if (!(keyEvent->modifiers() & Qt::ControlModifier) && keyEvent->key() != Qt::Key_Control)
2563 return QAbstractScrollArea::eventFilter(object, event);
2564
2565 // Find closest mdi area (in case we have a nested workspace).
2566 QMdiArea *area = mdiAreaParent(widget: static_cast<QWidget *>(object));
2567 if (!area)
2568 return QAbstractScrollArea::eventFilter(object, event);
2569
2570 const bool keyPress = (event->type() == QEvent::KeyPress);
2571
2572 // 1) Ctrl-Tab once -> activate the previously active window.
2573 // 2) Ctrl-Tab (Tab, Tab, ...) -> iterate through all windows (activateNextSubWindow()).
2574 // 3) Ctrl-Shift-Tab (Tab, Tab, ...) -> iterate through all windows in the opposite
2575 // direction (activatePreviousSubWindow())
2576 switch (keyEvent->key()) {
2577 case Qt::Key_Control:
2578 if (keyPress)
2579 area->d_func()->startTabToPreviousTimer();
2580 else
2581 area->d_func()->activateHighlightedWindow();
2582 break;
2583 case Qt::Key_Tab:
2584 case Qt::Key_Backtab:
2585 if (keyPress)
2586 area->d_func()->highlightNextSubWindow(increaseFactor: keyEvent->key() == Qt::Key_Tab ? 1 : -1);
2587 return true;
2588#if QT_CONFIG(rubberband)
2589 case Qt::Key_Escape:
2590 area->d_func()->hideRubberBand();
2591 break;
2592#endif
2593 default:
2594 break;
2595 }
2596 return QAbstractScrollArea::eventFilter(object, event);
2597 }
2598
2599 QMdiSubWindow *subWindow = qobject_cast<QMdiSubWindow *>(object);
2600
2601 if (!subWindow) {
2602 // QApplication events:
2603 if (event->type() == QEvent::ApplicationActivate && !d->active
2604 && isVisible() && !window()->isMinimized()) {
2605 d->activateCurrentWindow();
2606 } else if (event->type() == QEvent::ApplicationDeactivate && d->active) {
2607 d->setActive(subWindow: d->active, active: false, changeFocus: false);
2608 }
2609 return QAbstractScrollArea::eventFilter(object, event);
2610 }
2611
2612 if (subWindow->mdiArea() != this)
2613 return QAbstractScrollArea::eventFilter(object, event);
2614
2615 // QMdiSubWindow events:
2616 switch (event->type()) {
2617 case QEvent::Move:
2618 case QEvent::Resize:
2619 if (d->tileCalledFromResizeEvent)
2620 break;
2621 d->updateScrollBars();
2622 if (!subWindow->isMinimized())
2623 d->isSubWindowsTiled = false;
2624 break;
2625 case QEvent::Show:
2626#if QT_CONFIG(tabbar)
2627 if (d->tabBar) {
2628 const int tabIndex = d->childWindows.indexOf(t: subWindow);
2629 if (!d->tabBar->isTabEnabled(index: tabIndex))
2630 d->tabBar->setTabEnabled(index: tabIndex, enabled: true);
2631 }
2632#endif // QT_CONFIG(tabbar)
2633 Q_FALLTHROUGH();
2634 case QEvent::Hide:
2635 // Do not reset the isSubWindowsTiled flag if the event is a spontaneous system window event.
2636 // This ensures that tiling will be performed during the resizeEvent after an application
2637 // window minimize (hide) and then restore (show).
2638 if (!event->spontaneous())
2639 d->isSubWindowsTiled = false;
2640 break;
2641#if QT_CONFIG(rubberband)
2642 case QEvent::Close:
2643 if (d->childWindows.indexOf(t: subWindow) == d->indexToHighlighted)
2644 d->hideRubberBand();
2645 break;
2646#endif
2647#if QT_CONFIG(tabbar)
2648 case QEvent::WindowTitleChange:
2649 case QEvent::ModifiedChange:
2650 if (d->tabBar)
2651 d->tabBar->setTabText(index: d->childWindows.indexOf(t: subWindow), text: tabTextFor(subWindow));
2652 break;
2653 case QEvent::WindowIconChange:
2654 if (d->tabBar)
2655 d->tabBar->setTabIcon(index: d->childWindows.indexOf(t: subWindow), icon: subWindow->windowIcon());
2656 break;
2657#endif // QT_CONFIG(tabbar)
2658 default:
2659 break;
2660 }
2661 return QAbstractScrollArea::eventFilter(object, event);
2662}
2663
2664/*!
2665 \reimp
2666*/
2667void QMdiArea::paintEvent(QPaintEvent *paintEvent)
2668{
2669 Q_D(QMdiArea);
2670 QPainter painter(d->viewport);
2671 for (const QRect &exposedRect : paintEvent->region())
2672 painter.fillRect(exposedRect, d->background);
2673}
2674
2675/*!
2676 This slot is called by QAbstractScrollArea after setViewport() has been
2677 called. Reimplement this function in a subclass of QMdiArea to
2678 initialize the new \a viewport before it is used.
2679
2680 \sa setViewport()
2681*/
2682void QMdiArea::setupViewport(QWidget *viewport)
2683{
2684 Q_D(QMdiArea);
2685 if (viewport)
2686 viewport->setAttribute(Qt::WA_OpaquePaintEvent, on: d->background.isOpaque());
2687 // Take a copy because the child->setParent() call below may call QCoreApplication::sendEvent()
2688 // which may call unknown code that could e.g. recurse into the class modifying d->childWindows.
2689 const auto subWindows = d->childWindows;
2690 for (QMdiSubWindow *child : subWindows) {
2691 if (!sanityCheck(child, where: "QMdiArea::setupViewport"))
2692 continue;
2693 child->setParent(parent: viewport, f: child->windowFlags());
2694 }
2695}
2696
2697QT_END_NAMESPACE
2698
2699#include "moc_qmdiarea.cpp"
2700

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