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 | |
141 | QT_BEGIN_NAMESPACE |
142 | |
143 | using namespace Qt::StringLiterals; |
144 | using namespace QMdi; |
145 | |
146 | // Asserts in debug mode, gives warning otherwise. |
147 | static 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 | |
158 | static 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 | |
175 | static 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 | |
194 | static 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). |
208 | static 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) |
223 | QTabBar::Shape _q_tb_tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position); |
224 | #endif // QT_CONFIG(tabwidget) |
225 | |
226 | static 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 | */ |
245 | void 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 | */ |
289 | void 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 | */ |
332 | void 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 | */ |
364 | int 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 | */ |
380 | QRect 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 | */ |
398 | QList<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 | */ |
436 | QList<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 | */ |
457 | QList<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 | */ |
484 | QPoint 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 | */ |
503 | QPoint 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) |
518 | class QMdiAreaTabBar : public QTabBar |
519 | { |
520 | public: |
521 | QMdiAreaTabBar(QWidget *parent) : QTabBar(parent) {} |
522 | |
523 | protected: |
524 | void mousePressEvent(QMouseEvent *event) override; |
525 | #ifndef QT_NO_CONTEXTMENU |
526 | void contextMenuEvent(QContextMenuEvent *event) override; |
527 | #endif |
528 | |
529 | private: |
530 | QMdiSubWindow *subWindowFromIndex(int index) const; |
531 | }; |
532 | |
533 | /*! |
534 | \internal |
535 | */ |
536 | void 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 | */ |
556 | void QMdiAreaTabBar::(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 | */ |
599 | QMdiSubWindow *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 | */ |
620 | QMdiAreaPrivate::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 | */ |
662 | void 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 | */ |
702 | void 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 | |
735 | void 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 | |
759 | void 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 | |
770 | void 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 | */ |
783 | void 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 | */ |
836 | void 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 | */ |
875 | void 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 | */ |
931 | void QMdiAreaPrivate::arrangeMinimizedSubWindows() |
932 | { |
933 | if (!iconTiler) |
934 | iconTiler = new IconTiler; |
935 | rearrange(rearranger: iconTiler); |
936 | } |
937 | |
938 | /*! |
939 | \internal |
940 | */ |
941 | void 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 | */ |
970 | void 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 | |
979 | void 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 | */ |
997 | void 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 | */ |
1049 | void 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 | */ |
1074 | void 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 | */ |
1123 | void 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 | */ |
1189 | void 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 | |
1217 | QRect 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 | */ |
1281 | bool QMdiAreaPrivate::scrollBarsEnabled() const |
1282 | { |
1283 | return hbarpolicy != Qt::ScrollBarAlwaysOff || vbarpolicy != Qt::ScrollBarAlwaysOff; |
1284 | } |
1285 | |
1286 | /*! |
1287 | \internal |
1288 | */ |
1289 | bool 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 | */ |
1307 | void 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 | */ |
1323 | void 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 | |
1343 | QList<QMdiSubWindow*> |
1344 | QMdiAreaPrivate::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 | */ |
1387 | void 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 | */ |
1400 | QMdiSubWindow *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 | */ |
1454 | void 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) |
1495 | void 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 | */ |
1515 | void 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 | */ |
1594 | void 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 | */ |
1649 | void 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 | */ |
1668 | QMdiArea::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 | */ |
1683 | QMdiArea::~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 | */ |
1702 | QSize 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 | */ |
1728 | QSize 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 | */ |
1753 | QMdiSubWindow *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 | */ |
1785 | QMdiSubWindow *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 | */ |
1797 | void 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 | */ |
1823 | void 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 | */ |
1841 | QList<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 | */ |
1857 | void 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 | */ |
1884 | void 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 | */ |
1902 | void 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 | */ |
1936 | QMdiSubWindow *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 | */ |
1981 | void 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 | */ |
2034 | QBrush QMdiArea::background() const |
2035 | { |
2036 | return d_func()->background; |
2037 | } |
2038 | |
2039 | void 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 | */ |
2061 | QMdiArea::WindowOrder QMdiArea::activationOrder() const |
2062 | { |
2063 | Q_D(const QMdiArea); |
2064 | return d->activationOrder; |
2065 | } |
2066 | |
2067 | void 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 | */ |
2080 | void 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 | */ |
2091 | bool 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 | */ |
2105 | QMdiArea::ViewMode QMdiArea::viewMode() const |
2106 | { |
2107 | Q_D(const QMdiArea); |
2108 | return d->viewMode; |
2109 | } |
2110 | |
2111 | void 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 | */ |
2127 | bool QMdiArea::documentMode() const |
2128 | { |
2129 | Q_D(const QMdiArea); |
2130 | return d->documentMode; |
2131 | } |
2132 | |
2133 | void 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 | */ |
2152 | bool QMdiArea::tabsClosable() const |
2153 | { |
2154 | Q_D(const QMdiArea); |
2155 | return d->tabsClosable; |
2156 | } |
2157 | |
2158 | void 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 | */ |
2177 | bool QMdiArea::tabsMovable() const |
2178 | { |
2179 | Q_D(const QMdiArea); |
2180 | return d->tabsMovable; |
2181 | } |
2182 | |
2183 | void 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 | */ |
2205 | QTabWidget::TabShape QMdiArea::tabShape() const |
2206 | { |
2207 | Q_D(const QMdiArea); |
2208 | return d->tabShape; |
2209 | } |
2210 | |
2211 | void 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 | */ |
2231 | QTabWidget::TabPosition QMdiArea::tabPosition() const |
2232 | { |
2233 | Q_D(const QMdiArea); |
2234 | return d->tabPosition; |
2235 | } |
2236 | |
2237 | void 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 | */ |
2251 | void 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 | */ |
2265 | void 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 | */ |
2326 | void 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 | */ |
2350 | void 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 | */ |
2400 | bool 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 | */ |
2445 | void 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 | */ |
2462 | void 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 | */ |
2475 | void 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 | */ |
2486 | bool 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 | */ |
2540 | bool 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 | */ |
2656 | void 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 | */ |
2671 | void 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 | |
2686 | QT_END_NAMESPACE |
2687 | |
2688 | #include "moc_qmdiarea.cpp" |
2689 | |