diff options
Diffstat (limited to 'src/gui/widgets/qmdiarea.cpp')
-rw-r--r-- | src/gui/widgets/qmdiarea.cpp | 2597 |
1 files changed, 2597 insertions, 0 deletions
diff --git a/src/gui/widgets/qmdiarea.cpp b/src/gui/widgets/qmdiarea.cpp new file mode 100644 index 0000000..598d3b5 --- /dev/null +++ b/src/gui/widgets/qmdiarea.cpp @@ -0,0 +1,2597 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QMdiArea + \brief The QMdiArea widget provides an area in which MDI windows are displayed. + \since 4.3 + \ingroup application + \mainclass + + QMdiArea functions, essentially, like a window manager for MDI + windows. For instance, it draws the windows it manages on itself + and arranges them in a cascading or tile pattern. QMdiArea is + commonly used as the center widget in a QMainWindow to create MDI + applications, but can also be placed in any layout. The following + code adds an area to a main window: + + \snippet doc/src/snippets/mdiareasnippets.cpp 0 + + Unlike the window managers for top-level windows, all window flags + (Qt::WindowFlags) are supported by QMdiArea as long as the flags + are supported by the current widget style. If a specific flag is + not supported by the style (e.g., the + \l{Qt::}{WindowShadeButtonHint}), you can still shade the window + with showShaded(). + + Subwindows in QMdiArea are instances of QMdiSubWindow. They + are added to an MDI area with addSubWindow(). It is common to pass + a QWidget, which is set as the internal widget, to this function, + but it is also possible to pass a QMdiSubWindow directly.The class + inherits QWidget, and you can use the same API as with a normal + top-level window when programming. QMdiSubWindow also has behavior + that is specific to MDI windows. See the QMdiSubWindow class + description for more details. + + A subwindow becomes active when it gets the keyboard focus, or + when setFocus() is called. The user activates a window by moving + focus in the usual ways. The MDI area emits the + subWindowActivated() signal when the active window changes, and + the activeSubWindow() function returns the active subwindow. + + The convenience function subWindowList() returns a list of all + subwindows. This information could be used in a popup menu + containing a list of windows, for example. + + The subwindows are sorted by the the current + \l{QMdiArea::}{WindowOrder}. This is used for the subWindowList() + and for activateNextSubWindow() and acivatePreviousSubWindow(). + Also, it is used when cascading or tiling the windows with + cascadeSubWindows() and tileSubWindows(). + + QMdiArea provides two built-in layout strategies for + subwindows: cascadeSubWindows() and tileSubWindows(). Both are + slots and are easily connected to menu entries. + + \table + \row \o \inlineimage mdi-cascade.png + \o \inlineimage mdi-tile.png + \endtable + + \note The default scroll bar property for QMdiArea is Qt::ScrollBarAlwaysOff. + + \sa QMdiSubWindow +*/ + +/*! + \fn QMdiArea::subWindowActivated(QMdiSubWindow *window) + + QMdiArea emits this signal after \a window has been activated. When \a + window is 0, QMdiArea has just deactivated its last active window, and + there are no active windows on the workspace. + + \sa QMdiArea::activeSubWindow() +*/ + +/*! + \enum QMdiArea::AreaOption + + This enum describes options that customize the behavior of the + QMdiArea. + + \value DontMaximizeSubWindowOnActivation When the active subwindow + is maximized, the default behavior is to maximize the next + subwindow that is activated. Set this option if you do not want + this behavior. +*/ + +/*! + \enum QMdiArea::WindowOrder + + Specifies the criteria to use for ordering the list of child windows + returned by subWindowList(). The functions cascadeSubWindows() and + tileSubWindows() follow this order when arranging the windows. + + \value CreationOrder The windows are returned in the order of + their creation. + + \value StackingOrder The windows are returned in the order in + which they are stacked, with the top-most window being last in + the list. + + \value ActivationHistoryOrder The windows are returned in the order in + which they were activated. + + \sa subWindowList() +*/ + +/*! + \enum QMdiArea::ViewMode + \since 4.4 + + This enum describes the view mode of the area; i.e. how sub-windows + will be displayed. + + \value SubWindowView Display sub-windows with window frames (default). + \value TabbedView Display sub-windows with tabs in a tab bar. + + \sa setViewMode() +*/ + +#include "qmdiarea_p.h" + +#ifndef QT_NO_MDIAREA + +#include <QApplication> +#include <QStyle> +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) +#include <QMacStyle> +#endif +#include <QChildEvent> +#include <QResizeEvent> +#include <QScrollBar> +#include <QtAlgorithms> +#include <QMutableListIterator> +#include <QPainter> +#include <QFontMetrics> +#include <QStyleOption> +#include <QDesktopWidget> +#include <QDebug> +#include <qmath.h> +#include <private/qlayoutengine_p.h> + +QT_BEGIN_NAMESPACE + +using namespace QMdi; + +// Asserts in debug mode, gives warning otherwise. +static bool sanityCheck(const QMdiSubWindow * const child, const char *where) +{ + if (!child) { + const char error[] = "null pointer"; + Q_ASSERT_X(false, where, error); + qWarning("%s:%s", where, error); + return false; + } + return true; +} + +static bool sanityCheck(const QList<QWidget *> &widgets, const int index, const char *where) +{ + if (index < 0 || index >= widgets.size()) { + const char error[] = "index out of range"; + Q_ASSERT_X(false, where, error); + qWarning("%s:%s", where, error); + return false; + } + if (!widgets.at(index)) { + const char error[] = "null pointer"; + Q_ASSERT_X(false, where, error); + qWarning("%s:%s", where, error); + return false; + } + return true; +} + +static void setIndex(int *index, int candidate, int min, int max, bool isIncreasing) +{ + if (!index) + return; + + if (isIncreasing) { + if (candidate > max) + *index = min; + else + *index = qMax(candidate, min); + } else { + if (candidate < min) + *index = max; + else + *index = qMin(candidate, max); + } + Q_ASSERT(*index >= min && *index <= max); +} + +static inline bool useScrollBar(const QRect &childrenRect, const QSize &maxViewportSize, + Qt::Orientation orientation) +{ + if (orientation == Qt::Horizontal) + return childrenRect.width() > maxViewportSize.width() + || childrenRect.left() < 0 + || childrenRect.right() >= maxViewportSize.width(); + else + return childrenRect.height() > maxViewportSize.height() + || childrenRect.top() < 0 + || childrenRect.bottom() >= maxViewportSize.height(); +} + +// Returns the closest mdi area containing the widget (if any). +static inline QMdiArea *mdiAreaParent(QWidget *widget) +{ + if (!widget) + return 0; + + QWidget *parent = widget->parentWidget(); + while (parent) { + if (QMdiArea *area = qobject_cast<QMdiArea *>(parent)) + return area; + parent = parent->parentWidget(); + } + return 0; +} + +#ifndef QT_NO_TABWIDGET +static inline QTabBar::Shape tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position) +{ + const bool rounded = (shape == QTabWidget::Rounded); + if (position == QTabWidget::North) + return rounded ? QTabBar::RoundedNorth : QTabBar::TriangularNorth; + if (position == QTabWidget::South) + return rounded ? QTabBar::RoundedSouth : QTabBar::TriangularSouth; + if (position == QTabWidget::East) + return rounded ? QTabBar::RoundedEast : QTabBar::TriangularEast; + if (position == QTabWidget::West) + return rounded ? QTabBar::RoundedWest : QTabBar::TriangularWest; + return QTabBar::RoundedNorth; +} +#endif // QT_NO_TABWIDGET + +static inline QString tabTextFor(QMdiSubWindow *subWindow) +{ + if (!subWindow) + return QString(); + + QString title = subWindow->windowTitle(); + if (subWindow->isWindowModified()) { + title.replace(QLatin1String("[*]"), QLatin1String("*")); + } else { + extern QString qt_setWindowTitle_helperHelper(const QString&, const QWidget*); + title = qt_setWindowTitle_helperHelper(title, subWindow); + } + + return title.isEmpty() ? QMdiArea::tr("(Untitled)") : title; +} + +/*! + \internal +*/ +void RegularTiler::rearrange(QList<QWidget *> &widgets, const QRect &domain) const +{ + if (widgets.isEmpty()) + return; + + const int n = widgets.size(); + const int ncols = qMax(qCeil(qSqrt(qreal(n))), 1); + const int nrows = qMax((n % ncols) ? (n / ncols + 1) : (n / ncols), 1); + const int nspecial = (n % ncols) ? (ncols - n % ncols) : 0; + const int dx = domain.width() / ncols; + const int dy = domain.height() / nrows; + + int i = 0; + for (int row = 0; row < nrows; ++row) { + const int y1 = int(row * (dy + 1)); + for (int col = 0; col < ncols; ++col) { + if (row == 1 && col < nspecial) + continue; + const int x1 = int(col * (dx + 1)); + int x2 = int(x1 + dx); + int y2 = int(y1 + dy); + if (row == 0 && col < nspecial) { + y2 *= 2; + if (nrows != 2) + y2 += 1; + else + y2 = domain.bottom(); + } + if (col == ncols - 1 && x2 != domain.right()) + x2 = domain.right(); + if (row == nrows - 1 && y2 != domain.bottom()) + y2 = domain.bottom(); + if (!sanityCheck(widgets, i, "RegularTiler")) + continue; + QWidget *widget = widgets.at(i++); + QRect newGeometry = QRect(QPoint(x1, y1), QPoint(x2, y2)); + widget->setGeometry(QStyle::visualRect(widget->layoutDirection(), domain, newGeometry)); + } + } +} + +/*! + \internal +*/ +void SimpleCascader::rearrange(QList<QWidget *> &widgets, const QRect &domain) const +{ + if (widgets.isEmpty()) + return; + + // Tunables: + const int topOffset = 0; + const int bottomOffset = 50; + const int leftOffset = 0; + const int rightOffset = 100; + const int dx = 10; + + QStyleOptionTitleBar options; + options.initFrom(widgets.at(0)); + int titleBarHeight = widgets.at(0)->style()->pixelMetric(QStyle::PM_TitleBarHeight, &options, widgets.at(0)); +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + // ### Remove this after the mac style has been fixed + if (qobject_cast<QMacStyle *>(widgets.at(0)->style())) + titleBarHeight -= 4; +#endif + const QFontMetrics fontMetrics = QFontMetrics(QApplication::font("QWorkspaceTitleBar")); + const int dy = qMax(titleBarHeight - (titleBarHeight - fontMetrics.height()) / 2, 1); + + const int n = widgets.size(); + const int nrows = qMax((domain.height() - (topOffset + bottomOffset)) / dy, 1); + const int ncols = qMax(n / nrows + ((n % nrows) ? 1 : 0), 1); + const int dcol = (domain.width() - (leftOffset + rightOffset)) / ncols; + + int i = 0; + for (int row = 0; row < nrows; ++row) { + for (int col = 0; col < ncols; ++col) { + const int x = leftOffset + row * dx + col * dcol; + const int y = topOffset + row * dy; + if (!sanityCheck(widgets, i, "SimpleCascader")) + continue; + QWidget *widget = widgets.at(i++); + QRect newGeometry = QRect(QPoint(x, y), widget->sizeHint()); + widget->setGeometry(QStyle::visualRect(widget->layoutDirection(), domain, newGeometry)); + if (i == n) + return; + } + } +} + +/*! + \internal +*/ +void IconTiler::rearrange(QList<QWidget *> &widgets, const QRect &domain) const +{ + if (widgets.isEmpty() || !sanityCheck(widgets, 0, "IconTiler")) + return; + + const int n = widgets.size(); + const int width = widgets.at(0)->width(); + const int height = widgets.at(0)->height(); + const int ncols = qMax(domain.width() / width, 1); + const int nrows = n / ncols + ((n % ncols) ? 1 : 0); + + int i = 0; + for (int row = 0; row < nrows; ++row) { + for (int col = 0; col < ncols; ++col) { + const int x = col * width; + const int y = domain.height() - height - row * height; + if (!sanityCheck(widgets, i, "IconTiler")) + continue; + QWidget *widget = widgets.at(i++); + QPoint newPos(x, y); + QRect newGeometry = QRect(newPos.x(), newPos.y(), widget->width(), widget->height()); + widget->setGeometry(QStyle::visualRect(widget->layoutDirection(), domain, newGeometry)); + if (i == n) + return; + } + } +} + +/*! + \internal + Calculates the accumulated overlap (intersection area) between 'source' and 'rects'. +*/ +int MinOverlapPlacer::accumulatedOverlap(const QRect &source, const QList<QRect> &rects) +{ + int accOverlap = 0; + foreach (const QRect &rect, rects) { + QRect intersection = source.intersected(rect); + accOverlap += intersection.width() * intersection.height(); + } + return accOverlap; +} + + +/*! + \internal + Finds among 'source' the rectangle with the minimum accumulated overlap with the + rectangles in 'rects'. +*/ +QRect MinOverlapPlacer::findMinOverlapRect(const QList<QRect> &source, const QList<QRect> &rects) +{ + int minAccOverlap = -1; + QRect minAccOverlapRect; + foreach (const QRect &srcRect, source) { + const int accOverlap = accumulatedOverlap(srcRect, rects); + if (accOverlap < minAccOverlap || minAccOverlap == -1) { + minAccOverlap = accOverlap; + minAccOverlapRect = srcRect; + } + } + return minAccOverlapRect; +} + +/*! + \internal + Gets candidates for the final placement. +*/ +void MinOverlapPlacer::getCandidatePlacements(const QSize &size, const QList<QRect> &rects, + const QRect &domain,QList<QRect> &candidates) +{ + QSet<int> xset; + QSet<int> yset; + xset << domain.left() << domain.right() - size.width() + 1; + yset << domain.top(); + if (domain.bottom() - size.height() + 1 >= 0) + yset << domain.bottom() - size.height() + 1; + foreach (const QRect &rect, rects) { + xset << rect.right() + 1; + yset << rect.bottom() + 1; + } + + QList<int> xlist = xset.values(); + qSort(xlist.begin(), xlist.end()); + QList<int> ylist = yset.values(); + qSort(ylist.begin(), ylist.end()); + + foreach (int y, ylist) + foreach (int x, xlist) + candidates << QRect(QPoint(x, y), size); +} + +/*! + \internal + Finds all rectangles in 'source' not completely inside 'domain'. The result is stored + in 'result' and also removed from 'source'. +*/ +void MinOverlapPlacer::findNonInsiders(const QRect &domain, QList<QRect> &source, + QList<QRect> &result) +{ + QMutableListIterator<QRect> it(source); + while (it.hasNext()) { + const QRect srcRect = it.next(); + if (!domain.contains(srcRect)) { + result << srcRect; + it.remove(); + } + } +} + +/*! + \internal + Finds all rectangles in 'source' that overlaps 'domain' by the maximum overlap area + between 'domain' and any rectangle in 'source'. The result is stored in 'result'. +*/ +void MinOverlapPlacer::findMaxOverlappers(const QRect &domain, const QList<QRect> &source, + QList<QRect> &result) +{ + int maxOverlap = -1; + foreach (const QRect &srcRect, source) { + QRect intersection = domain.intersected(srcRect); + const int overlap = intersection.width() * intersection.height(); + if (overlap >= maxOverlap || maxOverlap == -1) { + if (overlap > maxOverlap) { + maxOverlap = overlap; + result.clear(); + } + result << srcRect; + } + } +} + +/*! + \internal + Finds among the rectangles in 'source' the best placement. Here, 'best' means the + placement that overlaps the rectangles in 'rects' as little as possible while at the + same time being as much as possible inside 'domain'. +*/ +QPoint MinOverlapPlacer::findBestPlacement(const QRect &domain, const QList<QRect> &rects, + QList<QRect> &source) +{ + QList<QRect> nonInsiders; + findNonInsiders(domain, source, nonInsiders); + + if (!source.empty()) + return findMinOverlapRect(source, rects).topLeft(); + + QList<QRect> maxOverlappers; + findMaxOverlappers(domain, nonInsiders, maxOverlappers); + return findMinOverlapRect(maxOverlappers, rects).topLeft(); +} + + +/*! + \internal + Places the rectangle defined by 'size' relative to 'rects' and 'domain' so that it + overlaps 'rects' as little as possible and 'domain' as much as possible. + Returns the position of the resulting rectangle. +*/ +QPoint MinOverlapPlacer::place(const QSize &size, const QList<QRect> &rects, + const QRect &domain) const +{ + if (size.isEmpty() || !domain.isValid()) + return QPoint(); + foreach (const QRect &rect, rects) { + if (!rect.isValid()) + return QPoint(); + } + + QList<QRect> candidates; + getCandidatePlacements(size, rects, domain, candidates); + return findBestPlacement(domain, rects, candidates); +} + +#ifndef QT_NO_TABBAR +class QMdiAreaTabBar : public QTabBar +{ +public: + QMdiAreaTabBar(QWidget *parent) : QTabBar(parent) {} + +protected: + void mousePressEvent(QMouseEvent *event); +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *event); +#endif + +private: + QMdiSubWindow *subWindowFromIndex(int index) const; +}; + +/*! + \internal +*/ +void QMdiAreaTabBar::mousePressEvent(QMouseEvent *event) +{ + if (event->button() != Qt::MidButton) { + QTabBar::mousePressEvent(event); + return; + } + + QMdiSubWindow *subWindow = subWindowFromIndex(tabAt(event->pos())); + if (!subWindow) { + event->ignore(); + return; + } + + subWindow->close(); +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \internal +*/ +void QMdiAreaTabBar::contextMenuEvent(QContextMenuEvent *event) +{ + QPointer<QMdiSubWindow> subWindow = subWindowFromIndex(tabAt(event->pos())); + if (!subWindow || subWindow->isHidden()) { + event->ignore(); + return; + } + +#ifndef QT_NO_MENU + QMdiSubWindowPrivate *subWindowPrivate = subWindow->d_func(); + if (!subWindowPrivate->systemMenu) { + event->ignore(); + return; + } + + QMdiSubWindow *currentSubWindow = subWindowFromIndex(currentIndex()); + Q_ASSERT(currentSubWindow); + + // We don't want these actions to show up in the system menu when the + // current sub-window is maximized, i.e. covers the entire viewport. + if (currentSubWindow->isMaximized()) { + subWindowPrivate->setVisible(QMdiSubWindowPrivate::MoveAction, false); + subWindowPrivate->setVisible(QMdiSubWindowPrivate::ResizeAction, false); + subWindowPrivate->setVisible(QMdiSubWindowPrivate::MinimizeAction, false); + subWindowPrivate->setVisible(QMdiSubWindowPrivate::MaximizeAction, false); + subWindowPrivate->setVisible(QMdiSubWindowPrivate::MaximizeAction, false); + subWindowPrivate->setVisible(QMdiSubWindowPrivate::StayOnTopAction, false); + } + + // Show system menu. + subWindowPrivate->systemMenu->exec(event->globalPos()); + if (!subWindow) + return; + + // Restore action visibility. + subWindowPrivate->updateActions(); +#endif // QT_NO_MENU +} +#endif // QT_NO_CONTEXTMENU + +/*! + \internal +*/ +QMdiSubWindow *QMdiAreaTabBar::subWindowFromIndex(int index) const +{ + if (index < 0 || index >= count()) + return 0; + + QMdiArea *mdiArea = qobject_cast<QMdiArea *>(parentWidget()); + Q_ASSERT(mdiArea); + + const QList<QMdiSubWindow *> subWindows = mdiArea->subWindowList(); + Q_ASSERT(index < subWindows.size()); + + QMdiSubWindow *subWindow = mdiArea->subWindowList().at(index); + Q_ASSERT(subWindow); + + return subWindow; +} +#endif // QT_NO_TABBAR + +/*! + \internal +*/ +QMdiAreaPrivate::QMdiAreaPrivate() + : cascader(0), + regularTiler(0), + iconTiler(0), + placer(0), +#ifndef QT_NO_RUBBERBAND + rubberBand(0), +#endif +#ifndef QT_NO_TABBAR + tabBar(0), +#endif + activationOrder(QMdiArea::CreationOrder), + viewMode(QMdiArea::SubWindowView), +#ifndef QT_NO_TABBAR + documentMode(false), +#endif +#ifndef QT_NO_TABWIDGET + tabShape(QTabWidget::Rounded), + tabPosition(QTabWidget::North), +#endif + ignoreGeometryChange(false), + ignoreWindowStateChange(false), + isActivated(false), + isSubWindowsTiled(false), + showActiveWindowMaximized(false), + tileCalledFromResizeEvent(false), + updatesDisabledByUs(false), + inViewModeChange(false), + indexToNextWindow(-1), + indexToPreviousWindow(-1), + indexToHighlighted(-1), + indexToLastActiveTab(-1), + resizeTimerId(-1), + tabToPreviousTimerId(-1) +{ +} + +/*! + \internal +*/ +void QMdiAreaPrivate::_q_deactivateAllWindows(QMdiSubWindow *aboutToActivate) +{ + if (ignoreWindowStateChange) + return; + + Q_Q(QMdiArea); + if (!aboutToActivate) + aboutToBecomeActive = qobject_cast<QMdiSubWindow *>(q->sender()); + else + aboutToBecomeActive = aboutToActivate; + Q_ASSERT(aboutToBecomeActive); + + foreach (QMdiSubWindow *child, childWindows) { + if (!sanityCheck(child, "QMdiArea::deactivateAllWindows") || aboutToBecomeActive == child) + continue; + // We don't want to handle signals caused by child->showNormal(). + ignoreWindowStateChange = true; + if(!(options & QMdiArea::DontMaximizeSubWindowOnActivation) && !showActiveWindowMaximized) + showActiveWindowMaximized = child->isMaximized() && child->isVisible(); + if (showActiveWindowMaximized && child->isMaximized()) { + if (q->updatesEnabled()) { + updatesDisabledByUs = true; + q->setUpdatesEnabled(false); + } + child->showNormal(); + } + if (child->isMinimized() && !child->isShaded() && !windowStaysOnTop(child)) + child->lower(); + ignoreWindowStateChange = false; + child->d_func()->setActive(false); + } +} + +/*! + \internal +*/ +void QMdiAreaPrivate::_q_processWindowStateChanged(Qt::WindowStates oldState, + Qt::WindowStates newState) +{ + if (ignoreWindowStateChange) + return; + + Q_Q(QMdiArea); + QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(q->sender()); + if (!child) + return; + + // windowActivated + if (!(oldState & Qt::WindowActive) && (newState & Qt::WindowActive)) + emitWindowActivated(child); + // windowDeactivated + else if ((oldState & Qt::WindowActive) && !(newState & Qt::WindowActive)) + resetActiveWindow(child); + + // windowMinimized + if (!(oldState & Qt::WindowMinimized) && (newState & Qt::WindowMinimized)) { + isSubWindowsTiled = false; + arrangeMinimizedSubWindows(); + // windowMaximized + } else if (!(oldState & Qt::WindowMaximized) && (newState & Qt::WindowMaximized)) { + internalRaise(child); + // windowRestored + } else if (!(newState & (Qt::WindowMaximized | Qt::WindowMinimized))) { + internalRaise(child); + if (oldState & Qt::WindowMinimized) + arrangeMinimizedSubWindows(); + } +} + +void QMdiAreaPrivate::_q_currentTabChanged(int index) +{ +#ifdef QT_NO_TABBAR + Q_UNUSED(index); +#else + if (!tabBar || index < 0) + return; + + // If the previous active sub-window was hidden, disable the tab. + if (indexToLastActiveTab >= 0 && indexToLastActiveTab < tabBar->count() + && indexToLastActiveTab < childWindows.count()) { + QMdiSubWindow *lastActive = childWindows.at(indexToLastActiveTab); + if (lastActive && lastActive->isHidden()) + tabBar->setTabEnabled(indexToLastActiveTab, false); + } + + indexToLastActiveTab = index; + Q_ASSERT(childWindows.size() > index); + QMdiSubWindow *subWindow = childWindows.at(index); + Q_ASSERT(subWindow); + activateWindow(subWindow); +#endif // QT_NO_TABBAR +} + +/*! + \internal +*/ +void QMdiAreaPrivate::appendChild(QMdiSubWindow *child) +{ + Q_Q(QMdiArea); + Q_ASSERT(child && childWindows.indexOf(child) == -1); + + if (child->parent() != q->viewport()) + child->setParent(q->viewport(), child->windowFlags()); + childWindows.append(QPointer<QMdiSubWindow>(child)); + + if (!child->testAttribute(Qt::WA_Resized) && q->isVisible()) { + QSize newSize(child->sizeHint().boundedTo(q->viewport()->size())); + child->resize(newSize.expandedTo(qSmartMinSize(child))); + } + + if (!placer) + placer = new MinOverlapPlacer; + place(placer, child); + + if (hbarpolicy != Qt::ScrollBarAlwaysOff) + child->setOption(QMdiSubWindow::AllowOutsideAreaHorizontally, true); + else + child->setOption(QMdiSubWindow::AllowOutsideAreaHorizontally, false); + + if (vbarpolicy != Qt::ScrollBarAlwaysOff) + child->setOption(QMdiSubWindow::AllowOutsideAreaVertically, true); + else + child->setOption(QMdiSubWindow::AllowOutsideAreaVertically, false); + + internalRaise(child); + indicesToActivatedChildren.prepend(childWindows.size() - 1); + Q_ASSERT(indicesToActivatedChildren.size() == childWindows.size()); + +#ifndef QT_NO_TABBAR + if (tabBar) { + tabBar->addTab(child->windowIcon(), tabTextFor(child)); + updateTabBarGeometry(); + if (childWindows.count() == 1 && !(options & QMdiArea::DontMaximizeSubWindowOnActivation)) + showActiveWindowMaximized = true; + } +#endif + + if (!(child->windowFlags() & Qt::SubWindow)) + child->setWindowFlags(Qt::SubWindow); + child->installEventFilter(q); + + QObject::connect(child, SIGNAL(aboutToActivate()), q, SLOT(_q_deactivateAllWindows())); + QObject::connect(child, SIGNAL(windowStateChanged(Qt::WindowStates, Qt::WindowStates)), + q, SLOT(_q_processWindowStateChanged(Qt::WindowStates, Qt::WindowStates))); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::place(Placer *placer, QMdiSubWindow *child) +{ + if (!placer || !child) + return; + + Q_Q(QMdiArea); + if (!q->isVisible()) { + // The window is only laid out when it's added to QMdiArea, + // so there's no need to check that we don't have it in the + // list already. appendChild() ensures that. + pendingPlacements.append(child); + return; + } + + QList<QRect> rects; + QRect parentRect = q->rect(); + foreach (QMdiSubWindow *window, childWindows) { + if (!sanityCheck(window, "QMdiArea::place") || window == child || !window->isVisibleTo(q) + || !window->testAttribute(Qt::WA_Moved)) { + continue; + } + QRect occupiedGeometry; + if (window->isMaximized()) { + occupiedGeometry = QRect(window->d_func()->oldGeometry.topLeft(), + window->d_func()->restoreSize); + } else { + occupiedGeometry = window->geometry(); + } + rects.append(QStyle::visualRect(child->layoutDirection(), parentRect, occupiedGeometry)); + } + QPoint newPos = placer->place(child->size(), rects, parentRect); + QRect newGeometry = QRect(newPos.x(), newPos.y(), child->width(), child->height()); + child->setGeometry(QStyle::visualRect(child->layoutDirection(), parentRect, newGeometry)); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::rearrange(Rearranger *rearranger) +{ + if (!rearranger) + return; + + Q_Q(QMdiArea); + if (!q->isVisible()) { + // Compress if we already have the rearranger in the list. + int index = pendingRearrangements.indexOf(rearranger); + if (index != -1) + pendingRearrangements.move(index, pendingRearrangements.size() - 1); + else + pendingRearrangements.append(rearranger); + return; + } + + QList<QWidget *> widgets; + const bool reverseList = rearranger->type() == Rearranger::RegularTiler; + const QList<QMdiSubWindow *> subWindows = subWindowList(activationOrder, reverseList); + QSize minSubWindowSize; + foreach (QMdiSubWindow *child, subWindows) { + if (!sanityCheck(child, "QMdiArea::rearrange") || !child->isVisible()) + continue; + if (rearranger->type() == Rearranger::IconTiler) { + if (child->isMinimized() && !child->isShaded() && !(child->windowFlags() & Qt::FramelessWindowHint)) + widgets.append(child); + } else { + if (child->isMinimized() && !child->isShaded()) + continue; + if (child->isMaximized() || child->isShaded()) + child->showNormal(); + minSubWindowSize = minSubWindowSize.expandedTo(child->minimumSize()) + .expandedTo(child->d_func()->internalMinimumSize); + widgets.append(child); + } + } + + if (active && rearranger->type() == Rearranger::RegularTiler) { + // Move active window in front if necessary. That's the case if we + // have any windows with staysOnTopHint set. + int indexToActive = widgets.indexOf((QWidget *)active); + if (indexToActive > 0) + widgets.move(indexToActive, 0); + } + + QRect domain = q->viewport()->rect(); + if (rearranger->type() == Rearranger::RegularTiler && !widgets.isEmpty()) + domain = resizeToMinimumTileSize(minSubWindowSize, widgets.count()); + + rearranger->rearrange(widgets, domain); + + if (rearranger->type() == Rearranger::RegularTiler && !widgets.isEmpty()) { + isSubWindowsTiled = true; + updateScrollBars(); + } else if (rearranger->type() == Rearranger::SimpleCascader) { + isSubWindowsTiled = false; + } +} + +/*! + \internal + + Arranges all minimized windows at the bottom of the workspace. +*/ +void QMdiAreaPrivate::arrangeMinimizedSubWindows() +{ + if (!iconTiler) + iconTiler = new IconTiler; + rearrange(iconTiler); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::activateWindow(QMdiSubWindow *child) +{ + if (childWindows.isEmpty()) { + Q_ASSERT(!child); + Q_ASSERT(!active); + return; + } + + if (!child) { + if (active) { + Q_ASSERT(active->d_func()->isActive); + active->d_func()->setActive(false); + resetActiveWindow(); + } + return; + } + + if (child->isHidden() || child == active) + return; + child->d_func()->setActive(true); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::activateCurrentWindow() +{ + QMdiSubWindow *current = q_func()->currentSubWindow(); + if (current && !isExplicitlyDeactivated(current)) { + current->d_func()->activationEnabled = true; + current->d_func()->setActive(true, /*changeFocus=*/false); + } +} + +void QMdiAreaPrivate::activateHighlightedWindow() +{ + if (indexToHighlighted < 0) + return; + + Q_ASSERT(indexToHighlighted < childWindows.size()); + if (tabToPreviousTimerId != -1) + activateWindow(nextVisibleSubWindow(-1, QMdiArea::ActivationHistoryOrder)); + else + activateWindow(childWindows.at(indexToHighlighted)); +#ifndef QT_NO_RUBBERBAND + hideRubberBand(); +#endif +} + +/*! + \internal +*/ +void QMdiAreaPrivate::emitWindowActivated(QMdiSubWindow *activeWindow) +{ + Q_Q(QMdiArea); + Q_ASSERT(activeWindow); + if (activeWindow == active) + return; + Q_ASSERT(activeWindow->d_func()->isActive); + + if (!aboutToBecomeActive) + _q_deactivateAllWindows(activeWindow); + Q_ASSERT(aboutToBecomeActive); + + // This is true only if 'DontMaximizeSubWindowOnActivation' is disabled + // and the previous active window was maximized. + if (showActiveWindowMaximized) { + if (!activeWindow->isMaximized()) + activeWindow->showMaximized(); + showActiveWindowMaximized = false; + } + + // Put in front to update activation order. + const int indexToActiveWindow = childWindows.indexOf(activeWindow); + Q_ASSERT(indexToActiveWindow != -1); + const int index = indicesToActivatedChildren.indexOf(indexToActiveWindow); + Q_ASSERT(index != -1); + indicesToActivatedChildren.move(index, 0); + internalRaise(activeWindow); + + if (updatesDisabledByUs) { + q->setUpdatesEnabled(true); + updatesDisabledByUs = false; + } + + Q_ASSERT(aboutToBecomeActive == activeWindow); + active = activeWindow; + aboutToBecomeActive = 0; + Q_ASSERT(active->d_func()->isActive); + +#ifndef QT_NO_TABBAR + if (tabBar && tabBar->currentIndex() != indexToActiveWindow) + tabBar->setCurrentIndex(indexToActiveWindow); +#endif + + if (active->isMaximized() && scrollBarsEnabled()) + updateScrollBars(); + + emit q->subWindowActivated(active); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::resetActiveWindow(QMdiSubWindow *deactivatedWindow) +{ + Q_Q(QMdiArea); + if (deactivatedWindow) { + if (deactivatedWindow != active) + return; + active = 0; + if ((aboutToBecomeActive || isActivated || lastWindowAboutToBeDestroyed()) + && !isExplicitlyDeactivated(deactivatedWindow) && !q->window()->isMinimized()) { + return; + } + emit q->subWindowActivated(0); + return; + } + + if (aboutToBecomeActive) + return; + + active = 0; + emit q->subWindowActivated(0); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::updateActiveWindow(int removedIndex, bool activeRemoved) +{ + Q_ASSERT(indicesToActivatedChildren.size() == childWindows.size()); + +#ifndef QT_NO_TABBAR + if (tabBar && removedIndex >= 0) { + tabBar->blockSignals(true); + tabBar->removeTab(removedIndex); + updateTabBarGeometry(); + tabBar->blockSignals(false); + } +#endif + + if (childWindows.isEmpty()) { + showActiveWindowMaximized = false; + resetActiveWindow(); + return; + } + + if (indexToHighlighted >= 0) { +#ifndef QT_NO_RUBBERBAND + // Hide rubber band if highlighted window is removed. + if (indexToHighlighted == removedIndex) + hideRubberBand(); + else +#endif + // or update index if necessary. + if (indexToHighlighted > removedIndex) + --indexToHighlighted; + } + + // Update indices list + for (int i = 0; i < indicesToActivatedChildren.size(); ++i) { + int *index = &indicesToActivatedChildren[i]; + if (*index > removedIndex) + --*index; + } + + if (!activeRemoved) + return; + + // Activate next window. + QMdiSubWindow *next = nextVisibleSubWindow(0, activationOrder, removedIndex); + if (next) + activateWindow(next); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::updateScrollBars() +{ + if (ignoreGeometryChange || !scrollBarsEnabled()) + return; + + Q_Q(QMdiArea); + QSize maxSize = q->maximumViewportSize(); + QSize hbarExtent = hbar->sizeHint(); + QSize vbarExtent = vbar->sizeHint(); + + if (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, q)) { + const int doubleFrameWidth = frameWidth * 2; + if (hbarpolicy == Qt::ScrollBarAlwaysOn) + maxSize.rheight() -= doubleFrameWidth; + if (vbarpolicy == Qt::ScrollBarAlwaysOn) + maxSize.rwidth() -= doubleFrameWidth; + hbarExtent.rheight() += doubleFrameWidth; + vbarExtent.rwidth() += doubleFrameWidth; + } + + const QRect childrenRect = active && active->isMaximized() + ? active->geometry() : viewport->childrenRect(); + bool useHorizontalScrollBar = useScrollBar(childrenRect, maxSize, Qt::Horizontal); + bool useVerticalScrollBar = useScrollBar(childrenRect, maxSize, Qt::Vertical); + + if (useHorizontalScrollBar && !useVerticalScrollBar) { + const QSize max = maxSize - QSize(0, hbarExtent.height()); + useVerticalScrollBar = useScrollBar(childrenRect, max, Qt::Vertical); + } + + if (useVerticalScrollBar && !useHorizontalScrollBar) { + const QSize max = maxSize - QSize(vbarExtent.width(), 0); + useHorizontalScrollBar = useScrollBar(childrenRect, max, Qt::Horizontal); + } + + if (useHorizontalScrollBar && hbarpolicy != Qt::ScrollBarAlwaysOn) + maxSize.rheight() -= hbarExtent.height(); + if (useVerticalScrollBar && vbarpolicy != Qt::ScrollBarAlwaysOn) + maxSize.rwidth() -= vbarExtent.width(); + + QRect viewportRect(QPoint(0, 0), maxSize); + const int startX = q->isLeftToRight() ? childrenRect.left() : viewportRect.right() + - childrenRect.right(); + + // Horizontal scroll bar. + if (isSubWindowsTiled && hbar->value() != 0) + hbar->setValue(0); + const int xOffset = startX + hbar->value(); + hbar->setRange(qMin(0, xOffset), + qMax(0, xOffset + childrenRect.width() - viewportRect.width())); + hbar->setPageStep(childrenRect.width()); + hbar->setSingleStep(childrenRect.width() / 20); + + // Vertical scroll bar. + if (isSubWindowsTiled && vbar->value() != 0) + vbar->setValue(0); + const int yOffset = childrenRect.top() + vbar->value(); + vbar->setRange(qMin(0, yOffset), + qMax(0, yOffset + childrenRect.height() - viewportRect.height())); + vbar->setPageStep(childrenRect.height()); + vbar->setSingleStep(childrenRect.height() / 20); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::internalRaise(QMdiSubWindow *mdiChild) const +{ + if (!sanityCheck(mdiChild, "QMdiArea::internalRaise") || childWindows.size() < 2) + return; + + QMdiSubWindow *stackUnderChild = 0; + if (!windowStaysOnTop(mdiChild)) { + foreach (QObject *object, q_func()->viewport()->children()) { + QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(object); + if (!child || !childWindows.contains(child)) + continue; + if (!child->isHidden() && windowStaysOnTop(child)) { + if (stackUnderChild) + child->stackUnder(stackUnderChild); + else + child->raise(); + stackUnderChild = child; + } + } + } + + if (stackUnderChild) + mdiChild->stackUnder(stackUnderChild); + else + mdiChild->raise(); +} + +QRect QMdiAreaPrivate::resizeToMinimumTileSize(const QSize &minSubWindowSize, int subWindowCount) +{ + Q_Q(QMdiArea); + if (!minSubWindowSize.isValid() || subWindowCount <= 0) + return q->viewport()->rect(); + + // Calculate minimum size. + const int columns = qMax(qCeil(qSqrt(qreal(subWindowCount))), 1); + const int rows = qMax((subWindowCount % columns) ? (subWindowCount / columns + 1) + : (subWindowCount / columns), 1); + const int minWidth = minSubWindowSize.width() * columns; + const int minHeight = minSubWindowSize.height() * rows; + + // Increase area size if necessary. Scroll bars are provided if we're not able + // to resize to the minimum size. + if (!tileCalledFromResizeEvent) { + QWidget *topLevel = q; + // Find the topLevel for this area, either a real top-level or a sub-window. + while (topLevel && !topLevel->isWindow() && topLevel->windowType() != Qt::SubWindow) + topLevel = topLevel->parentWidget(); + // We don't want sub-subwindows to be placed at the edge, thus add 2 pixels. + int minAreaWidth = minWidth + left + right + 2; + int minAreaHeight = minHeight + top + bottom + 2; + if (q->horizontalScrollBar()->isVisible()) + minAreaHeight += q->horizontalScrollBar()->height(); + if (q->verticalScrollBar()->isVisible()) + minAreaWidth += q->verticalScrollBar()->width(); + if (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, q)) { + const int frame = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, q); + minAreaWidth += 2 * frame; + minAreaHeight += 2 * frame; + } + const QSize diff = QSize(minAreaWidth, minAreaHeight).expandedTo(q->size()) - q->size(); + topLevel->resize(topLevel->size() + diff); + } + + QRect domain = q->viewport()->rect(); + + // Adjust domain width and provide horizontal scroll bar. + if (domain.width() < minWidth) { + domain.setWidth(minWidth); + if (q->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) + q->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + else if (q->horizontalScrollBar()->value() != 0) + q->horizontalScrollBar()->setValue(0); + } + // Adjust domain height and provide vertical scroll bar. + if (domain.height() < minHeight) { + domain.setHeight(minHeight); + if (q->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) + q->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + else if (q->verticalScrollBar()->value() != 0) + q->verticalScrollBar()->setValue(0); + } + return domain; +} + +/*! + \internal +*/ +bool QMdiAreaPrivate::scrollBarsEnabled() const +{ + return hbarpolicy != Qt::ScrollBarAlwaysOff || vbarpolicy != Qt::ScrollBarAlwaysOff; +} + +/*! + \internal +*/ +bool QMdiAreaPrivate::lastWindowAboutToBeDestroyed() const +{ + if (childWindows.count() != 1) + return false; + + QMdiSubWindow *last = childWindows.at(0); + if (!last) + return true; + + if (!last->testAttribute(Qt::WA_DeleteOnClose)) + return false; + + return last->d_func()->data.is_closing; +} + +/*! + \internal +*/ +void QMdiAreaPrivate::setChildActivationEnabled(bool enable, bool onlyNextActivationEvent) const +{ + foreach (QMdiSubWindow *subWindow, childWindows) { + if (!subWindow || !subWindow->isVisible()) + continue; + if (onlyNextActivationEvent) + subWindow->d_func()->ignoreNextActivationEvent = !enable; + else + subWindow->d_func()->activationEnabled = enable; + } +} + +/*! + \internal + \reimp +*/ +void QMdiAreaPrivate::scrollBarPolicyChanged(Qt::Orientation orientation, Qt::ScrollBarPolicy policy) +{ + if (childWindows.isEmpty()) + return; + + const QMdiSubWindow::SubWindowOption option = orientation == Qt::Horizontal ? + QMdiSubWindow::AllowOutsideAreaHorizontally : QMdiSubWindow::AllowOutsideAreaVertically; + const bool enable = policy != Qt::ScrollBarAlwaysOff; + foreach (QMdiSubWindow *child, childWindows) { + if (!sanityCheck(child, "QMdiArea::scrollBarPolicyChanged")) + continue; + child->setOption(option, enable); + } + updateScrollBars(); +} + +QList<QMdiSubWindow*> +QMdiAreaPrivate::subWindowList(QMdiArea::WindowOrder order, bool reversed) const +{ + QList<QMdiSubWindow *> list; + if (childWindows.isEmpty()) + return list; + + if (order == QMdiArea::CreationOrder) { + foreach (QMdiSubWindow *child, childWindows) { + if (!child) + continue; + if (!reversed) + list.append(child); + else + list.prepend(child); + } + } else if (order == QMdiArea::StackingOrder) { + foreach (QObject *object, viewport->children()) { + QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(object); + if (!child || !childWindows.contains(child)) + continue; + if (!reversed) + list.append(child); + else + list.prepend(child); + } + } else { // ActivationHistoryOrder + Q_ASSERT(indicesToActivatedChildren.size() == childWindows.size()); + for (int i = indicesToActivatedChildren.count() - 1; i >= 0; --i) { + QMdiSubWindow *child = childWindows.at(indicesToActivatedChildren.at(i)); + if (!child) + continue; + if (!reversed) + list.append(child); + else + list.prepend(child); + } + } + return list; +} + +/*! + \internal +*/ +void QMdiAreaPrivate::disconnectSubWindow(QObject *subWindow) +{ + if (!subWindow) + return; + + Q_Q(QMdiArea); + QObject::disconnect(subWindow, 0, q, 0); + subWindow->removeEventFilter(q); +} + +/*! + \internal +*/ +QMdiSubWindow *QMdiAreaPrivate::nextVisibleSubWindow(int increaseFactor, QMdiArea::WindowOrder order, + int removedIndex, int fromIndex) const +{ + if (childWindows.isEmpty()) + return 0; + + Q_Q(const QMdiArea); + const QList<QMdiSubWindow *> subWindows = q->subWindowList(order); + QMdiSubWindow *current = 0; + + if (removedIndex < 0) { + if (fromIndex >= 0 && fromIndex < subWindows.size()) + current = childWindows.at(fromIndex); + else + current = q->currentSubWindow(); + } + + // There's no current sub-window (removed or deactivated), + // so we have to pick the last active or the next in creation order. + if (!current) { + if (removedIndex >= 0 && order == QMdiArea::CreationOrder) { + int candidateIndex = -1; + setIndex(&candidateIndex, removedIndex, 0, subWindows.size() - 1, true); + current = childWindows.at(candidateIndex); + } else { + current = subWindows.back(); + } + } + Q_ASSERT(current); + + // Find the index for the current sub-window in the given activation order + const int indexToCurrent = subWindows.indexOf(current); + const bool increasing = increaseFactor > 0 ? true : false; + + // and use that index + increseFactor as a candidate. + int index = -1; + setIndex(&index, indexToCurrent + increaseFactor, 0, subWindows.size() - 1, increasing); + Q_ASSERT(index != -1); + + // Try to find another window if the candidate is hidden. + while (subWindows.at(index)->isHidden()) { + setIndex(&index, index + increaseFactor, 0, subWindows.size() - 1, increasing); + if (index == indexToCurrent) + break; + } + + if (!subWindows.at(index)->isHidden()) + return subWindows.at(index); + return 0; +} + +/*! + \internal +*/ +void QMdiAreaPrivate::highlightNextSubWindow(int increaseFactor) +{ + if (childWindows.size() == 1) + return; + + Q_Q(QMdiArea); + // There's no highlighted sub-window atm, use current. + if (indexToHighlighted < 0) { + QMdiSubWindow *current = q->currentSubWindow(); + if (!current) + return; + indexToHighlighted = childWindows.indexOf(current); + } + + Q_ASSERT(indexToHighlighted >= 0); + Q_ASSERT(indexToHighlighted < childWindows.size()); + + QMdiSubWindow *highlight = nextVisibleSubWindow(increaseFactor, activationOrder, -1, indexToHighlighted); + if (!highlight) + return; + +#ifndef QT_NO_RUBBERBAND + if (!rubberBand) { + rubberBand = new QRubberBand(QRubberBand::Rectangle, viewport); + // For accessibility to identify this special widget. + rubberBand->setObjectName(QLatin1String("qt_rubberband")); + rubberBand->setWindowFlags(rubberBand->windowFlags() | Qt::WindowStaysOnTopHint); + } +#endif + + // Only highlight if we're not switching back to the previously active window (Ctrl-Tab once). +#ifndef QT_NO_RUBBERBAND + if (tabToPreviousTimerId == -1) + showRubberBandFor(highlight); +#endif + + indexToHighlighted = childWindows.indexOf(highlight); + Q_ASSERT(indexToHighlighted >= 0); +} + +/*! + \internal + \since 4.4 +*/ +void QMdiAreaPrivate::setViewMode(QMdiArea::ViewMode mode) +{ + Q_Q(QMdiArea); + if (viewMode == mode || inViewModeChange) + return; + + // Just a guard since we cannot set viewMode = mode here. + inViewModeChange = true; + +#ifndef QT_NO_TABBAR + if (mode == QMdiArea::TabbedView) { + Q_ASSERT(!tabBar); + tabBar = new QMdiAreaTabBar(q); + tabBar->setDocumentMode(documentMode); +#ifndef QT_NO_TABWIDGET + tabBar->setShape(tabBarShapeFrom(tabShape, tabPosition)); +#endif + + isSubWindowsTiled = false; + + foreach (QMdiSubWindow *subWindow, childWindows) + tabBar->addTab(subWindow->windowIcon(), tabTextFor(subWindow)); + + QMdiSubWindow *current = q->currentSubWindow(); + if (current) { + tabBar->setCurrentIndex(childWindows.indexOf(current)); + // Restore sub-window (i.e. cleanup buttons in menu bar and window title). + if (current->isMaximized()) + current->showNormal(); + + viewMode = mode; + + // Now, maximize it. + if (!q->testOption(QMdiArea::DontMaximizeSubWindowOnActivation)) { + current->showMaximized(); + } + } else { + viewMode = mode; + } + + if (q->isVisible()) + tabBar->show(); + updateTabBarGeometry(); + + QObject::connect(tabBar, SIGNAL(currentChanged(int)), q, SLOT(_q_currentTabChanged(int))); + } else +#endif // QT_NO_TABBAR + { // SubWindowView +#ifndef QT_NO_TABBAR + delete tabBar; + tabBar = 0; +#endif // QT_NO_TABBAR + + viewMode = mode; + q->setViewportMargins(0, 0, 0, 0); + indexToLastActiveTab = -1; + + QMdiSubWindow *current = q->currentSubWindow(); + if (current && current->isMaximized()) + current->showNormal(); + } + + Q_ASSERT(viewMode == mode); + inViewModeChange = false; +} + +#ifndef QT_NO_TABBAR +/*! + \internal +*/ +void QMdiAreaPrivate::updateTabBarGeometry() +{ + if (!tabBar) + return; + + Q_Q(QMdiArea); +#ifndef QT_NO_TABWIDGET + Q_ASSERT(tabBarShapeFrom(tabShape, tabPosition) == tabBar->shape()); +#endif + const QSize tabBarSizeHint = tabBar->sizeHint(); + + int areaHeight = q->height(); + if (hbar && hbar->isVisible()) + areaHeight -= hbar->height(); + + int areaWidth = q->width(); + if (vbar && vbar->isVisible()) + areaWidth -= vbar->width(); + + QRect tabBarRect; +#ifndef QT_NO_TABWIDGET + switch (tabPosition) { + case QTabWidget::North: + q->setViewportMargins(0, tabBarSizeHint.height(), 0, 0); + tabBarRect = QRect(0, 0, areaWidth, tabBarSizeHint.height()); + break; + case QTabWidget::South: + q->setViewportMargins(0, 0, 0, tabBarSizeHint.height()); + tabBarRect = QRect(0, areaHeight - tabBarSizeHint.height(), areaWidth, tabBarSizeHint.height()); + break; + case QTabWidget::East: + if (q->layoutDirection() == Qt::LeftToRight) + q->setViewportMargins(0, 0, tabBarSizeHint.width(), 0); + else + q->setViewportMargins(tabBarSizeHint.width(), 0, 0, 0); + tabBarRect = QRect(areaWidth - tabBarSizeHint.width(), 0, tabBarSizeHint.width(), areaHeight); + break; + case QTabWidget::West: + if (q->layoutDirection() == Qt::LeftToRight) + q->setViewportMargins(tabBarSizeHint.width(), 0, 0, 0); + else + q->setViewportMargins(0, 0, tabBarSizeHint.width(), 0); + tabBarRect = QRect(0, 0, tabBarSizeHint.width(), areaHeight); + break; + default: + break; + } +#endif // QT_NO_TABWIDGET + + tabBar->setGeometry(QStyle::visualRect(q->layoutDirection(), q->contentsRect(), tabBarRect)); +} + +/*! + \internal +*/ +void QMdiAreaPrivate::refreshTabBar() +{ + if (!tabBar) + return; + + tabBar->setDocumentMode(documentMode); +#ifndef QT_NO_TABWIDGET + tabBar->setShape(tabBarShapeFrom(tabShape, tabPosition)); +#endif + updateTabBarGeometry(); +} +#endif // QT_NO_TABBAR + +/*! + Constructs an empty mdi area. \a parent is passed to QWidget's + constructor. +*/ +QMdiArea::QMdiArea(QWidget *parent) + : QAbstractScrollArea(*new QMdiAreaPrivate, parent) +{ + setBackground(palette().brush(QPalette::Dark)); + setFrameStyle(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setViewport(0); + setFocusPolicy(Qt::NoFocus); + QApplication::instance()->installEventFilter(this); +} + +/*! + Destroys the MDI area. +*/ +QMdiArea::~QMdiArea() +{ + Q_D(QMdiArea); + delete d->cascader; + d->cascader = 0; + + delete d->regularTiler; + d->regularTiler = 0; + + delete d->iconTiler; + d->iconTiler = 0; + + delete d->placer; + d->placer = 0; +} + +/*! + \reimp +*/ +QSize QMdiArea::sizeHint() const +{ + // Calculate a proper scale factor for QDesktopWidget::size(). + // This also takes into account that we can have nested workspaces. + int nestedCount = 0; + QWidget *widget = this->parentWidget(); + while (widget) { + if (qobject_cast<QMdiArea *>(widget)) + ++nestedCount; + widget = widget->parentWidget(); + } + const int scaleFactor = 3 * (nestedCount + 1); + + QSize desktopSize = QApplication::desktop()->size(); + QSize size(desktopSize.width() * 2 / scaleFactor, desktopSize.height() * 2 / scaleFactor); + foreach (QMdiSubWindow *child, d_func()->childWindows) { + if (!sanityCheck(child, "QMdiArea::sizeHint")) + continue; + size = size.expandedTo(child->sizeHint()); + } + return size.expandedTo(QApplication::globalStrut()); +} + +/*! + \reimp +*/ +QSize QMdiArea::minimumSizeHint() const +{ + Q_D(const QMdiArea); + QSize size(style()->pixelMetric(QStyle::PM_MdiSubWindowMinimizedWidth, 0, this), + style()->pixelMetric(QStyle::PM_TitleBarHeight, 0, this)); + size = size.expandedTo(QAbstractScrollArea::minimumSizeHint()); + if (!d->scrollBarsEnabled()) { + foreach (QMdiSubWindow *child, d->childWindows) { + if (!sanityCheck(child, "QMdiArea::sizeHint")) + continue; + size = size.expandedTo(child->minimumSizeHint()); + } + } + return size.expandedTo(QApplication::globalStrut()); +} + +/*! + Returns a pointer to the current subwindow, or 0 if there is + no current subwindow. + + This function will return the same as activeSubWindow() if + the QApplication containing QMdiArea is active. + + \sa activeSubWindow(), QApplication::activeWindow() +*/ +QMdiSubWindow *QMdiArea::currentSubWindow() const +{ + Q_D(const QMdiArea); + if (d->childWindows.isEmpty()) + return 0; + + if (d->active) + return d->active; + + if (d->isActivated && !window()->isMinimized()) + return 0; + + Q_ASSERT(d->indicesToActivatedChildren.count() > 0); + int index = d->indicesToActivatedChildren.at(0); + Q_ASSERT(index >= 0 && index < d->childWindows.size()); + QMdiSubWindow *current = d->childWindows.at(index); + Q_ASSERT(current); + return current; +} + +/*! + Returns a pointer to the current active subwindow. If no + window is currently active, 0 is returned. + + Subwindows are treated as top-level windows with respect to + window state, i.e., if a widget outside the MDI area is the active + window, no subwindow will be active. Note that if a widget in the + window in which the MDI area lives gains focus, the window will be + activated. + + \sa setActiveSubWindow(), Qt::WindowState +*/ +QMdiSubWindow *QMdiArea::activeSubWindow() const +{ + Q_D(const QMdiArea); + return d->active; +} + +/*! + Activates the subwindow \a window. If \a window is 0, any + current active window is deactivated. + + \sa activeSubWindow() +*/ +void QMdiArea::setActiveSubWindow(QMdiSubWindow *window) +{ + Q_D(QMdiArea); + if (!window) { + d->activateWindow(0); + return; + } + + if (d->childWindows.isEmpty()) { + qWarning("QMdiArea::setActiveSubWindow: workspace is empty"); + return; + } + + if (d->childWindows.indexOf(window) == -1) { + qWarning("QMdiArea::setActiveSubWindow: window is not inside workspace"); + return; + } + + d->activateWindow(window); +} + +/*! + Closes the active subwindow. + + \sa closeAllSubWindows() +*/ +void QMdiArea::closeActiveSubWindow() +{ + Q_D(QMdiArea); + if (d->active) + d->active->close(); +} + +/*! + Returns a list of all subwindows in the MDI area. If \a order is + CreationOrder (the default), the windows are sorted in the order + in which they were inserted into the workspace. If \a order is + StackingOrder, the windows are listed in their stacking order, + with the topmost window as the last item in the list. If \a order + is ActivationHistoryOrder, the windows are listed according to + their recent activation history. + + \sa WindowOrder +*/ +QList<QMdiSubWindow *> QMdiArea::subWindowList(WindowOrder order) const +{ + Q_D(const QMdiArea); + return d->subWindowList(order, false); +} + +/*! + Closes all subwindows by sending a QCloseEvent to each window. + You may receive subWindowActivated() signals from subwindows + before they are closed (if the MDI area activates the subwindow + when another is closing). + + Subwindows that ignore the close event will remain open. + + \sa closeActiveSubWindow() +*/ +void QMdiArea::closeAllSubWindows() +{ + Q_D(QMdiArea); + if (d->childWindows.isEmpty()) + return; + + d->isSubWindowsTiled = false; + foreach (QMdiSubWindow *child, d->childWindows) { + if (!sanityCheck(child, "QMdiArea::closeAllSubWindows")) + continue; + child->close(); + } + + d->updateScrollBars(); +} + +/*! + Gives the keyboard focus to the next window in the list of child + windows. The windows are activated in the order in which they are + created (CreationOrder). + + \sa activatePreviousSubWindow() +*/ +void QMdiArea::activateNextSubWindow() +{ + Q_D(QMdiArea); + if (d->childWindows.isEmpty()) + return; + + QMdiSubWindow *next = d->nextVisibleSubWindow(1, d->activationOrder); + if (next) + d->activateWindow(next); +} + +/*! + Gives the keyboard focus to the previous window in the list of + child windows. The windows are activated in the order in which + they are created (CreationOrder). + + \sa activateNextSubWindow() +*/ +void QMdiArea::activatePreviousSubWindow() +{ + Q_D(QMdiArea); + if (d->childWindows.isEmpty()) + return; + + QMdiSubWindow *previous = d->nextVisibleSubWindow(-1, d->activationOrder); + if (previous) + d->activateWindow(previous); +} + +/*! + Adds \a widget as a new subwindow to the MDI area. If \a + windowFlags are non-zero, they will override the flags set on the + widget. + + The \a widget can be either a QMdiSubWindow or another QWidget + (in which case the MDI area will create a subwindow and set the \a + widget as the internal widget). + + \note Once the subwindow has been added, its parent will be the + \e{viewport widget} of the QMdiArea. + + \snippet doc/src/snippets/mdiareasnippets.cpp 1 + + When you create your own subwindow, you must set the + Qt::WA_DeleteOnClose widget attribute if you want the window to be + deleted when closed in the MDI area. If not, the window will be + hidden and the MDI area will not activate the next subwindow. + + Returns the QMdiSubWindow that is added to the MDI area. + + \sa removeSubWindow() +*/ +QMdiSubWindow *QMdiArea::addSubWindow(QWidget *widget, Qt::WindowFlags windowFlags) +{ + if (!widget) { + qWarning("QMdiArea::addSubWindow: null pointer to widget"); + return 0; + } + + Q_D(QMdiArea); + // QWidget::setParent clears focusWidget so store it + QWidget *childFocus = widget->focusWidget(); + QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(widget); + + // Widget is already a QMdiSubWindow + if (child) { + if (d->childWindows.indexOf(child) != -1) { + qWarning("QMdiArea::addSubWindow: window is already added"); + return child; + } + child->setParent(viewport(), windowFlags ? windowFlags : child->windowFlags()); + // Create a QMdiSubWindow + } else { + child = new QMdiSubWindow(viewport(), windowFlags); + child->setAttribute(Qt::WA_DeleteOnClose); + child->setWidget(widget); + Q_ASSERT(child->testAttribute(Qt::WA_DeleteOnClose)); + } + + if (childFocus) + childFocus->setFocus(); + d->appendChild(child); + return child; +} + +/*! + Removes \a widget from the MDI area. The \a widget must be + either a QMdiSubWindow or a widget that is the internal widget of + a subwindow. Note that the subwindow is not deleted by QMdiArea + and that its parent is set to 0. + + \sa addSubWindow() +*/ +void QMdiArea::removeSubWindow(QWidget *widget) +{ + if (!widget) { + qWarning("QMdiArea::removeSubWindow: null pointer to widget"); + return; + } + + Q_D(QMdiArea); + if (d->childWindows.isEmpty()) + return; + + if (QMdiSubWindow *child = qobject_cast<QMdiSubWindow *>(widget)) { + int index = d->childWindows.indexOf(child); + if (index == -1) { + qWarning("QMdiArea::removeSubWindow: window is not inside workspace"); + return; + } + d->disconnectSubWindow(child); + d->childWindows.removeAll(child); + d->indicesToActivatedChildren.removeAll(index); + d->updateActiveWindow(index, d->active == child); + child->setParent(0); + return; + } + + bool found = false; + foreach (QMdiSubWindow *child, d->childWindows) { + if (!sanityCheck(child, "QMdiArea::removeSubWindow")) + continue; + if (child->widget() == widget) { + child->setWidget(0); + Q_ASSERT(!child->widget()); + found = true; + break; + } + } + + if (!found) + qWarning("QMdiArea::removeSubWindow: widget is not child of any window inside QMdiArea"); +} + +/*! + \property QMdiArea::background + \brief the background brush for the workspace + + This property sets the background brush for the workspace area + itself. By default, it is a gray color, but can be any brush + (e.g., colors, gradients or pixmaps). +*/ +QBrush QMdiArea::background() const +{ + return d_func()->background; +} + +void QMdiArea::setBackground(const QBrush &brush) +{ + Q_D(QMdiArea); + if (d->background != brush) { + d->background = brush; + d->viewport->setAttribute(Qt::WA_OpaquePaintEvent, brush.isOpaque()); + update(); + } +} + + +/*! + \property QMdiArea::activationOrder + \brief the ordering criteria for subwindow lists + \since 4.4 + + This property specifies the ordering criteria for the list of + subwindows returned by subWindowList(). By default, it is the window + creation order. + + \sa subWindowList() +*/ +QMdiArea::WindowOrder QMdiArea::activationOrder() const +{ + Q_D(const QMdiArea); + return d->activationOrder; +} + +void QMdiArea::setActivationOrder(WindowOrder order) +{ + Q_D(QMdiArea); + if (order != d->activationOrder) + d->activationOrder = order; +} + +/*! + If \a on is true, \a option is enabled on the MDI area; otherwise + it is disabled. See AreaOption for the effect of each option. + + \sa AreaOption, testOption() +*/ +void QMdiArea::setOption(AreaOption option, bool on) +{ + Q_D(QMdiArea); + if (on && !(d->options & option)) + d->options |= option; + else if (!on && (d->options & option)) + d->options &= ~option; +} + +/*! + Returns true if \a option is enabled; otherwise returns false. + + \sa AreaOption, setOption() +*/ +bool QMdiArea::testOption(AreaOption option) const +{ + return d_func()->options & option; +} + +/*! + \property QMdiArea::viewMode + \brief the way sub-windows are displayed in the QMdiArea. + \since 4.4 + + By default, the SubWindowView is used to display sub-windows. + + \sa ViewMode, setTabShape(), setTabPosition() +*/ +QMdiArea::ViewMode QMdiArea::viewMode() const +{ + Q_D(const QMdiArea); + return d->viewMode; +} + +void QMdiArea::setViewMode(ViewMode mode) +{ + Q_D(QMdiArea); + d->setViewMode(mode); +} + +#ifndef QT_NO_TABBAR +/*! + \property QMdiArea::documentMode + \brief whether the tab bar is set to document mode in tabbed view mode. + \since 4.5 + + Document mode is disabled by default. + + \sa QTabBar::documentMode, setViewMode() +*/ +bool QMdiArea::documentMode() const +{ + Q_D(const QMdiArea); + return d->documentMode; +} + +void QMdiArea::setDocumentMode(bool enabled) +{ + Q_D(QMdiArea); + if (d->documentMode == enabled) + return; + + d->documentMode = enabled; + d->refreshTabBar(); +} +#endif // QT_NO_TABBAR + +#ifndef QT_NO_TABWIDGET +/*! + \property QMdiArea::tabShape + \brief the shape of the tabs in tabbed view mode. + \since 4.4 + + Possible values for this property are QTabWidget::Rounded + (default) or QTabWidget::Triangular. + + \sa QTabWidget::TabShape, setViewMode() +*/ +QTabWidget::TabShape QMdiArea::tabShape() const +{ + Q_D(const QMdiArea); + return d->tabShape; +} + +void QMdiArea::setTabShape(QTabWidget::TabShape shape) +{ + Q_D(QMdiArea); + if (d->tabShape == shape) + return; + + d->tabShape = shape; + d->refreshTabBar(); +} + +/*! + \property QMdiArea::tabPosition + \brief the position of the tabs in tabbed view mode. + \since 4.4 + + Possible values for this property are described by the + QTabWidget::TabPosition enum. + + \sa QTabWidget::TabPosition, setViewMode() +*/ +QTabWidget::TabPosition QMdiArea::tabPosition() const +{ + Q_D(const QMdiArea); + return d->tabPosition; +} + +void QMdiArea::setTabPosition(QTabWidget::TabPosition position) +{ + Q_D(QMdiArea); + if (d->tabPosition == position) + return; + + d->tabPosition = position; + d->refreshTabBar(); +} +#endif // QT_NO_TABWIDGET + +/*! + \reimp +*/ +void QMdiArea::childEvent(QChildEvent *childEvent) +{ + Q_D(QMdiArea); + if (childEvent->type() == QEvent::ChildPolished) { + if (QMdiSubWindow *mdiChild = qobject_cast<QMdiSubWindow *>(childEvent->child())) { + if (d->childWindows.indexOf(mdiChild) == -1) + d->appendChild(mdiChild); + } + } +} + +/*! + \reimp +*/ +void QMdiArea::resizeEvent(QResizeEvent *resizeEvent) +{ + Q_D(QMdiArea); + if (d->childWindows.isEmpty()) { + resizeEvent->ignore(); + return; + } + +#ifndef QT_NO_TABBAR + d->updateTabBarGeometry(); +#endif + + // Re-tile the views if we're in tiled mode. Re-tile means we will change + // the geometry of the children, which in turn means 'isSubWindowsTiled' + // is set to false, so we have to update the state at the end. + if (d->isSubWindowsTiled) { + d->tileCalledFromResizeEvent = true; + tileSubWindows(); + d->tileCalledFromResizeEvent = false; + d->isSubWindowsTiled = true; + d->startResizeTimer(); + // We don't have scroll bars or any maximized views. + return; + } + + // Resize maximized views. + bool hasMaximizedSubWindow = false; + foreach (QMdiSubWindow *child, d->childWindows) { + if (sanityCheck(child, "QMdiArea::resizeEvent") && child->isMaximized() + && child->size() != resizeEvent->size()) { + child->resize(resizeEvent->size()); + if (!hasMaximizedSubWindow) + hasMaximizedSubWindow = true; + } + } + + d->updateScrollBars(); + + // Minimized views are stacked under maximized views so there's + // no need to re-arrange minimized views on-demand. Start a timer + // just to make things faster with subsequent resize events. + if (hasMaximizedSubWindow) + d->startResizeTimer(); + else + d->arrangeMinimizedSubWindows(); +} + +/*! + \reimp +*/ +void QMdiArea::timerEvent(QTimerEvent *timerEvent) +{ + Q_D(QMdiArea); + if (timerEvent->timerId() == d->resizeTimerId) { + killTimer(d->resizeTimerId); + d->resizeTimerId = -1; + d->arrangeMinimizedSubWindows(); + } else if (timerEvent->timerId() == d->tabToPreviousTimerId) { + killTimer(d->tabToPreviousTimerId); + d->tabToPreviousTimerId = -1; + if (d->indexToHighlighted < 0) + return; +#ifndef QT_NO_RUBBERBAND + // We're not doing a "quick switch" ... show rubber band. + Q_ASSERT(d->indexToHighlighted < d->childWindows.size()); + Q_ASSERT(d->rubberBand); + d->showRubberBandFor(d->childWindows.at(d->indexToHighlighted)); +#endif + } +} + +/*! + \reimp +*/ +void QMdiArea::showEvent(QShowEvent *showEvent) +{ + Q_D(QMdiArea); + if (!d->pendingRearrangements.isEmpty()) { + bool skipPlacement = false; + foreach (Rearranger *rearranger, d->pendingRearrangements) { + // If this is the case, we don't have to lay out pending child windows + // since the rearranger will find a placement for them. + if (rearranger->type() != Rearranger::IconTiler && !skipPlacement) + skipPlacement = true; + d->rearrange(rearranger); + } + d->pendingRearrangements.clear(); + + if (skipPlacement && !d->pendingPlacements.isEmpty()) + d->pendingPlacements.clear(); + } + + if (!d->pendingPlacements.isEmpty()) { + foreach (QMdiSubWindow *window, d->pendingPlacements) { + if (!window) + continue; + if (!window->testAttribute(Qt::WA_Resized)) { + QSize newSize(window->sizeHint().boundedTo(viewport()->size())); + window->resize(newSize.expandedTo(qSmartMinSize(window))); + } + if (!window->testAttribute(Qt::WA_Moved) && !window->isMinimized() + && !window->isMaximized()) { + d->place(d->placer, window); + } + } + d->pendingPlacements.clear(); + } + + d->setChildActivationEnabled(true); + d->activateCurrentWindow(); + + QAbstractScrollArea::showEvent(showEvent); +} + +/*! + \reimp +*/ +bool QMdiArea::viewportEvent(QEvent *event) +{ + Q_D(QMdiArea); + switch (event->type()) { + case QEvent::ChildRemoved: { + d->isSubWindowsTiled = false; + QObject *removedChild = static_cast<QChildEvent *>(event)->child(); + for (int i = 0; i < d->childWindows.size(); ++i) { + QObject *child = d->childWindows.at(i); + if (!child || child == removedChild || !child->parent() + || child->parent() != viewport()) { + if (!testOption(DontMaximizeSubWindowOnActivation)) { + // In this case we can only rely on the child being a QObject + // (or 0), but let's try and see if we can get more information. + QWidget *mdiChild = qobject_cast<QWidget *>(removedChild); + if (mdiChild && mdiChild->isMaximized()) + d->showActiveWindowMaximized = true; + } + d->disconnectSubWindow(child); + const bool activeRemoved = i == d->indicesToActivatedChildren.at(0); + d->childWindows.removeAt(i); + d->indicesToActivatedChildren.removeAll(i); + d->updateActiveWindow(i, activeRemoved); + d->arrangeMinimizedSubWindows(); + break; + } + } + d->updateScrollBars(); + break; + } + case QEvent::Destroy: + d->isSubWindowsTiled = false; + d->resetActiveWindow(); + d->childWindows.clear(); + qWarning("QMdiArea: Deleting the view port is undefined, use setViewport instead."); + break; + default: + break; + } + return QAbstractScrollArea::viewportEvent(event); +} + +/*! + \reimp +*/ +void QMdiArea::scrollContentsBy(int dx, int dy) +{ + Q_D(QMdiArea); + const bool wasSubWindowsTiled = d->isSubWindowsTiled; + d->ignoreGeometryChange = true; + viewport()->scroll(isLeftToRight() ? dx : -dx, dy); + d->arrangeMinimizedSubWindows(); + d->ignoreGeometryChange = false; + if (wasSubWindowsTiled) + d->isSubWindowsTiled = true; +} + +/*! + Arranges all child windows in a tile pattern. + + \sa cascadeSubWindows() +*/ +void QMdiArea::tileSubWindows() +{ + Q_D(QMdiArea); + if (!d->regularTiler) + d->regularTiler = new RegularTiler; + d->rearrange(d->regularTiler); +} + +/*! + Arranges all the child windows in a cascade pattern. + + \sa tileSubWindows() +*/ +void QMdiArea::cascadeSubWindows() +{ + Q_D(QMdiArea); + if (!d->cascader) + d->cascader = new SimpleCascader; + d->rearrange(d->cascader); +} + +/*! + \reimp +*/ +bool QMdiArea::event(QEvent *event) +{ + Q_D(QMdiArea); + switch (event->type()) { +#ifdef Q_WS_WIN + // QWidgetPrivate::hide_helper activates another sub-window when closing a + // modal dialog on Windows (see activateWindow() inside the the ifdef). + case QEvent::WindowUnblocked: + d->activateCurrentWindow(); + break; +#endif + case QEvent::WindowActivate: { + d->isActivated = true; + if (d->childWindows.isEmpty()) + break; + if (!d->active) + d->activateCurrentWindow(); + d->setChildActivationEnabled(false, true); + break; + } + case QEvent::WindowDeactivate: + d->isActivated = false; + d->setChildActivationEnabled(false, true); + break; + case QEvent::StyleChange: + // Re-tile the views if we're in tiled mode. Re-tile means we will change + // the geometry of the children, which in turn means 'isSubWindowsTiled' + // is set to false, so we have to update the state at the end. + if (d->isSubWindowsTiled) { + tileSubWindows(); + d->isSubWindowsTiled = true; + } + break; + case QEvent::WindowIconChange: + foreach (QMdiSubWindow *window, d->childWindows) { + if (sanityCheck(window, "QMdiArea::WindowIconChange")) + QApplication::sendEvent(window, event); + } + break; + case QEvent::Hide: + d->setActive(d->active, false, false); + d->setChildActivationEnabled(false); + break; +#ifndef QT_NO_TABBAR + case QEvent::LayoutDirectionChange: + d->updateTabBarGeometry(); + break; +#endif + default: + break; + } + return QAbstractScrollArea::event(event); +} + +/*! + \reimp +*/ +bool QMdiArea::eventFilter(QObject *object, QEvent *event) +{ + if (!object) + return QAbstractScrollArea::eventFilter(object, event); + + Q_D(QMdiArea); + // Global key events with Ctrl modifier. + if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { + + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + // Ingore key events without a Ctrl modifier (except for press/release on the modifier itself). +#ifdef Q_WS_MAC + if (!(keyEvent->modifiers() & Qt::MetaModifier) && keyEvent->key() != Qt::Key_Meta) +#else + if (!(keyEvent->modifiers() & Qt::ControlModifier) && keyEvent->key() != Qt::Key_Control) +#endif + return QAbstractScrollArea::eventFilter(object, event); + + // Find closest mdi area (in case we have a nested workspace). + QMdiArea *area = mdiAreaParent(static_cast<QWidget *>(object)); + if (!area) + return QAbstractScrollArea::eventFilter(object, event); + + const bool keyPress = (event->type() == QEvent::KeyPress) ? true : false; + + // 1) Ctrl-Tab once -> activate the previously active window. + // 2) Ctrl-Tab (Tab, Tab, ...) -> iterate through all windows (activateNextSubWindow()). + // 3) Ctrl-Shift-Tab (Tab, Tab, ...) -> iterate through all windows in the opposite + // direction (activatePreviousSubWindow()) + switch (keyEvent->key()) { +#ifdef Q_WS_MAC + case Qt::Key_Meta: +#else + case Qt::Key_Control: +#endif + if (keyPress) + area->d_func()->startTabToPreviousTimer(); + else + area->d_func()->activateHighlightedWindow(); + break; + case Qt::Key_Tab: + case Qt::Key_Backtab: + if (keyPress) + area->d_func()->highlightNextSubWindow(keyEvent->key() == Qt::Key_Tab ? 1 : -1); + return true; +#ifndef QT_NO_RUBBERBAND + case Qt::Key_Escape: + area->d_func()->hideRubberBand(); + break; +#endif + default: + break; + } + return QAbstractScrollArea::eventFilter(object, event); + } + + QMdiSubWindow *subWindow = qobject_cast<QMdiSubWindow *>(object); + + if (!subWindow) { + // QApplication events: + if (event->type() == QEvent::ApplicationActivate && !d->active + && isVisible() && !window()->isMinimized()) { + d->activateCurrentWindow(); + } else if (event->type() == QEvent::ApplicationDeactivate && d->active) { + d->setActive(d->active, false, false); + } + return QAbstractScrollArea::eventFilter(object, event); + } + + // QMdiSubWindow events: + switch (event->type()) { + case QEvent::Move: + case QEvent::Resize: + if (d->tileCalledFromResizeEvent) + break; + d->updateScrollBars(); + if (!subWindow->isMinimized()) + d->isSubWindowsTiled = false; + break; + case QEvent::Show: +#ifndef QT_NO_TABBAR + if (d->tabBar) { + const int tabIndex = d->childWindows.indexOf(subWindow); + if (!d->tabBar->isTabEnabled(tabIndex)) + d->tabBar->setTabEnabled(tabIndex, true); + } +#endif // QT_NO_TABBAR + // fall through + case QEvent::Hide: + d->isSubWindowsTiled = false; + break; +#ifndef QT_NO_RUBBERBAND + case QEvent::Close: + if (d->childWindows.indexOf(subWindow) == d->indexToHighlighted) + d->hideRubberBand(); + break; +#endif +#ifndef QT_NO_TABBAR + case QEvent::WindowTitleChange: + case QEvent::ModifiedChange: + if (d->tabBar) + d->tabBar->setTabText(d->childWindows.indexOf(subWindow), tabTextFor(subWindow)); + break; + case QEvent::WindowIconChange: + if (d->tabBar) + d->tabBar->setTabIcon(d->childWindows.indexOf(subWindow), subWindow->windowIcon()); + break; +#endif // QT_NO_TABBAR + default: + break; + } + return QAbstractScrollArea::eventFilter(object, event); +} + +/*! + \reimp +*/ +void QMdiArea::paintEvent(QPaintEvent *paintEvent) +{ + Q_D(QMdiArea); + QPainter painter(d->viewport); + const QVector<QRect> &exposedRects = paintEvent->region().rects(); + for (int i = 0; i < exposedRects.size(); ++i) + painter.fillRect(exposedRects.at(i), d->background); +} + +/*! + This slot is called by QAbstractScrollArea after setViewport() has been + called. Reimplement this function in a subclass of QMdiArea to + initialize the new \a viewport before it is used. + + \sa setViewport() +*/ +void QMdiArea::setupViewport(QWidget *viewport) +{ + Q_D(QMdiArea); + if (viewport) + viewport->setAttribute(Qt::WA_OpaquePaintEvent, d->background.isOpaque()); + foreach (QMdiSubWindow *child, d->childWindows) { + if (!sanityCheck(child, "QMdiArea::setupViewport")) + continue; + child->setParent(viewport, child->windowFlags()); + } +} + +QT_END_NAMESPACE + +#include "moc_qmdiarea.cpp" + +#endif // QT_NO_MDIAREA |