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

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