src/gui/kernel/qstackedlayout.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/kernel/qstackedlayout.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,543 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (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 Technology Preview License Agreement accompanying
+** this package.
+**
+** 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.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qstackedlayout.h"
+#include "qlayout_p.h"
+
+#include <qlist.h>
+#include <qwidget.h>
+#include "private/qlayoutengine_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QStackedLayoutPrivate : public QLayoutPrivate
+{
+    Q_DECLARE_PUBLIC(QStackedLayout)
+public:
+    QStackedLayoutPrivate() : index(-1), stackingMode(QStackedLayout::StackOne) {}
+    QList<QLayoutItem *> list;
+    int index;
+    QStackedLayout::StackingMode stackingMode;
+};
+
+/*!
+    \class QStackedLayout
+
+    \brief The QStackedLayout class provides a stack of widgets where
+    only one widget is visible at a time.
+
+    \ingroup geomanagement
+
+    QStackedLayout can be used to create a user interface similar to
+    the one provided by QTabWidget. There is also a convenience
+    QStackedWidget class built on top of QStackedLayout.
+
+    A QStackedLayout can be populated with a number of child widgets
+    ("pages"). For example:
+
+    \snippet doc/src/snippets/qstackedlayout/main.cpp 0
+    \codeline
+    \snippet doc/src/snippets/qstackedlayout/main.cpp 2
+    \snippet doc/src/snippets/qstackedlayout/main.cpp 3
+
+    QStackedLayout provides no intrinsic means for the user to switch
+    page. This is typically done through a QComboBox or a QListWidget
+    that stores the titles of the QStackedLayout's pages. For
+    example:
+
+    \snippet doc/src/snippets/qstackedlayout/main.cpp 1
+
+    When populating a layout, the widgets are added to an internal
+    list. The indexOf() function returns the index of a widget in that
+    list. The widgets can either be added to the end of the list using
+    the addWidget() function, or inserted at a given index using the
+    insertWidget() function. The removeWidget() function removes the
+    widget at the given index from the layout. The number of widgets
+    contained in the layout, can be obtained using the count()
+    function.
+
+    The widget() function returns the widget at a given index
+    position. The index of the widget that is shown on screen is given
+    by currentIndex() and can be changed using setCurrentIndex(). In a
+    similar manner, the currently shown widget can be retrieved using
+    the currentWidget() function, and altered using the
+    setCurrentWidget() function.
+
+    Whenever the current widget in the layout changes or a widget is
+    removed from the layout, the currentChanged() and widgetRemoved()
+    signals are emitted respectively.
+
+    \sa QStackedWidget, QTabWidget
+*/
+
+/*!
+    \fn void QStackedLayout::currentChanged(int index)
+
+    This signal is emitted whenever the current widget in the layout
+    changes.  The \a index specifies the index of the new current
+    widget, or -1 if there isn't a new one (for example, if there
+    are no widgets in the QStackedLayout)
+
+    \sa currentWidget(), setCurrentWidget()
+*/
+
+/*!
+    \fn void QStackedLayout::widgetRemoved(int index)
+
+    This signal is emitted whenever a widget is removed from the
+    layout. The widget's \a index is passed as parameter.
+
+    \sa removeWidget()
+*/
+
+/*!
+    \fn QWidget *QStackedLayout::widget()
+    \internal
+*/
+
+/*!
+    Constructs a QStackedLayout with no parent.
+
+    This QStackedLayout must be installed on a widget later on to
+    become effective.
+
+    \sa addWidget(), insertWidget()
+*/
+QStackedLayout::QStackedLayout()
+    : QLayout(*new QStackedLayoutPrivate, 0, 0)
+{
+}
+
+/*!
+    Constructs a new QStackedLayout with the given \a parent.
+
+    This layout will install itself on the \a parent widget and
+    manage the geometry of its children.
+*/
+QStackedLayout::QStackedLayout(QWidget *parent)
+    : QLayout(*new QStackedLayoutPrivate, 0, parent)
+{
+}
+
+/*!
+    Constructs a new QStackedLayout and inserts it into
+    the given \a parentLayout.
+*/
+QStackedLayout::QStackedLayout(QLayout *parentLayout)
+    : QLayout(*new QStackedLayoutPrivate, parentLayout, 0)
+{
+}
+
+/*!
+    Destroys this QStackedLayout. Note that the layout's widgets are
+    \e not destroyed.
+*/
+QStackedLayout::~QStackedLayout()
+{
+    Q_D(QStackedLayout);
+    qDeleteAll(d->list);
+}
+
+/*!
+    Adds the given \a widget to the end of this layout and returns the
+    index position of the \a widget.
+
+    If the QStackedLayout is empty before this function is called,
+    the given \a widget becomes the current widget.
+
+    \sa insertWidget(), removeWidget(), setCurrentWidget()
+*/
+int QStackedLayout::addWidget(QWidget *widget)
+{
+    Q_D(QStackedLayout);
+    return insertWidget(d->list.count(), widget);
+}
+
+/*!
+    Inserts the given \a widget at the given \a index in this
+    QStackedLayout. If \a index is out of range, the widget is
+    appended (in which case it is the actual index of the \a widget
+    that is returned).
+
+    If the QStackedLayout is empty before this function is called, the
+    given \a widget becomes the current widget.
+
+    Inserting a new widget at an index less than or equal to the current index
+    will increment the current index, but keep the current widget.
+
+    \sa addWidget(), removeWidget(), setCurrentWidget()
+*/
+int QStackedLayout::insertWidget(int index, QWidget *widget)
+{
+    Q_D(QStackedLayout);
+    addChildWidget(widget);
+    index = qMin(index, d->list.count());
+    if (index < 0)
+        index = d->list.count();
+    QWidgetItem *wi = QLayoutPrivate::createWidgetItem(this, widget);
+    d->list.insert(index, wi);
+    invalidate();
+    if (d->index < 0) {
+        setCurrentIndex(index);
+    } else {
+        if (index <= d->index)
+            ++d->index;
+        if (d->stackingMode == StackOne)
+            widget->hide();
+        widget->lower();
+    }
+    return index;
+}
+
+/*!
+    \reimp
+*/
+QLayoutItem *QStackedLayout::itemAt(int index) const
+{
+    Q_D(const QStackedLayout);
+    return d->list.value(index);
+}
+
+// Code that enables proper handling of the case that takeAt() is
+// called somewhere inside QObject destructor (can't call hide()
+// on the object then)
+
+class QtFriendlyLayoutWidget : public QWidget
+{
+public:
+    inline bool wasDeleted() const { return d_ptr->wasDeleted; }
+};
+
+static bool qt_wasDeleted(const QWidget *w) { return static_cast<const QtFriendlyLayoutWidget*>(w)->wasDeleted(); }
+
+
+/*!
+    \reimp
+*/
+QLayoutItem *QStackedLayout::takeAt(int index)
+{
+    Q_D(QStackedLayout);
+    if (index <0 || index >= d->list.size())
+        return 0;
+    QLayoutItem *item = d->list.takeAt(index);
+    if (index == d->index) {
+        d->index = -1;
+        if ( d->list.count() > 0 ) {
+            int newIndex = (index == d->list.count()) ? index-1 : index;
+            setCurrentIndex(newIndex);
+        } else {
+            emit currentChanged(-1);
+        }
+    } else if (index < d->index) {
+        --d->index;
+    }
+    emit widgetRemoved(index);
+    if (item->widget() && !qt_wasDeleted(item->widget()))
+        item->widget()->hide();
+    return item;
+}
+
+/*!
+    \property QStackedLayout::currentIndex
+    \brief the index position of the widget that is visible
+
+    The current index is -1 if there is no current widget.
+
+    \sa currentWidget(), indexOf()
+*/
+void QStackedLayout::setCurrentIndex(int index)
+{
+    Q_D(QStackedLayout);
+    QWidget *prev = currentWidget();
+    QWidget *next = widget(index);
+    if (!next || next == prev)
+        return;
+
+    bool reenableUpdates = false;
+    QWidget *parent = parentWidget();
+
+    if (parent && parent->updatesEnabled()) {
+        reenableUpdates = true;
+        parent->setUpdatesEnabled(false);
+    }
+
+    QWidget *fw = parent ? parent->window()->focusWidget() : 0;
+    if (prev) {
+        prev->clearFocus();
+        if (d->stackingMode == StackOne)
+            prev->hide();
+    }
+
+    d->index = index;
+    next->raise();
+    next->show();
+
+    // try to move focus onto the incoming widget if focus
+    // was somewhere on the outgoing widget.
+
+    if (parent) {
+        if (fw && (prev && prev->isAncestorOf(fw))) { // focus was on old page
+            // look for the best focus widget we can find
+            if (QWidget *nfw = next->focusWidget())
+                nfw->setFocus();
+            else {
+                // second best: first child widget in the focus chain
+                QWidget *i = fw;
+                while ((i = i->nextInFocusChain()) != fw) {
+                    if (((i->focusPolicy() & Qt::TabFocus) == Qt::TabFocus)
+                        && !i->focusProxy() && i->isVisibleTo(next) && i->isEnabled()
+                        && next->isAncestorOf(i)) {
+                        i->setFocus();
+                        break;
+                    }
+                }
+                // third best: incoming widget
+                if (i == fw )
+                    next->setFocus();
+            }
+        }
+    }
+    if (reenableUpdates)
+        parent->setUpdatesEnabled(true);
+    emit currentChanged(index);
+}
+
+int QStackedLayout::currentIndex() const
+{
+    Q_D(const QStackedLayout);
+    return d->index;
+}
+
+
+/*!
+    \fn void QStackedLayout::setCurrentWidget(QWidget *widget)
+
+    Sets the current widget to be the specified \a widget. The new
+    current widget must already be contained in this stacked layout.
+
+    \sa setCurrentIndex(), currentWidget()
+ */
+void QStackedLayout::setCurrentWidget(QWidget *widget)
+{
+    int index = indexOf(widget);
+    if (index == -1) {
+        qWarning("QStackedLayout::setCurrentWidget: Widget %p not contained in stack", widget);
+        return;
+    }
+    setCurrentIndex(index);
+}
+
+
+/*!
+    Returns the current widget, or 0 if there are no widgets in this
+    layout.
+
+    \sa currentIndex(), setCurrentWidget()
+*/
+QWidget *QStackedLayout::currentWidget() const
+{
+    Q_D(const QStackedLayout);
+    return d->index >= 0 ? d->list.at(d->index)->widget() : 0;
+}
+
+/*!
+    Returns the widget at the given \a index, or 0 if there is no
+    widget at the given position.
+
+    \sa currentWidget(), indexOf()
+*/
+QWidget *QStackedLayout::widget(int index) const
+{
+    Q_D(const QStackedLayout);
+     if (index < 0 || index >= d->list.size())
+        return 0;
+    return d->list.at(index)->widget();
+}
+
+/*!
+    \property QStackedLayout::count
+    \brief the number of widgets contained in the layout
+
+    \sa currentIndex(), widget()
+*/
+int QStackedLayout::count() const
+{
+    Q_D(const QStackedLayout);
+    return d->list.size();
+}
+
+
+/*!
+    \reimp
+*/
+void QStackedLayout::addItem(QLayoutItem *item)
+{
+    QWidget *widget = item->widget();
+    if (widget) {
+        addWidget(widget);
+        delete item;
+    } else {
+        qWarning("QStackedLayout::addItem: Only widgets can be added");
+    }
+}
+
+/*!
+    \reimp
+*/
+QSize QStackedLayout::sizeHint() const
+{
+    Q_D(const QStackedLayout);
+    QSize s(0, 0);
+    int n = d->list.count();
+
+    for (int i = 0; i < n; ++i)
+        if (QWidget *widget = d->list.at(i)->widget()) {
+            QSize ws(widget->sizeHint());
+            if (widget->sizePolicy().horizontalPolicy() == QSizePolicy::Ignored)
+                ws.setWidth(0);
+            if (widget->sizePolicy().verticalPolicy() == QSizePolicy::Ignored)
+                ws.setHeight(0);
+            s = s.expandedTo(ws);
+        }
+    return s;
+}
+
+/*!
+    \reimp
+*/
+QSize QStackedLayout::minimumSize() const
+{
+    Q_D(const QStackedLayout);
+    QSize s(0, 0);
+    int n = d->list.count();
+
+    for (int i = 0; i < n; ++i)
+        if (QWidget *widget = d->list.at(i)->widget())
+            s = s.expandedTo(qSmartMinSize(widget));
+    return s;
+}
+
+/*!
+    \reimp
+*/
+void QStackedLayout::setGeometry(const QRect &rect)
+{
+    Q_D(QStackedLayout);
+    switch (d->stackingMode) {
+    case StackOne:
+        if (QWidget *widget = currentWidget())
+            widget->setGeometry(rect);
+        break;
+    case StackAll:
+        if (const int n = d->list.count())
+            for (int i = 0; i < n; ++i)
+                if (QWidget *widget = d->list.at(i)->widget())
+                    widget->setGeometry(rect);
+        break;
+    }
+}
+
+/*!
+    \enum QStackedLayout::StackingMode
+    \since 4.4
+
+    This enum specifies how the layout handles its child widgets
+    regarding their visibility.
+
+    \value StackOne
+           Only the current widget is visible. This is the default.
+
+    \value StackAll
+           All widgets are visible. The current widget is merely raised.
+*/
+
+
+/*!
+    \property QStackedLayout::stackingMode
+    \brief determines the way visibility of child widgets are handled.
+    \since 4.4
+
+    The default value is StackOne. Setting the property to StackAll
+    allows you to make use of the layout for overlay widgets
+    that do additional drawing on top of other widgets, for example,
+    graphical editors.
+*/
+
+QStackedLayout::StackingMode QStackedLayout::stackingMode() const
+{
+    Q_D(const QStackedLayout);
+    return d->stackingMode;
+}
+
+void QStackedLayout::setStackingMode(StackingMode stackingMode)
+{
+    Q_D(QStackedLayout);
+    if (d->stackingMode == stackingMode)
+        return;
+    d->stackingMode = stackingMode;
+
+    const int n = d->list.count();
+    if (n == 0)
+        return;
+
+    switch (d->stackingMode) {
+    case StackOne:
+        if (const int idx = currentIndex())
+            for (int i = 0; i < n; ++i)
+                if (QWidget *widget = d->list.at(i)->widget())
+                    widget->setVisible(i == idx);
+        break;
+    case StackAll: { // Turn overlay on: Make sure all widgets are the same size
+        QRect geometry;
+        if (const QWidget *widget = currentWidget())
+            geometry = widget->geometry();
+        for (int i = 0; i < n; ++i)
+            if (QWidget *widget = d->list.at(i)->widget()) {
+                if (!geometry.isNull())
+                    widget->setGeometry(geometry);
+                widget->setVisible(true);
+            }
+    }
+        break;
+    }
+}
+
+QT_END_NAMESPACE