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